Bootstrap

STM32CubeMX系列09——SDIO(SD卡读写、SD卡移植FATFS文件系统)

====>>> 文章汇总(有代码汇总) <<<====

1. 准备工作

1.1. 简单扫盲

准备看看这方面的知识,一时间还没不清有什么区别,先补补课,不需要的跳过。
参考文章(内容来源):http://www.360doc.com/content/21/1125/22/59057945_1005908465.shtml

主要写这两个:SD卡、TF卡

共同点:SD、TF、MMC都是在MMC基础上演化发展不同的规范,比如物理尺寸,封装,电压,管脚,位宽,时钟信号等不同,但都使用相同的总线规范。

1.1.1. SD卡

SD卡(Secure Digital Card,安全数字卡)图片如下:
在这里插入图片描述
SD卡是(secure digital memory card)安全数码卡,是在MMC基础上发展起来的。
增加特色:

  1. 可以设置所存储的使用权限,防止数据被他人复制;
  2. 传输速度比2.11版mmc卡快。

特性:

  1. 可选通信协议:SD模式 和 SPI模式
  2. 可变时钟频率:0~25Mhz
  3. 通信电压范围:2.0~3.6V
  4. 数据寿命:10万次编程/擦除
  5. 正向兼容MMC卡
  6. 运行在25M的频率上,数据带宽是4位,因此最大传输速率是12.5MHz(12.5兆字节每秒)。

引脚定义:
在这里插入图片描述

1.1.2. TF卡

Micro SD Card,原名Trans-flash Card(TF卡),2004年正式更名为Micro SD Card。

特点:

  1. SD卡 比 TF卡 的尺寸要大。
  2. 应用于不同产品,SD卡一般都用在大一些的电子设备:如电脑,相机,AV等器材,而TF一般用在手机上。TF卡我们见的比较多,图片如下:
  3. SD 卡上有一个(lock)开关(上图中右边的图,卡左边有个波动的开关),即写保护开关,TF卡没有。

除此之外,其实没什么区别。另外:TF卡插入适配器(adapter)可以转换成SD卡,但SD卡一般无法转换成TF卡。
在这里插入图片描述
引脚定义:
在这里插入图片描述

1.1.3. SDIO接口

SDIO接口是在SD内存卡接口的基础上发展起来的接口,SDIO接口兼容以前的SD内存卡,并且可以连接SDIO接口的设备。

SDIO接口的信号传输模式有SPI、1-bit、4-bit三种。

  • 在SPI模式中,第8脚位被当成中断信号。其它脚位的功能和通信协定与SD记忆卡的标准规范一样。
  • 在SDIO总线定义中,DAT1信号线复用为中断线。
  • 在SDIO的 1BIT 模式下 DAT0 用来传输数据,DAT1用作中断线。
  • 在SDIO的 4BIT 模式下 DAT0-DAT3 用来传输数据,其中DAT1复用作中断线。

在这里插入图片描述

1.2. 所用硬件及原理图

此处硬件上不同的开发板可能略有差异,但是实际开发的过程中都是一样的。

STM32F103 普中-准端-Z100,主控 STM32F103ZET6。这个是直接用SD卡插上去就可以了。这里我们用这个开发板进行实验
在这里插入图片描述
正点原子Mini板,主控 STM32F103RCT6。这个是用了一个比较大的SD卡的卡槽,如果有SD卡可以直接插上去,如果是用的Micro SD Card卡,需要一个适配器才能连。
在这里插入图片描述

2. 创建工程

2.1. 选择主控

在这里插入图片描述

2.2. 系统配置

配置时钟源
在这里插入图片描述
配置debug模式(如果需要ST-Link下载及调试可以勾选)
在这里插入图片描述
配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)
在这里插入图片描述

2.3. 配置工程目录

在这里插入图片描述
在这里插入图片描述

3. SD卡读写实验

3.1. 原理图

在这里插入图片描述

3.2. 代码实现(轮询模式)

先把串口重定向配置一下,方便观察–>串口重定向配置<–

SDIO配置
在这里插入图片描述

