LV16-21-SDIO-03-读写SD卡实例

本文主要是STM32开发——读写SD卡实例的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
  • 通用
分类 网址说明
官方网站https://www.arm.com/ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.st.com/content/st_com/zh.htmlST官方网站,在这里我们可以找到STM32的相关文档
https://www.stmcu.com.cn/意法半导体ST中文官方网站,在这里我们可以找到STM32的相关中文参考文档
http://elm-chan.org/fsw/ff/00index_e.htmlFatFs文件系统官网
教程书籍《ARM Cortex-M3权威指南》ARM公司专家Joseph Yiu(姚文祥)的力作,中文翻译是NXP的宋岩
《ARM Cortex-M0权威指南》
《ARM Cortex-M3与Cortex-M4权威指南》
开发论坛http://47.111.11.73/forum.php开源电子网,正点原子的资料下载及问题讨论论坛
https://www.firebbs.cn/forum.php国内Kinetis开发板-野火/秉火(刘火良)主持的论坛,现也做STM32和i.MX RT
https://www.amobbs.com/index.php阿莫(莫进明)主持的论坛,号称国内最早最火的电子论坛,以交流Atmel AVR系列单片机起家,现已拓展到嵌入式全平台,其STM32系列帖子有70W+。
http://download.100ask.net/index.html韦东山嵌入式资料中心,有些STM32和linux的相关资料也可以来这里找。
博客参考http://www.openedv.com/开源网-原子哥个人博客
http://blog.chinaaet.com/jihceng0622博主是原Freescale现NXP的现场应用工程师
cortex-m-resources这其实并不算是一个博客,这是ARM公司专家Joseph Yiu收集整理的所有对开发者有用的官方Cortex-M资料链接(也包含极少数外部资源链接)
  • STM32
STM32STM32 HAL库开发实战指南——基于F103系列开发板野火STM32开发教程在线文档
STM32库开发实战指南——基于野火霸道开发板野火STM32开发教程在线文档
  • SD卡
SD Association提供了SD存储卡和SDIO卡系统规范
点击查看相关文件下载
STM32F103xx英文数据手册STM32F103xC/D/E系列的英文数据手册
STM32F103xx中文数据手册STM32F103xC/D/E系列的中文数据手册
STM32F10xxx英文参考手册(RM0008)STM32F10xxx系列的英文参考手册
STM32F10xxx中文参考手册(RM0008)STM32F10xxx系列的中文参考手册
Arm Cortex-M3 处理器技术参考手册-英文版Cortex-M3技术参考手册-英文版
STM32F10xxx Cortex-M3编程手册-英文版(PM0056)STM32F10xxx/20xxx/21xxx/L1xxxx系列Cortex-M3编程手册-英文版
SD卡相关资料——最新版本有关SD卡的一些资料可以从这里下载
SD卡相关资料——历史版本有关SD卡的一些历史版本资料可以从这里下载,比如后边看的SD卡2.0协议
SD 2.0 协议标准完整版这是一篇关于SD卡2.0协议的中文文档,还是比较有参考价值的,可以一看

一、硬件设计

我使用的是正点原子战舰V3的开发板,硬件连接如下图:

image-20230512193905411

二、STM32CubeMX配置

像LED、按键和串口的配置这里就不再赘述,这里只写SDIO相关配置。注意,这一部分中的中断和DMA相关配置是作为笔记写在这里,后边的测试函数并没有使用DMA以及中断相关的内容,都是使用的查询方式,当使用查询方式,但是又开启了DMA以及相关中断的时候,数据读写可能会失败,这里需要注意。

1. SDIO参数配置

image-20230512195221327

①、选择SDIO外设;

②、选择模式,这里选择SD 4 bits Wire bus;

③、Clock transition on which the bit capture is made: Rising transition。主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿,它设定 SDIO 时钟控制寄存器(SDIO_CLKCR)的 NEGEDGE 位的值,一般选择设置为上升沿。

④、SDIO Clock divider bypass: Disable。时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。

⑤、SDIO Clock output enable when the bus is idle: Disable the power save for the clock。节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

⑥、SDIO hardware flow control: The hardware control flow is disabled。硬件流控制选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

⑦、SDIOCLK clock divide factor: 1。时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数

1
CLK 线时钟频率=SDIOCLK/([CLKDIV+2])

