CubeMX中已经集成了FatFS库,生成代码时能够自动生成SD卡驱动并整合到FatFS中,移植起来非常的方便。下面以SD卡为例,讲解移植方法。
CubeMX配置
SDIO配置
首先需要配置SDIO,通常来讲都是按照下图的模板进行配置:
其中Mode
可以自行选择使用1条或4条数据线。下方的Parameter Steeings
一般不需要修改。
检测引脚配置
SD卡有一个检测引脚,这个可以连接到STM32的任意引脚上,我连接到了PC7,因此需要配置PC7引脚为输入模式。按照规格书,这个检测引脚为低电平有效,因此如果没有外置的上拉电阻,需要配置为上拉模式。这里我已经在外部接了上拉电阻,因此配置为无上下拉。
FatFS配置
在左侧的Middleware
中选择FATFS
,会进入FATFS配置界面:
在这里可以选择SD卡或者用户定义的设备(一般为FLASH或EEPROM),这里勾选SD Card
。
下方可以配置FatFS的各项参数,其实就和修改ffconf.h
的宏定义一样,各选项的作用可查看野火或网上的教程。
如果你和我一样,使用了SD卡检测引脚,那么还需要配置Platform Settings
,选择检测引脚:
至此,CubeMX配置完成,生成代码即可。
代码讲解
文件结构
打开生成的工程,会发现其中与FatFS相关的文件有这些:
涉及底层驱动实现的文件主要有以下三个:
ff_gen_drv.c:FATFS提供的通用驱动文件的实现
/**
* @brief Disk IO Driver structure definition
*/
typedef struct
{
DSTATUS (*disk_initialize) (BYTE); /*!< Initialize Disk Drive */
DSTATUS (*disk_status) (BYTE); /*!< Get Disk Status */
DRESULT (*disk_read) (BYTE, BYTE*, DWORD, UINT); /*!< Read Sector(s) */
#if _USE_WRITE == 1
DRESULT (*disk_write) (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0 */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT (*disk_ioctl) (BYTE, BYTE, void*); /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */
}Diskio_drvTypeDef;
/**
* @brief Global Disk IO Drivers structure definition
*/
typedef struct
{
uint8_t is_initialized[_VOLUMES];
const Diskio_drvTypeDef *drv[_VOLUMES];
uint8_t lun[_VOLUMES];
volatile uint8_t nbr;
}Disk_drvTypeDef;
/* Exported functions ------------------------------------------------------- */
uint8_t FATFS_LinkDriver(const Diskio_drvTypeDef *drv, char *path);
uint8_t FATFS_UnLinkDriver(char *path);
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, BYTE lun);
uint8_t FATFS_UnLinkDriverEx(char *path, BYTE lun);
uint8_t FATFS_GetAttachedDriversNbr(void);
sd_diskio.c:针对SD底层驱动实现,封装成为通用的底层驱动API
//sd_diskio.c文件内容
const Diskio_drvTypeDef SD_Driver = {
SD_initialize, //指向diskio.c中的disk_initialize()
SD_status, //指向diskio.c中的disk_status()
SD_read, //指向diskio.c中的disk_read()
#if _USE_WRITE == 1
SD_write, //指向diskio.c中的disk_write()
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
SD_ioctl, //指向diskio.c中的disk_ioctl()
#endif /* _USE_IOCTL == 1 */
}; //定义Diskio_drvTypeDef类型的SD_Driver
/* -------------------以上各函数的实现---------------------------*/
DSTATUS SD_initialize(BYTE lun){
Stat = STA_NOINIT;
if(BSP_SD_Init() == MSD_OK){
Stat &= ~STA_NOINIT;
}
return Stat;
}
DSTATUS SD_status(BYTE lun){
Stat = STA_NOINIT;
if(BSP_SD_GetCardState() == MSD_OK){
Stat &= ~STA_NOINIT;
}
return Stat;
}
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count){
DRESULT res = RES_ERROR;
uint32_t timeout = 100000;
if(BSP_SD_ReadBlocks((uint32_t*)buff,(uint32_t) (sector), count, SD_DATATIMEOUT) == MSD_OK){
while(BSP_SD_GetCardState()!= MSD_OK){
if (timeout-- == 0)
return RES_ERROR;
}
res = RES_OK;
}
return res;
}
#if _USE_WRITE == 1
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count){
DRESULT res = RES_ERROR;
uint32_t timeout = 100000;
if(BSP_SD_WriteBlocks((uint32_t*)buff,(uint32_t)(sector),count, SD_DATATIMEOUT) == MSD_OK){
while(BSP_SD_GetCardState()!= MSD_OK){
if (timeout-- == 0)
return RES_ERROR;
}
res = RES_OK;
}
return res;
}
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff){
DRESULT res = RES_ERROR;
BSP_SD_CardInfo CardInfo;
if (Stat & STA_NOINIT) return RES_NOTRDY;
switch (cmd){
/* Make sure that no pending write process */
case CTRL_SYNC :
res = RES_OK;
break;
/* Get number of sectors on the disk (DWORD) */
case GET_SECTOR_COUNT :
BSP_SD_GetCardInfo(&CardInfo);
*(DWORD*)buff = CardInfo.LogBlockNbr;
res = RES_OK;
break;
/* Get R/W sector size (WORD) */
case GET_SECTOR_SIZE :
BSP_SD_GetCardInfo(&CardInfo);
*(WORD*)buff = CardInfo.LogBlockSize;
res = RES_OK;
break;
/* Get erase block size in unit of sector (DWORD) */
case GET_BLOCK_SIZE :
BSP_SD_GetCardInfo(&CardInfo);
*(DWORD*)buff = CardInfo.LogBlockSize;
res = RES_OK;
break;
default:
res = RES_PARERR;
}
return res;
}
#endif /* _USE_IOCTL == 1 */
bsp_driver_sd.c:HAL库的二次封装,将基于SD卡的操作都在该文件下实现
//bsp_driver_sd.h文件内容
uint8_t BSP_SD_Init(void);
uint8_t BSP_SD_ITConfig(void);
void BSP_SD_DetectIT(void);
void BSP_SD_DetectCallback(void);
uint8_t BSP_SD_ReadBlocks(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_WriteBlocks(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks, uint32_t Timeout);
uint8_t BSP_SD_ReadBlocks_DMA(uint32_t *pData, uint32_t ReadAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_WriteBlocks_DMA(uint32_t *pData, uint32_t WriteAddr, uint32_t NumOfBlocks);
uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr);
void BSP_SD_IRQHandler(void);
void BSP_SD_DMA_Tx_IRQHandler(void);
void BSP_SD_DMA_Rx_IRQHandler(void);
uint8_t BSP_SD_GetCardState(void);
void BSP_SD_GetCardInfo(HAL_SD_CardInfoTypeDef *CardInfo);
uint8_t BSP_SD_IsDetected(void);
void BSP_SD_AbortCallback(void);
void BSP_SD_WriteCpltCallback(void);
void BSP_SD_ReadCpltCallback(void);
其余文件都是FatFS中的,其作用可查看相关教程。
初始化流程
为了更灵活的使用生成的代码,我们需要对其初始化流程有一个大致的了解。
查看生成的main
函数,发现其仅仅调用了这两个初始化函数:
MX_SDIO_SD_Init();
MX_FATFS_Init();
MX_SDIO_SD_Init()
函数在main.c
文件中定义:
static void MX_SDIO_SD_Init(void)
{
hsd.Instance = SDIO;
hsd.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING;
hsd.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE;
hsd.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE;
hsd.Init.BusWide = SDIO_BUS_WIDE_1B; //这里一定要改为1B
hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE;
hsd.Init.ClockDiv = 0;
}
此函数主要对hsd
结构体进行了一些初始化配置,但是没有应用。如果你之前生成过不含FatFS的SD代码,你会发现这里少了HAL_SD_Init()
和HAL_SD_ConfigWideBusOperation()
,它们被搬到了bsp_driver_sd.c
中,后面会讲到。
注意:这里的hsd.Init.BusWide
一定要改为1B
,Cube自动生成的参数是4B
这里就不得不说一下HAL库初始化过程中对SDIO配置的次数了。目前我查看代码得到的是2次:
第一次,HAL_SD_Init()
函数中,使用默认的400khz、1B,进行CMD命令初始化。
第二次,CMD命令初始化完成后,在HAL_SD_ConfigWideBusOperation()
函数中,时钟频率和宽度修改为cubemx中的设置。
MX_FATFS_Init()
函数在fatfs.c
文件中定义:
void MX_FATFS_Init(void)
{
/*## FatFS: Link the SD driver ###########################*/
retSD = FATFS_LinkDriver(&SD_Driver, SDPath);
}
递归的找下去,实际是调用了这个函数:
uint8_t FATFS_LinkDriverEx(const Diskio_drvTypeDef *drv, char *path, uint8_t lun)
{
uint8_t ret = 1;
uint8_t DiskNum = 0;
if(disk.nbr < _VOLUMES)
{
disk.is_initialized[disk.nbr] = 0;
disk.drv[disk.nbr] = drv;
disk.lun[disk.nbr] = lun;
DiskNum = disk.nbr++;
path[0] = DiskNum + '0';
path[1] = ':';
path[2] = '/';
path[3] = 0;
ret = 0;
}
return ret;
}
这部分初始化了路径和磁盘号相关参数。
注意:到这里为止,SDIO外设还没有完成初始化,因为只对结构体参数进行了配置,并没有实际应用
CubeMX生成的代码,将SDIO的初始化放到了FatF的挂载部分,当执行f_mount()
挂载函数时,会调用到find_volume()
,而后调用disk_initialize()
。函数内容如下:
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat = RES_OK;
if(disk.is_initialized[pdrv] == 0)
{
stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);//这里通向初始化函数
if(stat == RES_OK)
{
disk.is_initialized[pdrv] = 1;
}
}
return stat;
}
disk.drv[pdrv]->disk_initialize(disk.lun[pdrv])
指向了定义在`sd_diskio.c
文件中的SD_initialize()
函数,其内容如下:
DSTATUS SD_initialize(BYTE lun)
{
Stat = STA_NOINIT;
#if !defined(DISABLE_SD_INIT)
if(BSP_SD_Init() == MSD_OK)
{
Stat = SD_CheckStatus(lun);
}
#else
Stat = SD_CheckStatus(lun);
#endif
return Stat;
}
其中调用了BSP_SD_Init()
,其内容如下:
__weak uint8_t BSP_SD_Init(void)
{
uint8_t sd_state = MSD_OK;
/* Check if the SD card is plugged in the slot */
if (BSP_SD_IsDetected() != SD_PRESENT)
{
return MSD_ERROR;
}
/* HAL SD initialization */
sd_state = HAL_SD_Init(&hsd);
/* Configure SD Bus width (4 bits mode selected) */
if (sd_state == MSD_OK)
{
/* Enable wide operation */
if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
{
sd_state = MSD_ERROR;
}
}
return sd_state;
}
可以了,我们看到这里总算调用了两个SDIO初始化函数,至此,我们的SD设备初始化完成。
移植现有工程
根据我们之前的分析,将生成的代码移植到现有工程,需要移植FatFS文件和SD驱动文件。另外,由于在SDIO初始化时会调用MX相关函数,因此stm32f4xx_hal_msp.c
文件中的SDIO部分也需要移植。
复制文件
将生成的代码目录下的./FATFS/App
./FATFS/Target
./Middlewares/Third_Party/FatFs/src
复制到目标工程目录下的FATFS文件夹中。
Keil中新建Group,命名为FATFS,添加相关文件:
添加include路径(参考自己的路径修改):
注释掉ffconf.h
中的includes
,防止编译错误(自动生成的工程不会报错,原因未知。报错是由于include顺序造成的,提示找不到相关定义):
此时应该就可以正常编译并运行了,附上一份测试程序,需要启用MicroLIB
:
void Fatfs_test(void)
{
uint32_t byteswritten; /* File write counts */
uint32_t bytesread; /* File read counts */
uint8_t wtext[] = "This is STM32 working with FatFs"; /* File write buffer */
uint8_t rtext[100]; /* File read buffers */
char filename[] = "STM32cube.txt";
char SensorBuff[100];
printf("********* STM32CubeMX FatFs Example *********\r\n\r\n");
if(f_mount(&SDFatFS,SDPath,1) == FR_OK){
printf("f_mount sucess!!! \r\n");
if(f_open(&SDFile,filename,FA_CREATE_ALWAYS|FA_WRITE) == FR_OK){
printf("f_open file sucess!!! \r\n");
if(f_write(&SDFile,wtext,sizeof(wtext),&byteswritten) == FR_OK){
printf("f_write file sucess!!! \r\n");
printf("f_write Data : %s\r\n",wtext);
if(f_close(&SDFile) == FR_OK)
printf("f_close sucess!!! \r\n");
else
printf("f_close error : %d\r\n",retSD);
}
else
printf("f_write file error\r\n");
}
else
printf("f_open file error\r\n");
}else
printf("f_mount error : %d \r\n",retSD);
retSD = f_open(&SDFile, filename, FA_READ);
if(retSD)
printf("f_open file error : %d\r\n",retSD);
else
printf("f_open file sucess!!! \r\n");
retSD = f_read(&SDFile, rtext, sizeof(rtext), (UINT*)&bytesread);
if(retSD)
printf("f_read error!!! %d\r\n",retSD);
else{
printf("f_read sucess!!! \r\n");
printf("f_read Data : %s\r\n",rtext);
}
retSD = f_close(&SDFile);
if(retSD)
printf("f_close error!!! %d\r\n",retSD);
else
printf("f_close sucess!!! \r\n");
if(bytesread == byteswritten)
printf("FatFs is working well!!!\r\n");
if(f_open(&SDFile,(const char*)"Sensor.csv",FA_CREATE_ALWAYS|FA_WRITE) == FR_OK){
printf("Sensor.csv was opened/created!!!\r\n");
sprintf(SensorBuff, "Item,Temp,Humi,Light\r\n");
f_write(&SDFile,SensorBuff,strlen(SensorBuff),&byteswritten);
for(int i = 0; i < 10; i++){
sprintf(SensorBuff, "%d,%d,%d,%d\r\n",i + 1, i + 20, i + 30, i + 40);
f_write(&SDFile,SensorBuff,strlen(SensorBuff),&byteswritten);
f_sync(&SDFile);
}
f_close(&SDFile);
}
}
直接调用Fatfs_test()
即可。
如果你发现STM32cube.txt
文件打开失败,是由于FatFs默认没有开启长文件名支持导致的。需要将_USE_LFN
设置为0以外的数值,比如2。
设置完后,你会发现编译报错,缺少两个函数定义。这是缺少大小写转换函数,你需要把_CODE_PAGE
改为936并导入源码中的cc936.c
文件。
参考文章:
STM32CubeMX系列|FATFS文件系统
STM32F1 HAL库读写SD卡的操作要点
如何设置FatFs文件系统支持长文件名