SDIO分为两个部分:AHB interface和SDIO adapter。

  • AHB interface采用的时钟是HCLK/2=36MHz, 是用来访问STM32 SDIO本身的寄存器的。
  • SDIO adapter采用的时钟是SDIOCLK=HCLK=72MHz,SDIO_CK时钟线(PC12脚,单片机给SD卡提供的时钟)输出的时钟就是从这个上面分频得到的,分频公式为SDIOCLK/(CLKDIV+2)。CLKDIV就是hsd.Init.ClockDiv的值。(就是我们上面设定的值)
    • 当CLKDIV=70时,SDIO_CK输出的频率为72MHz/(70+2)=1MHz。在这个频率下可以不使用DMA收发数据。
    • 当CLKDIV=1时,SDIO_CK输出的频率为72MHz/(1+2)=24MHz。在这个频率下必须使用DMA收发数据。

前面第一章说了 SDIO的时钟频率为0~25Mhz(不同的卡不太一样),此处设置分频系数为34,而我们开发板的时钟频率为72Mhz,这里分频系数为34,也就是72/(34+2) = 2Mhz。(其他数也行,如果测试发现读写失败,就增大分频系数就好了)

然后生成工程。

main.c中,while(1)之前。

   /* USER CODE BEGIN 2 */
		printf("Micro SD Card Test... \r\n");
		
		uint8_t read_buf[512];		// 读数据缓存
		uint8_t write_buf[512];		// 写数据缓存

		/* SD卡状态 */
		int sdcard_status = 0;
		
		HAL_SD_CardCIDTypeDef sdcard_cid;

		/* 获取SD卡状态 */
		sdcard_status = HAL_SD_GetCardState(&hsd);
		// 处于数据传输模式的传输状态
		if(sdcard_status == HAL_SD_CARD_TRANSFER)
		{
			printf("SD card init ok!\r\n\r\n");

			// 打印SD卡基本信息
			printf("SD card information! \r\n");
			// 容量信息
			printf("CardCapacity(Byte): %llu \r\n",((unsigned long long)hsd.SdCard.BlockSize * hsd.SdCard.BlockNbr));
			// 块大小 默认都是512个字节
			printf("CardBlockSize(Byte): %d \r\n", hsd.SdCard.BlockSize);
			// 有多少个块
			printf("CardBlockNumber: %d \r\n", hsd.SdCard.BlockNbr);
			
			printf("RCA: %d \r\n", hsd.SdCard.RelCardAdd);
			
			printf("CardType: %d \r\n", hsd.SdCard.CardType);

			// 读取并打印SD卡的CID信息
			HAL_SD_GetCardCID(&hsd, &sdcard_cid);
			// 制造商
			printf("ManufacturerID: %d \r\n",sdcard_cid.ManufacturerID);
		}
		else
		{
			printf("SD card init fail! \r\n" );
			return 0;
		}
		
		/* 读取未操作之前的数据 */
		printf("------------------- Read SD card block data Test ------------------\r\n");
		/*
			读一个扇区的数据:
			0: 从第0个扇区开始。
			1:读一个扇区的数据。
			0xffff:等待时间。
			note:也就是只读了第0个扇区。
		*/
		sdcard_status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)read_buf, 0, 1, 0xffff);
		if(sdcard_status == HAL_OK)
		{ 
			printf("Read block data ok! \r\n");
			for(int i = 0; i < 512; i++)
			{
				printf("0x%02x ", read_buf[i]);
				if((i+1)%16 == 0)
				{
					printf("\r\n");
				}
			}
		}
		else
		{
			printf("Read block data fail! status = %d \r\n", sdcard_status);
		}
		

		/* 向SD卡块写入数据 */
		printf("------------------- Write SD card block data Test ------------------\r\n");
		/* 填充缓冲区数据 */
		for(int i = 0; i < 512; i++)
		{
			write_buf[i] = i % 256;
		}
		// 开始写入数据
		/*
			写一个扇区的数据:
			0: 从第0个扇区开始。
			1:写一个扇区的数据。
			0xffff:等待时间。
			note:也就是只写了第0个扇区。
		*/
		sdcard_status = HAL_SD_WriteBlocks(&hsd, (uint8_t *)write_buf, 0, 1, 0xffff);
		if(sdcard_status == HAL_OK)
		{ 
			/* 传输完成不代表写入完成,因此要等待SD卡状态变为可传输状态。擦除操作也是一样。 */
			printf("Writing block data. state = %d \r\n", HAL_SD_GetCardState(&hsd));
			while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
			printf("Write block data ok,state = %d \r\n", HAL_SD_GetCardState(&hsd));
		}
		else
		{
			printf("Write block data fail! status = %d \r\n", sdcard_status);
		}
		
		/* 读取写入之后的数据 */
		printf("------------------- Read SD card block data after Write ------------------\r\n");
		sdcard_status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)read_buf, 0, 1, 0xffff);
		if(sdcard_status == HAL_OK)
		{ 
			printf("Read block data ok! \r\n");
			for(int i = 0; i < 512; i++)
			{
				printf("0x%02x ", read_buf[i]);
				if((i+1)%16 == 0)
				{
					printf("\r\n");
				}
			}
		}
		else
		{
			printf("Read block data fail! status = %d \r\n", sdcard_status);
		}
		
		/* 擦除SD卡块 */
		printf("------------------- Block Erase -------------------------------\r\n");
		/*
			擦除512个扇区的数据:
			0: 从第0个扇区开始。
			1:一直擦除到512扇区。
			note:擦除第0到第512个扇区数据,也包括0和512,也就是一共512个。
		*/
		sdcard_status = HAL_SD_Erase(&hsd, 0, 512);
		// 等待擦除完毕
		if (sdcard_status == HAL_OK)
		{	
			printf("Erasing block. state = %d \r\n", HAL_SD_GetCardState(&hsd));
			while (HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
			printf("Erase block ok state = %d \r\n", HAL_SD_GetCardState(&hsd));
		}
		else
		{
			printf("Erase block fail! status = %d \r\n", sdcard_status);
		}
		
		/* 读取擦除之后的数据 */
		printf("------------------- Read SD card block data after Erase ------------------\r\n");
		sdcard_status = HAL_SD_ReadBlocks(&hsd, (uint8_t *)read_buf, 0, 1, 0xffff);
		if(sdcard_status == HAL_OK)
		{ 
			printf("Read block data ok \r\n" );
			for(int i = 0; i < 512; i++)
			{
				printf("0x%02x ", read_buf[i]);
				if((i+1)%16 == 0)
				{
					printf("\r\n");
				}
			}
		}
		else
		{
			printf("Read block data fail! status = %d \r\n", sdcard_status);
		}

		printf("------------------- Over ------------------\r\n");
		
  /* USER CODE END 2 */

实验效果
编译、烧录、串口下载。

Micro SD Card Test... 
SD card init ok!

SD card information! 
CardCapacity(Byte): 3965190144 
CardBlockSize(Byte): 512 
CardBlockNumber: 7744512 
RCA: 58916 
CardType: 1 
ManufacturerID: 3 
------------------- Read SD card block data Test ------------------
Read block data ok! 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
------------------- Write SD card block data Test ------------------
Writing block data. state = 7 
Write block data ok,state = 4 
------------------- Read SD card block data after Write ------------------
Read block data ok! 
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 
0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 
0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f 
0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f 
0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 
0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 
0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 
0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f 
0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 
0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 
0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf 
0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf 
0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf 
0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf 
0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef 
0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff 
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 
0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 
0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f 
0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f 
0x40 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4a 0x4b 0x4c 0x4d 0x4e 0x4f 
0x50 0x51 0x52 0x53 0x54 0x55 0x56 0x57 0x58 0x59 0x5a 0x5b 0x5c 0x5d 0x5e 0x5f 
0x60 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x6c 0x6d 0x6e 0x6f 
0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x7b 0x7c 0x7d 0x7e 0x7f 
0x80 0x81 0x82 0x83 0x84 0x85 0x86 0x87 0x88 0x89 0x8a 0x8b 0x8c 0x8d 0x8e 0x8f 
0x90 0x91 0x92 0x93 0x94 0x95 0x96 0x97 0x98 0x99 0x9a 0x9b 0x9c 0x9d 0x9e 0x9f 
0xa0 0xa1 0xa2 0xa3 0xa4 0xa5 0xa6 0xa7 0xa8 0xa9 0xaa 0xab 0xac 0xad 0xae 0xaf 
0xb0 0xb1 0xb2 0xb3 0xb4 0xb5 0xb6 0xb7 0xb8 0xb9 0xba 0xbb 0xbc 0xbd 0xbe 0xbf 
0xc0 0xc1 0xc2 0xc3 0xc4 0xc5 0xc6 0xc7 0xc8 0xc9 0xca 0xcb 0xcc 0xcd 0xce 0xcf 
0xd0 0xd1 0xd2 0xd3 0xd4 0xd5 0xd6 0xd7 0xd8 0xd9 0xda 0xdb 0xdc 0xdd 0xde 0xdf 
0xe0 0xe1 0xe2 0xe3 0xe4 0xe5 0xe6 0xe7 0xe8 0xe9 0xea 0xeb 0xec 0xed 0xee 0xef 
0xf0 0xf1 0xf2 0xf3 0xf4 0xf5 0xf6 0xf7 0xf8 0xf9 0xfa 0xfb 0xfc 0xfd 0xfe 0xff 
------------------- Block Erase -------------------------------
Erasing block. state = 7 
Erase block ok state = 4 
------------------- Read SD card block data after Erase ------------------
Read block data ok 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 
------------------- Over ------------------

3.4. 程序中设置参数

因为用了CubeMX之后,很多东西虽然会用了,但是到最后都没明白很多东西到底怎么设置的,因此这里还是要说明一下的。

  1. main.c的头文件中打开sdio.h
  2. sdio.h中有函数void MX_SDIO_SD_Init(void);
  3. 查看这个函数的定义,可以看到所有设置的参数 都在 SD_HandleTypeDef hsd;中设置
SD_HandleTypeDef hsd;

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_DISABLE;
  hsd.Init.ClockDiv = 34;
  if (HAL_SD_Init(&hsd) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B) != HAL_OK)
  {
    Error_Handler();
  }
}

