使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统

背景

在本系列的前两篇文章(
使用vscode + gcc进行 STM32 单片机开发(一)编译及调试
使用vscode + gcc进行 STM32 单片机开发(二)gcc环境 移植rtthread)

的基础上,我想再多写一点,一方面是想试试vscode环境开发STM32到底怎么样;另一方面是在和同事的私下讨论的时候,讨论到STM32的SD卡不怎么好用。

综上所示,本文主要描述的是在vscode + gcc的环境下,配合rt-thread RTOS系统,实现STM32的SD卡读写及文件系统的移植,并给出最终的SD卡读写速率测试结果。



相关知识

在阅读本文之前,为了确保有一个良好的阅读体验,需要提前了解一些知识。

  1. 本系列的前两篇文章,具体链接请见本文开头
  2. SD卡的基础知识,包括但不限于:
    · SD卡、MicroSD卡 、MMC卡、TF卡
    · 主机SD卡的接口定义
    · 主机和SD卡的通讯传输流程及协议(了解即可)
  3. DMA(Direct Memory Access)传输的概念
  4. 文件系统 的基本概念,常见的文件系统:FAT、NTFS、Ext等


开始

废话少说,言归正传。 下面开始我们的移植工作。

准备工作

先准备一下:

  • 硬件环境:
    • 一块STM32开发板,带SD卡接口。
    • 一张SD卡

  • 软件环境:
    • 本系列的前两篇文章所搭建的vscode + gcc + rtthread开发环境

第一步:在STM32CubeMX中开启SD卡接口

直接点点点就行了,如下图所示:

选择 Connectivity - SDMMC1, 选择 SD 4 bits Wide bus,下面开启NVIC中断。

这里有个比较关键的参数 SDMMC clock divide factor,指的是SD卡硬件接口的时钟分频参数,此参数决定最终的时钟频率。
根据SD卡接口的官方文档,可以查阅到其频率一般为1MHz~50MHz。 所以这里我们后面再根据实际情况动态的调整。

使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统_第1张图片
设置完成,生成代码 OK

这一步完成后,我们既可以简单的实现往SD卡里面读写二进制的数据了。 但是绝大多数情况下,我们往SD卡读写的都是以文件的形式,文件是有格式的二进制数据。 所以接下来我们开始第二步。


第二步:移植文件系统

文件系统的概念这里不再累述,请自行查阅。 这里我们选择移植FATFS文件系统,官方网站为:http://elm-chan.org/fsw/ff/00index_e.html。

这是好奇的同学就要问了,为什么你直接就选了FATFS呢? 为什么不选其他类似于 NTFS、Ext这些文件系统呢?
我的回答是: 因为我查了一圈资料,就只查到了FATFS的移植文档,其他文件系统我连源码都没找到 /汗

首先我们去上述的官网上下载FATFS的源码,一共就6、7个C文件和H文件。 下载后随意在项目里面找个顺眼的位置放着,等下把这个位置加到makefile里面进行编译。

这里我放到了 Core/Src目录下,如下图所示:
使用vscode + gcc进行 STM32 单片机开发(三)DMA读写SD卡,移植FATFS文件系统_第2张图片接下来把源文件和头文件加到Makefile里面去,这里需要你稍微去学习一下makefile,有很多种方法都可以添加,我这里随便选择一种,代码如下所示:

OBJECTS += Core/Src/ff14b/source/diskio.o Core/Src/ff14b/source/ff.o Core/Src/ff14b/source/ffsystem.o Core/Src/ff14b/source/ffunicode.o \
		   Core/Src/sd_card_speed.o
C_INCLUDES += -ICore/Src/ff14b/source \

ok,接下来进行编译。


不出意外的话,你会编译失败,报错部分函数未定义。 这是当然的咯,上述我们只是下载了FATFS源码而已,还没有和STM32的SD卡读写函数进行对接(移植)。

因此接下来我们进行移植。

根据报错提示,我们打开 diskio.c这个文件,参考官方的移植指南http://elm-chan.org/fsw/ff/doc/appnote.html#port。 做填空题,把里面的函数填上STM32的SD卡相关读写函数。

