LVGL and SD Card won't share SPI bus

All ESP32 boards running MicroPython.
Target audience: MicroPython users with an ESP32 board.
Saul
Posts: 1
Joined: Wed Jun 10, 2020 10:20 pm

Re: LVGL and SD Card won't share SPI bus

Post by Saul » Wed Jun 10, 2020 10:50 pm

I wanted to use the hSPI pins with the LCD/Touch and vSPI for the SD card.

So before compiling the LVGL/micropython firmware I Changed in the file:
/home/.../lv_micropython/lib/lv_bindings/driver/esp32/ili9341.py

To these parameters:
miso=12, mosi=13, clk=14, cs=15, dc=26, rst=27, power=32, backlight=33

# Used ESP32 devkitc
#hspi GPIO13 (MOSI), GPIO12(MISO), GPIO14(CLK) and GPIO15 (CS)
#vspi GPIO23 (MOSI), GPIO19(MISO), GPIO18(CLK) and GPIO5 (CS)

After compiling and uploading the firmware, I wired the lcd/touch and sd like this:
TOUCH SPI CONNECTIONS (hSPI)
* T_IRQ No connection required
* T_DO 12
* T_DIN 13
* T_CS 25
* T_CLK 14

TFT ILI9341 SPI CONNECTIONS (hSPI)
* SDO(MISO) 12
* LED 33
* SCK 14
* SDI(MOSI) 13
* DC 26
* RESET 27
* CS 15
* GND gnd
* VCC 3.3V

TFT SD CARD CONNECTIONS (vSPI):
* SD_SCK 18
* SD_MISO 19
* SD_MOSI 23
* SD_CS 5

I was having a real hard time with my SD cards in micropython so I a took an SPI trace of what the arduino library did and just updated the sdcard.py script to run similar (arguments and CRC values) to the arduino one.

don't know if this will work for you, but this seems to work for all my sd cards, and I tested before and after running the lvgl examples:
(save content below to esp_sdcard.py file for examples)

"""
MicroPython driver for SD cards using SPI bus.
Requires an SPI bus and a CS pin. Provides readblocks and writeblocks
methods so the device can be mounted as a filesystem.

# Examples of how to use
# ESP32 - mount sd card
import os, esp_sdcard
sd = esp_sdcard.Esp_sdcard('vspi', 5)
os.mount(sd, '/sd')

# If card was removed you will just need to init card again not spi bus
esp_sdcard.Esp_sdcard.init_card(sd)
# No need to remount if inserting the same SD card


# ESP32 (with original sdcard script)
import machine, esp_sdcard, os
vspi = machine.SPI(2)
vspi.init(baudrate=400000, polarity=0, phase=0, bits=8, firstbit=vspi.MSB, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19))
sd = esp_sdcard.Esp_sdcard(vspi, machine.Pin(5))
os.mount(sd, '/sd')

# List file on sd card
os.listdir('/sd')

# Try to creat and write to a file
f = open('/sd/data.txt', 'w')
f.write('some data')
f.close()

# Try to read from a file
f = open('/sd/data.txt')
f.read()
f.close()
"""

"""
Format:
(SPI bus, chip select pin, baud rate after SD initialization)

Values:
SPI bus: 'vspi' or 'hspi'
CS/SS: any free pin on your esp32
baud rate: 300k-2M? (default set to 400k)
"""

from micropython import const
import time, machine

class Esp_sdcard:

# Class constants
_CMD_TIMEOUT = const(100)
_R1_IDLE_STATE = const(1<<0)
# R1_ERASE_RESET = const(1 << 1)
_R1_ILLEGAL_COMMAND = const(1 << 2)
# R1_COM_CRC_ERROR = const(1 << 3)
# R1_ERASE_SEQUENCE_ERROR = const(1 << 4)
# R1_ADDRESS_ERROR = const(1 << 5)
# R1_PARAMETER_ERROR = const(1 << 6)
_TOKEN_CMD25 = const(0xFC)
_TOKEN_STOP_TRAN = const(0xFD)
_TOKEN_DATA = const(0xFE)

def __init__(self, spi='vspi', cs=5, baud=400000):

# Instance variables

# SPI Type
self.spi_type = spi
if self.spi_type == 'hspi':
self.spi = machine.SPI(1)
elif self.spi_type == 'vspi':
self.spi = machine.SPI(2)
else:
raise OSError("Invalid SPI")

# Chip Select Pin
try:
self.cs = machine.Pin(cs)
except:
raise OSError("Invalid CS pin")

# add speed variable
if baud > 100000:
self.baud = baud
else:
self.baud = 100000


