细说STM32单片机SD卡应用函数中调用各类库函数的方法

目录

一、阻塞模式直接读写访问SD卡的流程

1、阻塞式访问SD卡的文件架构

2、展开这个文件,查看其内的函数

3、这些函数的使用方法

二、DMA模式直接访问读写SD卡的流程

1、DMA访问SD卡的文件架构

2、展开这个文件后,看其内的函数

3、这些函数的使用方法

三、创建FATFS文件系统后访问SD卡的流程

四、创建FATFS文件系统并通过阻塞模式访问SD卡 

1、文件架构

(1)路径1,是2种方法的公共路径,用于初始化

(2)路径2,使用通用Disk IO函数

1)以f_read()为例解析

2)通用Disk IO函数通过指针指向器件的专用Disk IO函数

(3)路径3,使用SD专用Disk IO函数

五、创建FATFS文件系统并通过DMA模式访问SD卡 

1、文件架构

2、调用函数的流程 

3、回调函数的对应关系

4、其它函数


        本文旨在详细介绍STM32单片机访问(阻塞模式、非阻塞模式)SD卡,和STM32单片机开启文件系统FATFS后访问SD卡,在私有应用函数里调用STM32CubeIDE自动生成的各类驱动函数的最佳路径和方法。选择不同的驱动函数,编程的路径和方法是不一样的,但正确编程的结果是一样的。

        为了发布本文,本文作者已经发布相关文章4篇,为的是证明,选择不同的驱动函数,编程的路径和方法是不一样的。但一定有一种方法是最简单和最实用的。这些参考文章依次为:

        参考文章1:细说STM32单片机使用轮询模式直接访问SD卡的方法及其应用-优快云博客  https://wenchm.blog.youkuaiyun.com/article/details/149218456?spm=1011.2415.3001.5331

        参考文章2:细说STM32单片机使用DMA模式直接访问SD卡的方法及其应用_stm32 dma stream-优快云博客  https://wenchm.blog.youkuaiyun.com/article/details/149242997?spm=1011.2415.3001.5331

        参考文章3:细说STM32单片机SD卡的FatFS文件系统并使用轮询模式访问SD卡的方法及其应用_bsp code for sd-优快云博客  https://wenchm.blog.youkuaiyun.com/article/details/149288991?spm=1011.2415.3001.5331

        参考文章4:细说STM32单片机SD卡的FatFS文件系统并使用DMA模式访问SD卡的方法及其应用-优快云博客  https://wenchm.blog.youkuaiyun.com/article/details/149643115?spm=1011.2415.3001.5331

一、阻塞模式直接读写访问SD卡的流程

1、阻塞式访问SD卡的文件架构

        所需要的函数都在这里,且只能是在这里。

 

2、展开这个文件,查看其内的函数

3、这些函数的使用方法

        在自己的应用里,调用stm32f4xx_hal_sd.c里面的HAL_前缀或SD_前缀的函数。比如

二、DMA模式直接访问读写SD卡的流程

1、DMA访问SD卡的文件架构

        所需要的函数都在这里,且只能是这里。直接访问无论何种模式文件位置都是一样的。

 

2、展开这个文件后,看其内的函数

        这个函数与阻塞模式的函数架构一模一样:

3、这些函数的使用方法

        在自己的应用里,调用stm32f4xx_hal_sd.c里面的HAL_前缀或SD_前缀的函数。与阻塞模式的区别是,DMA模式的函数,通过DMA转换完成后,应该重写回调函数,以证明读写完成。

        比如:

/* HAL_SD_WriteBlocks_DMA(), Write SD card */
void SDCard_TestWrite_DMA() //DMA mode, test write
{
    printf("\r\n*** DMA Writing blocks *** \r\n\r\n");

    for(uint16_t i=0;i<BLOCKSIZE; i++)
    SDBuf_TX[i]=i; //generate data

    printf("Writing block 6. \r\n");
    printf("Data in [10:15] is: %d ",SDBuf_TX[10]);

    for (uint16_t j=11; j<=15;j++)
    {
        printf(", %d", SDBuf_TX[j]);
    }
    printf("\r\nHAL_SD_WriteBlocks_DMA() is to be called. \r\n");

    uint32_t BlockAddr=6; //Block Address
    uint32_t BlockCount=1; //Block Count
    HAL_SD_WriteBlocks_DMA(&hsd,SDBuf_TX,BlockAddr,BlockCount); //can erase block automatically
}

        回调函数是弱函数,需要重写的。

回调函数是弱函数,需要重写的。
/**
* @brief Rx Transfer completed callbacks
* @param hsd: Pointer SD handle
* @retval None
*/
__weak void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    /* Prevent unused argument(s) compilation warning */
    UNUSED(hsd);

    /* NOTE : This function should not be modified, when the callback is needed,
       the HAL_SD_RxCpltCallback can be implemented in the user file
    */
}

        重写后:

/* SD write callback func */
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
    printf("DMA write complete. \r\n");
    printf("HAL_SD_TxCpltCallback() is called. \r\n");
    printf("Reselect menu item or reset. \r\n");
}

/* SD read callback func */
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    printf("DMA read complete. \r\n");
    printf("HAL_SD_RxCpltCallback() is called. \r\n");
    printf("Data in [10:15] is: %d\r\n",SDBuf_RX[10]);

    for (uint16_t j=11; j<=15;j++)
    {
        printf(", %d", SDBuf_RX[j]);
    }
    printf("\r\nReselect menu item or reset. \r\n");
}

三、创建FATFS文件系统后访问SD卡的流程

        如图,创建文件系统后,有3个路径访问SD卡,其中,

        路径1,是根据IDE生成的文件,对SD卡及FATFS的初始化路径,是自动生成的,一般不需要编程;

        路径2,是根据IDE生成的文件,利用FATFS的通用文件ff_c里通用函数,f_前缀,按照图示路径,访问SD卡;

        路径3,是根据IDE生成的文件,利用FATFS的针对SD卡生成的专用驱动函数,SD_前缀、BSP_前缀,按照图示路径,访问SD卡;

 

四、创建FATFS文件系统并通过阻塞模式访问SD卡 

1、文件架构

        SD卡开启了FATFS后,会在文件树上,产生2个文件夹FATFS和MiddleWares:

(1)路径1,是2种方法的公共路径,用于初始化

        fatfs.c/.h → ff_gen_drv.h/.c → sd_diskio.h/.c

        具体:

        main.c → 初始化所有配置过的外设,其中,MX_FATFS_Init();  → 在此函数里调用fatfs.c里的void MX_FATFS_Init(void),函数中只有一句,retSD = FATFS_LinkDriver(&SD_Driver, SDPath);这个函数调用ff_gen_drv.c里的FATFS_LinkDriver(),把SD_Driver(驱动器)和SDPath(磁盘路径)关联起来了,其中SD_Driver,在sd_diskio.c/.h里定义SDPathfatfs.c里定义

(2)路径2,使用通用Disk IO函数

        初始化后,可以使用路径2的方法操作SD卡,在自己的应用里,调用ff.c里的函数,比如f_read() → 进一步地,在f_read()里调用diskio.c/.h里的disk_为前缀的5个函数(至少1个)→ 通过指针映射到sd_diskio.c/.h。→ bsp_driver_sd.c → HAL_驱动。

        具体:

        先调用f_open()打开(创建)一个文件,然后再调用ff.c里的其他函数,比如f_read()、f_write(),最后调用f_close(&file);关闭文件。

1)以f_read()为例解析

        APP  → ff.c里的f_read()   → diskio.c里的disk_read()   → sd_diskio.c里的SD_read() → bsp_driver_sd.c里的BSP_SD_ReadBlocks() → stm32f4xx_hal_sd.c里HAL驱动HAL_SD_ReadBlocks()。

extern Disk_drvTypeDef disk;
 
/**
* @brief Reads Sector(s)
* @param pdrv: Physical drive number (0..)
* @param *buff: Data buffer to store read data
* @param sector: Sector address (LBA)
* @param count: Number of sectors to read (1..128)
* @retval DRESULT: Operation result
*/
DRESULT disk_read (
        BYTE pdrv, 		 /* Physical drive nmuber to identify the drive */
        BYTE *buff,		 /* Data buffer to store read data */
        DWORD sector, 	 /* Sector address in LBA */
        UINT count		 /* Number of sectors to read */
        )
{
    DRESULT res;
 
    res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
    return res;
}

        函数其中有两个重要的结构体,ff_gen_drv.h里定义:

 

        disk.drv 是一个指向磁盘驱动器数组的指针,pdrv 是物理驱动器号,用于索引到具体的驱动器。

        disk.drv[pdrv] 获取该数组中索引(磁盘列表)为 pdrv 的驱动器。并且它的数据格式是Diskio_drvTypeDef,而结构体Diskio_drvTypeDef的一个成员就是disk_read()。表示为:

disk.drv[pdrv]->disk_read(),获取该驱动器的读取函数。

2)通用Disk IO函数通过指针指向器件的专用Disk IO函数

        在sd_diskio.h中声明:

extern const Diskio_drvTypeDef SD_Driver;

        在sd_diskio.c中定义结构体Diskio_drvTypeDef 的实体SD_Driver。

const Diskio_drvTypeDef SD_Driver =
{
    SD_initialize,
    SD_status,
    SD_read,
#if _USE_WRITE == 1
    SD_write,
#endif /* _USE_WRITE == 1 */
 
#if _USE_IOCTL == 1
    SD_ioctl,
#endif /* _USE_IOCTL == 1 */
};

        对应地,在diskio.h/.c中存在类似的声明和定义:

/*---------------------------------------*/
/* Prototypes for disk control functions */


DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
DWORD get_fattime (void);

        对应地,在ff_gen_drv.h中定义结构体Diskio_drvTypeDef:

