联盛德 HLK-W806 (十三): 运行FatFs读写FAT和exFat格式的SD卡/TF卡
目录
- 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明
- 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明
- 联盛德 HLK-W806 (三): 免按键自动下载和复位
- 联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD
- 联盛德 HLK-W806 (五): W801开发板上手报告
- 联盛德 HLK-W806 (六): I2C驱动SSD1306 128x64 OLED液晶屏
- 联盛德 HLK-W806 (七): 兼容开发板 LuatOS Air103
- 联盛德 HLK-W806 (八): 4线SPI驱动SSD1306/SSD1315 128x64 OLED液晶屏
- 联盛德 HLK-W806 (九): 软件SPI和硬件SPI驱动ST7789V液晶LCD
- 联盛德 HLK-W806 (十): 在 CDK IDE开发环境中使用WM-SDK-W806
- 联盛德 HLK-W806 (十一): 软件SPI和硬件SPI驱动ST7567液晶LCD
- 联盛德 HLK-W806 (十二): Makefile组织结构和编译流程说明
- 联盛德 HLK-W806 (十三): 运行FatFs读写FAT和exFat格式的SD卡/TF卡
关于SD卡和FatFs
SD卡和FatFs的介绍已经在 Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写 中详细说明, 对其工作机制和通信机制有兴趣的可以阅读. FatFs的作者写了一篇非常不错的介绍, How to Use MMC/SDC, 非常详细, 值得一读.
FatFs的移植
FatFs的文件结构
FatFs的文件结构如下:
├── diskio.c # 需要负责移植的开发人员实现的方法列表, 需要修改
├── diskio.h # 对外提供的头文件, 里面定义了状态和返回值的枚举
├── ff.c # FastFs的主要逻辑实现, 不需要修改
├── ffconf.h # 配置文件, 根据自己的环境和需求作修改
├── ff.h # FastFs头文件, 不需要修改
├── ffsystem.c # 与操作系统相关的实现, 不需要修改
├── ffunicode.c # 各个编码的字符集, 之前版本都在子目录, 现在合并到一个文件里, 方便多了
├── LICENSE.txt
移植需要实现的方法
FatFs已经将Fat格式的操作作了抽象化, 在新环境下运行FatFs, 只需要实现 diskio.c 中的几个方法
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
还需要实现RTC接口, 这样才能在创建文件时写入正确的时间
DWORD get_fattime (void);
W801/W806基于SPI的实现
完整的代码在演示用例下, 使用了最新的R0.14b版本的FatFs
其中对以上方法的实现是 fatfs_mmc.c 这个文件, 主要分成三个部分:
SPI基础方法
/* SPI transmit a byte */
static void MMC_SPI_TxByte(uint8_t data)
{
HAL_SPI_Transmit(&hspi, &data, 1, SPI_TIMEOUT);
}
/* SPI transmit buffer */
static void MMC_SPI_TxBuffer(uint8_t *buffer, uint16_t len)
{
HAL_SPI_Transmit(&hspi, buffer, len, SPI_TIMEOUT);
}
/* SPI receive a byte */
static uint8_t MMC_SPI_RxByte(void)
{
uint8_t dummy, data;
dummy = 0xFF;
HAL_SPI_TransmitReceive(&hspi, &dummy, &data, 1, SPI_TIMEOUT);
return data;
}
SD卡通信方法
/* wait SD ready */
static uint8_t MMC_ReadyWait(void)
{
uint8_t res;
uint32_t tickstart = HAL_GetTick();
/* if SD goes ready, receives 0xFF, timeout 500 */
do
{
res = MMC_SPI_RxByte();
} while ((res != 0xFF) && (HAL_GetTick() - tickstart < 500));
return res;
}
/* power on */
static void MMC_PowerOn(void)
{
uint8_t args[6];
uint32_t cnt = 0x1FFF;
/* transmit bytes to wake up */
MMC_CS_HIGH;
for(int i = 0; i < 10; i++)
{
MMC_SPI_TxByte(0xFF);
}
/* slave select */
MMC_CS_LOW;
/* make idle state */
args[0] = CMD0; /* CMD0:GO_IDLE_STATE */
args[1] = 0;
args[2] = 0;
args[3] = 0;
args[4] = 0;
args[5] = 0x95; /* CRC */
MMC_SPI_TxBuffer(args, sizeof(args));
/* wait response */
while ((MMC_SPI_RxByte() != 0x01) && cnt)
{
cnt--;
}
MMC_CS_HIGH;
MMC_SPI_TxByte(0XFF);
PowerFlag = 1;
}
/* power off */
static void MMC_PowerOff(void)
{
PowerFlag = 0;
}
/* check power flag */
static uint8_t MMC_CheckPower(void)
{
return PowerFlag;
}
/* receive data block */
static bool MMC_RxDataBlock(BYTE *buff, UINT len)
{
uint8_t token;
uint32_t tickstart = HAL_GetTick();
/* loop until receive a response or timeout, timeout 200ms */
do
{
token = MMC_SPI_RxByte();
} while((token == 0xFF) && (HAL_GetTick() - tickstart < 200));
/* invalid response */
if(token != 0xFE) return false;
/* receive data */
do {
*buff++ = MMC_SPI_RxByte();
} while(len--);
/* discard CRC */
MMC_SPI_RxByte();
MMC_SPI_RxByte();
return true;
}
/* transmit data block */
static bool MMC_TxDataBlock(const uint8_t *buff, BYTE token)
{
uint8_t resp = 0;
uint8_t i = 0;
/* wait SD ready */
if (MMC_ReadyWait() != 0xFF) return false;
/* transmit token */
MMC_SPI_TxByte(token);
/* if it's not STOP token, transmit data */
if (token != 0xFD)
{
MMC_SPI_TxBuffer((uint8_t*)buff, 512);
/* discard CRC */
MMC_SPI_RxByte();
MMC_SPI_RxByte();
/* receive response */
while (i <= 64)
{
resp = MMC_SPI_RxByte();
/* transmit 0x05 accepted */
if ((resp & 0x1F) == 0x05) break;
i++;
}
/* recv buffer clear */
while (MMC_SPI_RxByte() == 0);
}
/* transmit 0x05 accepted */
if ((resp & 0x1F) == 0x05) return true;
return false;
}
/* transmit command */
static BYTE MMC_SendCmd(BYTE cmd, uint32_t arg)
{
uint8_t crc, res;
/* wait SD ready */
if (MMC_ReadyWait() != 0xFF) return 0xFF;
/* transmit command */
MMC_SPI_TxByte(cmd); /* Command */
MMC_SPI_TxByte((uint8_t)(arg >> 24)); /* Argument[31..24] */
MMC_SPI_TxByte((uint8_t)(arg >> 16)); /* Argument[23..16] */
MMC_SPI_TxByte((uint8_t)(arg >> 8)); /* Argument[15..8] */
MMC_SPI_TxByte((uint8_t)arg); /* Argument[7..0] */
/* prepare CRC */
if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
else if(cmd == CMD8) crc = 0x87; /* CRC for CMD8(0x1AA) */
else crc = 1;
/* transmit CRC */
MMC_SPI_TxByte(crc);
/* Skip a stuff byte when STOP_TRANSMISSION */
if (cmd == CMD12) MMC_SPI_RxByte();
/* receive response */
uint8_t n = 10;
do {
res = MMC_SPI_RxByte();
} while ((res & 0x80) && --n);
return res;
}
diskio.c 接口方法的实现
因为篇幅比较长, 这里不贴完整代码了, 具体就是根据SD卡的标准, 维护一个状态变量, 在开始时加电初始化, 判断卡类型, 之后才能进行读写操作.
RTC 接口的实现
因为W806自带了RTC, 所以这个功能很容易实现, 只需要启动PMU后初始化一下RTC就可以使用. 这个方法返回的是4个字节的DWORD, 需要按照格式准备数据.
/**
* DWORD, 4-bytes, format:
*
* [25,31], year, [0,127] from 1980
* [21,24], month, [1,12]
* [16,20], day, [1,31]
* [11,15], hour, [0,23]
* [5,10], minute, [0,59]
* [0,4], second, [0,59]
*/
DWORD MMC_get_fattime(void)
{
DWORD val;
val = (rtc_time.Year - 80) << 25;
val += rtc_time.Month << 21;
val += rtc_time.Date << 16;
val += rtc_time.Hours << 11;
val += rtc_time.Minutes << 5;
val += rtc_time.Seconds;
return val;
}
FatFs参数的调整
在当前的移植中, 对以下参数进行了调整
- FF_USE_STRFUNC 0 -> 1 因为要用 f_puts 方法往文件里写字符串
- FF_USE_LFN 0 -> 1 开启长文件名支持, 否则文件名只支持8+3格式
- FF_LBA64 0 -> 1 这个选项和下面的选项, 是用于开启对exfat格式的支持, 这样才能正常挂载和读写64GB的TF卡
- FF_FS_EXFAT 0 -> 1
演示用例说明
接线
需要6根接线, 连线方式在演示用例的main.c中有说明
/******************************************************************************
* \brief Demo code of FatFs on SD Card with RTC
* \remarks test-board: HLK-W806-KIT-V1.0
*
* This test will perform the following tests:
* 1. Start RTC with time 2022-1-12 12:28:10
* 2. Mount SD Card
* 3. Scan SD Card and list files
* 4. Check if w806test_long_name.txt exists
* 5. Delete w806test_long_name.txt if it exists
* 6. Open w806test_long_name.txt, write and close
* 7. Open w806test_long_name.txt, read and close
* 8. Display time in main loop
*
* Pin Wiring:
* B14 -> CS/DAT3
* B15 -> SCK, SCL, CLK
* B16 -> MISO/DAT0/DO
* B17 -> MOSI/CMD/DI
* GND -> VSS
* 3.3V -> VDD
*
******************************************************************************/
演示代码的使用
正确连线后, 编译演示用例并烧录代码,
- 连接串口观察输出
- 在读卡器中插入SD卡(或TF卡)
- 按RESET键
- 可以观察到依次进行的初始化挂载, 展示文件列表, 检查并删除测试文件, 写入测试文件, 读取测试文件, 最后卸载SD/TF卡的过程
实际演示输出
2GB FAT TF卡
enter main␍␊
MPU_Init␍␊
RTC_Init␍␊
SPI_Init␍␊
MMC_disk_initialize␍␊
␍␊
CMD0␍␊
CMD8... succeeded, SDC V2+␍␊
ACMD41 ACMD41_HCS.. succeeded␍␊
CMD58 80 FF 80 00 type:04␍␊
f_mount succeeded␍␊
total:1922368KB, free:1922360KB␍␊
/w806test_long_name.txt 51␍␊
Test for w806test_long_name.txt...␊
Size: 51␊
Timestamp: 2022/01/12, 12:28␊
Attributes: ----A␊
file deleted␍␊
open to write: w806test_long_name.txt␍␊
close: w806test_long_name.txt␍␊
open to read: w806test_long_name.txt␍␊
read: w806test_long_name.txt␍␊
W806 SD Card I/O Example via SPI␊
-- Planet 4032-877␍␊
done␍␊
close: w806test_long_name.txt
Unmount sd card␍␊
f_unmount succeeded
64GB exFat TF卡
enter main␍␊
MPU_Init␍␊
RTC_Init␍␊
SPI_Init␍␊
MMC_disk_initialize␍␊
␍␊
CMD0␍␊
CMD8... succeeded, SDC V2+␍␊
ACMD41 ACMD41_HCS.. succeeded␍␊
CMD58 C0 FF 80 00 type:0C␍␊
f_mount succeeded␍␊
Total:61036544KB, Free:61029120KB␍␊
/first.txt 54␍␊
/System Volume Information/WPSettings.dat 12␍␊
/System Volume Information/IndexerVolumeGuid 76␍␊
/? 504400␍␊
/? 12608␍␊
Test for w806test_long_name.txt...␊
Size: 51␊
Timestamp: 2022/01/12, 12:28␊
Attributes: ----A␊
file deleted␍␊
open to write: w806test_long_name.txt␍␊
close: w806test_long_name.txt␍␊
open to read: w806test_long_name.txt␍␊
read: w806test_long_name.txt␍␊
W806 SD Card I/O Example via SPI␊
-- Planet 4032-877␍␊
done␍␊
close: w806test_long_name.txt␍␊
Unmount sd card␍␊
f_unmount succeeded
结束
以上是对W801/W806上移植R0.14b版本FatFs的说明, 演示用例因为是作为演示使用, 所以代码并未根据实际需要进行组织, 并且使用了大量的printf.
如果在项目中使用, fatfs_mmc.c 基本可以复用, 但是在main.c中的方法需要重新组织.