一、前言
在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)運行效果
-
測試
+關注
關注
8文章
5281瀏覽量
126604 -
嵌入式
+關注
關注
5082文章
19111瀏覽量
304845 -
STM32
+關注
關注
2270文章
10896瀏覽量
355767
發布評論請先 登錄
相關推薦
評論