根据实际情况不同,这里的函数实现都不同,下面我给一个我的写法:

disk_status函数:如下图所示,根据GPIO9的状态(我的开发板上是这个引脚)判断SD卡是否插入

STATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	int result;
	GPIO_PinState card_det_pin;
	switch (pdrv) {
	case DEV_RAM :
		card_det_pin = HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_9);

		result = (card_det_pin == GPIO_PIN_RESET);
		// translate the reslut code here
		stat = (result ? 0 : STA_NODISK);
		return stat;

	case DEV_MMC :
		//result = MMC_disk_status();

		// translate the reslut code here

		return STA_NODISK;

	case DEV_USB :
		//result = USB_disk_status();

		// translate the reslut code here

		return STA_NODISK;
	}
	return STA_NOINIT;
}

disk_initialize函数:无需改动
disk_read函数:如下图所示,先声明一段DMA的buffer,buffer制定一个“段名”.dma,这样的话后续在编译时,可以根据.dma这个段名,把buffer强制固定到特定的内存段上。 这是因为STM32H7系列,有多个RAM内存地址,分别为:

DTCMRAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
RAM_D1 (xrw) : ORIGIN = 0x24000000, LENGTH = 512K
RAM_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 288K
RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K
ITCMRAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64K

而SD卡要求的DMA必须在DTCMRAM上,否则无法工作。具体请查询STM32H7的官方手册,有详细说明。

然后再声明一个DMA读取完毕的回调函数,其中把RxCplt置1。
disk_read函数中发起一次DMA读取,然后就一直等待变量RxCplt置1。

volatile uint8_t RxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_rx_buffer[10240]; //10kb
void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
  SCB_InvalidateDCache_by_Addr((uint32_t*)dma_rx_buffer, sizeof(dma_rx_buffer)/4);
  RxCplt=1;
}

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;
	HAL_StatusTypeDef result;

	switch (pdrv) {
	case DEV_RAM :
		// translate the arguments here
		while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);
		
		// result = HAL_SD_ReadBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 500);
		result = HAL_SD_ReadBlocks_DMA(&hsd1, (uint8_t*)dma_rx_buffer, (uint32_t)sector, (uint32_t)count);
		if(result!=HAL_OK){
			return result;
		}
		while(RxCplt==0);
		RxCplt=0;
		SCB_CleanDCache_by_Addr(dma_rx_buffer, sizeof(dma_rx_buffer)/4);
		memcpy(buff, dma_rx_buffer, count*BLOCKSIZE);
		// translate the reslut code here
		switch (result)
		{
		case HAL_OK:
			return RES_OK;
		case HAL_ERROR:
			return RES_ERROR;
		case HAL_BUSY:
			return RES_ERROR;
		case HAL_TIMEOUT:
			return RES_ERROR;
		default:
			return RES_ERROR;
		}

	case DEV_MMC :
		// translate the arguments here

		//result = MMC_disk_read(buff, sector, count);

		// translate the reslut code here

		return RES_NOTRDY;

	case DEV_USB :
		// translate the arguments here

		//result = USB_disk_read(buff, sector, count);

		// translate the reslut code here

		return RES_NOTRDY;
	}

	return RES_PARERR;
}

disk_write函数和disk_read函数类似,如下图所示,不再累述:

volatile uint8_t TxCplt=0;;
static uint8_t __attribute__((section(".dma"))) dma_tx_buffer[10240]; //10kb