self.cmdbuf = bytearray(6)
self.dummybuf = bytearray(512)
self.tokenbuf = bytearray(1)

for i in range(512):
self.dummybuf = 0xFF
self.dummybuf_memoryview = memoryview(self.dummybuf)

# Initialise the SPI bus
self.init_spi(100000)

# Initialise SD card
self.init_card()


def init_spi(self, baud):

# Init CS pin
self.cs.init(self.cs.OUT, value=1)

# Init SPI
if self.spi_type == 'hspi':
self.spi.init(baudrate=baud, polarity=0, phase=0, bits=8, firstbit=self.spi.MSB, sck=machine.Pin(14), mosi=machine.Pin(13), miso=machine.Pin(12))
else: # vspi
self.spi.init(baudrate=baud, polarity=0, phase=0, bits=8, firstbit=self.spi.MSB, sck=machine.Pin(18), mosi=machine.Pin(23), miso=machine.Pin(19))


def init_card(self):

# Set CS pin high
self.cs(1)

# clock card at least 100 cycles with cs high
for i in range(16):
self.spi.write(b"\xFF")

# CMD0: Reset card; should return _R1_IDLE_STATE
for i in range(5):
self.init_cmd(0, 0, 0x95, 2) # Read Two bytes first being garbage
#check sd card is in idle mode
if (self.dummybuf_memoryview[1] == _R1_IDLE_STATE): # only care about the second byte
break

if i== 4:
print(bytes(self.dummybuf_memoryview[0:2])) # all bytes read
raise OSError("no SD card")

#TODO: Clean used buffer to not get false positive on next command

# CMD59: arduino library does this command, but breaks things for me
#self.init_cmd(59, 0x01, 0x83, 2)
#for now i'll do nothing with output but should be resp of 0x01
#TODO: clean buffers


# CMD8: determine card version
self.init_cmd(8, 0x01AA, 0x87, 6)
# New cards/v2 will not have errors with this command
if self.dummybuf_memoryview[1] == _R1_IDLE_STATE: # Only care about the second byte
self.init_card_v2()

#arduinio runs these one more time and all response should be zero
#TODO: check response and do somthing about it
self.init_cmd(58, 0, 0xFD, 6)
self.init_cmd(55, 0, 0x65, 2)
self.init_cmd(41, 0x40100000, 0xCD, 2)

# Too many posibilities
#elif (self.dummybuf_memoryview[1] == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND)):
# self.init_card_v1()

# Check if card is atleast in idel state ignoring all other possibilites
elif self.dummybuf_memoryview[1] & _R1_IDLE_STATE == _R1_IDLE_STATE:
self.init_card_v1()

else:
print(bytes(self.dummybuf_memoryview[0:6])) # all bytes read
raise OSError("couldn't determine SD card version")
#TODO: clean buffers


# Clean borrowed buffers
self.dummybuf_memoryview[0] = 0xFF
self.dummybuf_memoryview[1] = 0xFF
self.dummybuf_memoryview[2] = 0xFF
self.dummybuf_memoryview[3] = 0xFF
self.dummybuf_memoryview[4] = 0xFF
self.dummybuf_memoryview[5] = 0xFF
self.dummybuf_memoryview[6] = 0xFF # just in case


# CMD9: response R2 (R1 byte + 16-byte block read)
# Get the number of sectors
#if self.cmd(9, 0, 0, 0, False) != 0: # didnt work for my card
if self.cmd(9, 0, 0xAF, 0, False) != 0:
raise OSError("no response from SD card")
csd = bytearray(16)
self.readinto(csd)
if csd[0] & 0xC0 == 0x40: # CSD version 2.0
self.sectors = ((csd[8] << 8 | csd[9]) + 1) * 1024
elif csd[0] & 0xC0 == 0x00: # CSD version 1.0 (old, <=2GB)
c_size = csd[6] & 0b11 | csd[7] << 2 | (csd[8] & 0b11000000) << 4
c_size_mult = ((csd[9] & 0b11) << 1) | csd[10] >> 7
self.sectors = (c_size + 1) * (2 ** (c_size_mult + 2))
else:
raise OSError("SD card CSD format not supported")
# print('sectors', self.sectors)

# CMD16: set block length to 512 bytes
if self.cmd(16, 512, 0) != 0:
raise OSError("can't set 512 block size")

# set to high data rate now that it's initialised
self.init_spi(self.baud) #maybe have some speed presets for user

# send dummy byte for clock sync
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)


#TODO: test v1 card
def init_card_v1(self):