问题:上面的函数中有一个 hsd.Init.BusWide = SDIO_BUS_WIDE_1B;,但是我们配置的不是四线的吗。

答案:(其实也不太明白为什么,但是先这样吧):
SD卡可以采用1位数据线模式,也可以采用4位数据线模式。但是必须确保STM32单片机的SDIO设置的数据线位宽,和SD卡上设置的数据线位宽是一致的。
如果将 hsd.Init.BusWide 设为 SDIO_BUS_WIDE_4B,然后执行HAL_SD_Init 函数,只能把STM32单片机的SDIO设置为4位位宽,SD卡上还是用的1位位宽。
所以通常的做法是 hsd.Init.BusWide 设为 SDIO_BUS_WIDE_1B,HAL_SD_Init 执行完成后,再调用 HAL_SD_ConfigWideBusOperation(&hsd, SDIO_BUS_WIDE_4B),这个函数可以将STM32和SD卡同时设为4位模式。

发现在有的固件包中,使用cubemx生成工程后,默认的就是 hsd.Init.BusWide = SDIO_BUS_WIDE_4B; 但是程序生成后发现初始化无法通过,还是需要手动改回来,改成1B。

3.3. 代码实现(DMA模式)

应用中一般都使用DMA传输模式

我们把原有的配置的频率提高一点(非必须)
在这里插入图片描述
然后需要添DMA的配置
在这里插入图片描述
可以看到,作为一个传输数据的东西,SDIO是有两个方向的。但是只能添加一个DMA Handle,要么选择收,要么选择发。实际上SDIO收发数据是可以用同一个DMA Handle的。

