STM32的SD卡读取
SD卡简介
本质是nandflash+控制芯片。SD卡系列有三种:SD卡,MiniSD卡和MicroSD卡(TF卡)。特点:容量大,高安全性,体积小,传输速率块,接口简单。
SD卡主要规格参数如图:
脚位数对应实卡的金手指数
SD卡 的存储结构:
有8个寄存器,但不能直接进行读写操作,需要通过命令来控制。SD卡根据收到的命令要求对内部寄存器进行修改。
CID:卡标识寄存器,128bit
提供制造商ID,应用ID,产品名称,版本,序列号,制造日期等信息
RCA:相对卡地址寄存器,16bit,仅SDIO模式下有,初始化时动态地由卡建立,经主机核准
OCR:操作条件寄存器, 32bit
主要是SD卡的操作电压等信息,关注15~23位可以知道SD卡支持的电压范围,SD卡支持范围中相应位是置1的,还要关注30位的CCS,卡容量状态,为1是SDHC卡,为0是SDSC卡
SCR:SD配置寄存器,64比特
关注DAT Bus widths supported ,SD_BUS_WIDTHS位,设置有几根数据,0设置1位(DAT0),2设置4位(DAT0~3),默认值是0
CSD:卡特定数据寄存器,128比特
提供SD卡操作条件相关信息和数据,主要看CSD_STRUCTURE位,可以区分版本,0是1.0版本,1是2.0版本
SD卡驱动方式
可通过SPI接口或者SDIO接口,不同接口SD卡引脚功能不一样。
SD卡引脚对应这两种接口的引脚功能定义:
TF卡8pin,SD卡9pin,TF卡只比SD卡少一个电源引脚。注:S电源,I输入,O推挽输出,PP复用推挽。SD卡和micorSD只有引脚和形状大小不同,内部结构是类似的,操作时序也完全相同,可以使用相同的代码驱动。
SDIO接口通信线:CLK/CMD/DAT0/DAT3/DAT1/DAT2
CMD:命令线,SDIO主机通过该线发送命令控制SD卡,若命令要求响应,SD卡也是通过该线传输响应信息
DAT0~3:数据线,用于接收或发送数据。SD卡可将DAT0拉低表示处于忙状态(SPI接口的MISO也有该特性)
SPI接口通信线:CLK/MOSI/MISO/CS
SD卡认识
SD卡命令和响应:
SD卡有多种命令和响应,他们的格式定义及含义在SD卡协议2.0的第三和第四章有详细介绍,发送命令时主机只能通过CMD引脚发送给SD卡,串行逐位发送时先发送最高位(MSB),然后是次高位这样类推。SD卡强制性类命令有,基本命令,块读取命令,块写入命令,擦除命令,加锁命令,特定于应用命令。
SD卡命令格式:
6个字节组成,固定48位,发送数据时高位在前。
起始位(47)固定0,停止位(0)固定是1,传输方向(46)为1表命令,0则表响应,注意使用SDIO 接口驱动,CRC7校验值必须正确,而SPI接口驱动,CRC7校验默认关闭即为伪CRC。
SD卡总共有12类命令,分为CLASS0~CLASS11。常用的命令有以下这些:
大部分的命令是初始化的时候用的,应用相关命令(ACMD),通用命令(CMD),发送ACMD前需先发CMD55
SD卡响应:
分短响应(48bit)和长响应(136bit),R1,R1b,R3,R6,R7属于短响应,R2属于长响应,五类响应
响应格式:举例SDIO接口下的R1响应
R1响应:通过40~45这6位(响应内容中的commandindex)可获知响应的是哪个命令
R2响应:通过1~127位,CID寄存器内容作为CMD2的响应,CSD寄存器内容作为CMD9的响应
R3响应:8~39位,OCR寄存器的值作为ACMD41的响应
R7响应:16~19位,专用于命令CMD8的响应,返回卡支持电压范围和检测模式
R6响应:24~39位,专用于命令CMD3的响应(RCA响应)卡相对地址
SD卡操作模式和卡状态:
无效模式,卡识别模式(识别总线上的SD卡类型)和数据传输模式(读写操作),每个操作模式下SD卡都有几种状态。状态之间通过命令控制实现切换卡状态的切换
SD卡有两种数据模式,一种是常规的8位宽,即一次按一字节传输,另一种是一次按512字节传输
SD卡操作步骤
(1)SD初始化:
主机上电后所有卡处于空闲状态,主机也可通过CMD0软复位进入空闲状态(无效卡不会复位)
主机与SD卡通信之前需先确定双方在互相支持的电压范围内,SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。空闲状态下发送CMD8用于验证卡接口操作条件的(主要是电压支持),即主机给SD卡发其当前支持的工作电压,如果SD卡不支持主机电压就无响应返回空闲状态,还可用于区分卡类型因为CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡
空闲状态下发ACMD41主机看SD卡的OCR寄存器。对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应(R3响应)的CCS位为1说明为高容量SD卡,否则为标准卡。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55。对COM8不响应的卡在发CMD55时如果不支持CMD55可说明为MMC卡或不能识别卡此时可再发CMD1命令激活MMC卡如果有响应则为MMC卡,CMD55有响应为V1.0版本卡。
准备状态下发送CMD2获取卡(V1.x卡或V2.0卡)的CID寄存器,处于准备状态的卡在发送CID之后就进入识别状态,卡识别模式,之后主机就发送CMD3获取卡相对地址,卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。对SD卡读写之前需要识别卡的种类
CMD7用于选卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是处于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。如果主机发送读操作命令,卡会进入发送数据状态,传输完成或者发CMD12中止会回到传输状态,写则是数据接收状态,还会进入编程状态,因为SD卡写前要预擦除,编程完可发CMD7取消卡进入卡断开连接状态,然后再进入待机状态。
(2)写操作:
SD卡单块数据块写入流程:发送CMD16指令设置数据块大小,发送CMD13指令查询卡状态,等待READY_FOR_DATA位=1,发送CMD2指令开始写入数据,写一个数据块的数据。
SD卡多块数据块写入流程:发送CMD16指令设置数据块大小,发送ACMD23指令预擦除数据块,发送CMD25指令开始写数据块,写入第一个数据块的数据~第N个数据块的数据,发送 CMD12指令结束数据块写入,发送CMD13指令查询卡状态,等待SD卡写入过程结束,完成多块多块数据的写入。
(3)读操作:
SD卡单块数据块读取流程:发送CMD16指令设置数据块大小,等待CMD16的R1响应,发送CMD17指令开始读数据块,等待CMD17响应R1,读一个数据块的数据
SD卡多块数据读取流程:发送CMD16等待响应,发送CMD18指令等待响应,读第一个数据块的数据~读第n个数据块的数据,发送CMD12指令结束数据块读取等待CMD12响应R1,结束多块数据块读取。
SDIO接口简介
SDIO,全称为安全数字输入/输出接口,多媒体卡(MMC卡),SD存储卡都有SDIO设备。
SDIO框图:
STM32F1的SDIO控制器包含2个部分:SDIO适配器和AHB总线接口,其功能框图如图
SDIO适配器提供SD卡特有功能:产生时钟,发送命令,接收响应,双向数据传输
SDIO适配器里边有控制单元,负责电源管理和时钟管理功能。
有命令通道:控制命令发送并接收响应,有命令通道状态机(CPSM)
数据通道:负责主机和卡之间传输数据,有数据通道状态机(DPSM)
数据FIFO,具有发送和接收数据缓冲器。TXACT和RXACT标志表明当前发送还是接收状态。读空FIFO后继续读会下溢,写满FIFO后继续写会上溢,可以开启硬件流控解决这个问题。
SDIO时钟:
从框图可看出,有三个时钟。
卡时钟(SDIO_CK):SD卡是指频率可以在0MHz至25MHz间变化,通信时钟,每个时钟周期在命令和数据线上传输1位命令或数据。计算公式:SDIO_CK=SDIOCLK/(2+CLKDIV)。注意:在SD卡刚刚初始化的时候,其时钟频率(SDIO_CK)是不能超过400Khz的,否则可能无法完成初始化。在初始化以后,就可以设置时钟频率到最大了(但不可超过SD 卡的最大操作时钟频率)。复位后默认情况下SDIO_D0用于数据传输。初始化后主机可以改变数据总线的宽度(通过ACMD6命令设置)。
SDIO适配器时钟(SDIOCLK):
该时钟用于驱动 SDIO 适配器,其频率等于 48MHz,并用于产生 SDIO_CK 时钟
AHB总线时钟(HCLK/2):该时钟用于驱动SDIO的AHB总线接口
SDIO命令与响应
SDIO命令格式:
SDIO 的命令分为应用相关命令(ACMD)和通用命令(CMD)两部分,应用相关命令(ACMD)
的发送,必须先发送通用命令(CMD55),然后才能发送应用相关命令(ACMD)。
所有的命令都是由 STM32F1 发出,其中开始位、传输位、CRC7 和结束位由 SDIO 硬件控
制,我们需要设置的就只有命令索引和参数部分。命令索引在SDIO_CMD寄存器里面设置,命令参数则由寄存器SDIO_ARG设置
SDIO响应格式:
短响应的:
命令索引存放在SDIO_RESPCMD寄存器,参数则存放在SDIO_RESP1寄存器里面。在收到 R1 响应后,我们可以从 SDIO_RESPCMD 寄存器和 SDIO_RESP1 寄存器分别读出 命令索引和卡状态信息。
长响应的:
则仅留CID/CSD位域,存放在SDIO_RESP1~SDIO_RESP4等4个寄存器。
SDIO数据传输:
对于 SDI/SDIO 存储器,数据是以数据块的形式传输的,而对于 MMC 卡,数据是以数据块或者数据流的形式传输。
SDIO(多)数据块读操作:
SDIO(多)数据块写操作:
数据块写操作同数据块读操作基本类似,只是数据块写的时候,多了一个繁忙判断,新的
数据块必须在 SD 卡非繁忙的时候发送。这里的繁忙信号由 SD 卡拉低 SDIO_D0,以表示繁忙,
SDIO 硬件自动控制,不需要我们软件处理。
SDIO适配器寄存器
SDIO电源控制寄存器(SDIO_POWER):
让SDIO上电,开启卡时钟
SDIO时钟控制寄存器(SDIO_CLKCR):
当SDIO_CK频率过快时可能会导致SD卡失败,此时建议降低频率试试
SDIO命令寄存器(SDIO_CMD):
一般都开启CPSM(命令状态机),位6,7按需设置
SDIO命令响应寄存器(SDIO_RESPCMD):
该寄存器为32位,但只有低6位有效用于存储最后收到的命令响应中的命令索引
SDIO响应寄存器组(SDIO_RESP1~SDIO_RESP4):
共由 4 个 32 位寄存器组成,用于存放接收到的卡响应部分信息
SDIO数据定时器寄存器(SDIO_DTIMER):
用于存储以卡总线时钟(SDIO_CK)为周期的数据超时时间,一个计数器将从SDIO_DTIMER寄存器加载数值,并在数据通道状态机(DPSM)进入Wait_R或繁忙状态时进行递减计数,当DPSM处在这些状态时,如果计数器减为0,则设置超时标志
SDIO数据长度寄存器(SDIO_DLEN):
低25位有效,用于设置需要传输的数据字节长度
SDIO数据控制寄存器(SDIO_DCTRL):
SDIO状态寄存器(STA):
SDIO数据FIFO寄存器(SDIO_FIFO):
数据 FIFO 寄存器包括接收和发送 FIFO,由一组连续的 32 个地址上的 32 个寄存器组成。我们要从 SD 卡读数据,就必须读 SDIO_FIFO寄存器,要写数据到 SD 卡,则要写 SDIO_FIFO 寄存器。SDIO 将这 32 个地址分为 16 个一组, 发送接收各占一半。我们每次读写的时候,最多就是读取发送 FIFO 或写入接收 FIFO 的一半 大小的数据,也就是 8 个字(32 个字节),这里特别提醒,我们操作 SDIO_FIFO(不论读出还 是写入)必须是以 4 字节对齐的内存进行操作,否则将导致出错!
SDIO相关HAL库驱动:
__HSL_RCC_SDIO_CLK_ENABLE 用于SDIO时钟使能
HAL_SD_Init,HAL_SD_Msplint用于初始SD卡和SDIO的底层硬件
HAL_SD_ConfigWideBusOperation用于配置总线宽度
HAL_SD_ReadBlocks 读取块数据
HAL_SD_WriteBlocks 写入块数据
SDIO_SendCommand函数用于发送,SDIO_GetResponse用于接收响应
SDIO外设相关结构体:
SD_InitTypeDef,SDIO_CmdInitTypedef,SDIO_DataInitTypeDef,Hal_SD_CardInfoTypeDef
驱动文件:halsd.c封装初始化,读写,擦除等驱动接口操作
halsdmmc对接SDIO寄存器与SD卡通信
代码部分:
SD卡初始化:
SD_HandleTypeDef SDCARD_Handler; //SD卡句柄
HAL_SD_CardInfoTypeDef SDCardInfo; //SD卡信息结构体
//SD卡初始化
//返回值:0 初始化正确;其他值,初始化错误
u8 SD_Init(void)
{
u8 SD_Error;
//初始化时的时钟不能大于400KHZ
SDCARD_Handler.Instance=SDIO;
SDCARD_Handler.Init.ClockEdge=SDIO_CLOCK_EDGE_RISING; //上升沿
SDCARD_Handler.Init.ClockBypass=SDIO_CLOCK_BYPASS_DISABLE; //不使用bypass模式,直接用HCLK进行分频得到SDIO_CK
SDCARD_Handler.Init.ClockPowerSave=SDIO_CLOCK_POWER_SAVE_DISABLE; //空闲时不关闭时钟电源
SDCARD_Handler.Init.BusWide=SDIO_BUS_WIDE_1B; //1位数据线
SDCARD_Handler.Init.HardwareFlowControl=SDIO_HARDWARE_FLOW_CONTROL_ENABLE;//开启硬件流控
SDCARD_Handler.Init.ClockDiv=SDIO_TRANSFER_CLK_DIV; //SD传输时钟频率最大25MHZ
SD_Error=HAL_SD_Init(&SDCARD_Handler);
if(SD_Error!=HAL_OK) return 1;
SD_Error=HAL_SD_ConfigWideBusOperation(&SDCARD_Handler,SDIO_BUS_WIDE_4B);//使能宽总线模式
if(SD_Error!=HAL_OK) return 2;
return 0;
}
//SDMMC底层驱动,时钟使能,引脚配置,DMA配置
//此函数会被HAL_SD_Init()调用
//hsd:SD卡句柄
void HAL_SD_MspInit(SD_HandleTypeDef *hsd)
{
// DMA_HandleTypeDef TxDMAHandler,RxDMAHandler;
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_SDIO_CLK_ENABLE(); //使能SDIO时钟
// __HAL_RCC_DMA2_CLK_ENABLE(); //使能DMA2时钟
__HAL_RCC_GPIOC_CLK_ENABLE(); //使能GPIOC时钟
__HAL_RCC_GPIOD_CLK_ENABLE(); //使能GPIOD时钟
//PC8,9,10,11,12
GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12;
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //推挽复用
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure); //初始化
//PD2
GPIO_Initure.Pin=GPIO_PIN_2;
HAL_GPIO_Init(GPIOD,&GPIO_Initure); //初始化
}
判断SD卡是否可以传输数据:
#define SD_TRANSFER_OK ((uint8_t)0x00)
#define SD_TRANSFER_BUSY ((uint8_t)0x01)
//判断SD卡是否可以传输(读写)数据
//返回值:SD_TRANSFER_OK 传输完成,可以继续下一次传输
// SD_TRANSFER_BUSY SD卡正忙,不可以进行下一次传输
u8 SD_GetCardState(void)
{
return((HAL_SD_GetCardState(&SDCARD_Handler)==HAL_SD_CARD_TRANSFER )?SD_TRANSFER_OK:SD_TRANSFER_BUSY);
}
获取卡信息:
//得到卡信息
//cardinfo:卡信息存储区
//返回值:错误状态
u8 SD_GetCardInfo(HAL_SD_CardInfoTypeDef *cardinfo)
{
u8 sta;
sta=HAL_SD_GetCardInfo(&SDCARD_Handler,cardinfo);
return sta;
}
读SD卡:
#define SD_TIMEOUT ((uint32_t)100000000) //超时时间
//SD_ReadDisk/SD_WriteDisk函数专用buf,当这两个函数的数据缓存区地址不是4字节对齐的时候,
//需要用到该数组,确保数据缓存区地址是4字节对齐的.
__align(4) u8 SDIO_DATA_BUFFER[512];
//读SD卡
//buf:读数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态;0,正常;其他,错误代码;
u8 SD_ReadDisk(u8* buf,u32 sector,u32 cnt)
{
u8 sta=HAL_OK;
u32 timeout=SD_TIMEOUT;
long long lsector=sector;
INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
sta=HAL_SD_ReadBlocks(&SDCARD_Handler, (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;
}
//关闭所有中断
void INTX_DISABLE(void)
{
__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
__ASM volatile("cpsie i");
}
写SD卡:
//写SD卡
//buf:写数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态;0,正常;其他,错误代码;
u8 SD_WriteDisk(u8 *buf,u32 sector,u32 cnt)
{
u8 sta=HAL_OK;
u32 timeout=SD_TIMEOUT;
long long lsector=sector;
INTX_DISABLE();//关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
sta=HAL_SD_WriteBlocks(&SDCARD_Handler,(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;
}
作者:白菜小周