最底层: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。