在生成的代码中:
sdio.c文件中有个void HAL_SD_MspInit(SD_HandleTypeDef* sdHandle)函数。
在这里插入图片描述
设置完 hdma.Init 的其他成员后,调用了HAL_DMA_Init(&hdma_sdio)初始化DMA2_Channel4,之后,调用了两次__HAL_LINKDMA();函数,宏将hdma同时绑定到 hsd 的 hdmarx 和 hdmatx上。

中断优先级配置,一般用到DMA的地方,都需要把DMA的优先级比其他的优先级要低
在这里插入图片描述

编写一个DMA读写函数(主要是为了在传输前更改DMA传输方向):

/**
 * @brief   SD卡DMA读数据(开始前重新初始化DMA,更改传输方向)
 *
 * @param   hsd
 * @param   pData
 * @param   BlockAdd
 * @param   NumberOfBlocks
 *
 * @return  none
 */
HAL_StatusTypeDef SDIO_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
    HAL_StatusTypeDef Return_Status;
    HAL_SD_CardStateTypeDef SD_Card_Status;

    do
    {
        SD_Card_Status = HAL_SD_GetCardState(hsd);
    } while(SD_Card_Status != HAL_SD_CARD_TRANSFER);

    /* SDIO DMA DeInit */
    /* SDIO DeInit */
    HAL_DMA_DeInit(&hdma_sdio);
    /* SDIO DMA Init */
    /* SDIO 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();
    }

    __HAL_LINKDMA(hsd, hdmarx, hdma_sdio);

    Return_Status = HAL_SD_ReadBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);

    return Return_Status;
}

/**
 * @brief   SD卡DMA写数据(开始前重新初始化DMA,更改传输方向)
 *
 * @param   hsd
 * @param   pData
 * @param   BlockAdd
 * @param   NumberOfBlocks
 *
 * @return  none
 */