SDIO_CK 引脚的时钟信号在卡识别模式时要求不超过 400KHz,而在识别后的数据传输模式时则希望有更高的速度(最大不超过 25MHz),所以会针对这两种模式配置 SDIOCLK 的时钟。这里暂时将SDIOCLK clock divede factor 参数选择为 1,SDIOCLK为72MHz,可以得到最大频率24MHz,但还是要注意,我们使用的SD卡支不支持这么高的一个频率,所以还是要以实际情况而定。

2. DMA配置

SDIO 外设支持生成 DMA 请求,使用 DMA 传输可以提高数据传输效率,因此在 SDIO 的控制代码中,可以把它设置为 DMA 传输模式或轮询模式,ST 标准库提供 SDIO 示例中针对这两个模式做了区分处理。应用中一般都使用DMA 传输模式。

image-20230512195853431

①、选择SDIO外设;

②、选择DMA Settings;

③、添加一个DMA请求,选择我们开启的SDIO;

④、Direction:数据传输的方向,这里是外设到内存;

⑤、Priority:当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通 道编号,编号越低优先权越高,比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

⑥、Mode:Normal,表示单次传输,传输一次后终止传输。Circular,表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止。

⑦、Increment Address:Peripheral,表示外设地址自增。Memory,表示内存地址自增。

⑧、Data Width: Byte,一个字节。 Half Word,半个字,等于两字节。 Word,一个字,等于四字节。

3. NVIC配置

DMA及SDIO中断这里也进行配置,原则是全局中断优先级高于DMA中断:

image-20230512200341947

4. 我的时钟树

我的时钟树配置如下:

image-20230512200530597

三、读写SD卡

STM32CubeMX创建的工程会直接帮我们完成一些流程,比如SD卡初始化,读写等。我们其实也可以通过HAL固件库中的例程来学习,例如HAL库和标准库的相关sample路径如下:

image-20230512215827133

1. SD卡初始化

1.1 初始化流程

image-20230512220142891

1.2 初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
hsd.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_ENABLE;
hsd.Init.ClockDiv = 1;
if (HAL_SD_Init(&hsd) != HAL_OK)
{
Error_Handler();
}
if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
{
Error_Handler();
}
}

这里就不往里边深究了,这里有一个坑,需要注意:

(1) BusWide 我们通过STM32CubeMX配置的工程,在开启SDIO的时候选择的 SD 4 bits Wide bus,SDIOCLK clock divide factor选择0的话(此时最高频率为36M了),我们生成的代码为:

1
2
3
4
5
6
7
8
9
// 中间部分省略......
hsd.Init.BusWide = SDIO_BUS_WIDE_4B;//这里需要设置成1位
// 中间部分省略......
hsd.Init.ClockDiv = 0;// 这里至少需要是1
// 中间部分省略......
if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
{
Error_Handler();
}

这种情况下,直接卡死在初始化函数中,无法运行。其实就 SDIOCLK clock divide factor 一点就把这个代码干掉了,因为选择0的话,分频出来就是72/2=36M,我们使用的卡并不支持这么高频率,所以这里最小要设置为1,但是即便改为1,程序也是无法运行的,依然卡死。这是因为BusWide宽度为4,其实我并没有找到手册中说明在初始化阶段的时候总线宽度必须为1位,但是网上的资料都是1位,而STM32CubeMX软件生成的,这里就被配置成4位,我们改为1位的话,程序就可以正常跑,具体哪里有要求的话,后边发现了再补充。

(2)SDIO的时候选择的 SD 1 bit ,SDIOCLK clock divide factor选择 1 的话,默认生成的代码如下

1
2
3
4
5
6
7
8
// 中间部分省略......
hsd.Init.BusWide = SDIO_BUS_WIDE_1B;
// 中间部分省略......
hsd.Init.ClockDiv = 1;
if (HAL_SD_Init(&hsd) != HAL_OK)
{
Error_Handler();
}

这样是没有问题的,程序可以正常执行,后边若是不设置 4 位的话,无非就是传输的慢一些。

1.3 GPIO配置

对于SDIO的引脚的配置在函数 HAL_SD_MspInit 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**SDIO GPIO Configuration
PC8 ------> SDIO_D0 PC9 ------> SDIO_D1
PC10 ------> SDIO_D2 PC11 ------> SDIO_D3
PC12 ------> SDIO_CK PD2 ------> SDIO_CMD
*/
// 开启对应外设的时钟
__HAL_RCC_SDIO_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();

// 引脚配置
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

1.4 DMA配置