for i in range(_CMD_TIMEOUT):
#self.init_cmd(55, 0, 0xE5, 2)
#self.init_cmd(55, 0, 0x65, 2)
self.init_cmd(55, 0, 0, 2)
#TODO: clean buffers

#self.init_cmd(41, 0x40100000, 0xCD, 2)
self.init_cmd(41, 0x40000000, 0, 2)
if (self.dummybuf_memoryview[1] == 0): # Only care about the second byte
#if the response is 0x00 your good
# and set cdv = 512
self.cdv = 1
break
#TODO: clean buffers

if i == (_CMD_TIMEOUT - 1):
print(bytes(self.dummybuf_memoryview[0:2]))
raise OSError("timeout waiting for v1 card")

def init_card_v2(self):

# cmd58 should only be available on newer cards
##self.init_cmd(58, 0, 0, 6)
self.init_cmd(58, 0, 0xFD, 6)
if (self.dummybuf_memoryview[1] != _R1_IDLE_STATE):
self.init_card_v1() # maybe its a v1 card
#TODO: check this case with v1 card

#TODO: clean buffers

for i in range(_CMD_TIMEOUT):
#CMD55...
#self.init_cmd(55, 0, 0, 2)
self.init_cmd(55, 0, 0x65, 2)
#TODO: clean buffers

#ACM41...
#self.init_cmd(41, 0x40000000, 0, 2)
#self.init_cmd(41, 0x40000000, 0x77, 2)
self.init_cmd(41, 0x40100000, 0xCD, 2)
if (self.dummybuf_memoryview[1] == 0): # Only care about the second byte
#if the response is 0x00 your good
# and set cdv = 1
self.cdv = 1
break
#TODO: clean buffers

if i == (_CMD_TIMEOUT - 1):
print(bytes(self.dummybuf_memoryview[0:2]))
raise OSError("timeout waiting for v2 card")

# Add method to check for SD card is inserted
# return true or false and clean up sd mount

def init_cmd(self, cmd=None, arg=0, crc=0, resp_size=2): #resp size in bytes

# send dummy byte before every command
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)


self.cs(0)

# Create and send the command
buf = self.cmdbuf
buf[0] = 0x40 | cmd
buf[1] = arg >> 24
buf[2] = arg >> 16
buf[3] = arg >> 8
buf[4] = arg
buf[5] = crc
self.spi.write(buf)

# Get response from SD and save to buffer
self.spi.readinto(self.dummybuf_memoryview[0:resp_size], 0xFF)

# For debugging sd card init
#print(bytes(buf)) # Command
#print(bytes(self.dummybuf_memoryview[0:resp_size])) # Response

self.cs(1)


def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):

# CMD17 Some cards need specific crc for read block?
if cmd == 17 or cmd == 24:
crc = 0x55 # 32G samsung HC SD

# send dummy byte before every command
self.cs(0)
self.spi.write(b"\xFF")
self.cs(1)

self.cs(0)

# create and send the command
buf = self.cmdbuf
buf[0] = 0x40 | cmd
buf[1] = arg >> 24
buf[2] = arg >> 16
buf[3] = arg >> 8
buf[4] = arg
buf[5] = crc
self.spi.write(buf)

if skip1:
self.spi.readinto(self.tokenbuf, 0xFF)

# wait for the response (response[7] == 0)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
response = self.tokenbuf[0]
# For debugging sd card commands
#print(bytes(buf)) # Command
#print(bytes(response)) # Response

if not (response & 0x80):
# this could be a big-endian integer that we are getting here
for j in range(final):
self.spi.write(b"\xff")
if release:
self.cs(1)
self.spi.write(b"\xff")
#print(bytes(response)) # Debug Response
return response

# timeout
self.cs(1)
self.spi.write(b"\xff")
return -1


def readinto(self, buf):
self.cs(0)

# read until start byte (0xff)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
if self.tokenbuf[0] == _TOKEN_DATA:
break
else:
self.cs(1)
raise OSError("timeout waiting for response")

# read data
mv = self.dummybuf_memoryview
if len(buf) != len(mv):
mv = mv[: len(buf)]
self.spi.write_readinto(mv, buf)

# read checksum
self.spi.write(b"\xff")
self.spi.write(b"\xff")

self.cs(1)
self.spi.write(b"\xff")

def write(self, token, buf):
self.cs(0)

# send: start of block, data, checksum
self.spi.read(1, token)
self.spi.write(buf)
self.spi.write(b"\xff")
self.spi.write(b"\xff")

