详解sd协议以及裸机和u-boot中的sd卡驱动(2)

本文为该系列的第二篇。

3. sd卡驱动

3.1 引入

经过第2章我们知道,要想实现读写sd卡,需要按照sd协议规定的基本传输单位(命令、响应、数据)以及流程(初始化、读、写),向sd卡发送信号或者从sd卡接收信号。

为了简化cpu的操作,人们在soc内部设计了一个sd控制器,这个sd控制器专门按照sd规定的协议进行收发信号,并把我们上述提到的基本传输单位命令、响应以及数据的内容简化到一个个寄存器,比如s3c2440内部的sd控制器中包含有:

  • SDICmdArg寄存器:用来保存命令的参数,当我们需要发送命令到sd卡时,对于命令的参数,只需要写入该寄存器即可;

  • SDICmdCon寄存器:低八位用来保存命令的编号,当我们需要发送命令到sd卡时,对于命令的编号,只需要写入该寄存器即可;

  • SDIRSP0——SDIRSP3寄存器:用来保存响应的内容,当我们需要读取sd卡发来的响应值时,便可以读取该寄存器;

  • SDIDAT寄存器:用来保存数据的内容,当我们需要读取从sd卡发来的数据或者向sd卡写入数据时,便可以操作该寄存器;

等等还有很多寄存器,上面只是简单举例说明。

现在我们来回答问题2:cpu如何控制sd控制器来发出各种信号?

答案便是:通过读写其内部的寄存器,这也是sd驱动的主要内容。

现在,我想大家应该明白了,所谓sd卡驱动即是:

通过sd控制器提供的简化功能,按照sd协议的规定通过cpu读写其相应的寄存器,以此来达到向sd卡发出或者从sd卡接收一系列信号的目的,最终完成读写sd卡的功能。

下面从裸机、u-boot层面对sd卡驱动进行分析,并着重比较其实现的异同。

3.2 裸机中的sd卡驱动

注:本裸机驱动来自我之前写的移植u-boot 2020文章中的spl工程。

现在我们已经知道,sd卡驱动的内容主要便是按照sd协议的规定读写sd控制器中的寄存器,这在裸机驱动中表现得最为明显。

对于裸机中的sd卡驱动,主要围绕如下三个功能的实现而展开:

  • 初始化sd卡

  • 读sd卡

  • 写sd卡

3.2.1 初始化sd卡

初始化的过程就是按照sd协议中规定的流程进行的。

过程如下:

  1. 初始化连接sd卡的gpio引脚以及sd控制器;

  2. 等待74个CLK

  3. 发送CMD0复位卡

  4. 发送CMD1检测是否为sd卡或者mmc卡

  5. 发送CMD2获取sd卡的CID

  6. 发送CMD3设置sd卡或者mmc卡的RCA

  7. 发送CMD9获取sd卡的CSD

  8. 发送CMD7选中sd卡

  9. 发送CMD13查询是否为传输状态

  10. 发送ACMD6设置总线带宽

