[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分) - SD卡(SPI模式)驱动
上一讲,我们完成了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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | #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 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<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 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<<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)做说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 | // 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #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) #endif int main( void ) { SD_CARD_DEMO(); while (1) { } return 0; } |
jtag-uart打印的信息截图如下。
(黄色为SD卡初始化调试信息;绿色为CID寄存器信息;青色为CSD寄存器信息)
下面我们通过WinHex读取SD卡的第一扇区的内容,注意与上图对比。
对比数据显示,SD_CARD_Read_Data_LBA函数可实现SD卡块读取动作。
其他问题
下面讲下如何在SD卡内读取二进制文件。我先使用Notspad++(或记事本)新建一个文件,保存为SD卡的某个位置,命名为test.bin。
简单起见,我直接把sd_card.h另存到我的SD卡内(FAT32格式),命名为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触摸屏驱动测试
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步