# check the response
if (self.spi.read(1, 0xFF)[0] & 0x1F) != 0x05:
self.cs(1)
self.spi.write(b"\xff")
return

# wait for write to finish
while self.spi.read(1, 0xFF)[0] == 0:
pass

self.cs(1)
self.spi.write(b"\xff")

def write_token(self, token):
self.cs(0)
self.spi.read(1, token)
self.spi.write(b"\xff")
# wait for write to finish
while self.spi.read(1, 0xFF)[0] == 0x00:
pass

self.cs(1)
self.spi.write(b"\xff")

def readblocks(self, block_num, buf):
nblocks = len(buf) // 512
assert nblocks and not len(buf) % 512, "Buffer length is invalid"
if nblocks == 1:
# CMD17: set read address for single block
if self.cmd(17, block_num * self.cdv, 0, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
# receive the data and release card
self.readinto(buf)
else:
# CMD18: set read address for multiple blocks
if self.cmd(18, block_num * self.cdv, 0, release=False) != 0:
# release the card
self.cs(1)
raise OSError(5) # EIO
offset = 0
mv = memoryview(buf)
while nblocks:
# receive the data and release card
self.readinto(mv[offset : offset + 512])
offset += 512
nblocks -= 1
if self.cmd(12, 0, 0xFF, skip1=True):
raise OSError(5) # EIO

def writeblocks(self, block_num, buf):
nblocks, err = divmod(len(buf), 512)
assert nblocks and not err, "Buffer length is invalid"
if nblocks == 1:
# CMD24: set write address for single block
if self.cmd(24, block_num * self.cdv, 0) != 0:
raise OSError(5) # EIO

# send the data
self.write(_TOKEN_DATA, buf)
else:
# CMD25: set write address for first block
if self.cmd(25, block_num * self.cdv, 0) != 0:
raise OSError(5) # EIO
# send the data
offset = 0
mv = memoryview(buf)
while nblocks:
self.write(_TOKEN_CMD25, mv[offset : offset + 512])
offset += 512
nblocks -= 1
self.write_token(_TOKEN_STOP_TRAN)

def ioctl(self, op, arg):
if op == 4: # get number of blocks
return self.sectors

PM-TPI
Posts: 37
Joined: Fri Jun 28, 2019 3:09 pm

Re: LVGL and SD Card won't share SPI bus

Post by PM-TPI » Fri Jun 12, 2020 9:00 pm

Mike Teachman wrote:
Sat May 23, 2020 3:03 pm

No. I have never used the ILI9341 micropython driver in any of my littlevgl projects. At this point I have no reason to switch to the micropython driver as the modified C driver does what I need. It's a case of "if it's not broken, don't fix it".
Mike
How would I go about adding the modxpt2046.c touch driver?

BTW the fix for the incorrect colors was to comment out...

Code: Select all

    /*Byte swapping is required   RMG I removed because I did'nt see any like code in ili9341.py
    uint32_t i;
    uint8_t * color_u8 = (uint8_t *) color_p;
    uint8_t color_tmp;
    for(i = 0; i < size * 2; i += 2) {
        color_tmp = color_u8[i + 1];
        color_u8[i + 1] = color_u8[i];
        color_u8[i] = color_tmp;
    }
    */
    

User avatar
Mike Teachman
Posts: 100
Joined: Mon Jun 13, 2016 3:19 pm
Location: Victoria, BC, Canada

Re: LVGL and SD Card won't share SPI bus

Post by Mike Teachman » Sat Jun 13, 2020 6:24 pm

PM-TPI wrote:
Fri Jun 12, 2020 9:00 pm
How would I go about adding the modxpt2046.c touch driver?

BTW the fix for the incorrect colors was to comment out...
For the touch driver: sorry, I do not have any experience with this feature. I have only used LVGL to display graphics. The MicroPython category at the LVGL forum might have some ideas on adding this to the build and getting it working?
https://forum.lvgl.io/c/micropython

Congrats on getting your display to work !

anichang
Posts: 2
Joined: Sun May 09, 2021 12:55 am

Re: LVGL and SD Card won't share SPI bus

Post by anichang » Sun May 09, 2021 1:08 am

Looks like no fix has been mainlined.

I tried to change the python driver by adding a sharedspi flag to the init functions, which would skip the gpios setup and spi master init but, if the sharedspi flag is set to true, the subsequent call spi_bus_add_device() fails with ret = 259 (0x103, ESP_ERR_INVALID_STATE in esp_err.h). No need to say that the SPI bus is already initialized by sd card driver.

So I modified modILI9341.c driver as Mike Teachman did, but I got an "ImportError: no module named 'ILI9341'". I guessed the C driver was replaced by the pure/hybrid python driver, so I re-added ${LV_BINDINGS_DIR}/driver/esp32/modlvesp32.c to lv_bindings/mkrules.cmake . And I can see the object file being built. But still, can't load ILI9341 module. What am I missing?

regards

PM-TPI
Posts: 37
Joined: Fri Jun 28, 2019 3:09 pm

Re: LVGL and SD Card won't share SPI bus

Post by PM-TPI » Mon May 10, 2021 3:47 pm

With these following changes I was able to Share SPI with TFT, Touch, and SDCard module that came with uP
by using modILI9341.c and modxpt2046.c drivers.

In file mpconfigport.h from the ports/esp32 directory un-comment the mp_module_ILI9341 and mp_module_xpt2046 in two locations.
These are the .c drivers to include in build

Code: Select all

extern const struct _mp_obj_module_t mp_module_rtch;
extern const struct _mp_obj_module_t mp_module_lodepng;
extern const struct _mp_obj_module_t mp_module_ILI9341;
extern const struct _mp_obj_module_t mp_module_xpt2046;

#if MICROPY_PY_LVGL
#define MICROPY_PORT_LVGL_DEF \
    { MP_OBJ_NEW_QSTR(MP_QSTR_lvgl), (mp_obj_t)&mp_module_lvgl }, \
    { MP_OBJ_NEW_QSTR(MP_QSTR_lvesp32), (mp_obj_t)&mp_module_lvesp32 }, \
    { MP_OBJ_NEW_QSTR(MP_QSTR_ILI9341), (mp_obj_t)&mp_module_ILI9341 }, \
    { MP_OBJ_NEW_QSTR(MP_QSTR_xpt2046), (mp_obj_t)&mp_module_xpt2046 },

// lvesp needs to delete the timer task upon soft reset
I remove ...
ili9XXX.py
ili9341.py
xpt2046.py
files from "lv_micropython\lib\lv_bindings\driver\esp32\" directory.

In modILI9341.c and modxpt2046.c files change...
.flags=SPI_DEVICE_HALFDUPLEX to .flags=0

and in modILI9341.c move */ down to comment out "Byte swapping" which caused color issue.

Code: Select all

	/*Byte swapping is required
	uint32_t i;
	uint8_t * color_u8 = (uint8_t *) color_p;
	uint8_t color_tmp;
	for(i = 0; i < size * 2; i += 2) {
		color_tmp = color_u8[i + 1];
		color_u8[i + 1] = color_u8[i];
		color_u8[i] = color_tmp;
	}
	*/
In Main.py code use your specific pins but use spihost=2, miso=19, mosi=23, clk=18 only.

Code: Select all

from machine import Pin, SPI
import lvgl as lv
import lvesp32  
import ILI9341 as ili
from xpt2046 import xpt2046

lv.init()
sd = SDCard(slot=2, cs=Pin(14), freq=20_000_000)
# vspi = SPI(2, miso=Pin(19), mosi=Pin(23), sck=Pin(18)) # use this if you don't want SDCard

# -----------------------------------------------------------------------------
disp = ili.display(spihost=2, miso=19, mosi=23, clk=18, cs=15, \
dc=13, rst=0, backlight=27, mhz=20) 
disp.init()

disp_buf1 = lv.disp_buf_t()
buf1_1 = bytearray(320*10)
disp_buf1.init(buf1_1, None, len(buf1_1) //4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.buffer = disp_buf1
disp_drv.flush_cb = disp.flush
disp_drv.hor_res = 240
disp_drv.ver_res = 320
disp_drv.register()

# -----------------------------------------------------------------------------
touch = xpt2046(spihost=2, cs=5, irq=4, mhz=5, x_inv=False, \
y_inv=True, x_min=240, y_min=320, x_max=3783, y_max=3948)
touch.init()

indev_drv = lv.indev_drv_t()
indev_drv.init()
indev_drv.type = lv.INDEV_TYPE.POINTER
indev_drv.read_cb = touch.read
indev_drv.register()
This all worked with LVGL v6

But new problem with v7
Using esp-idf v4.1 lv_micropython dev micropython v1.14
I no longer get touch callbacks...
I believe the issue to be this line....
STATIC bool xpt2046_read(lv_indev_data_t *data); ~line119
but the doc states...
bool xpt2046_read(lv_indev_drv_t * drv, lv_indev_data_t*data)
I can not build using this different line.
Someone who understands .c code would probably see the problem.

Hope This Helps

Post Reply