最底层:SPI只负责收发字节
/* 发送一个字节,同时接收一个字节 */
uint8_t SPI4_RW(uint8_t dat);
/* 连续接收len个字节,内部不断发送0xFF产生时钟 */
void SPI4_RxMulti(uint8_t *buff, uint32_t len);
/* 连续发送len个字节 */
void SPI4_TxMulti(const uint8_t *buff, uint32_t len);
实际上,后两个都是调用第一个实现的。
SPI只负责传输,不理解“文件”“扇区”“SD命令”。
中间层:SD驱动负责把字节组织成SD协议
在SPI收发基础上,sd_spi.c实现:
/* 初始化GPIO、供电和SPI外设 */
void SD_SPI_Init(void);
/* 发送CMD0、CMD8、ACMD41等,完成SD卡协议初始化 */
DSTATUS SD_SPI_Initialize(void);
/* 查询SD卡状态 */
DSTATUS SD_SPI_Status(void);
/* 从sector开始,读取count个扇区到buff */
DRESULT SD_SPI_ReadSectors(BYTE *buff, DWORD sector, UINT count);
/* 将buff写入从sector开始的count个扇区 */
DRESULT SD_SPI_WriteSectors(const BYTE *buff, DWORD sector, UINT count);
/* 获取卡的总扇区数 */
DWORD SD_SPI_GetSectorCount(void);
例如:
SD_SPI_ReadSectors(buffer, 100, 2);
意思是:
从第100号扇区开始
读取100、101两个扇区
共1024字节
保存到buffer
驱动内部负责发送CMD18、等待数据令牌、接收数据等细节。
给FatFs的接口:diskio.c中的五个函数
真正交给FatFs调用的是这五个接口:
| FatFs接口 | 作用 | 当前对接的SD驱动 |
|---|---|---|
disk_initialize() | 初始化磁盘 | SD_SPI_Initialize() |
disk_status() | 查询磁盘状态 | SD_SPI_Status() |
disk_read() | 读取若干扇区 | SD_SPI_ReadSectors() |
disk_write() | 写入若干扇区 | SD_SPI_WriteSectors() |
disk_ioctl() | 同步写入、查询容量等 | SD状态和容量等接口 |
其中disk_ioctl()常用命令:
CTRL_SYNC /* 等待底层待完成的写操作结束 */
GET_SECTOR_COUNT /* 总扇区数 */
GET_SECTOR_SIZE /* 每扇区字节数,当前为512 */
GET_BLOCK_SIZE /* 擦除块包含的扇区数,主要供格式化使用 */
当前固定512字节配置下,FatFs已经知道扇区大小;这里仍实现了对应查询。
另外,当前启用文件时间戳,还需要:
DWORD get_fattime(void);
接好之后,应用就直接操作文件
先初始化并挂载:
SD_SPI_Init();
f_mount(&fs, "0:", 1);
随后应用使用:
f_open();
f_read();
f_write();
f_close();
例如读取文件,实际调用关系是:
应用:f_read()
↓
FatFs:查找文件对应的簇和扇区
↓
diskio:disk_read()
↓
SD驱动:SD_SPI_ReadSectors()
↓
SD协议:发送CMD17/CMD18,接收数据块
↓
SPI:SPI4_RW()
移植时,重点就是:让底层能够正确读写SD卡扇区,再把这套能力接到diskio.c的五个接口上。之后文件名、目录、空间分配等工作就交给FatFs。