一、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 协议理解与仿真调试
版权声明:本文为CSDN博主「可乐飞冰5399」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_47921205/article/details/121980508
一、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 协议理解与仿真调试
版权声明:本文为CSDN博主「可乐飞冰5399」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_47921205/article/details/121980508
暂无评论