代码如下:

 u8 sd_init()
 {
 int i;
 
 debug("\r\nSDI初始化开始!");
 
 GPEUP  = 0xf83f;   // The pull up 1111 1000 0011 1111 必须上拉
     GPECON = 0xaaaaaaaa; // 1010 1010 1010 1010 1010 1010 1010 1010
     SDICSTA = 0xffff;   //SDI指令状态
     SDIDSTA = 0xffff;//SDI数据状态
 
 SDIPRE = 124; // 400KHz 波特率设置 频率 PCLK/400K -1
     SDICON = 1; // Type A, clk enable SDI控制
     SDIFSTA |= 1 << 16; //FIFO reset
 SDIBSIZE = 0x200; // 512byte(128word) SDI块大小
 SDIDTIMER = 0x7fffff; // Set timeout count 数据传输超时时间
 
 //等待74个CLK
     for(i=0;i<0x1000;i++);
 
 //先执行CMD0,复位
 CMD0();
 
 //判断卡的类型
 if(SDI_MMC_OCR())
 SDCard.sdiType = 1;  //卡为MMC
 else
 SDCard.sdiType = 0;  //卡为SD
 
 //检测SD卡
 if (SDI_SD_OCR()) {
 debug("SD is ready\r\n");
 } else{
 debug("Initialize fail\r\nNo Card assertion\r\n");
         return 0;
    }  
 
 //读CID
 if (CMD2(SDCard.cCardCID)) {
 debug("CID\r\n");
 debug("MID = %d\r\n",SDCard.cCardCID[0]);
 debug("OLD = %d\r\n",(SDCard.cCardCID[1]*0X100)+SDCard.cCardCID[2]);
 debug("生产厂家:%s\r\n",(SDCard.cCardCID+3));
 debug("生产日期:20%d,%d\r\n",((SDCard.cCardCID[13]&0x0f)<<4)+((SDCard.cCardCID[14]&0xf0)>>4),(SDCard.cCardCID[14]&0x0f));
 } else {
 debug("Read Card CID is fail!\r\n");
 return 0;
 }
 
 //设置RCA       MMC的RCA=1   SD的RCA=0
 //MMC
 if (SDCard.sdiType==1) {
 if (CMD3(1,&SDCard.sdiRCA)) {
 SDCard.sdiRCA = 1;
 SDIPRE = 2;  //16MHZ
 debug("MMC Card RCA = 0x%x\r\n",SDCard.sdiRCA);
 debug("MMC Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
 } else {
 debug("Read MMC RCA is fail!\r\n");
 return 0;
 }
 //SD
 } else {
 if (CMD3(0,&SDCard.sdiRCA)) {
 SDIPRE = 1; // Normal clock=25MHz
 debug("SD Card RCA = 0x%x\r\n",SDCard.sdiRCA);
 debug("SD Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
 } else {
 debug("Read SD RCA is fail!\r\n");
 return 0;
 }
 }
 
 //读CSD
 if (CMD9(SDCard.sdiRCA,SDCard.lCardCSD)) {
 SDCard.lCardSize = (((SDCard.lCardCSD[1]&0x0000003f)<<16)+((SDCard.lCardCSD[2]&0xffff0000)>>16)+1)*512;
 SDCard.lSectorSize = ((SDCard.lCardCSD[2]>>6)&0x0000007f)+1;
 debug("Read Card CSD OK!\r\n");
 debug("0x%08x\r\n",SDCard.lCardCSD[0]);
 debug("0x%08x\r\n",SDCard.lCardCSD[1]);
 debug("0x%08x\r\n",SDCard.lCardCSD[2]);
 debug("0x%08x\r\n",SDCard.lCardCSD[3]);
 debug("卡容量为:%dKB,%dMB\r\n",SDCard.lCardSize,SDCard.lCardSize/1024);
 } else {
 debug("Read Card CSD Fail!\r\n");
 return 0;
 }
 
 //选中卡 CMD7 进入传输状态
 //1表示选中卡
 if (select_card(1,SDCard.sdiRCA)) {
 debug("Card sel desel OK!\r\n");
 } else {
 debug("Card sel desel fail!\r\n");
 return 0;
 }
 
 //CMD13 查询是否为传输状态
 while ((CMD13(SDCard.sdiRCA) & 0x1e00) != 0x800);
 
 //设置总线带宽 ACMD6
 if (Set_bus_Width(SDCard.sdiType,1,SDCard.sdiRCA)) {
 SDCard.sdiWide = 1;
 debug("Bus Width is 4bit\r\n");
 } else {
 SDCard.sdiWide = 0;
 debug("Bus Width is 1bit\r\n");
 }
 
 return 1;
 }

3.2.2 读sd卡

读sd卡的过程也是按照sd协议中规定的流程进行的。

过程如下:

  1. 设置sd控制器;

  2. 发送CMD18多块读sd卡

  3. 通过sd控制器中的SDIDAT寄存器获取读到的数据到指定内存地址

  4. 清除sd控制器中的状态寄存器

  5. 发送CMD12停止多块读传输

代码如下:

 /*
  * sd_read_sector - 从SD卡中读出指定块起始地址的单个或多个数据块
  *
  * @addr 被读块的起始地址
  * @buffer 用于接收读出数据的缓冲区
  * @block_num 读的块数
  *
  * @return
  *0 读块操作不成功
  *1 读块操作成功
  *
  */
 u8 sd_read_sector(u32 *buf, u32 addr, u32 block_num)
 {
 u32 i=0;
 u32 status=0;
 
 SDIDTIMER = 0x7fffff; // Set timeout count
 SDIBSIZE = 0x200; // 512byte(128word)
 SDIFSTA = SDIFSTA | (1 << 16); // FIFO reset
 SDIDCON = (block_num << 0) | (2 << 12) | (1 << 14) | (SDCard.sdiWide << 16) | (1 << 17) | (1 << 19) | (2 << 22);
 
 //debug("enter sd_read_sector(): src_block = %d, block_num = %d\r\n", addr, block_num);
 
 //发送读多个块指令
 while (CMD18(addr) != 1)
 SDICSTA = 0xF << 9;
 
 //开始接收数据到缓冲区
 while (i < block_num * 128) {
 //检查是否超时和CRC校验是否出错
 if (SDIDSTA & 0x60) {
 //清除超时标志和CRC错误标志
 SDIDSTA = (0x3 << 0x5);
 return -1;
 }
 
 status = SDIFSTA;
 //如果接收FIFO中有数据
 if ((status & 0x1000) == 0x1000) {
 *buf = SDIDAT;
 buf++;
 i++;
 }
 }
 
 SDIDCON = SDIDCON & ~(7 << 12);
 SDIFSTA = SDIFSTA & 0x200;//Clear Rx FIFO Last data Ready
 SDIDSTA = 0x10;//Clear data Tx/Rx end detect
 
 //发送结束指令
 while (CMD12() != 1)
 SDICSTA = 0xF << 9;
 debug("leave sd_read_sector(): src_block = %d, block_num = %d\r\n", addr, block_num);
 
 return 0;
 }

3.2.3 写sd卡

与读sd卡过程相似,写sd卡的过程也是按照sd协议中规定的流程进行的。

过程如下:

  1. 设置sd控制器;

  2. 发送CMD25多块写sd卡

  3. 将指定内存地址处的要写入sd卡的数据写到sd控制器中的SDIDAT寄存器

  4. 设置sd控制器;

  5. 发送CMD12停止多块写传输

代码如下:

 /*
  * sd_write_sector - 向SD卡的一个或多个数据块写入数据
  *
  * @addr 被写块的起始地址
  * @buffer 用于发送数据的缓冲区
  * @block_num 块数
  *
  * @return
  *0 数据写入操作失败
  *1 数据写入操作成功
  *
  */
 u8 sd_write_sector(u32 *buf, u32 addr, u32 block_num)
 {
 u16 i = 0;
 u32 status = 0;
 
 SDIDTIMER = 0x7fffff; // Set timeout count
 SDIBSIZE = 0x200; // 512byte(128word)
 SDIFSTA = SDIFSTA | (1 << 16); // FIFO reset
 SDIDCON = (block_num << 0) | (3 << 12) | (1 << 14) | (1 << 16) | (1 << 17) | (1 << 20) | (2 << 22);
 
 //发送写多个块指令
 while (CMD25(addr) != 1)
 SDICSTA = 0xF << 9;
 
 //开始传递数据到缓冲区
 while (i < block_num * 128) {
 status = SDIFSTA;
 
 //如果发送FIFO可用,即FIFO未满
 if ((status & 0x2000) == 0x2000) {
 SDIDAT = *buf;
 buf++;
 i++;
 }
 }
 
 SDIDCON = SDIDCON & ~(7 << 12);
 
 //发送结束指令
 while (CMD12() != 1)
 SDICSTA=0xF<<9;
 
 //等待数据发送结束
 do {
 status = SDIDSTA;
 } while ((status & 0x2) == 0x2);
 
 SDIDSTA = status;
 SDIDSTA = 0xf4;
 
 return 0;
 }

3.2.4 具体命令实现举例

在前面的sd卡初始化、读、写函数中,主要的内容均是按照sd协议的规定发送一系列的命令,那具体每个命令是怎么实现的呢?下面举例说明:

  • CMD9:获取卡的CSD寄存器的值

    过程如下:

    1. 指定使用哪个sd卡,将卡的RCA作为命令的参数写入SDICARG寄存器

    2. 将命令的编号写入SDICCON寄存器,同时指定响应类型为长响应,并启动向sd卡发送该命令

    3. 读取sd控制器中的状态寄存器,检查命令是否发送完成

    4. 待命令发送完成后,读取SDIRSP0—SDIRSP3寄存器获取响应值,也即sd卡的CSD寄存器的值

    代码如下:

     //获取卡的CSD寄存器的值
     u8 CMD9(u16 iRCA,u32 *lCSD)
     {
     SDICARG = iRCA<<16; // (RCA,stuff bit)
     SDICCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x49; // long_resp, wait_resp, start
     
     if(!SDI_Check_CMD_End(9, 1))
     return 0;
     
     *(lCSD+0) = SDIRSP0;
     *(lCSD+1) = SDIRSP1;
     *(lCSD+2) = SDIRSP2;
     *(lCSD+3) = SDIRSP3;
     
         return 1;
     }
  • CMD18:读取多个数据块

    过程如下:

    1. 指定读取sd卡的地址,将地址命令的参数写入SDICARG寄存器

    2. 将命令的编号写入SDICCON寄存器,并启动向sd卡发送该命令

    3. 读取sd控制器中的状态寄存器,检查命令是否发送完成

    代码如下:

     //读取多个数据块
     u8 CMD18(u32 addr)
     {
         //STEP1:发送指令
         SDICARG = addr; //设定指令参数
         SDICCON = (1<<9)|(1<<8)|0x52; //发送CMD18指令
         
         if(SDI_Check_CMD_End(18,1))
          return 1;
         else
          return 0;
     }
  • CMD25:写入多个数据块

    过程如下:

    1. 指定写入sd卡的地址,将地址命令的参数写入SDICARG寄存器

    2. 将命令的编号写入SDICCON寄存器,并启动向sd卡发送该命令

    3. 读取sd控制器中的状态寄存器,检查命令是否发送完成

    代码如下:

     //写入多个数据块
     u8 CMD25(u32 addr)
     {
         //STEP1:发送指令
         SDICARG = addr; //设定指令参数
         SDICCON = (1<<9)|(1<<8)|0x59; //发送CMD25指令
         
         if(SDI_Check_CMD_End(25,1))
        return 1;
         else
        return 0;
     }

3.2.5 优缺点总结

  • 优点:

    代码简单,容易理解,函数功能清晰明确,代码流程基本按照sd协议的规定走下去,便于初学者学习寄存器的操作与sd协议

  • 缺点:

    复用性、扩展性差,当需要同时支持多个sd控制器的时候,改动会非常大

  • 发表于 2020-07-18 00:32
  • 阅读 ( 25 )
  • 分类:经验分享

0 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 谢工 5 文章
  7. Litchi_Zheng 5 文章
  8. 星星之火 5 文章