我们在配置工程的时候是选择了DMA传输的,DMA的相关配置也在函数 HAL_SD_MspInit 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* SDIO DMA Init */
hdma_sdio.Instance = DMA2_Channel4;
hdma_sdio.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_sdio.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_sdio.Init.MemInc = DMA_MINC_ENABLE;
hdma_sdio.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_sdio.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_sdio.Init.Mode = DMA_NORMAL;
hdma_sdio.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_sdio) != HAL_OK)
{
Error_Handler();
}

/* Several peripheral DMA handle pointers point to the same DMA handle.
Be aware that there is only one channel to perform all the requested DMAs. */
/* Be sure to change transfer direction before calling
HAL_SD_ReadBlocks_DMA or HAL_SD_WriteBlocks_DMA. */
__HAL_LINKDMA(sdHandle,hdmarx,hdma_sdio);
__HAL_LINKDMA(sdHandle,hdmatx,hdma_sdio);

每一行什么意思,可以看前边DMA传输的笔记,这里就不再赘述了。

1.5 NVIC配置

NVIC的配置也写在函数 HAL_SD_MspInit 中了:

1
2
HAL_NVIC_SetPriority(SDIO_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SDIO_IRQn);

这里我们看到主要是配置了SDIO的中。那DMA的中断去哪了?在哪设置的?我们前边也开了啊。在这里:

1
2
3
4
5
6
7
8
9
10
11
12
void MX_DMA_Init(void)
{

/* DMA controller clock enable */
__HAL_RCC_DMA2_CLK_ENABLE();

/* DMA interrupt init */
/* DMA2_Channel4_5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Channel4_5_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA2_Channel4_5_IRQn);

}

DMA的初始化中,开启了DMA的时钟和中断,在SD卡初始化的时候是对DMA做了配置,在SD卡初始化流程前已经做了DMA的初始化啦。

2. 获取SD卡状态

2.1 HAL库函数获取SD卡状态

初始化结束后,我们可以判断一下SD卡是否处于数据传输的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HAL_SD_CardStateTypeDef HAL_SD_GetCardState(SD_HandleTypeDef *hsd)
{
HAL_SD_CardStateTypeDef cardstate = HAL_SD_CARD_TRANSFER;
uint32_t errorstate = HAL_SD_ERROR_NONE;
uint32_t resp1 = 0;

errorstate = SD_SendStatus(hsd, &resp1);
if(errorstate != HAL_OK)
{
hsd->ErrorCode |= errorstate;
}

cardstate = (HAL_SD_CardStateTypeDef)((resp1 >> 9U) & 0x0FU);

return cardstate;
}

SD卡的状态定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef enum
{
HAL_SD_CARD_READY = 0x00000001U, /*!< Card state is ready */
HAL_SD_CARD_IDENTIFICATION = 0x00000002U, /*!< Card is in identification state */
HAL_SD_CARD_STANDBY = 0x00000003U, /*!< Card is in standby state */
HAL_SD_CARD_TRANSFER = 0x00000004U, /*!< Card is in transfer state */
HAL_SD_CARD_SENDING = 0x00000005U, /*!< Card is sending an operation */
HAL_SD_CARD_RECEIVING = 0x00000006U, /*!< Card is receiving operation information */
HAL_SD_CARD_PROGRAMMING = 0x00000007U, /*!< Card is in programming state */
HAL_SD_CARD_DISCONNECTED = 0x00000008U, /*!< Card is disconnected */
HAL_SD_CARD_ERROR = 0x000000FFU /*!< Card response Error */
}HAL_SD_CardStateTypeDef;

2.2 我们自己封装一个

有时候我们其实并不需要这么多状态,我们可以封装一层,用与读写SD卡。

1
2
3
4
5
6
7
8
9
10
11
// 需要定义两个宏
#define SD_TRANSFER_OK ((uint8_t)0x00)
#define SD_TRANSFER_BUSY ((uint8_t)0x01)

//判断SD卡是否可以传输(读写)数据
//返回值:SD_TRANSFER_OK 传输完成,可以继续下一次传输
// SD_TRANSFER_BUSY SD卡正忙,不可以进行下一次传输
uint8_t SD_GetCardState(void)
{
return((HAL_SD_GetCardState(&hsd)==HAL_SD_CARD_TRANSFER )?SD_TRANSFER_OK:SD_TRANSFER_BUSY);
}

