【MicroPython ESP32】通过sdcard模块软SPI读取SD卡实例

【MicroPython ESP32】通过sdcard模块软SPI读取SD卡实例


  • 相关篇《【MicroPython ESP32】通过sdcard模块读取SD卡实例
  • 本实验基于Thonny平台开发。esp32固件版本MicroPython v1.19.1 on 2022-06-18; ESP32 module with ESP32
  • Micro SD卡模块(TF卡读写卡器 SPI 带电平转换芯片)
    在这里插入图片描述
  • Micropython ESP32对TF卡容量可支持128GB,本人没有对此做过验证,目前手上没有这么大容量的卡,但是需要注意的是,对某些未知的TF不支持,不管容量多少就是识别不到。另外需要注意的是;目前只支持 FAT/FAT32格式的卡.。
  • 128GB容量TF卡相关验证:《ESP32测试使用128G TF卡
  • sdcard模块
    在这里插入图片描述

sdcard模块可以在MicroPython源码中找到。
在这里插入图片描述

"""
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.

Example usage on pyboard:

    import pyb, sdcard, os
    sd = sdcard.SDCard(pyb.SPI(1), pyb.Pin.board.X5)
    pyb.mount(sd, '/sd2')
    os.listdir('/')

Example usage on ESP8266:

    import machine, sdcard, os
    sd = sdcard.SDCard(machine.SPI(1), machine.Pin(15))
    os.mount(sd, '/sd')
    os.listdir('/')

"""

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=1320000):
        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[i] = 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[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) << 10 | csd[7] << 2 | csd[8] >> 6
            c_size_mult = (csd[9] & 0b11) << 1 | csd[10] >> 7
            read_bl_len = csd[5] & 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("[SDCard] 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:
                self.cmd(58, 0, 0, -4)  # 4-byte response, negative means keep the first byte
                ocr = self.tokenbuf[0]  # get first byte of response, which is OCR
                if not ocr & 0x40:
                    # SDSC card, uses byte addressing in read/write/erase commands
                    self.cdv = 512
                else:
                    # SDHC/SDXC card, uses block addressing in read/write/erase commands
                    self.cdv = 1
                # print("[SDCard] 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[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]
            if not (response & 0x80):
                # this could be a big-endian integer that we are getting here
                # if final<0 then store the first byte to tokenbuf and discard the rest
                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[0] == _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)[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
        if op == 5:  # get block size in bytes
            return 512



Soft SPI接线说明

# 接线说明:
# MISO -> GPTO13
# MOSI -> GPIO12
# SCK -> GPIO 14
# CS -> GPIO27
  • SPI通讯
1.GND-for the ground pins.
2.VCC-for the supply voltage.
3.MISO-for the SPI Master Input Slave Output pin.
4.MOSI-for the SPI Master Output Slave Input pin.
5.SCK-for the SPI Serial Clock pin.
6.CS-for the SPI Chip Select pin.

  1. gnd -用于接地插脚。
  2. vcc- 电源电压。
  3. miso-用于SPI主输入从输出引脚。
  4. mosi -用于SPI主输出从输入引脚。
  5. sck -用于SPI串行时钟引脚。
  6. cs -用于SPI芯片选择引脚。

  • esp32 Dev kit V1
    在这里插入图片描述
  • 本实例需要引入sdcard模块

运行代码前,需要先将sdcard模块保存到MicroPython设备当中。

在这里插入图片描述
在这里插入图片描述

实例代码

import os
from machine import Pin, SoftSPI
from sdcard import SDCard
# 接线说明:
# MISO -> GPTO13
# MOSI -> GPIO12
# SCK -> GPIO 14
# CS -> GPIO27
spisd=SoftSPI(-1, miso=Pin(13), mosi=Pin(12), sck=Pin(14))
sd=SDCard(spisd, Pin(27))
print('Root directory:{}'.format(os.listdir()))
vfs=os.VfsFat(sd)
os.mount(vfs,'/sd')
print('Root directory:{}'.format(os.listdir()))
os.chdir('sd')
print('SD Card contains:{}'.format(os.listdir()))

在这里插入图片描述

  • 添加对SD卡容量信息读取
import os
from machine import Pin, SoftSPI
from sdcard import SDCard
# 接线说明:
# MISO -> GPTO13
# MOSI -> GPIO12
# SCK -> GPIO 14
# CS -> GPIO27
spisd=SoftSPI(-1, miso=Pin(13), mosi=Pin(12), sck=Pin(14))
sd=SDCard(spisd, Pin(27))
print('Root directory:{}'.format(os.listdir()))
vfs=os.VfsFat(sd)
os.mount(vfs,'/sd')
r = os.statvfs('/sd')
print('SD capacity: {} B / {} M'.format(r[0] * r[2], r[0] * r[2]/1024/1024))
print('free space: {} B / {} M'.format(r[0] * r[3], r[0] * r[3]/1024/1024))
print('Root directory:{}'.format(os.listdir()))
os.chdir('sd')
print('SD Card contains:{}'.format(os.listdir()))

在这里插入图片描述

要通过ESP32-S3读取SD卡中的音频文件,并通过I2S输出,请按照以下步骤操作: 1. 首先,您需要将SD卡连接到ESP32-S3。您可以使用SPI协议连接SD卡,或使用ESP32-S3的SDMMC主机控制器。请确保正确配置SD卡相关引脚和协议。 2. 接下来,您需要使用ESP-IDF中的I2S驱动程序将音频数据从SD卡读取并通过I2S输出。您可以使用i2s_write_expand()函数来将16位PCM音频数据转换为I2S数据格式,并使用i2s_write()函数将数据发送到I2S总线。 3. 在使用I2S驱动程序之前,请确保正确配置I2S接口。您可以使用i2s_driver_install()函数安装I2S驱动程序,并使用i2s_set_pin()函数设置I2S引脚。请参考ESP-IDF文档以获取更多详细信息。 4. 最后,您需要编写代码来打开音频文件并从SD卡读取数据。您可以使用ESP-IDF中的文件系统API来访问SD卡上的文件,例如fopen()、fread()和fclose()函数。请确保正确处理文件读取错误和文件结束条件。 下面是一个示例代码片段,用于从SD卡读取16位PCM音频文件,并将其发送到I2S总线: ```c #include "esp_partition.h" #include "esp_vfs_fat.h" #include "driver/i2s.h" #define I2S_NUM 0 #define I2S_SAMPLE_RATE 44100 #define I2S_SAMPLE_BITS 16 #define I2S_CHANNEL_NUM 2 #define I2S_DMA_BUF_COUNT 8 void app_main() { // 初始化I2S接口 i2s_config_t i2s_config = { .mode = I2S_MODE_MASTER | I2S_MODE_TX, .sample_rate = I2S_SAMPLE_RATE, .bits_per_sample = I2S_SAMPLE_BITS, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = I2S_COMM_FORMAT_I2S, .dma_buf_count = I2S_DMA_BUF_COUNT, .dma_buf_len = 1024, .intr_alloc_flags = 0, }; i2s_pin_config_t i2s_pin_config = { .bck_io_num = 26, .ws_io_num = 25, .data_out_num = 33, .data_in_num = I2S_PIN_NO_CHANGE, }; i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL); i2s_set_pin(I2S_NUM, &i2s_pin_config); // 初始化SD卡 esp_vfs_fat_sdmmc_mount_config_t mount_config = { .format_if_mount_failed = true, .max_files = 5, }; sdmmc_host_t host = SDMMC_HOST_DEFAULT(); sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, NULL); // 打开音频文件 FILE* f = fopen("/sdcard/test.pcm", "rb"); if (!f) { printf("failed to open file\n"); return; } // 读取数据并发送到I2S总线 int16_t buf[1024]; size_t read_len; while ((read_len = fread(buf, sizeof(int16_t), 1024, f)) > 0) { size_t i2s_len = read_len * 2; uint8_t i2s_buf[i2s_len]; i2s_write_expand(I2S_NUM, buf, read_len, I2S_SAMPLE_BITS, i2s_buf, i2s_len); i2s_write(I2S_NUM, i2s_buf, i2s_len, portMAX_DELAY); } // 关闭文件和卸载SD卡 fclose(f); esp_vfs_fat_sdmmc_unmount(); } ``` 请注意,以上代码仅供参考,并且可能需要根据您的具体用例进行修改。另外,读取和发送音频数据时需要使用合适的缓冲区大小和数量,以避免数据溢出和下溢。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值