jicheng0622

【原创】MC9S12XS128的SPI模式读写SD卡底层驱动(附例程)

0
阅读(14623)

    博客大赛结束了,貌似飞思卡尔博客区里一下子冷清了不少,没了往日争奇斗艳百花盛开的热闹,少了往日拍手叫好的好文章和好测评,哎,为了飞思卡尔这把火能继续燃烧下去,大家的热情继续保持住,俺就继续加点柴。(咳咳,说的自己都有点觉着太伟大了,呵呵。)

    说实话,自从去年11月份开始写博客,也写了十几篇了(在chinaaet真的算是少的了,不过保证都是原创的),如今突然觉着有点词穷了,当然创作和开源的热情还在,只是感觉一是自己能力有限,二是在科研方面思维不够严谨,当然也有一部分是刚放假回来没找到状态,哈哈,所以打算暂缓几天出Kinetis系列了,一个是想先找回放假前的状态,还有就是温故知新去了(钻到Kinetis的E文datasheet恶补一下)。

    所以今天就拿出一个老古董的程序分享一下,说是老古董是因为这是我N年前(当时写的DG128后来我又移植到XS128了)给俺们学校智能车工作室写的SD卡驱动(当时时势所逼呀,迫切需要建立一个SD卡调试系统,事后证明当时的决定还是很英明神武的,呼呼,看着小弟们现在调车这么方便,想起俺们当年全凭感觉调车,那个惨啊,哎,又往事重提了...),其实还写了一个简单的FAT32的文件系统,不过由于文件系统介绍起来太麻烦(其实还有一个原因是当时自己水平太差,代码风格太烂,怕献丑,咳咳)这里就不提了,本篇就只介绍SPI模式读写SD卡的底层驱动,下面进入正题:

(1)首先简单介绍一下SD卡,SD卡(Secure Digital Memory Card)是一种基于半导体技术的快速闪存记忆卡,被广泛用于数码相机,手机,PDA等便携式设备上,拥有高容量记忆,数据传输率快,灵活性好和安全性强等特点,哈哈,更细致的介绍搜搜百度百科就知道了。

(2)接下来就介绍设计到技术上的问题了,对SD卡的操作有两种模式,一种是SD模式,一种是SPI模式。SD模式速度快,安全性好,不过它需要引脚多而且主要是驱动起来复杂,当然有些片子自带SD模式的硬件接口(Kineits一些系列就自带)这样就方便多了,不过对HCS12XS这类没有SD接口系列来说就麻烦了,而SPI模式就方便许多了,虽然速度上没有SD模式快,不过贵在目前大多数片子都自带SPI硬件资源,就更别提SPI总线定义的祖宗(Motorola)的“亲儿子”飞思卡尔了,然后。。。就有了然后了,哈哈。下面就是两种模式的接口图:

(3)硬件接口电路,由于SD卡是3.3v供电的,而XS128IO电平是标准的TTL电平的,这就涉及到电平匹配的问题了,最安全可靠的办法是在MCU和SD卡之间加电平转换电路(专门的芯片或者加三极管转换),不过为了简化电路,一般在数据线之间串接10~100欧姆的电阻就可以使用了,实际应用中证明没有问题。硬件电路如下图:

(4)驱动代码部分,为最底层驱动部分,经过测试,该代码在SPI10M时钟以下数据传输正常,高于10M不正常,所以建议如果要求不高的话选择5M左右的时钟,下面为主要代码部分:

#include"mc9s12xs128.h"

#include"Sd_System.h"
 
#define  sd_cs PTJ_PTJ6 
 
void spi_init(void)
 
  MODRR_MODRR4=1;    //把SPI0加到PM口
