安德鲁

[原创][连载].基于SOPC的简易数码相框 - Nios II SBTE部分(软件部分) - SD卡(SPI模式)驱动

0
阅读(7400)

上一讲,我们完成了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;ibyte 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)函数读取到想要的数据。


源码下载

 lcd_at_nios_nii_part.zip

目录

[原创][连载].基于SOPC的简易数码相框 -  Quartus II部分(硬件部分)

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  配置工作

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  SD卡(SPI模式)驱动

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  TFT-LCD(控制器为ILI9325)驱动

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  从SD卡内读取图片文件,然后显示在TFT-LCD上

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  优化工作

[原创][连载].基于SOPC的简易数码相框 -  Nios II SBTE部分(软件部分)-  ADS7843触摸屏驱动测试