3. 显示SD卡信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//通过串口打印SD卡相关信息
void show_sdcard_info(void)
{
uint64_t CardCap; //SD卡容量
HAL_SD_CardCIDTypeDef SDCard_CID;
HAL_SD_CardInfoTypeDef SDCardInfo; //SD卡信息结构体

/* 检测SD卡是否正常(处于数据传输模式的传输状态) */
if(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER)
{
printf("SD card init fail!\r\n" );
return;
}
printf("Initialize SD card successfully!\r\n");
HAL_SD_GetCardCID(&hsd, &SDCard_CID); //获取CID
HAL_SD_GetCardInfo(&hsd, &SDCardInfo); //获取SD卡信息
switch(SDCardInfo.CardType)
{
case CARD_SDSC:
{
if(SDCardInfo.CardVersion == CARD_V1_X)
printf("Card Type :SDSC V1 \r\n");
else if(SDCardInfo.CardVersion == CARD_V2_X)
printf("Card Type :SDSC V2 \r\n");
}
break;
case CARD_SDHC_SDXC:
printf("Card Type :SDHC \r\n");
break;
}
CardCap=(uint64_t)(SDCardInfo.LogBlockNbr)*(uint64_t)(SDCardInfo.LogBlockSize); //计算SD卡容量
printf("Card ManufacturerID:%d \r\n",SDCard_CID.ManufacturerID); //制造商ID
printf("Card RCA :%d \r\n",SDCardInfo.RelCardAdd); //卡相对地址
printf("LogBlockNbr :%d \r\n",(uint32_t)(SDCardInfo.LogBlockNbr)); //显示逻辑块数量
printf("LogBlockSize :%d \r\n",(uint32_t)(SDCardInfo.LogBlockSize)); //显示逻辑块大小
printf("Card Capacity :%d MB\r\n",(uint32_t)(CardCap>>20)); //显示容量
printf("Card BlockSize :%d \r\n\r\n",SDCardInfo.BlockSize); //显示块大小
}

4. 数据操作

SD 卡数据操作一般包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块操作和多块操作。

4.1 擦除函数

1
2
3
4
5
6
7
8
9
10
/**
* @brief Erases the specified memory area of the given SD card.
* @note This API should be followed by a check on the card state through
* HAL_SD_GetCardState().
* @param hsd: Pointer to SD handle
* @param BlockStartAdd: Start Block address
* @param BlockEndAdd: End Block address
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SD_Erase(SD_HandleTypeDef *hsd, uint32_t BlockStartAdd, uint32_t BlockEndAdd);

函数比较长,这里就不全贴了,额,看了一下,我对寄存器也不是很熟,这个里边有一些没搞懂,知道这有个函数,知道怎么用就算了,后边有必要相惜学习的话,再补充。

4.2 开关中断函数

后边我们读写的时候,若是采取查询方式,需要关闭总中断,读写完成后再打开。

  • 开关中断定义
1
2
3
4
5
6
7
8
9
10
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
  • 超时时间的宏
1
#define SD_TIMEOUT 			((uint32_t)100000000)  	//超时时间

4.3 数据写入操作

数据写入可分为单块数据写入和多块数据写入,这里只分析单块数据写入,多块的与之类似。 SD卡数据写入之前并没有硬性要求擦除写入块,这与 SPI Flash 芯片写入是不同的。 ST 官方的 SD卡写入函数包括扫描查询方式和 DMA 传输方式,都有分别对应的函数,这里先用查询方式。

4.3.1 查询方式

1
HAL_StatusTypeDef HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);

我们来写一个写入数据的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint32_t cnt)
{
uint8_t sta=HAL_OK;
uint32_t timeout=SD_TIMEOUT;
long long lsector=sector;
INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
sta=HAL_SD_WriteBlocks(&hsd,(uint8_t*)buf,lsector,cnt,SD_TIMEOUT);//多个sector的写操作

//等待SD卡写完
while(HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER)
{
if(timeout-- == 0)
{
sta=SD_TRANSFER_BUSY;
}
}
INTX_ENABLE();//开启总中断
return sta;
}

4.3.2 DMA方式

注意:STM32F103的SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA(主要是为了更改数据传输的方向)。

1
HAL_StatusTypeDef HAL_SD_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);

我们这个就可以参考HAL库文件中给我们提供的参考例程啦,参考文件路径如下:

1
2
STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.c
STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.h

这个还需要移植,暂时先搁置,后边有需要再补充。

4.4 数据读取操作

同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。

4.4.1 查询方式

1
HAL_StatusTypeDef HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout);

我们封装一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
uint8_t SD_ReadDisk(uint8_t* buf,uint32_t sector,uint32_t cnt)
{
uint8_t sta=HAL_OK;
uint32_t timeout=SD_TIMEOUT;
long long lsector=sector;
INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
sta=HAL_SD_ReadBlocks(&hsd, (uint8_t*)buf,lsector,cnt,SD_TIMEOUT);//多个sector的读操作

//等待SD卡读完
while(SD_GetCardState()!=SD_TRANSFER_OK)
{
if(timeout-- == 0)
{
sta=SD_TRANSFER_BUSY;
}
}
INTX_ENABLE();//开启总中断
return sta;
}

4.4.2 DMA方式

注意:STM32F103的SDIO DMA每次由读数据变为写数据或者由写数据变为读数据时,都需要重新初始化DMA(主要是为了更改数据传输的方向)。

1
HAL_StatusTypeDef HAL_SD_ReadeBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks);

我们这个就可以参考HAL库文件中给我们提供的参考例程啦,参考文件路径如下:

1
2
STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.c
STM32Cube_FW_F1_V1.8.0/Drivers/BSP/STM3210E_EVAL/stm3210e_eval_sd.h

这个还需要移植,暂时先搁置,后边有需要再补充。

5. 测试函数

注意:由于上边我开启了SDIO中断和DMA中断,但是最后却又使用了查询的方式去读写数据,所以会出现读写失败的情况,当我关掉SDIO中断和DMA中断后,恢复正常。

5.1 相关宏和变量定义

1
2
3
4
5
6
#define BLOCK_START_ADDR         0     /* Block start address      */
#define NUM_OF_BLOCKS 5 /* Total number of blocks */
#define BUFFER_WORDS_SIZE ((BLOCKSIZE * NUM_OF_BLOCKS) >> 2) /* Total data size in bytes */

