在本系列的前两篇文章(
使用vscode + gcc进行 STM32 单片机开发(一)编译及调试
使用vscode + gcc进行 STM32 单片机开发(二)gcc环境 移植rtthread)
的基础上,我想再多写一点,一方面是想试试vscode环境开发STM32到底怎么样;另一方面是在和同事的私下讨论的时候,讨论到STM32的SD卡不怎么好用。
综上所示,本文主要描述的是在vscode + gcc的环境下,配合rt-thread RTOS系统,实现STM32的SD卡读写及文件系统的移植,并给出最终的SD卡读写速率测试结果。
在阅读本文之前,为了确保有一个良好的阅读体验,需要提前了解一些知识。
废话少说,言归正传。 下面开始我们的移植工作。
先准备一下:
直接点点点就行了,如下图所示:
选择 Connectivity - SDMMC1
, 选择 SD 4 bits Wide bus
,下面开启NVIC中断。
这里有个比较关键的参数
SDMMC clock divide factor
,指的是SD卡硬件接口的时钟分频参数,此参数决定最终的时钟频率。
根据SD卡接口的官方文档,可以查阅到其频率一般为1MHz~50MHz。 所以这里我们后面再根据实际情况动态的调整。
这一步完成后,我们既可以简单的实现往SD卡里面读写二进制的数据了。 但是绝大多数情况下,我们往SD卡读写的都是以文件的形式,文件是有格式的二进制数据。 所以接下来我们开始第二步。
文件系统的概念这里不再累述,请自行查阅。 这里我们选择移植FATFS文件系统,官方网站为:http://elm-chan.org/fsw/ff/00index_e.html。
这是好奇的同学就要问了,为什么你直接就选了FATFS呢? 为什么不选其他类似于 NTFS、Ext这些文件系统呢?
我的回答是: 因为我查了一圈资料,就只查到了FATFS的移植文档,其他文件系统我连源码都没找到 /汗
首先我们去上述的官网上下载FATFS的源码,一共就6、7个C文件和H文件。 下载后随意在项目里面找个顺眼的位置放着,等下把这个位置加到makefile里面进行编译。
这里我放到了 Core/Src
目录下,如下图所示:
接下来把源文件和头文件加到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;
}
上述函数写完后,再进行编译,应该就没问题了。
这一步其实就相对比较简单了,我们写入一个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卡的测试结果
最后,代码写的不怎么样,我就没有上传了。 如果有任何疑问,欢迎在下面留言,我看到了就会回复。