《ESP32-S3使用指南—MicroPython版 V1.0》第二十九章 SD卡实验
1)实验平台:正点原子ESP32S3开发板
2)购买链接:https://detail.tmall.com/item.htm?id=768499342659
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-347618-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学ESP32S3快速入门视频教程:https://www.bilibili.com/video/BV1sH4y1W7Tc
6)正点原子FPGA交流群:132780729
第二十九章 SD卡实验
SD卡是最常见的小型可移动存储介质之一,它有多种尺寸和物理外形。MMC卡是类似的可移动存储设备,而eMMC设备是电气类似的存储设备,旨在嵌入其他系统。所有这三种形式共享一个通用协议来与它们的主机系统进行通信,并且高级支持对它们来说看起来都是一样的。因此,在MicroPython中,它们被抽象为一个名为machine.SDCard的对象。在本章中,我们将通过machine.SDCard对象来驱动这些存储设备。
本章分为如下几个小节:
29.1 SD卡操作模块
29.2 硬件设计
29.3 软件设计
29.4 下载验证
29.1 SD卡操作模块
在MicroPython中,uos模块和machine.SDCard对象都与SD卡操作有关。uos模块提供了一些用于操作文件系统的函数,例如listdir(列出目录中的文件)、mkdir(创建目录)、chdir(切换当前工作目录)、getcwd(获取当前工作目录)、remove(删除文件)、rename(重命名文件或文件夹)、stat(获取文件或文件夹的状态信息)、chmod(改变文件或文件夹的权限)、utime(修改文件或文件夹的访问时间和修改时间)以及system(执行系统命令)。
而machine.SDCard对象是MicroPython中用于表示SD卡的抽象对象。它提供了一组统一的API来与SD卡进行通信,包括初始化SD卡、获取SD卡信息、读取和写入块等操作。在使用MicroPython进行SD卡操作时,通常会结合使用uos模块和machine.SDCard对象。
现在,作者将对这些模块的构造函数和对象的使用方法进行讲解。
29.1.1 uos模块
MicroPython的内置模块uos主要提供文件系统操作服务。该模块实现了CPython模块的一个子集,同时具有一些特殊的特点和限制。以下是其主要特点和应用场景,以及需要注意的事项。
一、主要特点:
1,提供基本的“操作系统”服务,如获取系统信息、生成随机数、更改或获取当前目录、列出或创建或删除或重命名文件或目录等。
2,提供了文件系统访问和挂载的功能,例如在虚拟文件系统中挂载多个“真实”文件系统,或者使用不同类型的文件系统格式,如FAT、littlefs等。
3,提供终端重定向和复制的功能,例如在给定的类似流对象上复制或切换MicroPython终端(REPL)。
4,uos模块使用的文件系统操作语法是CPython os模块的一个子集,也是POSIX标准文件系统操作的一个子集。支持的函数和方法有:uname、urandom、chdir、getcwd、ilistdir、listdir、mkdir、remove、rmdir、rename、stat、statvfs、sync、mount、umount、dupterm等。不支持的有:chown、chmod、link、symlink等。
二、应用场景:
1,对文件系统进行管理和操作,例如创建或删除或修改文件或目录,或者获取文件或目录的属性或状态。
2,对不同类型的存储设备进行访问和挂载,例如使用SD卡或SPI闪存等外部存储设备扩展内部存储空间,或者使用不同的文件系统格式适应不同的性能和兼容性需求。
3,对终端进行重定向和复制,例如在不同的通信接口上使用MicroPython终端(REPL),或者在多个终端上同时输出或输入数据。
三、需注意事项:
1,uos模块的功能和性能可能因不同的端口而异,因此在开发可移植的MicroPython应用程序时,应该尽量避免依赖特定的文件系统操作语法或结果。
2,uos模块在编译和执行文件系统操作时可能会消耗较多的内存和时间,因此在处理大量或复杂的文件或目录时,应该注意优化代码和资源管理。
3,uos模块在编译和执行文件系统操作时可能会遇到无效或不合法的路径、参数、数据等,或者文件错误、权限错误、设备错误等异常情况。这些情况会引发OSError或ValueError异常,并给出错误信息。应该使用try-except语句来捕获并处理这些异常。
下面我们打开Thonny软件,在Shell交互窗口下使用dir命令获取uos模块提供的函数,如下所示:
>>> import machine, uos
>>> dir(uos)
['__class__', '__name__', 'remove', 'VfsFat', 'VfsLfs2', '__dict__', 'chdir', 'dupterm', 'dupterm_notify', 'getcwd', 'ilistdir', 'listdir', 'mkdir', 'mount', 'rename', 'rmdir', 'stat', 'statvfs', 'sync', 'umount', 'uname', 'unlink', 'urandom']
>>> 可以看到。MicroPython的uos模块提供了十几种方法(函数),这些方法足够我们去操作SD卡了。
29.1.2 machine.SDCard对象
machine.SDCard是一个用于表示SD卡的抽象对象,它提供了一组统一的API来访问和操作SD卡。通过使用machine.SDCard对象,开发人员可以轻松地读取和写入SD卡上块的地址、获取SD卡的容量和可用空间等操作。下面讲解的是SDCard对象的构造函数与方法。
一、SDCard构造函数
SDCard的构造对象方法如下:
class machine.SDCard(slot=1, width=1, cd=None, wp=None, sck=None,
miso=None, mosi=None, cs=None, freq=20000000)
使用示例:sd = machine.SDCard(slot=2,width=8,sck=12, miso=13, mosi=11, cs=2) 该构造方法的参数描述,如下表所示。
表29.1.1 machine.SDCard构造方法参数描述
返回值:SDCard对象。
上表的slot参数选择接口如下表所示。
表29.1.2 Slot端号分配
从上表可知,如果我们使用SPI驱动SD卡,则选择2与3号端口。
注意:正点原子ESP32-S3开发板的SD卡使用的是SPI协议进行通信,所以SD卡构造函数中,我们仅使用上表绿色的参数。
二、SDCard类的方法
打开Thonny软件,在Shell交互窗口下使用dir命令获取SDCard类的方法,如下所示:
>>> import machine
>>> dir(machine.SDCard)
['__class__', '__name__', '__bases__', '__del__', '__dict__', 'deinit', 'info', 'ioctl', 'readblocks', 'writeblocks']
>>> 可以看到。通过使用machine.SDCard对象,开发人员可以轻松地读取和写入SD卡上块的地址、获取SD卡的容量和可用空间等操作。
29.2 硬件设计
1. 例程功能
本章实验功能简介:系统打开SD卡根目录下的test.txt文件,然后在此文件下写入“Hello ALIENTEK”字符串数据,写入完成后读取此文件的内容,并输出至SPILCD显示屏上。
2. 硬件资源
1)XL9555
IIC_INT-IO0(需在P5连接IO0)
IIC_SDA-IO41
IIC_SCL-IO42
2)SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在P5端口,使用跳线帽将IO_SET和LCD_DC相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
3)SD
CS-IO2
SCK-IO12
MOSI-IO11
MISO-IO13
3. 原理图
SD接口与ESP32-S3的连接关系,如下图所示:
图29.2.1 SD接口与ESP32-S3的连接电路图
29.3 软件设计
29.3.1 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。下面看看本实验的程序流程图。
图29.3.1.1 程序流程图
29.3.2 程序解析
本书籍的代码都在main.py脚本下编写的,读者可在光盘资料下找到对应的源码。SD卡实验main.py源码如下:import machine, uos
import time
"""
* @brief 程序入口
* @param 无
* @retval 无
"""
if__name__ == '__main__':
# Slot 2 uses pins sck = 12, cs = 2, miso = 13, mosi = 11
sd = machine.SDCard(slot=2,width=8,sck=12, miso = 13, mosi=11, cs=2)
# 重新当前系统文件目录
print('挂载SD前的系统目录:{}'.format(uos.listdir()))
# 使用uos.VfsFat类创建一个FAT文件系统对象
vfs = uos.VfsFat(sd)
# 挂在到SD/sd
uos.mount(vfs,'/sd')
# 重新查询系统文件目录
print('挂载SD后的系统目录:{}'.format(uos.listdir()))
# 打开test.txt文件,如果SD卡目录没有会重新创建test.txt文件
with open("/sd/test.txt", "w") as f:
for i in range(1, 50):
# 从这个文件写数据
f.write(str(i)+"\n")
# 从sd卡目录下读取test.txt文件内容
with open("/sd/test.txt", "r") as f:
# 打印读取的内容
print(f.read())
# 卸載SD卡
uos.umount('/sd') 首先,我们需要实例化SD卡对象,并配置SPI协议通信和SPI管脚。然后,使用uos.VfsFat类创建一个FAT文件系统对象,并调用uos.mount方法挂载SD卡。接着,我们需要检查SD卡的挂载是否成功。如果挂载失败,系统目录将不显示SD卡目录。最后,我们需要打开SD卡根目录下的test.txt文件,在此文件下写入1到50的数字,写入完成后,读取此文件的内容,并在串口上输出读取的内容。
在使用这种方法驱动SD卡的情况下,可能会引发一个问题,导致SPI LCD显示屏无法正常显示。问题的根源在于正点原子ESP32-S3开发板的SD卡与SPI LCD共享一个SPI接口。在这种情况下,MicroPython提供的machine_sdcard.c驱动并没有返回SPI控制块,这使得LCD驱动无法获取到SPI控制块以调用SPI收发函数发送相关的数据和命令。
因此,为了实现SD卡与SPI LCD的兼容性,作者引用了别人的一个用于驱动SD卡Python脚本(SPI接口)。通过该脚本,可以在实例化SPI的情况下驱动SD卡和SPI LCD显示屏。下面是sdcard.py脚本的代码示例:
from micropython import const
import time
_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)
class SDCard:
def __init__(self, spi, cs, baudrate=20000000):
self.spi = spi
self.cs = cs
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 card
self.init_card(baudrate)
def init_spi(self, baudrate):
try:
master = self.spi.MASTER
except AttributeError:
# on ESP8266
self.spi.init(baudrate=baudrate, phase=0, polarity=0)
else:
# on pyboard
self.spi.init(master, baudrate=baudrate, phase=0, polarity=0)
def init_card(self, baudrate):
# init CS pin
self.cs.init(self.cs.OUT, value=1)
# init SPI bus; use low data rate for initialisation
self.init_spi(100000)
# clock card at least 100 cycles with cs high
for i in range(16):
self.spi.write(b"\xff")
# CMD0: init card; should return _R1_IDLE_STATE (allow 5 attempts)
for _ in range(5):
if self.cmd(0, 0, 0x95) == _R1_IDLE_STATE:
break
else:
raise OSError("no SD card")
# CMD8: determine card version
r = self.cmd(8, 0x01AA, 0x87, 4)
if r == _R1_IDLE_STATE:
self.init_card_v2()
elif r == (_R1_IDLE_STATE | _R1_ILLEGAL_COMMAND):
self.init_card_v1()
else:
raise OSError("couldn't determine SD card version")
# get the number of sectors
# CMD9: response R2 (R1 byte + 16-byte block read)
if self.cmd(9, 0, 0, 0, False) != 0:
raise OSError("no response from SD card")
csd = bytearray(16)
self.readinto(csd)
if csd & 0xC0 == 0x40:# CSD version 2.0
self.sectors = ((csd << 8 | csd) + 1) * 1024
elif csd & 0xC0 == 0x00:# CSD version 1.0 (old, <=2GB)
c_size = (csd & 0b11) << 10 | csd << 2 | csd >> 6
c_size_mult = (csd & 0b11) << 1 | csd >> 7
read_bl_len = csd & 0b1111
capacity = (c_size + 1) * (2 ** (c_size_mult + 2))
* (2**read_bl_len)
self.sectors = capacity // 512
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(baudrate)
def init_card_v1(self):
for i in range(_CMD_TIMEOUT):
self.cmd(55, 0, 0)
if self.cmd(41, 0, 0) == 0:
# SDSC card, uses byte addressing in read/write/erase commands
self.cdv = 512
# print(" v1 card")
return
raise OSError("timeout waiting for v1 card")
def init_card_v2(self):
for i in range(_CMD_TIMEOUT):
time.sleep_ms(50)
self.cmd(58, 0, 0, 4)
self.cmd(55, 0, 0)
if self.cmd(41, 0x40000000, 0) == 0:
# 4-byte response, negative means keep the first byte
self.cmd(58, 0, 0, -4)
# get first byte of response, which is OCR
ocr = self.tokenbuf
if not ocr & 0x40:
self.cdv = 512
else:
self.cdv = 1
# print(" v2 card")
return
raise OSError("timeout waiting for v2 card")
def cmd(self, cmd, arg, crc, final=0, release=True, skip1=False):
self.cs(0)
# create and send the command
buf = self.cmdbuf
buf = 0x40 | cmd
buf = arg >> 24
buf = arg >> 16
buf = arg >> 8
buf = arg
buf = crc
self.spi.write(buf)
if skip1:
self.spi.readinto(self.tokenbuf, 0xFF)
# wait for the response (response == 0)
for i in range(_CMD_TIMEOUT):
self.spi.readinto(self.tokenbuf, 0xFF)
response = self.tokenbuf
if not (response & 0x80):
if final < 0:
self.spi.readinto(self.tokenbuf, 0xFF)
final = -1 - final
for j in range(final):
self.spi.write(b"\xff")
if release:
self.cs(1)
self.spi.write(b"\xff")
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 == _TOKEN_DATA:
break
time.sleep_ms(1)
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) & 0x1F) != 0x05:
self.cs(1)
self.spi.write(b"\xff")
return
# wait for write to finish
while self.spi.read(1, 0xFF) == 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) == 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 += 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 += 512
nblocks -= 1
self.write_token(_TOKEN_STOP_TRAN)
def ioctl(self, op, arg):
if op == 4:# get number of blocks
return self.sectors
if op == 5:# get block size in bytes
return 512 可以看到,这个脚本通过使用SD命令来实现对SD卡的读写操作。在脚本的构造函数中,需要传入三个参数来实例化SD卡对象,分别是SPI控制块、CS片选管脚以及SPI的速率。这些参数在实例化SD卡对象时是必需的。此外,值得注意的是,驱动SD卡的速率一般不能超过24M,相关内容可以参考SD卡数据手册。
首先,作者在Thonny软件中新建了一个文本文件。然后,在此文本文件中粘贴了上述的SD卡驱动代码,并将其命名为sdcard.py文件。接着,将这个文件保存到了MicroPython设备中,如下图所示:
图29.3.2.1 添加sdcard.py脚本
在main.py脚本的测试代码,如下所示:
from machine import Pin,SPI,I2C
from sdcard import SDCard
import atk_xl9555 as io_ex
import atk_lcd as lcd
import time
import uos
"""
* @brief 程序入口
* @param 无
* @retval 无
"""
if __name__ == '__main__':
x = 0
# IIC初始化
i2c0 = I2C(0, scl = Pin(42), sda = Pin(41), freq = 400000)
# XL9555初始化
xl9555 = io_ex.init(i2c0)
# 复位LCD
xl9555.write_bit(io_ex.SLCD_RST,0)
time.sleep_ms(100)
xl9555.write_bit(io_ex.SLCD_RST,1)
time.sleep_ms(100)
# 初始化SPI
spi = SPI(2,baudrate = 20000000, sck = Pin(12), mosi=Pin(11), miso=Pin(13))
# 初始化LCD,lcd = 0为正点原子2.4寸屏幕;lcd = 1为正点原子1.3寸SPILCD屏幕;
display = lcd.init(spi,dc = Pin(40,Pin.OUT,Pin.PULL_UP,value = 1),
cs = Pin(21,Pin.OUT,Pin.PULL_UP,value = 1),dir = 1,lcd = 0)
# 打开背光
xl9555.write_bit(io_ex.SLCD_PWR,1)
time.sleep_ms(100)
sd = SDCard(spi,Pin(2,Pin.OUT))
# 实验信息
display.string(30, 50, 240, 32, 32, "ESP32-S3",lcd.RED)
display.string(30, 80, 240, 24, 24, "SD TEST",lcd.RED)
display.string(30, 110, 240, 16, 16, "ATOM@ALIENTEK",lcd.RED)
display.string(30, 130, 200, 16, 16, "File Read:", lcd.BLUE)
# 挂在到SD/sd
uos.mount(sd,'/sd')
# 重新查询系统文件目录
print('挂载SD后的系统目录:{}'.format(uos.listdir()))
with open("/sd/test.txt", "w") as f:
f.write(str("Hello ALIENTEK"))
# 从sd卡目录下读取hello.txt文件内容
with open("/sd/test.txt", "r") as f:
# 打印读取的内容
data = f.read()
display.string(130, 130, 200, 16, 16, str(data), lcd.BLUE)
# 卸載SD卡
uos.umount('/sd') 在上述源代码中,首先实例化了IIC、XL9555、SPI、LCD和SD卡对象。接着,系统显示了实验信息。随后,使用了uos模块的方法来挂载SD卡。最后,在SD卡的目录下创建了一个名为test.txt的文件,并将字符串数据“Hello ALIENTEK”写入该文件。同时,读取了该文件的内容,并将其显示在LCD显示屏上。
29.4 下载验证
程序下载到开发板后,可以看到SPILCD显示SD卡目录下的test.txt文件的内容,如下图所示。
图30.4.1 RGBLCD显示效果图
页:
[1]