HAL_StatusTypeDef SDIO_WriteBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)
{
    HAL_StatusTypeDef Return_Status;
    HAL_SD_CardStateTypeDef SD_Card_Status;

    do
    {
        SD_Card_Status = HAL_SD_GetCardState(hsd);
    } while(SD_Card_Status != HAL_SD_CARD_TRANSFER);

    /* SDIO DMA DeInit */
    /* SDIO DeInit */
    HAL_DMA_DeInit(&hdma_sdio);
    /* SDIO DMA Init */
    /* SDIO Init */
    hdma_sdio.Instance = DMA2_Channel4;
    hdma_sdio.Init.Direction = DMA_MEMORY_TO_PERIPH;
    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();
    }

    __HAL_LINKDMA(hsd, hdmatx, hdma_sdio);

    Return_Status = HAL_SD_WriteBlocks_DMA(hsd, pData, BlockAdd, NumberOfBlocks);

    return Return_Status;
}

编写一个DMA传输测试函数,并在main.c中while(1)之前运行:

/**
 * @brief   SD卡DMA读写测试(会破坏建立的FATFS系统)
 *
 * @param   none
 *
 * @return  none
 */
void SD_read_writer_dma_test(void)
{
    /* SD卡状态 */
    int sdcard_status = 0;

    HAL_SD_CardCIDTypeDef sdcard_cid;

    uint8_t read_buf[512];      // 读数据缓存
    uint8_t write_buf[512];     // 写数据缓存

    /* 读取未操作之前的数据 */
    printf("------------------- Read SD card block data Test ------------------\r\n");
	/*
        读一个扇区的数据:
        0: 从第0个扇区开始。
        1:读一个扇区的数据。
        note:也就是只读了第0个扇区。
    */
	sdcard_status = SDIO_ReadBlocks_DMA(&hsd, (uint8_t *)read_buf, 0, 1);
    
    if(sdcard_status == HAL_OK)
    {
        printf("Read block data ok! \r\n");
        for(int i = 0; i < 512; i++)
        {
            printf("0x%02x ", read_buf[i]);
            if((i + 1) % 16 == 0)
            {
                printf("\r\n");
            }
        }
    }
    else
    {
        printf("Read block data fail! status = %d \r\n", sdcard_status);
    }

    /* 向SD卡块写入数据 */
    printf("------------------- Write SD card block data Test ------------------\r\n");
    /* 填充缓冲区数据 */
    for(int i = 0; i < 512; i++)
    {
        write_buf[i] = i % 256;
    }
    // 开始写入数据
    /*
        写一个扇区的数据:
        0: 从第0个扇区开始。
        1:写一个扇区的数据。
        note:也就是只写了第0个扇区。
    */
	sdcard_status = SDIO_WriteBlocks_DMA(&hsd, (uint8_t *)write_buf, 0, 1);
	
    if(sdcard_status == HAL_OK)
    {
        /* 传输完成不代表写入完成,因此要等待SD卡状态变为可传输状态。擦除操作也是一样。 */
        printf("Writing block data. state = %d \r\n", HAL_SD_GetCardState(&hsd));
        while(HAL_SD_GetCardState(&hsd) == HAL_SD_CARD_PROGRAMMING);
        printf("Write block data ok,state = %d \r\n", HAL_SD_GetCardState(&hsd));
    }
    else
    {
        printf("Write block data fail! status = %d \r\n", sdcard_status);
    }

    /* 读取写入之后的数据 */
    printf("------------------- Read SD card block data after Write ------------------\r\n");
    
	sdcard_status = SDIO_ReadBlocks_DMA(&hsd, (uint8_t *)read_buf, 0, 1);
    
	if(sdcard_status == HAL_OK)
    {
        printf("Read block data ok! \r\n");
        for(int i = 0; i < 512; i++)
        {
            printf("0x%02x ", read_buf[i]);
            if((i + 1) % 16 == 0)
            {
                printf("\r\n");
            }
        }
    }
    else
    {
        printf("Read block data fail! status = %d \r\n", sdcard_status);
    }
}

