[原创][连载].基于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部分(软件部分) - 配置工作

image image

步骤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 <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)做说明。

// 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 <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打印的信息截图如下。

image (黄色为SD卡初始化调试信息;绿色为CID寄存器信息;青色为CSD寄存器信息)

image (第0扇区的内容)

下面我们通过WinHex读取SD卡的第一扇区的内容,注意与上图对比。

image 

对比数据显示,SD_CARD_Read_Data_LBA函数可实现SD卡块读取动作。

其他问题

下面讲下如何在SD卡内读取二进制文件。我先使用Notspad++(或记事本)新建一个文件,保存为SD卡的某个位置,命名为test.bin。

简单起见,我直接把sd_card.h另存到我的SD卡内(FAT32格式),命名为test.bin。

image 

image  (查看test.bin的属性)

在FAT16/32内,文件的数据总是从某个扇区的0字节开始连续存储的,若文件较大则需要连续存储n个扇区;需要注意的是最后的一个扇区如果没有存满,则补0。上面我们通过查看属性,得知test.bin的文件大小为718字节,即需要占用718/512=1.4,取2,即2个扇区。下面使用WinHex来查看文件的数据如何存储。Crtl+F7,打开目录查看器,选择test.bin文件。注意到test.bin的标识id和左下角显示的扇区地址移植。拖动

image

拖动文本,直到文本的结尾。 观察imageimage ,即占用了第81336和81337两个扇区。知道了扇区地址和扇区内的字节偏移,即可使用void SD_CARD_Read_Data_LBA(u32 LBA,u16 n_bytes,u8 *buf)函数读取到想要的数据。

image 

源码下载

 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触摸屏驱动测试

posted @ 2010-12-27 14:52  _安德鲁  阅读(5748)  评论(3编辑  收藏  举报