基于STM32采用CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

描述

  一、前言
 在STM32项目开发中,经常会用到存储芯片存储数据。 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复;在存储芯片里也会存放很多资源文件。比如,开机音乐,界面上的菜单图标,字库文件,方便设备开机加载。
 为了让单片机更加方便的读写这些资源文件,通常都会加文件系统,如果没有文件系统,直接读取写扇区的方式,对数据不好管理。 这篇文章就手把手教大家,在STM32上完成FATFS文件系统的移植;主控芯片采用STM32F103ZET6, 存储芯片我这里采用(雷龙) CS创世 SD NAND 。 SD NAND 简单来说就是贴片式SD卡,使用起来与普通的SD卡一样,简单的区别就是:比TF卡稳定,比eMMC便宜。 下面章节里会详细介绍下 CS创世 SD NAND。
 下面是CS创世 SD NAND 与STM32开发的板的接线实物图:
 这是读写扇区测试的结果:
 二、SD NAND 介绍
 我当前使用的SD NAND型号是,CSNP32GCR01-AOW,容量是4GB。
 下面是通过编写STM32代码读取的存储信息:
 芯片的详细参数如下:
 【1】不用写驱动程序自带坏块管理
 【2】尺寸小巧,简单易用,兼容性强,稳定可靠,固件可定制,LGA-8封装
 【3】标准SDIO接口,兼容SPI,兼容拔插式TF卡/SD卡,可替代普通TF卡/SD卡
 【4】尺寸6.2x8mm,直接贴片,不占空间
 【5】内置平均读写算法,通过1万次随机掉电测试
 【6】耐高低温,机贴手贴都非常方便
 【7】速度级别Class10(读取速度23.5MB/S写入速度12.3MB/S)
 【8】支持标准的SD 2.0协议,用户可以直接移植标准驱动代码,省去了驱动代码编程环节。支持TF卡启动的SOC都可以用SD NAND
 【9】比TF卡稳定,比eMMC便宜
 这是官网申请的样品,焊接了转接板,可以直接插在SD卡卡槽上测试。 最终选型之后,设计PCB板时,设计接口,直接贴片上去使用,非常稳定,抖动也不会导致,外置卡TF卡这种容易松动的问题。
 三、编写SD NAND驱动代码
 SD NAND 的驱动代码与正常的SD卡协议是一样的,支持标准的SD 2.0协议,下面我就直接贴出写好的驱动代码。
 包括了模拟SPI,硬件SPI,SDIO等3种方式,完成对SD NAND 的读写。我当前使用的主控板子是STM32F103ZET6,如果你使用的板子不是这一款,可能还是其他的CPU也没关系;我这里直接贴出了SPI模拟时序的驱动代码,可以直接移植到任何单片机上使用,代码拷贝过去也只需要修改GPIO口即可,非常方便。
 3.1 SPI模拟时序驱动方式
 (1)整体工程代码
 这是当前工程的截图: 代码采用寄存器风格编写,非常简洁。
 当前工程完成SD NAND卡初始化,扇区的读写,测试芯片基本的使用情况。
 (2) sd.c
 #include "sdcard.h"    
 static u8  SD_Type=0;  //存放SD卡的类型
 /*
 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
 函数参数:data是要写入的数据
 返 回 值:读到的数据
 */
 u8 SDCardReadWriteOneByte(u8 DataTx)
 {  
     u8 i;
     u8 data=0;
     for(i=0;i<8;i++)
     {
         SDCARD_SCK=0;
         if(DataTx&0x80)SDCARD_MOSI=1;
         else SDCARD_MOSI=0;
         SDCARD_SCK=1;
         DataTx<<=1;
         data<<=1;
         if(SDCARD_MISO)data|=0x01;
     }
     return data;
 }
 //4种: 边沿两种、电平是两种
 /*
 函数功能:底层SD卡接口初始化
 本程序SPI接口如下:
 PC11  片选 SDCardCS
 PC12  时钟 SDCardSCLK
 PD2   输出 SPI_MOSI--主机输出从机输入
 PC8   输入 SPI_MISO--主机输入从机输出
 */
 void SDCardSpiInit(void)
 {
   /*1. 开启时钟*/
   RCC->APB2ENR|=1<<5;     //使能PORTD时钟
 RCC->APB2ENR|=1<<4;     //使能PORTC时钟
   /*2. 配置GPIO口模式*/
   GPIOC->CRH&=0xFFF00FF0;
   GPIOC->CRH|=0x00033008;
   GPIOD->CRL&=0xFFFFF0FF;
   GPIOD->CRL|=0x00000300;
   /*3. 上拉*/
   GPIOC->ODR|=1<<8;
   GPIOC->ODR|=1<<11;
   GPIOC->ODR|=1<<12;
   GPIOD->ODR|=1<<2;
 }
 /*
 函数功能:取消选择,释放SPI总线
 */
 void SDCardCancelCS(void)
 {
 SDCARD_CS=1;
   SDCardReadWriteOneByte(0xff);//提供额外的8个时钟
 }
 /*
 函数 功 能:选择sd卡,并且等待卡准备OK
 函数返回值:0,成功;1,失败;
 */
 void SDCardSelectCS(void)
 {
 SDCARD_CS=0;
 SDCardWaitBusy();//等待成功
 }
 /*
 函数 功 能:等待卡准备好
 函数返回值:0,准备好了;其他,错误代码
 */
 void SDCardWaitBusy(void)
 {
 while(SDCardReadWriteOneByte(0XFF)!=0XFF){}
 }
 /*
 函数功能:等待SD卡回应
 函数参数:
 Response:要得到的回应值
 返 回 值:
 0,成功得到了该回应值
 其他,得到回应值失败
 */
 u8 SDCardGetAck(u8 Response)
 {
 u16 Count=0xFFFF;//等待次数       
 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应     
 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败   
 else return SDCard_RESPONSE_NO_ERROR;//正确回应
 }
 /*
 函数功能:从sd卡读取一个数据包的内容
 函数参数:
 buf:数据缓存区
 len:要读取的数据长度.
 返回值:
 0,成功;其他,失败;
 */
 u8 SDCardRecvData(u8*buf,u16 len)
 {      
 if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE
     while(len--)//开始接收数据
     {
         *buf=SDCardReadWriteOneByte(0xFF);
         buf++;
     }
     //下面是2个伪CRC(dummy CRC)
     SDCardReadWriteOneByte(0xFF);
     SDCardReadWriteOneByte(0xFF);        
     return 0;//读取成功
 }
 /*
 函数功能:向sd卡写入一个数据包的内容 512字节
 函数参数:
 buf 数据缓存区
 cmd 指令
 返 回 值:0表示成功;其他值表示失败;
 */
 u8 SDCardSendData(u8*buf,u8 cmd)
 {
 u16 t;      
 SDCardWaitBusy();  //等待忙状态
 SDCardReadWriteOneByte(cmd);
 if(cmd!=0XFD)//不是结束指令
 {
 for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间
     SDCardReadWriteOneByte(0xFF); //忽略crc
     SDCardReadWriteOneByte(0xFF);
   t=SDCardReadWriteOneByte(0xFF); //接收响应
 if((t&0x1F)!=0x05)return 2;   //响应错误        
 }          
     return 0;//写入成功
 }
 /*
 函数功能:向SD卡发送一个命令
 函数参数:
 u8 cmd   命令 
 u32 arg  命令参数
 u8 crc   crc校验值
 返回值:SD卡返回的响应
 */   
 u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)
 {
 u8 r1;
 SDCardCancelCS();               //取消上次片选
 SDCardSelectCS(); //选中SD卡
 //发送数据
 SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令
 SDCardReadWriteOneByte(arg >> 24);
 SDCardReadWriteOneByte(arg >> 16);
 SDCardReadWriteOneByte(arg >> 8);
 SDCardReadWriteOneByte(arg);   
 SDCardReadWriteOneByte(crc); 
 if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
 do
 {
 r1=SDCardReadWriteOneByte(0xFF);
 }while(r1&0x80);   //等待响应,或超时退出
    return r1; //返回状态值
 }
 /*
 函数功能:获取SD卡的CID信息,包括制造商信息
 函数参数:u8 *cid_data(存放CID的内存,至少16Byte)   
 返 回 值:
 0:成功,1:错误
 */
 u8 GetSDCardCISDCardOutnfo(u8 *cid_data)
 {
     u8 r1;    
     //发SDCard_CMD10命令,读CID
     r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
     if(r1==0x00)
   {
 r1=SDCardRecvData(cid_data,16);//接收16个字节的数据  
     }
 SDCardCancelCS();//取消片选
 if(r1)return 1;
 else return 0;
 }
 /*
 函数说明:
 获取SD卡的CSD信息,包括容量和速度信息
 函数参数:
 u8 *cid_data(存放CID的内存,至少16Byte)     
 返 回 值:
 0:成功,1:错误
 */
 u8 GetSDCardCSSDCardOutnfo(u8 *csd_data)
 {
 u8 r1;  
 r1=SendSDCardCmd(SDCard_CMD9,0,0x01);    //发SDCard_CMD9命令,读CSD
 if(r1==0)
 {
 r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 
 }
 SDCardCancelCS();//取消片选
 if(r1)return 1;
 else return 0;
 }  
 /*
 函数功能:获取SD卡的总扇区数(扇区数)   
 返 回 值:
 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)
 说   明:
 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过.
 */
 u32 GetSDCardSectorCount(void)
 {
     u8 csd[16];
     u32 Capacity;  
   u16 csize;       
     if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0; //取CSD信息,如果期间出错,返回0
     if((csd[0]&0xC0)==0x40)  //SDHC卡,按照下面方式计算
     {
 csize = csd[9] + ((u16)csd[8] << 8) + 1;
 Capacity = (u32)csize << 10;//得到扇区数      
     }
     return Capacity;
 }
 /*
 函数功能: 初始化SD卡
 返 回 值: 非0表示初始化失败!
 */
 u8 SDCardDeviceInit(void)
 {
   u8 r1;      // 存放SD卡的返回值
   u8 buf[4];  
 u16 i;
 SDCardSpiInit();//初始化底层IO口
   for(i=0;i<10;i++)SDCardReadWriteOneByte(0xFF); //发送最少74个脉冲
 do
 {
 r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置
 }while(r1!=0X01);
   SD_Type=0;   //默认无卡
 if(r1==0X01)
 {
 if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1)  //SD V2.0
 {
 for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);
 if(buf[2]==0X01&&buf[3]==0XAA)    //卡是否支持2.7~3.6V
 {
 do
 {
 SendSDCardCmd(SDCard_CMD55,0,0X01);     //发送SDCard_CMD55
 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41
 }while(r1);
 if(SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始
 {
 for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC;    //检查CCS
 else SD_Type=SDCard_TYPE_V2;   
 }
 }
 }
 }
 printf("SD_Type=0x%X\r\n",SD_Type);
 SDCardCancelCS();       //取消片选
 if(SD_Type)return 0;  //初始化成功返回0
 else if(r1)return r1; //返回值错误值    
 return 0xaa;          //其他错误
 }
 /*
 函数功能:读SD卡
 函数参数:
 buf:数据缓存区
 sector:扇区
 cnt:扇区数
 返回值:
 0,ok;其他,失败.
 说  明:
 SD卡一个扇区大小512字节
 */
 u8 SDCardReadData(u8*buf,u32 sector,u32 cnt)
 {
 u8 r1;
 if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址
 if(cnt==1)
 {
 r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令
 if(r1==0)   //指令发送成功
 {
 r1=SDCardRecvData(buf,512); //接收512个字节    
 }
 }else
 {
 r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令
 do
 {
 r1=SDCardRecvData(buf,512);//接收512个字节  
 buf+=512;  
 }while(--cnt && r1==0); 
 SendSDCardCmd(SDCard_CMD12,0,0X01); //发送停止命令
 }   
 SDCardCancelCS();//取消片选
 return r1;//
 }
 /*
 函数功能:向SD卡写数据
 函数参数:
 buf:数据缓存区
 sector:起始扇区
 cnt:扇区数
 返回值:
 0,ok;其他,失败.
 说  明:
 SD卡一个扇区大小512字节
 */
 u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt)
 {
 u8 r1;
 if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址
 if(cnt==1)
 {
 r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令
 if(r1==0)//指令发送成功
 {
 r1=SDCardSendData(buf,0xFE);//写512个字节    
 }
 }
 else
 {
 if(SD_Type!=SDCard_TYPE_MMC)
 {
 SendSDCardCmd(SDCard_CMD55,0,0X01);
 SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令
 }
   r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令
 if(r1==0)
 {
 do
 {
 r1=SDCardSendData(buf,0xFC);//接收512个字节  
 buf+=512;  
 }while(--cnt && r1==0);
 r1=SDCardSendData(0,0xFD);//接收512个字节 
 }
 }   
 SDCardCancelCS();//取消片选
 return r1;//
 }
 (3) sd.h
 #ifndef SD_H
 #define SD_H_  
 #include "stm32f10x.h"
 #include "led.h"
 #include "usart.h"
 /*----------------------------------------------
 本程序SPI接口如下:
 PC11  片选 SDCardCS
 PC12  时钟 SDCardSCLK
 PD2   输出 SPI_MOSI--主机输出从机输入
 PC8   输入 SPI_MISO--主机输入从机输出
 ------------------------------------------------*/
 #define SDCARD_CS PCout(11)
 #define SDCARD_SCK PCout(12)
 #define SDCARD_MOSI PDout(2)
 #define SDCARD_MISO PCin(8)
 // SD卡类型定义  
 #define SDCard_TYPE_ERR     0X00  //卡类型错误
 #define SDCard_TYPE_MMC     0X01  //MMC卡
 #define SDCard_TYPE_V1      0X02
 #define SDCard_TYPE_V2      0X04
 #define SDCard_TYPE_V2HC    0X06    
 // SD卡指令表      
 #define SDCard_CMD0    0       //卡复位
 #define SDCard_CMD1    1
 #define SDCard_CMD8    8       //命令8 ,SEND_IF_COND
 #define SDCard_CMD9    9       //命令9 ,读CSD数据
 #define SDCard_CMD10   10      //命令10,读CID数据
 #define SDCard_CMD12   12      //命令12,停止数据传输
 #define SDCard_CMD13   16      //命令16,设置扇区大小 应返回0x00
 #define SDCard_CMD17   17      //命令17,读扇区
 #define SDCard_CMD18   18      //命令18,读Multi 扇区
 #define SDCard_CMD23   23      //命令23,设置多扇区写入前预先擦除N个block
 #define SDCard_CMD24   24      //命令24,写扇区
 #define SDCard_CMD25   25      //命令25,写多个扇区
 #define SDCard_CMD41   41      //命令41,应返回0x00
 #define SDCard_CMD55   55      //命令55,应返回0x01
 #define SDCard_CMD58   58      //命令58,读OCR信息
 #define SDCard_CMD59   59      //命令59,使能/禁止CRC,应返回0x00、
 /*SD卡回应标记字*/
 #define SDCard_RESPONSE_NO_ERROR      0x00   //正确回应
 #define SDCard_SD_IN_IDLE_STATE       0x01   //闲置状态
 #define SDCard_SD_ERASE_RESET         0x02   //擦除复位
 #define SDCard_RESPONSE_FAILURE       0xFF   //响应失败
 //函数声明              
 u8 SDCardReadWriteOneByte(u8 data);                 //底层接口,SPI读写字节函数
 void SDCardWaitBusy(void);             //等待SD卡准备
 u8 SDCardGetAck(u8 Response);         //获得应答
 u8 SDCardDeviceInit(void);             //初始化
 u8 SDCardReadData(u8*buf,u32 sector,u32 cnt);     //读块(扇区)
 u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt);   //写块(扇区)
 u32 GetSDCardSectorCount(void);            //读扇区数
 u8 GetSDCardCISDCardOutnfo(u8 *cid_data);           //读SD卡CID
 u8 GetSDCardCSSDCardOutnfo(u8 *csd_data);           //读SD卡CSD
 #endif
 (4)运行效果
 3.2 SPI硬件时序方式
 上面的3.1小节是采用SPI模拟时序驱动SD NAND,STM32本身集成有SPI硬件模块,可以直接利用STM32硬件SPI接口读写。
 下面贴出底层的适配代码。 上面贴出的驱动代码里,已经将驱动接口部分和协议逻辑部分区分开了,替换底层的SIP读写代码非常方便。
 (1)主要替换的代码
 /*
 函数功能:SPI初始化(模拟SPI)
 硬件连接:
 MISO--->PB14
 MOSI--->PB15
 SCLK--->PB13
 */
 void SPI_Init(void)
 {
 /*开启时钟*/
 RCC->APB1ENR|=1<<14;   //开启SPI2时钟
 RCC->APB2ENR|=1<<3;    //PB
 GPIOB->CRH&=0X000FFFFF; //清除寄存器
 GPIOB->CRH|=0XB8B00000;
 GPIOB->ODR|=0X7<<13;     //PB13/14/15上拉--输出高电平
 /*SPI2基本配置*/
 SPI2->CR1=0X0;  //清空寄存器
 SPI2->CR1|=0<<15; //选择“双线双向”模式
 SPI2->CR1|=0<<11; //使用8位数据帧格式进行发送/接收;
 SPI2->CR1|=0<<10; //全双工(发送和接收);
 SPI2->CR1|=1<<9;  //启用软件从设备管理
 SPI2->CR1|=1<<8;  //NSS
 SPI2->CR1|=0<<7;  //帧格式,先发送高位
 SPI2->CR1|=0x0<<3;//当总线频率为36MHZ时,SPI速度为18MHZ,高速。
 SPI2->CR1|=1<<2;  //配置为主设备
 SPI2->CR1|=1<<1;  //空闲状态时, SCK保持高电平。
 SPI2->CR1|=1<<0;  //数据采样从第二个时钟边沿开始。
 SPI2->CR1|=1<<6;  //开启SPI设备。
 }
 /*
 函数功能:SPI读写一个字节
 */
 u8 SPI_ReadWriteOneByte(u8 data_tx)
 {
     u16 cnt=0;  
     while((SPI2->SR&1<<1)==0)  //等待发送区空--等待发送缓冲为空
     {
       cnt++;
       if(cnt>=65530)return 0;    //超时退出  u16=2个字节
     }
     SPI2->DR=data_tx;            //发送一个byte 
     cnt=0;
     while((SPI2->SR&1<<0)==0)  //等待接收完一个byte   
     {
       cnt++;
       if(cnt>=65530)return 0;    //超时退出
     }        
     return SPI2->DR;           //返回收到的数据
 }
 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节
 函数参数:data是要写入的数据
 返 回 值:读到的数据
 */
 u8 SDCardReadWriteOneByte(u8 DataTx)
 {  
     return SPI_ReadWriteOneByte(DataTx);
 }
 (2)运行效果

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分