程序运行结果同上。

4. SD卡移植FATFS文件系统

4.1. FATFS配置

CubeMX配置还是很方便的。

保留上一节轮询模式的配置不变,添加FATFS系统的配置。
在这里插入图片描述
其他参数保持默认即可。

中文文件名,其实也可以选,但是没必要,因为占用的内存比较大。
文件名最大长度256(默认),这里设置64,如果使用的超过64,f_open可能会失败。

还需要配置一个SD卡插入引脚,如果不配置 生成文件时会报错,所以这里即使没有硬件连接,也可以任意设置一引脚,生成工程后不需要的话直接注释掉。
在这里插入图片描述
把栈空间大小设置大一点
在这里插入图片描述
生成工程后,目录如下。可以发现和以往的工程相比多了两个文件夹。
在这里插入图片描述

4.2. 修改SD卡插入检测代码

生成工程后,首先修改SD卡插入检测代码。

bsp_driver.sd.c文件中,可以看到__weak uint8_t BSP_SD_Init(void),该部分代码是检测SD卡是否正常工作。其中红框中的函数BSP_SD_IsDetected()是用于检测SD卡是否插入(或者写保护开关是否拨动,在前文SD卡介绍中有写)。
在这里插入图片描述
函数BSP_SD_IsDetected()定义如下,可以看到,如果监测到SD卡未插入(写保护)就无法正常初始化。
在这里插入图片描述
其中,这部分也就是检测我们配置时任意配置的那个引脚的高低电平。
在这里插入图片描述
明白原理后,我们可以直接不管这部分的,因此需要进行修改。

  1. 可以选择直接把这个引脚在硬件上拉低
  2. 软件上在这行之前把引脚电平拉低(设置为下拉)
  3. 修改代码跳过这行。

这里我采用第三种,直接把BSP_SD_IsDetected()函数中的这部分屏蔽掉。
在这里插入图片描述

这样要小心,你重新生成工程的话,这部分还要改。

4.3. 代码实现