// RDRM|=0x34;      //PORTM的2,4,5口将驱动能力1/5,用来驱动sd卡,减少损坏SD卡的几率
  DDRJ_DDRJ6=1;     //PTJ的6口为输出用于SS
  
  SPI0CR1=0x5e;
  SPI0BR  = 0x07;    //慢速,用于sd初始化
  //SPI0BR=0x00;     //快速,用于spi数据传输,越快越好
}
/*********************************************************/
//function:spi_write spi_read
//description:spi模式读写数据(查询方式)
/***********************************************************/
void spi_write(byte jc_data) 
{
  while(!SPI0SR_SPTEF);
  (void)SPI0SR;      //刷新标志
  SPI0DRL=jc_data;   // 清除标志
  while (!SPI0SR_SPIF);
  return( (void)SPI0DRL);
}
byte spi_read(byte jc_data)
{
  while(!SPI0SR_SPTEF);
  (void)SPI0SR;      //刷新标志
  SPI0DRL=jc_data;  // 清除标志
  while (!SPI0SR_SPIF);
  return(SPI0DRL);
}
/*********************************************************/
//function:jc_sd_cmd
//description:给SD卡发送命令
//input: 48个字节,前8位为CMD指令,接着32位为地址参数,
//       最后8位为CRC校验(该模式在SPI模式下无效)
/***********************************************************/
byte jc_sd_cmd(byte cmd,dword arg,byte crc)
{
 byte r1,retry=0;
 
 spi_write(0xff);
 sd_cs=0;
 spi_write(cmd|0x40);
 arg=arg << 9;
 spi_write(arg >> 24); //传送32位地址
 spi_write(arg >> 16);
 spi_write(arg >> 8);
 spi_write(arg);
 spi_write(crc); 
 do
 {
    r1=spi_read(0xff);
    retry++;
    if(retry==250) 
    {
      retry=0;
      break;
    }
       
 }while(r1==0xff);
 
 sd_cs=1;
 spi_write(0xff);
 return(r1);
}
/*********************************************************/
//function: sd_init
//description:sd卡初始化函数
/***********************************************************/
byte sd_init(void)
{
  
  byte i=0,r1=0;
  word retry=0;
  for(i=0;i<10;i++)
      spi_write(0xff);      //等待74个时钟周期,sd工作电压升至正常值
  do
    {
       //发送CMD0,让SD卡进入IDLE状态
 
       r1 = jc_sd_cmd(0,0,0x95);
       retry++;
 
    } while ((r1 != 0x01) && (retry < 1000));
  if (retry==1000) return 1; 
  retry=0;
  //发送cmd55+acmd41使sd卡工作在spi模式
 do
    {
        r1=jc_sd_cmd(55,0,0xff);
       if(r1==0x01)
        r1=jc_sd_cmd(41,0,0xff);
        retry++;
    } while ((r1 != 0x00) && (retry < 1000));
   if (retry==1000) return 1;
  retry=0;
  
  SPI0CR1=0;
  SPI0BR  = 0x00;         //sd初始化完后转入快速模式
  SPI0CR1=0x5e; 
             
  return 0;
}
/*********************************************************/
//function:sd_rdata
//description:从sd卡读取指定长度数据
/***********************************************************/
byte sd_rdata(byte * data,word len)
{
  byte r1=0,retry=0;
  sd_cs=0;
  do
  {
    r1=spi_read(0xff);
    retry++;
  }while(r1!=0xfe&&retry<200);
  if(retry==200) return r1;
  retry=0;
  while(len--)
  {
    * data=spi_read(0xff);
    data++;
  }
  spi_write(0xff);          //这两句是读伪指令
  spi_write(0xff);
  sd_cs=1;
  spi_write(0xff);
  return 0;
}
/*********************************************************/
//function:sd_writesingleblock
//description:sd卡写单块数据
//input:预留ram区的指针,扇区号(注意为物理扇区号)
/***********************************************************/
byte sd_writesingleblock(byte *data,dword sector)
{
  byte r1=0,retry=0;
  word i=0;
  r1=jc_sd_cmd(24,sector,0);
  
  if(r1!=0) return r1;
  sd_cs=0;
  spi_write(0xff);       //先发三个空数据等待sd卡准备好
  spi_write(0xff);
  spi_write(0xff);
  spi_write(0xfe);     //发送起始令牌
  for(i=0;i<512;i++)
  {
    spi_write(*data++);
  } 
    
  spi_write(0xff);
  spi_write(0xff);
  r1=spi_read(0xff);
  if((r1&0x1f)!=0x05) 
  return (r1);
  while(spi_read(0xff)==0);
  sd_cs=1;
  spi_write(0xff);
  return 0;  
}
 
    至此为本篇SD卡底层驱动,代码为早期写的可能不是很正规,有看不懂的地儿可以下面留言,呵呵。献丑之作,希望能对有需要的网友有所帮助。
    附件为完整SD卡底层驱动C文件,需要的话可以直接下载~