文章目錄
- 步驟1 添加sd_card檔案夾到APP工程路徑
- 步驟2 編寫代碼
- 步驟3 調用SD卡驅動函數
上一講,我們完成了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 messagevoid 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 <unistd.h>#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 structuretypedef 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 structuretypedef struct{ u8 data[16]; u32 capacity_MB; u8 READ_BL_LEN; u16 C_SIZE; u8 C_SIZE_MULT;}CSD_Info_STR;// flagsu16 gByteOffset=0; // byte offset in one sectoru16 gSectorOffset=0; // sector offset in SD-CARDbool 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 initvoid SD_CARD_Port_Init(){ sd_CLK=1; sd_DOUT=1; sd_nCS=1;}// write a byte to SD-CARDvoid 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-CARDu8 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-CARDu8 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<n_bytes;i++) *buf++=SD_CARD_Read_Byte(); SD_CARD_Read_Byte();// dummy CRC SD_CARD_Read_Byte();// dummy CRC sd_nCS=1; // disable SD-CARD return 0;}// return: [0]-success or something error!u8 SD_CARD_Read_Sector_Start(u32 sector){ u8 retry; // CMD16 for reading Blocks u8 CMD[]={0x51,0x00,0x00,0x00,0x00,0xFF}; u8 temp; // address conversation(logic block address-->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<n_bytes) && (gByteOffset<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 sectorvoid 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<<CSD.READ_BL_LEN)); SD_CARD_DEBUG((" device size: %d\n", CSD.C_SIZE)); SD_CARD_DEBUG((" device size multiplier: %d\n", CSD.C_SIZE_MULT)); SD_CARD_DEBUG((" device capacity: %d MB\n", CSD.capacity_MB));}void SD_CARD_DEMO(void){ u16 i; u8 buf[512]; // init SD-CARD while(SD_CARD_Init() != 0x55); // Get CID & CSD SD_CARD_Get_Info(); // read the 1st block(sector) of SD-Card SD_CARD_Read_Data_LBA(0,512,buf); for(i=0; i<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-CARDu8 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 <stdio.h> // printf()#include <unistd.h> // 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)#endifint 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)函數讀取到想要的資料。
源碼下載
lcd_at_nios_nii_part.zip
目錄
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觸控螢幕驅動測試