main.c中的while(1)之前添加如下代码:

	FATFS fs;                       /* FatFs 文件系统对象 */
	FIL file;                       /* 文件对象 */
	FRESULT f_res;                  /* 文件操作结果 */
	UINT fnum;                      /* 文件成功读写数量 */
	BYTE ReadBuffer[1024] = {0};    /* 读缓冲区 */
	BYTE WriteBuffer[] =            /* 写缓冲区 */
						"This is STM32 working with FatFs \r\n";
  
	printf("\r\n ****** FatFs Example ****** \r\n \r\n");
    
    // 在外部 SD 卡挂载文件系统,文件系统挂载时会对 SD 卡初始化
	// note:必须先要保证SD卡正常拥有FAT文件系统,如果没有会失败。
    f_res = f_mount(&fs, "0:", 1);
    
    /*----------------------- 格式化测试 ---------------------------*/
    printf("\r\n ****** Register the file system object to the FatFs module ****** \r\n");
		
    /* 如果没有文件系统就格式化创建创建文件系统 */
    if(f_res == FR_NO_FILESYSTEM)
    {
        printf("The SD card does not yet have a file system and is about to be formatted... \r\n");
        /* 格式化 */
        f_res = f_mkfs("0:", 0, 0);
        if(f_res == FR_OK)
        {
            printf("The SD card successfully formatted the file system\r\n");
            /* 格式化后,先取消挂载 */
            f_res = f_mount(NULL, "0:", 1);
            /* 重新挂载 */
            f_res = f_mount(&fs, "0:", 1);
        }
        else
        {
            printf("The format failed\r\n");
            while(1);
        }
    }
    else if(f_res != FR_OK)
    {
        printf(" mount error : %d \r\n", f_res);
        while(1);
    }
    else
    {
        printf(" mount sucess!!! \r\n");
    }
    
    /*----------------------- 文件系统测试:写测试 -----------------------------*/
    /* 打开文件,如果文件不存在则创建它 */
    printf("\r\n ****** Create and Open new text file objects with write access ****** \r\n");
    
	f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_CREATE_ALWAYS | FA_WRITE);
    if(f_res == FR_OK)
    {
        printf(" open file sucess!!! \r\n");
        /* 将指定存储区内容写入到文件内 */
        printf("\r\n****** Write data to the text files ******\r\n");
        f_res = f_write(&file, WriteBuffer, sizeof(WriteBuffer), &fnum);
        if(f_res == FR_OK)
        {
            printf(" write file sucess!!! (%d)\n", fnum);
            printf(" write Data : %s\r\n", WriteBuffer);
        }
        else
        {
            printf(" write file error : %d\r\n", f_res);
        }
        /* 不再读写,关闭文件 */
        f_close(&file);
    }
    else
    {
        printf(" open file error : %d\r\n", f_res);
    }
    
    /*------------------- 文件系统测试:读测试 ------------------------------------*/
    printf("\r\n****** Read data from the text files ******\r\n");
    f_res = f_open(&file, "0:FatFs STM32cube.txt", FA_OPEN_EXISTING | FA_READ);
    if(f_res == FR_OK)
    {
        printf(" open file sucess!!! \r\n");
        f_res = f_read(&file, ReadBuffer, sizeof(ReadBuffer), &fnum);
        if(f_res == FR_OK)
        {
            printf("read sucess!!! (%d)\n", fnum);
            printf("read Data : %s\r\n", ReadBuffer);
        }
        else
        {
            printf(" read error!!! %d\r\n", f_res);
        }
    }
    else
    {
        printf(" open file error : %d\r\n", f_res);
    }
    /* 不再读写,关闭文件 */
    f_close(&file);
    /* 不再使用文件系统,取消挂载文件系统 */
    f_mount(NULL, "0:", 1);
    /* 操作完成,停机 */

效果测试
编译、烧录、用串口查看效果。
在这里插入图片描述
拔下SD卡,插入电脑,也可以看到这个文件。
在这里插入图片描述

4.4. 注意事项

f_open、f_write、f_read如果偶尔有问题;f_mkfs报错 FS_DISK_ERR,可以加上了SDIO硬件流使能试试。
在这里插入图片描述

别人的文章里看到的,实际上我测试没有出现这个问题,先放这吧。

;