基于STM32CubeMx的SD卡文件读取

一、SD卡协议原理

1、有关SD卡

很多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片,SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做到很大(32GB 以上),支持 SPI/SDIO 驱动,而且有多种体积的尺寸可供选择(标准的 SD 卡尺寸,以及 TF 卡尺寸等),能满足不同应用的要求。只需要少数几个 IO 口即可外扩一个高达 32GB 以上的外部存储器,容量从几十 M 到几十G 选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。

2、1.SDIO协议

SD卡(Secure Digital Memory Card)在我们的生活中已经非常普遍了,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI接口,另外一种就是 SDIO接口。SDIO 全称是 安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡 都有 SDIO接口。STM32F103系列控制器有一个 SDIO主机接口,它可以与 MMC卡、SD卡、SD I/O卡 以及 CE-ATA 设备进行数据传输。

3、SD卡物理结构

一般SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5个部分。
在这里插入图片描述

  • 存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;
  • 电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;
  • 卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;
  • 接口驱动器控制SD卡引脚的输入输出。

4、SD卡寄存器

SD卡总共有8个寄存器,用于设定或表示SD卡信息。这些寄存器只能通过对应的命令访问,SDIO定义64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。
在这里插入图片描述

5、SD卡下SPI操作模式

SD卡初始化
SPI操作模式下:在SD卡收到复位命令时,CS为有效电平(低电平),则SPI模式被启用,在发送CMD之前要先发送74个时钟,64个为内部供电上升时间,10个用于SD卡同步;之后才能开始CMD操作,在初始化时CLK时钟不能超过400KHz。

1、初始化与SD卡连接的硬件条件(MCU的SPI配置,IO口配置);
2、上电延时(>74个CLK);
3、复位卡(CMD0),进入IDLE状态;
4、发送CMD8,检查是否支持2.0协议;
5、根据不同协议检查SD卡(命令包括:CMD55、CMD41、CMD58和CMD1等);
6、取消片选,发多8个CLK,结束初始化

这样我们就完成了对SD卡的初始化,注意末尾发送的8个CLK是提供SD卡额外的时钟,完成某些操作。通过SD卡初始化,我们可以知道SD卡的类型(V1、V2、V2HC或者MMC),在完成了初始化之后,就可以开始读写数据了。

SD卡读取与写入
1、发送CMD17;
2、接收卡响应R1;
3、接收数据起始令牌0XFE;
4、接收数据;
5、接收2个字节的CRC,如果不使用CRC,这两个字节在读取后可以丢掉。
6、禁止片选之后,发多8个CLK;

以上就是一个典型的读取SD卡数据过程,SD卡的写于读数据差不多,写数据通过CMD24来实现,具体过程如下:

1、发送CMD24;
2、接收卡响应R1;
3、发送写数据起始令牌0XFE;
4、发送数据;
5、发送2字节的伪CRC;
6、禁止片选之后,发多8个CLK;

二、实例

点击FILE->New project新建工程,选择要用的芯片,这里我使用的是STM32F103C8。

在这里插入图片描述
点击Middleware,选择FATFS模式。
在这里插入图片描述
配置SYS,选择调试模式为Serial Wire
在这里插入图片描述
Pinout View界面配置PA4为GPIO_Output模式。
在这里插入图片描述
点击Connectivity,配置SPI1为 Full-Duplex Master模式。
在这里插入图片描述
接着配置USART1为异步模式。
在这里插入图片描述
最后修改最小栈容量为0x1400,否则会导致调试时死机。
在这里插入图片描述
然后修改相关工程信息就点击Generate code生成代码。然后就下图所示两个文件复制到工程目录下。
在这里插入图片描述
并将他们添加到工程中,如下图所示。
在这里插入图片描述
编写user_diskio.c

#include <string.h>
#include "ff_gen_drv.h"
#include "diskio.h"		/* Declarations of disk functions */
#include "SDdriver.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);  
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read, 
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */  
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
  uint8_t res;
	res = SD_init();//SD_Initialize() 
		 	if(res)//STM32 SPIµÄbug,ÔÚsd¿¨²Ù×÷ʧ°ÜµÄʱºòÈç¹û²»Ö´ÐÐÏÂÃæµÄÓï¾ä,¿ÉÄܵ¼ÖÂSPI¶ÁдÒì³£
			{
				SPI_setspeed(SPI_BAUDRATEPRESCALER_256);
				spi_readwrite(0xff);//Ìṩ¶îÍâµÄ8¸öʱÖÓ
				SPI_setspeed(SPI_BAUDRATEPRESCALER_2);
			}
	if(res)return  STA_NOINIT;
	else return RES_OK; //³õʼ»¯³É¹¦
  /* USER CODE END INIT */
}
 
