这篇blog来说说基于simple-spi这个ipcore编写spi模式的SD Card裸机的驱动程序,移植依赖分不清什么SD卡啊,micro SD啊,miniSD,MMC,SDIO啊,SDHC啊等等一大堆的概念,今天抽了点时间百科和wiki扫盲去了,顺便把总结的贴出来,留自己以后回顾~
MMC:Multimedia Card(多媒体记忆卡),基于NAND-Flash技术,衍生版有出RS-MMC(小尺寸的多媒体卡)、双电压小尺寸多媒体卡(DV-RS-MMC)。4.x规范引入升级版MMC plus,MMC4卡和RS-MMC4(移动式MMC,老式RS-MMC的山寨),并且引入secureMMC规范
SD:Secure Digital Memory Card,基于MMC卡格式发展而来,同样是NAND-Flash技术,SD设备兼容MMC卡存取,而MMC设备不能存取SD卡(因为卡槽不一致,不能互插)。
miniSD:个人理解是小型的SD卡,兼容SD卡,电器属性稍微有点区别。
microSD:原名Trans-flash Card(TF卡),04年更名microSD Card,比miniSD尺寸更小的SD卡。
SDHC:Secure Digital High Capacity(高容量SD卡4G~32G),鉴定完毕(本人手机用的也只microSD咧,SDHC用在什么方面?求解释),规定使用FAT32文件系统。
SDIO:Secure Digital Input and Output Card(安全数字输入输出卡),在SD标准上定义的一种外设接口。(接口?我能不能理解成类似USB一样,不过就是把外设的接口做成SDIO,那有USB了为毛还要用这个接口,求大神解释)
目前SD卡差不多取代完MMC卡了,不过SD的卡槽可以卡得进MMC,所以是兼容MMC的,也因为这样MMC卡,因为用MMC卡可以不交SD协议的版税,所以还在用,至于miniSD和microSD套进去插到SD卡槽就可以用了,SDHC没见过,解释不了了,SDIO的设备也没用过,可以去淘宝YY一下,SDIO接口的wifi,蓝牙等等的,但是比起USB接口的来得贵,大神,求解释啊,应用场合是什么?
这世界貌似好多东西我不能够理解啊~除了坑爹还是坑爹~
再说说那个操作接口和时序规范的问题
(转一下wiki上面关于各种技术对比的图,很一目了然)
SD卡比起MMC多了2pins,都是用来做数据线的;
miniSD比起SD又多了2pins,现在只做预留,没什么用处,其他和SD卡一样
microSD比SD少了1pin?貌似比对我手上的那些原理图是少了个VSS?
因为历史原因,SPI时序基本在这里面都会支持,还有SD总线的1bit mode是支持的,但是SD卡用于高速情况是一般我们选择是4bit mode,我自己是没用过SD总线的1 bit mode,通常我会选择跑高速数据选择SD总线的4 bit mode,低速跑SPI。
有这个SD 1 bit mode为毛还要SPI mode咧?所以这个世界除了坑爹还是坑爹,但是像我们这些读过一点点书的人都知道:“存在就是合理”这个碉堡了的名言。
1.你见过大部分SOC(特别是低端)集成SPI controller还是支持SD Bus 和controller多
2.写过SD卡驱动的朋友们处理CRC校验你耗资源多不多(硬件无CRC校验)
3.SD的CMD线与DATA线之间有可能同时产生数据,对没有SD硬件模块的主机支持起来难度较高(引用别人blog的原话)
挑点重点来讲讲,也因为我只知道这么多了,见笑见笑!!!
下面就是讲讲SD/MMC规范SPI时序的SD卡读写过程了,至于SD 4bit mode就先不细说了,以后有时间编写程序再发个blog~
至于官方的SD specs我没怎么看过,太长了~真心看不动~google一下有没有别人总结过的经验了,就算把官方的specs翻译成中文也懒得看了~
SD发展了这么多年头,民间总结的资料我觉得比起看官方的spces来得实际很多,至于关于SD卡的资料推荐《ALIENTEK战舰STM32开发板》里面的光盘,这里绝对我不是托,只是朋友再用这款开发板,而里面整理好关于SD卡的资料我真心觉得不错。
到此,根据写好的基于simple-spi这个core的SD驱动,总结最简单的流程
一、Initialize:
1.开发板上电
2.延时>74clocks
3.写CMD0,复位SD卡
4.写CMD8,检查版本
5.写CMD58,查询OCR,获取卡供电情况
6.写CMD1,激活卡
7.8 clocks后,禁止SD的CS
二,read block:
1.写CMD17
2.等到R1格式的命令应答
3.读取起始令牌0xFE
4.读512 bytes
5.丢弃2 bytes的CRC校验bytes
6.8 clocks后,禁止SD的CS
三、write block:
1.写CMD24
2.等到R1格式的命令应答
3.插入若干clocks
4.写起始令牌0xFE
5.写512 bytes
6.写2 bytes CRC校验bytes(dummy bytes)
7.接收响应数据0x05
8.8 clocks后,禁止SD的CS
四、write blocks:
1.写CMD25
2.等到R1格式的命令应答
3.读取起始令牌0xFC
4.写512 bytes
5.写2 bytes CRC校验bytes(dummy bytes)
6.接收响应数据0x05
7.等待SD card空闲
8.重复第2步
至于关于SD卡的命令集google一下一大堆,了解一下常用的命令就OK啦,或者参看官方的SD specs 2.x的第7 chapter关于SPI Mode的描述,里面有关于命令集合时序图。
废话不多了,关于SD的基础知识和SPI的操作解释到这里,至于朋友们还需要更多的SD的知识可以去wiki关于SD的详解。
http://en.wikipedia.org/wiki/Secure_Digital
下面就根据SD卡SPI的时序图和simple-spi这个core的spces来敲SD card的driver
还是来讲讲编写好的simple-spi的几个封装函数先了,这些函数咧,是opencore社区的牛牛写好的,我就不另外自己去编了~省点精力~
首先在高速读写SD卡的时候咧,用轮询方式比用中断方式去读写SD卡效率要高(真的吗?我自己没研究过),但是我轻信了这句话,所以没用中断去写SD的driver,所以这里仅仅介绍用到的API啦~
这个驱动文件在u-boot/driver/spi中可以找到oc_simple_spi.c~
至于对应的simple-spi的代码我用的是orpsocv2里面的,orpsocv2修改了simple-spi支持slave的选择功能,这份驱动就是按照修改过的core编写的,上代码了~
只贴出来用到的函数,具体参考simple-spi的specs把寄存器配置都稍微看懂。
void
spi_core_enable(int core)
{
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) |= SIMPLESPI_SPCR_SPE;
}
void
spi_core_disable(int core)
{
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) &= ~SIMPLESPI_SPCR_SPE;
}
void
spi_core_clock_setup(int core, char polarity, char phase, char rate,
char ext_rate)
{
char spcr = REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR));
if (polarity)
spcr |= SIMPLESPI_SPCR_CPOL;
else
spcr &= ~SIMPLESPI_SPCR_CPOL;
if (phase)
spcr |= SIMPLESPI_SPCR_CPHA;
else
spcr &= ~SIMPLESPI_SPCR_CPHA;
spcr = (spcr & ~SIMPLESPI_SPCR_SPR) | (rate & SIMPLESPI_SPCR_SPR);
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) = spcr;
char sper = REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPER));
sper = (sper & ~SIMPLESPI_SPER_ESPR) | (ext_rate & SIMPLESPI_SPER_ESPR);
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPER)) = sper;
}
// No decode on slave select lines, so assert correct bit to select slave
void
spi_core_slave_select(int core, char slave_sel_dec)
{
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SSPU)) = slave_sel_dec;
}
int
spi_core_data_avail(int core)
{
return !!!(REG8((SPI_BASE_ADR[core]+SIMPLESPI_SPSR))&SIMPLESPI_SPSR_RFEMPTY);
}
int
spi_core_write_avail(int core)
{
return !!!(REG8((SPI_BASE_ADR[core]+SIMPLESPI_SPSR))&SIMPLESPI_SPSR_WFFULL);
}
// Should call spi_core_write_avail() before calling this, we don't check
void
spi_core_write_data(int core, char data)
{
REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPDR)) = data;
}
char
spi_core_read_data(int core)
{
return REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPDR));
}
然后稍微理解用到的最通常的读写函数,就可以转到SD卡driver的编写了~驱动参考振南兄的znFAT里SD驱动流程编写
好,第一个,reset过程函数SDReset(),对着时序图来说话
按照上面描述过的初始化流程,送74clocks,cs拉低,送命令CMD0(0x4000000095),间隔8*clock*n后,等待从设备的0x1应答,cs再次拉高
必须注意的是,reset和初始化时时钟速率必须降低,至于速率降到多少貌似还没有定论,我初始化的时候时降到了300K左右,貌似400K一下都可以操作。
unsigned char SDReset(void){
unsigned char times, temp, i;
unsigned char pcmd[] = {0x40,0x00,0x00,0x00,0x00,0x95}; // CMD0
// send SDCard 74 clocks
spi_core_slave_select(SD, SDDisable);
SetSDClockInitRate(SD); // slow down sdcard clock speed
for(i=0; i<0x0f; i++){
spi_write_ignore_read(SD, 0xff); // 8*15 = 120 clocks
}
// send CMD0
spi_core_slave_select(SD, SDEnable);
times = 0;
do{
temp = SDWriteCmd(pcmd);
times++;
if(times == TRY_TIME){
return INIT_CMD0_ERROR;
}
}while(temp!=0x01);
spi_core_slave_select(SD, SDDisable);
spi_write_ignore_read(SD, 0xff);
return 0;
}
初始化SD卡时序:cs拉低,送命令CMD0(0x41000000ff),间隔8*clock*n后,等待从设备的0x0应答,cs再次拉高
初始化代码:
unsigned char SDInit(void){
unsigned char times, temp;
unsigned char pcmd[] = {0x41,0x40,0x00,0x00,0x00,0xff};
spi_core_slave_select(SD, SDEnable);
times = 0;
do{
temp = SDWriteCmd(pcmd);
times++;
if(times==TRY_TIME){
return INIT_CMD1_ERROR;
}
}while(temp!=0x00);
SetSDClockTransferRate(SD); //set sdcrad clock as transfre clock
spi_core_slave_select(SD, SDDisable);
spi_write_ignore_read(SD, 0xff);
return 0;
}
至于CID和CSD的读取,程序里没有实现,有兴趣的朋友可以根据CMD0和CMD1的时序自己敲个代码上去补充完整
在默认情况下,SD卡的读写block大小都是512bytes,所以就按照最通常的block大小进行读写操作。
block读操作:cs拉低,送命令CMD17(0x51000000ff),间隔8*clock*n后,等待从设备的0x0应答,再次间隔间隔8*clock*n,读取开始字节(0xfe),读取512bytes,丢弃2bytes的CRC,cs再次拉高
读写一个section的代码:
unsigned char SDReadSector(unsigned long addr,unsigned char *buffer){
unsigned char temp,times;
unsigned char i;
unsigned char pcmd[]={0x51,0x00,0x00,0x00,0x00,0xff}; // CMD17
addr<<=9;
pcmd[1]=addr>>24;
pcmd[2]=addr>>16;
pcmd[3]=addr>>8;
pcmd[4]=addr;
spi_core_slave_select(SD, SDEnable);
times = 0;
do{
temp = SDWriteCmd(pcmd);
times++;
if(times==TRY_TIME){
return READ_BLOCK_ERROR;
}
}while(temp!=0x00);
// check if datas ready ,then recevie datas and two bytes CRC(ignored)
do{
temp = spi_read_ignore_write(SD);
}while(temp!=0xfe);
for(i=0; i<64; i++){
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
*(buffer++) = spi_read_ignore_write(SD);
}
spi_read_ignore_write(SD);
spi_read_ignore_write(SD);
spi_core_slave_select(SD, SDDisable);
spi_write_ignore_read(SD, 0xff);
return 0;
}
block写操作:cs拉低,送命令CMD24(0x58000000ff),间隔8*clock*n后,等待从设备的0x0应答,再次间隔间隔8*clock*n,写入开始字节(0xfe),写入512bytes,写2bytes的dummy CRC,读取应答字节0x05,等待SD忙状态结束,cs再次拉高。
写操作代码:
unsigned char SDWriteSector(unsigned long addr,unsigned char *buffer){
unsigned char temp,times;
unsigned char i;
unsigned char pcmd[] = {0x58,0x00,0x00,0x00,0x00,0xff}; // CMD24
addr<<=9;
// *((unsigned long *)(pcmd+1))=addr;
pcmd[1]=addr>>24;
pcmd[2]=addr>>16;
pcmd[3]=addr>>8;
pcmd[4]=addr;
spi_core_slave_select(SD, SDEnable);
times = 0;
do{
temp = SDWriteCmd(pcmd);
times++;
if(times==TRY_TIME){
return temp;
}
}while(temp!=0x00);
// insert some clocks
for(i=0; i<10; i++){
spi_read_ignore_write(SD);
}
// write 512 bytes to SD Card ,and two bytes CRC(ignored)
spi_write_ignore_read(SD, 0xfe);
for(i=0; i<64; i++){
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
}
spi_write_ignore_read(SD, 0xff);
spi_write_ignore_read(SD, 0xff);
// check if datas have been written to SD Card
temp = spi_read_ignore_write(SD);
if((temp & 0x1F)!=0x05){
spi_core_slave_select(SD, SDDisable);
return WRITE_BLOCK_ERROR;
}
do{
temp = spi_read_ignore_write(SD);
}while(temp!=0xff);
spi_core_slave_select(SD, SDDisable);
spi_write_ignore_read(SD, 0xff);
return 0;
}
对于多个blocks的连续写操作,直接参考代码吧,只是用连续写操作命令CMD18,命令的参数段位写入datas时的address,好吧,有了写操作过程连续写的过程也不难,上代码咯~
连续写n个sections代码:
unsigned char SDWritenSector(unsigned long nsec,unsigned long addr,unsigned char *buffer){
unsigned char temp,times;
unsigned long i, j;
unsigned char pcmd[] = {0x59,0x00,0x00,0x00,0x00,0xff};
unsigned char *temp_buf = buffer;
if(sd_ver==0x05 || !addr_mode) addr<<=9;
pcmd[1]=addr>>24;
pcmd[2]=addr>>16;
pcmd[3]=addr>>8;
pcmd[4]=addr;
spi_core_slave_select(SD, SDEnable);
times = 0;
do{
temp = SDWriteCmd(pcmd);
times++;
if(times==TRY_TIME){
return temp;
}
}while(temp!=0x00);
// insert some clocks
for(i=0; i<10; i++){
spi_read_ignore_write(SD);
}
// write datas to sections
for(j=0; j<nsec; j++){
// write 512 bytes to SD Card ,and two bytes CRC(ignored)
spi_write_ignore_read(SD, 0xfc);
for(i=0; i<64; i++){
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
spi_write_ignore_read(SD, *buffer++);
}
spi_write_ignore_read(SD, 0xff);
spi_write_ignore_read(SD, 0xff);
// check if datas have been write to SD Card
temp = spi_read_ignore_write(SD);
if((temp & 0x1F)!=0x05){
spi_core_slave_select(SD, SDDisable);
return WRITE_BLOCK_ERROR;
}
while(spi_read_ignore_write(SD)!=0xff);
buffer=temp_buf;
}
spi_write_ignore_read(SD, 0xfd);
while(spi_read_ignore_write(SD)!=0xff);
spi_core_slave_select(SD, SDDisable);
spi_write_ignore_read(SD, 0xff);
return 0;
}
OK,到这里SD卡的驱动就基本可以用了,全部的源码的话可以有兴趣的朋友邮件我,接下来就是还有一些用到的函数都讲讲吧。
SD驱动的读写函数
void spi_write_ignore_read(int core, char dat){
spi_core_write_data(core, dat);
while (!(spi_core_data_avail(core))); // Wait for the transaction (should generate a byte)
spi_core_read_data(core);
}
char spi_read_ignore_write(int core){
spi_core_write_data(core, 0xff);
while (!(spi_core_data_avail(core))); // Wait for the transaction (should generate a byte)
return spi_core_read_data(core);
}
Oc-simple-spi的初始化函数
void SpiCoreInit(int core){
// disable spi core, and deselect sdcard
spi_core_enable(core);
spi_core_slave_select(core, SDDisable);
// clear read buffer
while (spi_core_data_avail(core)){
spi_core_read_data(core);
}
// setup default clock = sysclk/128
spi_core_clock_setup(core, 0, 0, 0x01, 0x02);
// enable spi core
spi_core_slave_select(core, SDEnable);
spi_core_enable(core);
}
Oc-simple-spi的时钟速率切换函数
void SetSDClockInitRate(int core){
spi_core_disable(core);
// set sdcard clock = sysclk/128
spi_core_clock_setup(core, 0, 0, 0x01, 0x02);
spi_core_enable(core);
}
void SetSDClockTransferRate(int core){
spi_core_disable(core);
// set sdcard clock = sysclk/2
spi_core_clock_setup(core, 0, 0, 0x00, 0x00);
spi_core_enable(core);
}
SD驱动的初始化函数,这个函数用于下一节提到的在移植znFat时需要驳接的SD卡复位函数
unsigned char SDReady(void){
SDReset();
SDCheckVersion();
SDGetAddrMode();
return SDInit();
}
好,没了,驱动写到这里就OK了,下节根据znFat的移植教程移植这个文件系统