/**
  * @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;

         于是,diskio.c里的函数就和sd_diskio.c里的函数一一对应起来了。

        定义这个结构体变量实体内部成员,为器件的专用Disk IO函数,这样就通过指针,把通用Disk IO函数指向了器件专用Disk IO函数。

        使用中间件里面生成的函数,去访问SD卡。这个方法的特点是操作函数,并不区分硬件是什么?

        基础的行为函数,比如,创建、读、写、打开文件、关闭文件、等,f_前缀,f_putc。

        例如:

void fatTest_ReadBinFile(TCHAR *filename)
{
	FIL file;
	FRESULT res=f_open(&file,filename, FA_READ);

	if(res == FR_OK)
	{
		TCHAR str[50];
		f_gets(str,50, &file);								//Read 1 string

		UINT  bw=0;											//The actual number of bytes read
		uint32_t pointCount, sampFreq;						//Save variables for reading data
		f_read(&file, &pointCount, sizeof(uint32_t), &bw);	//Number of data points
		f_read(&file, &sampFreq, sizeof(uint32_t), &bw);	//Sampling frequency

		uint32_t  value;
		for(uint16_t i=0; i< pointCount; i++)
			f_read(&file, &value, sizeof(uint32_t), &bw);

		//USART display
		printf("Reading BIN file: %s",str);
		printf("Sampling freq: %ld\r\n",sampFreq);
		printf("Point count: %ld\r\n",pointCount);
	}
	else if (res==FR_NO_FILE)
		printf("File does not exist.\r\n");
	else
		printf("f_open() error.\r\n");

	f_close(&file);
}

(3)路径3,使用SD专用Disk IO函数

         在sd_diskio.c的沙箱里创建私有函数 → 调用sd_diskio.c里的SD_read() → bsp_driver_sd.c里的BSP_SD_ReadBlocks() → stm32f4xx_hal_sd.c里HAL驱动HAL_SD_ReadBlocks()。

/* SD_write(), Write SD card */
void SDCard_TestWrite()	//DMA mode, test write
{
	printf("\r\n***  Writing blocks *** \r\n\r\n");

	for(uint16_t i=0;i<BLOCKSIZE; i++)
		SDBuf_TX[i]=i; 						//generate data

	printf("Writing block 6. \r\n");
	printf("Data in [10:15] is: %d ",SDBuf_TX[10]);

	for (uint16_t j=11; j<=15;j++)
	{
		printf(", %d", SDBuf_TX[j]);
	}
	printf("\r\n SD_write() is to be called. \r\n");

	uint32_t BlockAddr=6;		//Block Address
	uint32_t BlockCount=1;		//Block Count
	BYTE lun={};

	if(SD_write(lun,SDBuf_TX,BlockAddr,BlockCount) == RES_OK)  //can erase block automatically
	//SD_write(lun,SDBuf_TX,BlockAddr,BlockCount);
	{
		printf("SD_write complete. \r\n");
	}
}

        类似SD_write()、 SD_read()这样的函数,比如上面调用的SD_write()是否开启DMA,SD_write()的实现的代码是不一样的,但外函数的表象是一样的,所以类似上面的函数,无论在DMA模式还是在阻塞模式,都可以正常使用。

、创建FATFS文件系统并通过DMA模式访问SD卡 

1、文件架构

2、调用函数的流程 

        例如,在应用里给SD卡写操作,建立一个私有函数,这个私有函数要建立在sd_diskio.c里面。

        SDCard_TestRead_DMA(), →在这个函数里调用SD_read() , → 在这个函数里调用BSP_SD_ReadBlocks_DMA(),这个函数是弱函数,是可以重写的,→ 在这个函数里调用HAL_SD_ReadBlocks_DMA();

3、回调函数的对应关系

        调用BSP_SD_ReadBlocks_DMA()完成DMA转换后的回调函数void BSP_SD_ReadCpltCallback(void)在sd_diskio.c里面都写好了的。不需要用户再去重写。

/**
* @brief Rx Transfer completed callbacks
* @param hsd: SD handle
* @retval None
*/
void BSP_SD_ReadCpltCallback(void)
{
    ReadStatus = 1;
}

        调用HAL_SD_ReadBlocks_DMA()完成DMA转换后的回调函数void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)在bsp_driver_sd.c里面都写好了的。并且就是调用BSP_SD_ReadBlocks_DMA()的回调函数BSP_SD_ReadCpltCallback不需要用户再去重写。

/**
* @brief Rx Transfer completed callback
* @param hsd: SD handle
* @retval None
*/
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
    BSP_SD_ReadCpltCallback();
}

 

4、其它函数

  • 如,SD_read、SD_write、SD_status、SD_ioctl、SD_initialize、SD_CheckStatusWithTimeout、SD_CheckStatusWithTimeout都经历同样的操作流程:APP里调用sd_diskio.c里面前缀为SD_的函数,→调用或重写bsp_driver_sd.c里面的前缀为BSP_的弱函数,其中的回调函数也是自动完成的→调用bsp_driver_sd.c里面的HAL_前缀的函数,其中的回调函数也是自动完成的,并且等效于BSP_前缀的回调函数
  • 除了上面的操作SD卡的流程,其他的函数,先在自己的应用里调用bsp_driver_sd.c,然后→调用HAL_前缀的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wenchm

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值