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: 31
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: 93
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 !

Post Reply