void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
  TxCplt=1;
}
DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	DRESULT res;
	HAL_StatusTypeDef result;
	int retry = 5;

	switch (pdrv) {
	case DEV_RAM :
		// translate the arguments here
		while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);
		memcpy(dma_tx_buffer, buff, count*BLOCKSIZE);
		SCB_CleanDCache_by_Addr(dma_tx_buffer, sizeof(dma_tx_buffer)/4);
		result = HAL_SD_WriteBlocks_DMA(&hsd1, (uint8_t*)dma_tx_buffer, (uint32_t)sector, (uint32_t)count);
		while(TxCplt==0);
		TxCplt=0;


		// for(;retry>0;retry--){
		// 	while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);
		// 	result = HAL_SD_WriteBlocks(&hsd1, (uint8_t*)buff, (uint32_t)sector, (uint32_t)count, 5000);
		// 	if(result == HAL_OK) break;
		// }
		

		// translate the reslut code here
		switch (result)
		{
		case HAL_OK:
			return RES_OK;
		case HAL_ERROR:
			return RES_ERROR;
		case HAL_BUSY:
			return RES_ERROR;
		case HAL_TIMEOUT:
			return RES_ERROR;
		default:
			return RES_ERROR;
		}

		// translate the reslut code here

		return res;

	case DEV_MMC :
		// translate the arguments here

		// result = MMC_disk_write(buff, sector, count);

		// translate the reslut code here

		return RES_NOTRDY;

	case DEV_USB :
		// translate the arguments here

		// result = USB_disk_write(buff, sector, count);

		// translate the reslut code here

		return RES_NOTRDY;
	}

	return RES_PARERR;
}

disk_ioctl函数:whille循环等待SD读写完毕,如下图所示:

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
	case DEV_RAM :

		// Process of the command for the RAM drive
		switch (cmd)
		{
		case CTRL_SYNC:
			/* code */
			while(HAL_SD_GetCardState(&hsd1)!=HAL_SD_CARD_TRANSFER);
			return RES_OK;
			break;
		
		case GET_SECTOR_COUNT:
			*(DWORD*)buff = hsd1.SdCard.BlockNbr;
			return RES_OK;
			break;

		case GET_BLOCK_SIZE:
			*(DWORD*)buff = hsd1.SdCard.BlockSize;
			return RES_OK;
			break;
		
		default:
			break;
		}

		return RES_ERROR;

	case DEV_MMC :

		// Process of the command for the MMC/SD card

		return RES_NOTRDY;

	case DEV_USB :

		// Process of the command the USB drive

		return RES_NOTRDY;
	}

	return RES_PARERR;
}

上述函数写完后,再进行编译,应该就没问题了。


第三步:测试SD卡读写速度

这一步其实就相对比较简单了,我们写入一个100MB的文件,来测试一下写入的速度,测试代码如下:

#include 
#include 
#include 
#include "sd_card_speed.h"
static const int SIZE_MB=100;
static const long long SIZE_BYTE = SIZE_MB*1024*1024;

/*
@brief: 此函数测试sd卡的读写速度,
@note:  测试方法,写入200MB的文件,
@return: 返回速度 单位 b/s 
*/
static int write_count=0;
int sd_card_write_speed(FATFS* fs){
    char filename[64]={0};
    int ret;
    ret = snprintf(&filename, sizeof(filename), "/SD_CARD_SPEED_TEST_%dMB", SIZE_MB);
    if(ret < 0){
        rt_kprintf("snprintf error\r\n");
        return -1;
    }

    // write 200MB in
    // cpu have 1MB RAM, so we write 200KB each time
    int data_size_each = 200*1024;
    uint8_t *data = rt_malloc(data_size_each);
    if(data == NULL){
        rt_kprintf("rt_malloc failed!\r\n");
        return -1;
    }

    FRESULT f_ret;
    FIL fil;
    f_ret = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE);
    if(f_ret != FR_OK){
        rt_kprintf("f_open failed, error code is %d\r\n", f_ret);
        return -1;
    }

    for(int i=0;i< SIZE_BYTE/(long long)data_size_each;i++){
        int bw = 0;
        f_ret = f_write(&fil, data, data_size_each, &bw);
        if(f_ret != FR_OK){
            rt_kprintf("f_write failed, error code is %d\r\n", f_ret);
            return -1;
        }
        if(bw != data_size_each){
            rt_kprintf("bw != data_size_each, bw is %d\r\n", bw);
        }
        write_count++;
    }

    f_close(&fil);
    return 0;
}

int sd_card_read_speed(){

}

在main函数里面调用这个函数,并打印一下执行前后的时间t1和t2,即可算出SD卡的读写速度。


TODO:补图,SD卡的测试结果



结语

最后,代码写的不怎么样,我就没有上传了。 如果有任何疑问,欢迎在下面留言,我看到了就会回复。

你可能感兴趣的:(STM32,嵌入式,单片机,stm32,vscode)