简介
选路由用“芯”最关键
Ralink (雷凌,台湾)
Broadcom(博通,美国)
Atheros(创锐讯,美国)
Realtek(瑞昱,台湾)
大事件:被收购
PIN | SDIO 4-BIT MODE | SDIO 1-BIT MODE | ||
1 | CD/DAT3 | Data line 3 | N/C | Not used |
2 | CMD | Command line | CMD | Command line |
3 | VSS1 | Ground | VSS1 | Ground |
4 | VDD | Supply voltage | VDD | Supply voltage |
5 | CLK | Clock | CLK | Clock |
6 | VSS2 | Ground | VSS2 | Ground |
7 | DAT[0] | Data line 0 | DATA | Data line |
8 | DAT[1] | Data line1 or INT line | IRQ | Interruption |
9 | DAT[2] | Data line2 or read wait | RW | Read wait |
PIN | SD MODE | ||
1 | Name | Type | Description |
2 | CD/DATA3 | I/O/PP | Card detect/data line3 |
3 | CMD | Pp | Command/response |
4 | Vss1 | S | Ground |
5 | VDD | S | Supply voltage |
6 | Vss2 | S | Ground |
7 | DAT0 | I/O/PP | Data line0 |
8 | DAT1 | I/O/PP | Data line1 |
9 | DAT2 | I/O/PP | Data line2 |
实验描述 | MicroSD卡(SDIO模式)测试实验,采用4bit数据线模式。没有跑文件系统,只是单纯地读block并将测试信息通过串口1在电脑的超级终端上 |
硬件连接 | PC12-SDIO-CLK:CLK PC9-SDIO-D1:DATA1 |
用到的库文件 | startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.c FWlib/stm32f10x_usart.c FWlib/ FWlib/ FWlib/ |
用户编写的文件 | USER/main.cUSER/stm32f10x_it.cUSER/usart1.cUSER/ |
野火STM32开发板 MicroSD卡硬件原理图:
野火STM32开发板的CPU ( STM32F103VET6 )具有一个SDIO接口。SD/SDIO/MMC主机接口可以支持MMC卡系统规范4.2版中的3个不同的数据总线模式:1位(默认)、4位和8位。在8位模式下,该接口可以使数据传输速率达到48MHz,该接口兼容SD存储卡规范2.0版。SDIO存储卡规范2.0版支持两种数据总线模式:1位(默认)和4位。
目前的芯片版本只能一次支持一个SD/SDIO/MMC 4.2版的卡,但可以同时支持多个MMC 4.1版或之前版本的卡。除了SD/SDIO/MMC,这个接口完全与CE-ATA数字协议版本1.1兼容。
1.3 SD协议
大多数人原来没有了解过SD协议,又看到SDIO的驱动有2000多行,感觉无从下手。所以野火重新写了这个文档进行详细的解释,帮助大家更快地跨过这道槛。
附资料:《Simplified_Physical_Layer_Spec.pdf》,这个资料包含了SDIO协议中SD存储卡的部分。
下面野火结合STM32的SDIO,分析SD协议,让大家对它先有个大概了解,更具体的说明在代码中展开。
SDIO接口图
一.从SDIO的时钟说起。
SDIO_CK时钟是通过PC12引脚连接到SD卡的,是SDIO接口与SD卡用于同步的时钟。
SDIO选配器挂载到AHB总线上,通过HCLK二分频输入到适配器得到SDIO_CK的时钟,这时SDIO_CK = HCLK/(2+CLKDIV)。其中CLKDIV是SDIO_CLK(寄存器)中的CLKDIV位。
另外,SDIO_CK也可以由SDIOCLK通过设置bypass模式直接得到,这时SDIO_CK = SDIOCLK=HCLK。
通过下面的库函数来配置时钟:
SDIO_Init(&SDIO_InitStructure);
对SD卡的操作一般是大吞吐量的数据传输,所以采用DMA来提高效率,SDIO采用的是DMA2中的通道4。在数据传输的时候SDIO可向DMA发出请求。
二.讲解SDIO的命令、数据传输方式。
SDIO的所有命令及命令响应,都是通过SDIO-CMD引脚来传输的。
命令只能由host即STM32的SDIO控制器发出。SDIO协议把命令分成了11种,包括基本命令,读写命令还有ACMD系列命令等。其中,在发送ACMD命令前,要先向卡发送编号为CMD55的命令。
参照下面的命令格式图,其中的start bit,transmission bit ,crc7,endbit,都是由STM32中的SDIO硬件完成,我们在软件上配置的时候只需要设置command index和命令参数argument。Command index就是命令索引(编号),如CMD0,CMD1…被编号成0,1...。有的命令会包含参数,读命令的地址参数等,这个参数被存放在argument段。
SD卡命令格式
可以通过下面的函数来配置、发送命令:
SDIO_SendCommand(&SDIO_CmdInitStructure);
SD卡对host的各种命令的回复称为响应,除了CMD0命令外,SD卡在接收到命令都会返回一个响应。对于不同的命令,会有不同的响应格式,共7种,分为长响应型(136bit)和短响应型(48bit)。以下图,响应6(R6)为例:
SD卡命令响应格式(R6)
SDIO通过CMD接收到响应后,硬件去除头尾的信息,把command index 保存到SDIO_RESPCMD寄存器,把argument field内容保存存储到SDIO_RESPx寄存器中。这两个值可以分别通过下面的库函数得到。
SDIO_GetCommandResponse();
SDIO_GetResponse(SDIO_RESP1);
数据写入,读取。请看下面的写数据时序图,在软件上,我们要处理的只是读忙。另外,我们的实验中用的是Micro SD卡,有4条数据线,默认的时候SDIO采用1条数据线的传输方式,更改为4条数据线模式要通过向卡发送命令来更改。
SD卡的多块写入时序图
三.卡的种类。
STM32的SDIO支持SD存储卡,SD I/O卡 ,MMC卡。
其中SDI/O卡与SD存储卡是有区别的,SDI/O卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDI/O wifi, SDI/O cmos相机等。而SD存储卡就是我们平时常见的单纯用于存储数据的卡。
可使用SDIO接口类型的卡
本实验中使用的Micro SD卡属于SDSC(标准容量,最大两G)卡。介绍卡的种类是因为SD协议中的命令也支持这三种类型的卡,因此对STM32中的SDIO接口进行初始化后,上电后就要对接入的卡进行检测、分类,这个过程是通过向卡发送一系列不同的命令,根据卡不同的响应来进行分类。
下面进入代码展开具体讲解。
1.4 代码分析
首先要添加用的库文件,在工程文件夹下Fwlib下我们需添加以下库文件:
FWlib/stm32f10x_gpio.c
FWlib/stm32f10x_rcc.c
FWlib/stm32f10x_usart.c
FWlib/stm32f10x_sdio.c
FWlib/stm32f10x_dma.c
FWlib/misc.c
还要在 stm32f10x_conf.h中把相应的头文件添加进来:
#include
#include
#include
#include
#include
#include
保持良好的习惯,从main函数开始分析:
int
{
NVIC_Configuration();
USART1_Config();
Status
printf(
if(Status
{
printf(
}
else
{
printf("\r\n
printf("\r\n
}
printf(
printf(
printf(
printf(
printf(
SD_EraseTest();
SD_SingleBlockTest();
SD_MultiBlockTest();
while
{}
}
main函数的流程简单明了:
1、用NVIC_Configuration()初始化好SDIO的中断;
2、用USART1_Config()配置好用于返回调试信息的串口,SD_Init()开始进行SDIO的初始化;
3、最后分别用SD_EraseTest()、SD_SingleBlockTest()、SD_MultiBlockTest()进行擦除,单数据块读写,多数据块读写测试。
下面我们先进入SDIO驱动函数的大头——SD_Init()进行分析:
SD_Error
{
SD_Error
GPIO_Configuration();
SDIO_DeInit();
errorstatus
if
{
return(errorstatus);
}
errorstatus
if
{
return(errorstatus);
}
SDIO_InitStructure.SDIO_ClockDiv
SDIO_InitStructure.SDIO_ClockEdge
SDIO_InitStructure.SDIO_ClockBypass
SDIO_InitStructure.SDIO_ClockPowerSave
SDIO_InitStructure.SDIO_BusWide
SDIO_InitStructure.SDIO_HardwareFlowControl
SDIO_Init(&SDIO_InitStructure);
if
{
errorstatus
}
if
{
errorstatus
}
if
{
errorstatus
}
return(errorstatus);
}
先从整体上了解这个SD_Init()函数:
1.用
2.分别调用了SD_PowerON()和SD_InitializeCards()函数,这两个函数共同实现了上面提到的卡检测、识别流程。
3.调用SDIO_Init(&SDIO_InitStructure)库函数配置SDIO的时钟,数据线宽度,硬件流(在读写数据的时候,开启硬件流是和很必要的,可以减少出错)
4. 调用SD_GetCardInfo(&SDCardInfo)获取sd卡的CSD寄存器中的内容,在main函数里输出到串口的数据就是这个时候从卡读取得到的。
5. 调用SD_SelectDeselect()选定后面即将要操作的卡。
6.调用SD_EnableWideBusOperation(SDIO_BusWide_4b)开启4bit数据线模式
如果SD_Init()函数能够执行完整个流程,并且返回值是SD_OK的话则说明初始化成功,就可以开始进行擦除、读写的操作了。
下面进入SD_PowerON()函数,分析完这个函数大家就能了解SDIO如何接收、发送命令了。
SD_Error
{
SD_Error
uint32_t
uint32_t
SDIO_InitStructure.SDIO_ClockDiv
SDIO_InitStructure.SDIO_ClockEdge
SDIO_InitStructure.SDIO_ClockBypass
SDIO_InitStructure.SDIO_ClockPowerSave
SDIO_InitStructure.SDIO_BusWide
SDIO_InitStructure.SDIO_HardwareFlowControl
SDIO_Init(&SDIO_InitStructure);
SDIO_SetPowerState(SDIO_PowerState_ON);
SDIO_ClockCmd(ENABLE);
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
CardType
SDType
}
else
{
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
}
//发送cmd55,用于检测是sd卡还是mmc卡,或是不支持的卡
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
while
{
//因为下面要用到ACMD41,是ACMD命令,在发送ACMD命令前都要先向卡发送CMD55
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);//没响应CMD55,返回
}
//acmd41,命令参数由支持的电压范围及HCS位组成,HCS位置一来区分卡是SDSc还是sdhc
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
response
validvoltage
count++;
}
if
{
errorstatus
return(errorstatus);
}
if
{
CardType
}
}
return(errorstatus);
}
这个函数的流程就是卡的上电、识别操作,如下图:
卡的上电,识别流程:
截图来自《Simplified_Physical_Layer_Spec.pdf》 page27
代码中所有的判断语句都是根据这个图的各个识别走向展开的,最终把卡分为1.0版的SD存储卡,2.0版的SDSC卡和2.0版的SDHC卡。
在这个代码流程中有两点要注意一下:
1.初始化的时钟。SDIO_CK的时钟分为两个阶段,在初始化阶段SDIO_CK的频率要小于400KHz,初始化完成后可把SDIO_CK调整成高速模式,高速模式时超过24M要开启bypass模式,对于SD存储卡即使开启bypass,最高频率不能超过25MHz。
2.CMD8命令。
CMD8命令格式。
CMD8命令中的VHS是用来确认主机SDIO是否支持卡的工作电压的。Check pattern部分可以是任何数值,若SDIO支持卡的工作电压,卡会把接收到的check pattern数值原样返回给主机。
CMD8命令的响应格式R7:
在驱动程序中调用了CmdResp7Error()来检验卡接收命令后的响应。
3.ACMD41命令。
这个命令也是用来进一步检查SDIO是否支持卡的工作电压的,协议要它在调用它之前必须先调用CMD8,另外还可以通过它命令参数中的HCS位来区分卡是SDHC卡还是SDSC卡。
确认工作电压时循环地发送ACMD41,发送后检查在SD卡上的OCR寄存器中的pwr_up位,若pwr_up位置为1,表明SDIO支持卡的工作电压,卡开始正常工作。
同时把ACMD41中的命令参数HCS位置1,卡正常工作的时候检测OCR寄存器中的CCS位,若CCS位为1则说明该卡为SDHC卡,为零则为SDSC卡。
因为ACMD41命令属于ACMD命令,在发送ACMD命令前都要先发送CMD55.
ACMD41命令格式
ACMD41命令的响应(R3),返回的是OCR寄存器的值
OCR寄存器的内容
SD卡上电确认成功后,进入SD_InitializeCards()函数:
SD_Error
{
SD_Error
uint16_t
if
{
errorstatus
return(errorstatus);
}
if
{
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
CID_Tab[0]
CID_Tab[1]
CID_Tab[2]
CID_Tab[3]
}
if
||
{
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
}
if
{
RCA
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
CSD_Tab[0]
CSD_Tab[1]
CSD_Tab[2]
CSD_Tab[3]
}
errorstatus
return(errorstatus);
}
这个函数向卡发送了CMD2和CMD3命令
1.CMD2
CMD2命令是要求卡返回它的CID寄存器的内容。
命令的响应格式(R2)。
因为命令格式是136位的,属于长响应。软件接收的信息有128位。在长响应的时候通过SDIO_GetResponse(SDIO_RESP4);中的不同参数来获取CID中的不同数据段的数据。
2.CMD3
CMD3命令是要求卡向主机发送卡的相对地址。在接有多个卡的时候,主机要求接口上的卡重新发一个相对地址,这个地址跟卡的实际ID不一样。比如接口上接了5个卡,这5个卡的相对地址就分别为1,2,3,4,5.以后主机SDIO对这几个卡寻址就直接使用相对地址。这个地址的作用就是为了寻址更加简单。
接下来我们回到SD_Init()函数。分析到这里大家应该对SDIO的命令发送和响应比较清楚了。在SD_InitializeCards()之后的SD_GetCardInfo(&SDCardInfo)、 SD_SelectDeselect()和
SD_EnableWideBusOperation(SDIO_BusWide_4b)的具体实现就不再详细分析了,实际就是发送相应的命令,对卡进行相应的操作。
接下来分析main函数中的SD_MultiBlockTest()多块数据读写函数,让大家了解SDIO是怎样传输数据的。
void
{
Fill_Buffer(Buffer_MultiBlock_Tx,
if
{
Status
Status
while(SD_GetStatus()
}
if
{
Status
Status
while(SD_GetStatus()
}
if
{
TransferStatus2
}
if(TransferStatus2
printf("\r\n
else
printf("\r\n
}
把这个函数拿出来分析最重要的一点就是让大家注意在调用了SD_WriteMultiBlocks()这一类读写操作的函数后,一定要调用SD_WaitWriteOperation()[在读数据时调用SD_WaitReadOperation]和SD_GetStatus()来确保数据传输已经结束再进行其它操作。其中的SD_WaitWriteOperation()是用来等待DMA把缓冲的数据传输到SDIO的FIFO的;而SD_GetStatus()是用来等待卡与SDIO之间传输数据完毕的。
最后进入SD_WriteMultiBlocks()函数分析:
SD_Error
{
SD_Error
__IO
TransferError
TransferEnd
StopCondition
SDIO->DCTRL
if
{
BlockSize
WriteAddr
}
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
//
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
SDIO_CmdInitStructure.SDIO_Argument
SDIO_CmdInitStructure.SDIO_CmdIndex
SDIO_CmdInitStructure.SDIO_Response
SDIO_CmdInitStructure.SDIO_Wait
SDIO_CmdInitStructure.SDIO_CPSM
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus
if
{
return(errorstatus);
}
SDIO_DataInitStructure.SDIO_DataTimeOut
SDIO_DataInitStructure.SDIO_DataLength
SDIO_DataInitStructure.SDIO_DataBlockSize
SDIO_DataInitStructure.SDIO_TransferDir
SDIO_DataInitStructure.SDIO_TransferMode
SDIO_DataInitStructure.SDIO_DPSM
SDIO_DataConfig(&SDIO_DataInitStructure);
SDIO_ITConfig(SDIO_IT_DATAEND,
SDIO_DMACmd(ENABLE);
SD_DMA_TxConfig((uint32_t
return(errorstatus);
}
写操作在发送正式的多块写入命令CMD25前调用了CMD23进行预写,这样有利于提高写入的速度。在代码的最后调用了SDIO_ITConfig(),SDIO的数据传输结束中断就是这个时候开启的,数据传输结束时,就进入到stm32f10x_it.c文件中的中断服务函数SDIO_IRQHandler()中处理了,中断服务函数主要就是负责清中断。
最后讲一下官方原版的驱动中的一个bug。
在官方原版的SDIO驱动的SD_ReadBlock()、SD_ReadMultiBlocks()、SD_WriteBlock()和SD_WriteMultiBlocks()这几个函数中,发送读写命令前,漏掉了发送一个CMD16命令,这个命令用于设置读写SD卡的块大小。缺少这个命令很容易导致程序运行时卡死在循环检测DMA传输结束的代码中,网上很多人直接移植ST官方例程时,用3.5版库函数和这个4.5版的SDIO驱动移植失败,就是缺少了这段用CMD16设置块大小的代码。
到这里,终于讲解完毕啦!这个讲解如果能让你从对SDIO一无所知到大概了解的话,我的目标就达到啦,想要更深入了解还是要好好地配合这个例程中我在代码中的注释和附带资料SD2.0协议《Simplified_Physical_Layer_Spec.pdf》好好研究一番! ^_^
注意:这个例程是没有跑文件系统的,而是直接就去读卡的block,这样的话就会破坏卡的分区,在实验完成之后,你再把卡插到电脑上时,电脑会提示你要重新初始化卡,这是正常想象,并不是本实验把你的卡弄坏了,如果卡原来有资料的请先把数据备份了再进行测试。但跑文件系统时就不会出现这种问题,有关文件系统的操作将在下一讲的教程中讲解。
1.5实验现象
将野火STM32开发板供电(DC5V),插上JLINK,插上串口线(两头都是母的交叉线),插上MicroSD卡( 我用的是1G ,经测试,本驱动也适用于2G以上的卡(sdhc卡)),打开超级终端,配置超级终端为115200 8-N-1,将编译好的程序下载到开发板,即可看到超级终端打印出如下信息: