[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分) - SD卡(SPI模式)驱动
0赞上一讲,我们完成了Nios II SBTE的配置工作。下面讲解如何根据已有参考资料(手册及代码)编写SD卡驱动。
准备工具及资料
1.WinHex
2. Efronc的博文SD/MMC 接口及上电时序、SD/MMC 内部寄存器、SD/MMC SPI模式下命令集
驱动编写及调试
步骤1 添加sd_card文件夹到APP工程路径
如何添加,请参考[原创][连载].基于SOPC的简易数码相框 – Nios II SBTE部分(软件部分) - 配置工作。
步骤2 编写代码
SD卡有很多标准,此处选用最简单的SD 1-线模式,即SPI模式。
代码2.1 sd_card.h
#ifndef SD_CARD_H_ #define SD_CARD_H_ #include "my_types.h" #include "my_regs.h" #define ENABLE_SD_CARD_DEBUG // turn on debug message void SD_CARD_Port_Init(); void SD_CARD_Write_Byte(u8 byte); u8 SD_CARD_Read_Byte(); u8 SD_CARD_Write_CMD(u8 *CMD); // u8 SD_CARD_Init(); u8 SD_CARD_Write_Sector(u32 addr,u8 *buf); u8 SD_CARD_Read_Sector(u8 *CMD,u8 *buf,u16 n_bytes); u8 SD_CARD_Read_Sector_Start(u32 sector); void SD_CARD_Read_Data(u16 n_bytes,u8 *buf); void SD_CARD_Read_Data_LBA(u32 LBA,u16 n_bytes,u8 *buf); void SD_CARD_Read_Sector_End(); u8 SD_CARD_Read_CSD(u8 *buf); u8 SD_CARD_Read_CID(u8 *buf); void SD_CARD_Get_Info(void); void SD_CARD_DEMO(void); #endif /* SD_CARD_H_ */
第5~6行,加入自定义的宏,统一代码风格。第9行,打开调试信息显示开关。调试正确后,可用添加注释的方式的关闭开关。
第12行void SD_CARD_Port_Init(),为SPI接口的初始函数。
第13~14行void SD_CARD_Write_Byte(u8 byte)和u8 SD_CARD_Read_Byte(),为SPI写字节和读字节函数。
第15行u8 SD_CARD_Write_CMD(u8 *CMD),为SD卡写命令函数。
第17行u8 SD_CARD_Init(),为SD卡的初始化函数。这个函数需要特别注意,因为SPI模式的模式的SD卡需要低速率收发数据来初始化SD卡。
第18~19行u8 SD_CARD_Write_Sector(u32 addr,u8 *buf)和u8 SD_CARD_Read_Sector(u8 *CMD,u8 *buf,u16 n_bytes)为SD卡写块和读块函数;需要注意的是,一般的SD卡的块有512字节,而通过WinHex 查看的SD卡的每个扇区也是512字节。为了统一风格,此处一律写作Sector。
第21行void SD_CARD_Read_Data_LBA(u32 LBA,u16 n_bytes,u8 *buf),比较好用,其参数LBA为Winhex中可查看的扇区地址,及逻辑块地址;有了这个函数,我们后续的工作就方便了需要。
第24~25行u8 SD_CARD_Read_CSD(u8 *buf)和u8 SD_CARD_Read_CID(u8 *buf),为读取SD卡的CSD和CID寄存器函数。
其他的函数请参考源代码自行解析。
代码2.2 sd_card.c
#include#include "sd_card.h" // debug switch #ifdef ENABLE_SD_CARD_DEBUG #include "debug.h" #define SD_CARD_DEBUG(x) DEBUG(x) #else #define SD_CARD_DEBUG(x) #endif // error macro #define INIT_CMD0_ERROR 0x01 #define INIT_CMD1_ERROR 0x02 #define WRITE_BLOCK_ERROR 0x03 #define READ_BLOCK_ERROR 0x04 // SD-CARD(SPI mode) initial with low speed // insert a certain delay #define SD_CARD_INIT_DELAY usleep(10) // CID info structure typedef union { u8 data[16]; struct { u8 MID; // Manufacture ID; Binary u8 OLD[2];// OEM/Application ID; ASCII u8 PNM[5];// Product Name; ASCII u8 PRV; // Product Revision; BCD u8 PSN[4];// Serial Number; Binary u8 MDT[2];// Manufacture Data Code; BCD; upper 4 bits of first byte are reserved u8 CRC; // CRC7_checksum; Binary; LSB are reserved }; }CID_Info_STR; // CSD info structure typedef struct { u8 data[16]; u32 capacity_MB; u8 READ_BL_LEN; u16 C_SIZE; u8 C_SIZE_MULT; }CSD_Info_STR; // flags u16 gByteOffset=0; // byte offset in one sector u16 gSectorOffset=0; // sector offset in SD-CARD bool gSectorOpened=FALSE;// set to 1 when a sector is opened. bool gSD_CARDInit=FALSE; // set it to 1 when SD-CARD is initialized // SD-CARD port init void SD_CARD_Port_Init() { sd_CLK=1; sd_DOUT=1; sd_nCS=1; } // write a byte to SD-CARD void SD_CARD_Write_Byte(u8 byte) { u8 i; for(i=0;i<8;i++) { // MSB First sd_DIN=(byte >> (7-i)) & 0x1; sd_CLK=0;if(gSD_CARDInit) SD_CARD_INIT_DELAY; sd_CLK=1;if(gSD_CARDInit) SD_CARD_INIT_DELAY; } } // read a byte to SD-CARD u8 SD_CARD_Read_Byte() { u8 i,byte; byte=0; for(i=0;i<8;i++) { // MSB First sd_CLK=0;if(gSD_CARDInit) SD_CARD_INIT_DELAY; byte<<=1;if(sd_DOUT) byte++; sd_CLK=1;if(gSD_CARDInit) SD_CARD_INIT_DELAY; } return byte; } // write a command to SD-CARD // return: the second byte of response register of SD-CARD u8 SD_CARD_Write_CMD(u8 *CMD) { u8 temp,retry; u8 i; sd_nCS=1; // set chipselect (disable SD-CARD) SD_CARD_Write_Byte(0xFF); // send 8 clock impulse sd_nCS=0; // clear chipselect (enable SD-CARD) // write 6 bytes command to SD-CARD for(i=0;i<6;i++) SD_CARD_Write_Byte(*CMD++); // get 16 bits response SD_CARD_Read_Byte(); // read the first byte, ignore it. retry=0; do { // only last 8 bits is valid temp=SD_CARD_Read_Byte(); retry++; } while((temp==0xff) && (retry<100)); return temp; } // SD-CARD initialization(SPI mode) u8 SD_CARD_Init() { u8 retry,temp; u8 i; u8 CMD[]={0x40,0x00,0x00,0x00,0x00,0x95}; SD_CARD_Port_Init(); usleep(1000); SD_CARD_DEBUG(("SD-CARD Init!\n")); gSD_CARDInit=TRUE; // Set init flag of SD-CARD for(i=0;i<10;i++) SD_CARD_Write_Byte(0xff);// send 74 clock at least!!! // write CMD0 to SD-CARD retry=0; do { // retry 200 times to write CMD0 temp=SD_CARD_Write_CMD(CMD); retry++; if(retry==200) return INIT_CMD0_ERROR;// CMD0 error! } while(temp!=1); //write CMD1 to SD-CARD CMD[0]=0x41;// Command 1 CMD[5]=0xFF; retry=0; do { // retry 100 times to write CMD1 temp=SD_CARD_Write_CMD(CMD); retry++; if(retry==100) return INIT_CMD1_ERROR;// CMD1 error! } while(temp!=0); gSD_CARDInit=FALSE; // clear init flag of SD-CARD sd_nCS=1; // disable SD-CARD SD_CARD_DEBUG(("SD-CARD Init Suc!\n")); return 0x55;// All commands have been taken. } // writing a Block(512Byte, 1 sector) to SD-CARD // return 0 if sector writing is completed. u8 SD_CARD_Write_Sector(u32 addr,u8 *buf) { u8 temp,retry; u16 i; // CMD24 for writing blocks u8 CMD[]={0x58,0x00,0x00,0x00,0x00,0xFF}; SD_CARD_DEBUG(("Write A Sector Starts!!\n")); addr=addr << 9;// addr=addr * 512 CMD[1]=((addr & 0xFF000000) >>24 ); CMD[2]=((addr & 0x00FF0000) >>16 ); CMD[3]=((addr & 0x0000FF00) >>8 ); // write CMD24 to SD-CARD(write 1 block/512 bytes, 1 sector) retry=0; do { // retry 100 times to write CMD24 temp=SD_CARD_Write_CMD(CMD); retry++; if(retry==100) return(temp);//CMD24 error! } while(temp!=0); // before writing, send 100 clock to SD-CARD for(i=0;i<100;i++) SD_CARD_Read_Byte(); // write start byte to SD-CARD SD_CARD_Write_Byte(0xFE); SD_CARD_DEBUG(("\n")); // now write real bolck data(512 bytes) to SD-CARD for(i=0;i<512;i++) SD_CARD_Write_Byte(*buf++); SD_CARD_DEBUG(("CRC-Byte\n")); SD_CARD_Write_Byte(0xFF);// dummy CRC SD_CARD_Write_Byte(0xFF);// dummy CRC // read response temp=SD_CARD_Read_Byte(); if( (temp & 0x1F)!=0x05 ) // data block accepted ? { sd_nCS=1; // disable SD-CARD return WRITE_BLOCK_ERROR;// error! } // wait till SD-CARD is not busy while(SD_CARD_Read_Byte()!=0xff){}; sd_nCS=1; // disable SD-CARD SD_CARD_DEBUG(("Write Sector suc!!\n")); return 0; } // read bytes in a block(normally 512KB, 1 sector) from SD-CARD // return 0 if no error. u8 SD_CARD_Read_Sector(u8 *CMD,u8 *buf,u16 n_bytes) { u16 i; u8 retry,temp; // write CMD to SD-CARD retry=0; do { // Retry 100 times to write CMD temp=SD_CARD_Write_CMD(CMD); retry++; if(retry==100) return READ_BLOCK_ERROR;// block read error! } while(temp!=0); // read start byte form SD-CARD (0xFE/Start Byte) while(SD_CARD_Read_Byte()!=0xfe); // read bytes in a block(normally 512KB, 1 sector) from SD-CARD for(i=0;i byte address) sector=sector << 9;// sector=sector * 512 CMD[1]=((sector & 0xFF000000) >>24 ); CMD[2]=((sector & 0x00FF0000) >>16 ); CMD[3]=((sector & 0x0000FF00) >>8 ); // write CMD16 to SD-CARD retry=0; do { temp=SD_CARD_Write_CMD(CMD); retry++; if(retry==100) return READ_BLOCK_ERROR;// READ_BLOCK_ERROR } while( temp!=0 ); // read start byte form SD-CARD (feh/start byte) while (SD_CARD_Read_Byte() != 0xfe); SD_CARD_DEBUG(("Open a Sector Succ!\n")); gSectorOpened=TRUE; return 0; } void SD_CARD_Read_Data(u16 n_bytes,u8 *buf) { u16 i; for(i=0;((i <512));i++) { *buf++=SD_CARD_Read_Byte(); gByteOffset++;// increase byte offset in a sector } if(gByteOffset==512) { SD_CARD_Read_Byte(); // Dummy CRC SD_CARD_Read_Byte(); // Dummy CRC gByteOffset=0; // clear byte offset in a sector gSectorOffset++; // one sector is read completely gSectorOpened=FALSE; // set to 1 when a sector is opened sd_nCS=1; // disable SD-CARD } } // read block date by logic block address(sector offset) void SD_CARD_Read_Data_LBA(u32 LBA,u16 n_bytes,u8 *buf) { // if one sector is read completely; open the next sector if(gByteOffset==0) SD_CARD_Read_Sector_Start(LBA); SD_CARD_Read_Data(n_bytes,buf); } // dummy read out the rest bytes in a sector void SD_CARD_Read_Sector_End() { u8 temp[1]; while((gByteOffset!=0x00) | (gSectorOpened==TRUE)) SD_CARD_Read_Data(1,temp); // dummy read } // read CSD registers of SD-CARD // return 0 if no error. u8 SD_CARD_Read_CSD(u8 *buf) { // command for reading CSD registers u8 CMD[]={0x49,0x00,0x00,0x00,0x00,0xFF}; return SD_CARD_Read_Sector(CMD,buf,16);// read 16 bytes } // read CID register of SD-CARD // return 0 if no error. u8 SD_CARD_Read_CID(u8 *buf) { // command for reading CID registers u8 CMD[]={0x4A,0x00,0x00,0x00,0x00,0xFF}; return SD_CARD_Read_Sector(CMD,buf,16);//read 16 bytes } void SD_CARD_Get_Info(void) { CID_Info_STR CID; CSD_Info_STR CSD; SD_CARD_Read_CID(CID.data); SD_CARD_DEBUG(("SD-CARD CID:\n")); SD_CARD_DEBUG((" Manufacturer ID(MID): 0x%.2X\n", CID.MID)); SD_CARD_DEBUG((" OEM/Application ID(OLD): %c%c\n", CID.OLD[0], CID.OLD[1])); SD_CARD_DEBUG((" Product Name(PNM): %c%c%c%c%c\n", CID.PNM[0], CID.PNM[1], CID.PNM[2], CID.PNM[3], CID.PNM[4])); SD_CARD_DEBUG((" Product Revision: 0x%.2X\n", CID.PRV)); SD_CARD_DEBUG((" Serial Number(PSN): 0x%.2X%.2X%.2X%.2X\n", CID.PSN[0], CID.PSN[1], CID.PSN[2], CID.PSN[3])); SD_CARD_DEBUG((" Manufacture Date Code(MDT): 0x%.1X%.2X\n", CID.MDT[0] & 0x0F, CID.MDT[1])); SD_CARD_DEBUG((" CRC-7 Checksum(CRC7):0x%.2X\n", CID.CRC >> 1)); SD_CARD_Read_CSD(CSD.data); CSD.C_SIZE = ((CSD.data[6]&0x03) << 10) | (CSD.data[7] << 2) | ((CSD.data[8]&0xC0) >>6); CSD.C_SIZE_MULT = ((CSD.data[9]&0x03) << 1) | ((CSD.data[10]&0x80) >> 7); CSD.READ_BL_LEN = (CSD.data[5]&0x0F); CSD.capacity_MB = (((CSD.C_SIZE)+1) << (((CSD.C_SIZE_MULT) +2) + (CSD.READ_BL_LEN))) >> 20; SD_CARD_DEBUG(("SD-CARD CSD:\n")); SD_CARD_DEBUG((" max.read data block length: %d\n", 1< <512; i++) { SD_CARD_DEBUG(("%.2X ", buf[i])); if((i+1) % 16 == 0) SD_CARD_DEBUG(("\n")); } }
源码很长,我简单说明其中比较重要的几点。
第58行,申明一个bool型的全局变量bool gSD_CARDInit=FALSE;我们在u8 SD_CARD_Init()函数中将此变量置一或清零,然后在函数void SD_CARD_Write_Byte(u8 byte)和u8 SD_CARD_Read_Byte()检测此变量,以实现慢速率SPI初始化SD卡。
我们拿void SD_CARD_Write_Byte(u8 byte)做说明。
// read a byte to SD-CARD u8 SD_CARD_Read_Byte() { u8 i,byte; byte=0; for(i=0;i<8;i++) { // MSB First sd_CLK=0;if(gSD_CARDInit) SD_CARD_INIT_DELAY; byte<<=1;if(sd_DOUT) byte++; sd_CLK=1;if(gSD_CARDInit) SD_CARD_INIT_DELAY; } return byte; }
由于是采用GPIO模拟SPI总线,而Nios II(100MHz nios/f)的GPIO比较慢,因此无需延时即可实现25MHz的速率。但是初始化SD卡的时候必须采用
低于400KHz的时钟,需要插入适当延时。我以前也说过Nios II的延时不准,故此延时需要多次调试。我在第23行,使用一个宏来设定需要插入的延时。
由于CID寄存器的信息与字节比较对齐,因此第27~40行,使用了联合体来储存CID寄存器。而CSD寄存器内容比较零散,就没有采用联合体,而是使用了结构体(第44~51行)来存储信息。这样做的目的主要是为了理解方便,但是对存储器是比较浪费的。
第326行void SD_CARD_DEMO(void)函数中,先初始化SD卡,然后读取其第0个块(扇区)的内容。
关于SD(SPI)的寄存器结构、存储结构和指令体系等,请自行认真阅读相关资料,此处不解析。
步骤3 调用SD卡驱动函数
代码3.1 main.c
#include// printf() #include // usleep() #include "my_types.h" // 数据类型 #include "debug.h" // debug #include "sd_card.h" #define ENABLE_APP_DEBUG // turn on debug message #ifdef ENABLE_APP_DEBUG #define APP_DEBUG(x) DEBUG(x) #else #define APP_DEBUG(x) #endif int main(void) { SD_CARD_DEMO(); while(1) { } return 0; }
jtag-uart打印的信息截图如下。
(黄色为SD卡初始化调试信息;绿色为CID寄存器信息;青色为CSD寄存器信息)
(第0扇区的内容)
下面我们通过WinHex读取SD卡的第一扇区的内容,注意与上图对比。
对比数据显示,SD_CARD_Read_Data_LBA函数可实现SD卡块读取动作。
其他问题
下面讲下如何在SD卡内读取二进制文件。我先使用Notspad++(或记事本)新建一个文件,保存为SD卡的某个位置,命名为test.bin。
简单起见,我直接把sd_card.h另存到我的SD卡内(FAT32格式),命名为test.bin。
(查看test.bin的属性)
在FAT16/32内,文件的数据总是从某个扇区的0字节开始连续存储的,若文件较大则需要连续存储n个扇区; 需要注意的是最后的一个扇区如果没有存满,则补0。上面我们通过查看属性,得知test.bin的文件大小为718字节,即需要占用 718/512=1.4,取2,即2个扇区。下面使用WinHex来查看文件的数据如何存储。Crtl+F7,打开目录查看器,选择test.bin文 件。注意到test.bin的标识id和左下角显示的扇区地址移植。拖动
拖动文本,直到文本的结尾。 观察和,即占用了第81336和81337两个扇区。知道了扇区地址和扇区内的字节偏移,即可使用void SD_CARD_Read_Data_LBA(u32 LBA,u16 n_bytes,u8 *buf)函数读取到想要的数据。
源码下载
目录
1[原创][连载].基于SOPC的简易数码相框 - Quartus II部分(硬件部分)
2[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- 配置工作
3[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- SD卡(SPI模式)驱动
4[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- TFT-LCD(控制器为ILI9325)驱动
5[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- 从SD卡内读取图片文件,然后显示在TFT-LCD上
6[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- 优化工作
7[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分)- ADS7843触摸屏驱动测试