/**
  * @brief  Gets Disk Status 
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
  switch (pdrv)
	{
		case 0 :
			return RES_OK;
		case 1 :
			return RES_OK;
		case 2 :
			return RES_OK;
		default:
			return STA_NOINIT;
	}
  /* USER CODE END STATUS */
}

/**
  * @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 USER_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 */
)
{
  /* USER CODE BEGIN READ */
  uint8_t res;
	if( !count )
	{    
		return RES_PARERR;  /* count²»ÄܵÈÓÚ0£¬·ñÔò·µ»Ø²ÎÊý´íÎó */
	}
	switch (pdrv)
	{
		case 0:
		    res=SD_ReadDisk(buff,sector,count);	 
				if(res == 0){
					return RES_OK;
				}else{
					return RES_ERROR;
				}                                               
		default:
			return RES_ERROR;
	}
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)  
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT USER_write (
	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
	const BYTE *buff,   /* Data to be written */
	DWORD sector,       /* Sector address in LBA */
	UINT count          /* Number of sectors to write */
)
{ 
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */
  uint8_t  res;
	if( !count )
	{    
		return RES_PARERR;  /* count²»ÄܵÈÓÚ0£¬·ñÔò·µ»Ø²ÎÊý´íÎó */
	}
	switch (pdrv)
	{
		case 0:
		    res=SD_WriteDisk((uint8_t *)buff,sector,count);
				if(res == 0){
					return RES_OK;
				}else{
					return RES_ERROR;
				}                                                
		default:return RES_ERROR;
	}
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation  
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res;
	 switch(cmd)
	    {
		    case CTRL_SYNC:
						SD_CS(1);
						do{
							HAL_Delay(20);
						}while(spi_readwrite(0xFF)!=0xFF);
						res=RES_OK;
						SD_CS(0);
		        break;	 
		    case GET_SECTOR_SIZE:
		        *(WORD*)buff = 512;
		        res = RES_OK;
		        break;	 
		    case GET_BLOCK_SIZE:
		        *(WORD*)buff = 8;
		        res = RES_OK;
		        break;	 
		    case GET_SECTOR_COUNT:
		        *(DWORD*)buff = SD_GetSectorCount();
		        res = RES_OK;
		        break;
		    default:
		        res = RES_PARERR;
		        break;
	    }
		return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

三、实验结果

编译无误后将工程烧录进STM32,这里一定注意我们在用USB转TTL为STM32供电的时候一定需要接到5V上面去,并且在用STM32给SD卡模块供电的时候一定一定要接到5V电源上,否则我们无法驱动该SD模块

本次实验所用的SD卡模块如下图所示:
在这里插入图片描述
STM32与SD模块的连线如下:
在这里插入图片描述
打开串口调试助手可以看到以下结果,针对SD卡文件的每个步骤都有相应的字段输出,来具体判断究竟进行到了什么地步。
在这里插入图片描述
在这里插入图片描述
打开hello.txt,发现左侧的序号乱码了。
在这里插入图片描述
修改main.c,第一位+10修改为+0保持为0,第二位+10修改为+1
在这里插入图片描述
重新编译后烧录出来的结果就正确了。
在这里插入图片描述
而且写入次数超过11次后,会不断返回while值,这与我们编写的代码相一致。

四、总结

这个实验难度系数不是很大,但是一开始卡在了SD模块初始化,还卡了很久,后面才发现是因为供电的问题,了解SPI协议且SD模块成功初始化后,操作流程就十分顺畅了。在这排错的过程中逻辑分析仪功不可没,通过分析测出来的时序波形,才发现SD卡根本没初始化成功。

五、参考资料

STM32用cube配置FATFS模式下SPI读写SD卡
STM32 基础系列教程 15 - SPI 协议理解与仿真调试

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值