uint32_t aTxBuffer[BUFFER_WORDS_SIZE];
uint32_t aRxBuffer[BUFFER_WORDS_SIZE];

5.2 读数据测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//测试SD卡的读取,从secaddr地址开始,读取seccnt个扇区的数据
//secaddr:扇区地址
//seccnt :扇区数
void sd_test_read(uint32_t secaddr, uint32_t seccnt)
{
uint32_t i;
uint8_t sta = 0;
sta=SD_ReadDisk(aRxBuffer, secaddr, seccnt);//读取secaddr扇区开始的内容
if(sta==0)
{
printf("SECTOR %d DATA:\r\n", secaddr);
for(i=0;i<seccnt*512;i++)
printf("%#x ", aRxBuffer[i]);//打印secaddr开始的扇区数据
printf("\r\nDATA ENDED\r\n");
}
else
printf("read rest err:%d\r\n",sta);
}

5.3 写数据测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//测试SD卡的写入(慎用,最好写全是0XFF的扇区,否则可能损坏SD卡.),从secaddr地址开始,写入seccnt个扇区的数据
//secaddr:扇区地址
//seccnt:扇区数
void sd_test_write(uint32_t secaddr,uint32_t seccnt)
{
uint32_t i;
uint8_t sta=0;
for(i=0;i<seccnt*512;i++)
aTxBuffer[i]=i*3; //初始化写入的数据,是3的倍数.
sta=SD_WriteDisk(aTxBuffer, secaddr, seccnt); //从secaddr扇区开始写入seccnt个扇区内容
if(sta==0)
printf("Write over!\r\n");
else
printf("write test err:%d\r\n",sta);
}

5.4 测试函数

1
2
3
4
5
6
7
8
9
10
11
12
13
void SD_Test(void)
{
#if 0
if(HAL_SD_Erase(&hsd, 0, 512) != HAL_OK)
{
printf("HAL_SD_Erase failed!!!\r\n");
}
else
printf("HAL_SD_Erase success!!!\r\n");
#endif
sd_test_write(0, 1);
sd_test_read(0, 1);
}

6. 踩的几个坑

6.1 时钟问题

初始化的时候,时钟分频设置为1,发现写入总是失败,后来加大,改为4正常,这说明我使用的SD卡并不支持24Mz,,所以有的时候若是写入总是失败的话,可以考虑降低SDIO时钟。

5.2 擦除问题

很奇怪,我擦除后直接写入的话,写不进去,写入操作是失败的,但是擦除是成功的,而且读取擦除的扇区的数据为全0,当关闭擦除部分代码的时候,重新烧写,是可以重新写入成功的,其实有些SD卡是不需要擦除的,因为SD卡内部控制器会帮我们做这件事,等后边找到详细说明了再补充。