天天看點

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

1)實驗平台:正點原子STM32mini開發闆

2)摘自《正點原子STM32 不完全手冊(HAL 庫版)》關注官方微信号公衆号,擷取更多資料:正點原子

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

第三十三章 SD 卡實驗

很多單片機系統都需要大容量儲存設備,以存儲資料。目前常用的有 U 盤,FLASH 晶片,

SD 卡等。他們各有優點,綜合比較,最适合單片機系統的莫過于 SD 卡了,它不僅容量可以做

到很大(32Gb 以上),而且支援 SPI 接口,友善移動,并且有幾種體積的尺寸可供選擇(标準

的 SD 卡尺寸,以及 TF 卡尺寸等),能滿足不同應用的要求。

隻需要 4 個 IO 口即可外擴一個最大達 32GB 以上的外部存儲器,容量從幾十 M 到幾十 G

選擇尺度很大,更換也很友善,程式設計也簡單,是單片機大容量外部存儲器的首選。

ALIENTKE MiniSTM32 開發闆自帶了标準的 SD 卡接口(在背面),可使用 STM32 自帶的

SPI 接口驅動,本章我們使用 SPI 驅動,最高通信速度可達 18Mbps,每秒可傳輸資料 2M 位元組

以上,對于一般應用足夠了。在本章中,我們将向大家介紹,如何在 ALIENTEK MiniSTM32

開發闆上實作 SD 卡的讀取。本章分為如下幾個部分:

33.1 SD 卡簡介

33.2 硬體設計

33.3 軟體設計

33.4 下載下傳驗證

33.1 SD 卡簡介

SD 卡(Secure Digital Memory Card)中文翻譯為安全數位卡,它是在 MMC 的基礎上發

展而來,是一種基于半導體快閃記憶器的新一代記憶裝置,它被廣泛地于便攜式裝置上使用,

例如數位相機、個人數位助理(PDA)和多媒體播放器等。SD 卡由日本松下、東芝及美國 SanDisk

公司于 1999 年 8 月共同開發研制。大小猶如一張郵票的 SD 記憶卡,重量隻有 2 克,但卻擁

有高記憶容量、快速資料傳輸率、極大的移動靈活性以及很好的安全性。按容量分類,可以将

SD 卡分為 3 類:SD 卡、SDHC 卡、SDXC 卡。如表 33.1.1 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.1 SD 卡按容量分類

SD 卡和 SDHC 卡協定基本相容,但是 SDXC 卡,同這兩者差別就比較大了,本章我們讨

論的主要是 SD/SDHC 卡(簡稱 SD 卡)。

SD 卡一般支援 2 種操作模式:

1,SD 卡模式(通過 SDIO 通信);

2,SPI 模式;

主機可以選擇以上任意一種模式同 SD 卡通信,SD 卡模式允許 4 線的高速資料傳輸。SPI

模式允許簡單的通過 SPI 接口來和 SD 卡通信,這種模式同 SD 卡模式相比就是喪失了速度。

SD 卡的引腳排序如下圖 33.1.1 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

圖 33.1.1 SD 卡引腳排序圖

SD 卡引腳功能描述如表 33.1.2 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.2 SD 卡引腳功能表

SD 卡隻能使用 3.3V 的 IO 電平,是以,MCU 一定要能夠支援 3.3V 的 IO 端口輸出。注意:

在 SPI 模式下,CS/MOSI/MISO/CLK 都需要加 10~100K 左右的上拉電阻。

SD 卡有 5 個寄存器,如表 33.1.3 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗
kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.3 SD 卡相關寄存器

關于這些寄存器的較長的描述,請參考CD光牒相關 SD 卡資料。我們在這裡就不描述了。接下

來,我們看看 SD 卡的指令格式,如表 33.1.4 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.4 SD 卡指令格式

SD 卡的指令由 6 個位元組組成,位元組 1 的最高 2 位固定為 01,低 6 位為指令号(比如 CMD16,

為 10000B 即 16 進制的 0X10,完整的 CMD16,第一個位元組為 01010000,即 0X10+0X40)。

位元組 2~5 為指令參數,有些指令是沒有參數的。

位元組 6 的高七位為 CRC 值,最低位恒定為 1。

SD 卡的指令總共有 12 類,分為 Class0~Class11,本章,我們僅介紹幾個比較重要的指令,

如表 33.1.5 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.5 SD 卡部分指令

上表中,大部分的指令是初始化的時候用的。表中的 R1、R3 和 R7 等是 SD 卡的回應,SD

卡和單片機的通信采用發送應答機制,如圖 33.1.2 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

圖 33.1.2 SD 卡指令傳輸過程

每發送一個指令,SD 卡都會給出一個應答,以告知主機該指令的執行情況,或者傳回主

機需要擷取的資料。SPI 模式下,SD 卡針對不同的指令,應答可以是 R1~R7,R1 的應答,各位描述如表 33.1.6 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

表 33.1.6 R1 響應各位描述

R2~R7 的響應,我們就不介紹了,請的大家參考 SD 卡 2.0 協定。接下來,我們看看 SD 卡

初始化過程。因為我們使用的是 SPI 模式,是以先得讓 SD 卡進入 SPI 模式。方法如下:在 SD

卡收到複位指令(CMD0)時,CS 為有效電平(低電平)則 SPI 模式被啟用。不過在發送 CMD0

之前,要發送>74 個時鐘,這是因為 SD 卡内部有個供電電壓上升時間,大概為 64 個 CLK,剩

下的 10 個 CLK 用于 SD 卡同步,之後才能開始 CMD0 的操作,在卡初始化的時候,CLK 時鐘

最大不能超過 400Khz!。

接着我們看看 SD 卡的初始化,SD 卡的典型初始化過程如下:

1、初始化與 SD 卡連接配接的硬體條件(MCU 的 SPI 配置,IO 口配置);

2、上電延時(>74 個 CLK);

3、複位卡(CMD0),進入 IDLE 狀态;

4、發送 CMD8,檢查是否支援 2.0 協定;

5、根據不同協定檢查 SD 卡(指令包括:CMD55、CMD41、CMD58 和 CMD1 等);

6、取消片選,發多 8 個 CLK,結束初始化

這樣我們就完成了對 SD 卡的初始化,注意末尾發送的 8 個 CLK 是提供 SD 卡額外的時鐘,

完成某些操作。通過 SD 卡初始化,我們可以知道 SD 卡的類型(V1、V2、V2HC 或者 MMC),

在完成了初始化之後,就可以開始讀寫資料了。

SD 卡讀取資料,這裡通過 CMD17 來實作,具體過程如下:

1、發送 CMD17;

2、接收卡響應 R1;

3、接收資料起始令牌 0XFE;

4、接收資料;

5、接收 2 個位元組的 CRC,如果不使用 CRC,這兩個位元組在讀取後可以丢掉。

6、禁止片選之後,發多 8 個 CLK;

以上就是一個典型的讀取 SD 卡資料過程,SD 卡的寫于讀資料差不多,寫資料通過 CMD24

來實作,具體過程如下:

1、發送 CMD24;

2、接收卡響應 R1;

3、發送寫資料起始令牌 0XFE;

4、發送資料;

5、發送 2 位元組的僞 CRC;

6、禁止片選之後,發多 8 個 CLK;

以上就是一個典型的寫 SD 卡過程。關于 SD 卡的介紹,我們就介紹到這裡,更詳細的介

紹請參考CD光牒資料→ 7,硬體資料→SD 卡資料→ SD 卡 V2.0 協定。

33.2 硬體設計

本章實驗功能簡介:開機的時候先初始化 SD 卡,如果 SD 卡初始化完成,則提示 LCD 初

始化成功。按下 KEY0,讀取 SD 卡扇區 0 的資料,然後通過序列槽發送到電腦。如果沒初始化

通過,則在 LCD 上提示初始化失敗。 同樣用 DS0 來訓示程式正在運作。

本實驗用到的硬體資源有:

1) 訓示燈 DS0

2) KEY0 按鍵

3) 序列槽

4) TFTLCD 子產品

5) SD 卡

前面四部分,在之前的執行個體已經介紹過了,這裡我們介紹一下 MiniSTM32 開發闆闆載的

SD 卡接口和 STM32 的連接配接關系,如圖 33.2.1 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

圖33.2.1 SD卡接口與STM32連接配接原理圖

從圖中可以看出,SD卡通過4根信号線與STM32連接配接,SD卡的片選(SD_CS)連接配接PA3,

SD卡的SPI接口,連接配接在STM32的SPI1上面,硬體連接配接就這麼簡單,這裡要注意的是SPI1被3

個外設共用了:SD卡、W25Q64和NRF24L01,在使用SD卡的時候,必須禁止其他外設的片

選,以防幹擾。

33.3 軟體設計

打開上一章的工程,由于本章還需要用到SPI功能,是以,先添加spi.c。然後,在HARDWARE

檔案夾下建立一個 SD 的檔案夾。然後建立一個 MMC_SD.C 和 MMC_SD.H 的檔案儲存在 SD

檔案夾下,并将這個檔案夾加入頭檔案包含路徑。

打開 MMC_SD.C 檔案,在該檔案裡面,我們輸入與 SD 卡相關的操作代碼,這裡由于篇

幅限制,我們不貼出所有代碼,僅介紹兩個最重要的函數,第一個是 SD_Initialize 函數,該函

數源碼如下:

//初始化 SD 卡

u8 SD_Init(void)

{

u8 r1;

// 存放 SD 卡的傳回值

u16 retry;

// 用來進行逾時計數

u8 buf[4];

u16 i;

SD_SPI_Init();

//初始化 IO

SD_SPI_SpeedLow(); //設定到低速模式

for(i=0;i<10;i++)SD_SPI_ReadWriteByte(0XFF);//發送最少 74 個脈沖

retry=20;

do

{

r1=SD_SendCmd(CMD0,0,0x95);//進入 IDLE 狀态

}while((r1!=0X01) && retry--);

SD_Type=0;//預設無卡

if(r1==0X01)

{

if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0

{

for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);

//Get trailing return value of R7 resp

if(buf[2]==0X01&&buf[3]==0XAA)//卡是否支援 2.7~3.6V

{

retry=0XFFFE;

do

{

SD_SendCmd(CMD55,0,0X01); //發送 CMD55

r1=SD_SendCmd(CMD41,0x40000000,0X01);//發送 CMD41

}while(r1&&retry--);

if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//鑒别 SD2.0 卡版本開始

{

for(i=0;i<4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);//得到 OCR 值

if(buf[0]&0x40)SD_Type=SD_TYPE_V2HC; //檢查 CCS

else SD_Type=SD_TYPE_V2;

}

}

}else//SD V1.x/ MMC V3

{

SD_SendCmd(CMD55,0,0X01);

//發送 CMD55

r1=SD_SendCmd(CMD41,0,0X01);

//發送 CMD41

if(r1<=1)

{

SD_Type=SD_TYPE_V1;

retry=0XFFFE;

do //等待退出 IDLE 模式

{

SD_SendCmd(CMD55,0,0X01); //發送 CMD55

r1=SD_SendCmd(CMD41,0,0X01);//發送 CMD41

}while(r1&&retry--);

}else//MMC 卡不支援 CMD55+CMD41 識别

{

SD_Type=SD_TYPE_MMC;//MMC V3

retry=0XFFFE;

do //等待退出 IDLE 模式

{

r1=SD_SendCmd(CMD1,0,0X01);//發送 CMD1

}while(r1&&retry--);

}

if(retry==0||SD_SendCmd(CMD16,512,0X01)!=0)SD_Type=SD_TYPE_ERR;

//錯誤的卡

}

}

SD_DisSelect();//取消片選

SD_SPI_SpeedHigh();//高速

if(SD_Type)return 0;

else if(r1)return r1;

return 0xaa;//其他錯誤

}

該函數先設定與 SD 相關的 IO 口及 SPI 初始化,然後發送 CMD0,進入 IDLE 狀态,并設定

SD 卡為 SPI 模式通信,然後判斷 SD 卡類型,完成 SD 卡的初始化,注意該函數調用的 SD_SPI_Init

等函數,實際是對 SPI1 的相關函數進行了一層封裝,友善移植。另外一個要介紹的函數是

SD_ReadDisk,該函數用于從 SD 卡讀取一個扇區的資料(這裡一般為 512 位元組),該函數代碼如

下:

//讀 SD 卡

//buf:資料緩存區

//sector:扇區

//cnt:扇區數

//傳回值:0,ok;其他,失敗.

u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)

{

u8 r1;

if(SD_Type!=SD_TYPE_V2HC)sector <<= 9;//轉換為位元組位址

if(cnt==1)

{

r1=SD_SendCmd(CMD17,sector,0X01);//讀指令

if(r1==0) r1=SD_RecvData(buf,512);//指令發送成功,接收 512 個位元組

}else

{

r1=SD_SendCmd(CMD18,sector,0X01);//連續讀指令

do

{

r1=SD_RecvData(buf,512); //接收 512 個位元組

buf+=512;

}while(--cnt && r1==0);

SD_SendCmd(CMD12,0,0X01); //發送停止指令

}

SD_DisSelect();//取消片選

return r1;

}

此函數根據要讀取扇區的多少,發送 CMD17/CMD18 指令,然後讀取一個/多個扇區的資料,

詳細見代碼,這裡我們就不多介紹了。儲存 MMC_SD.C 檔案,并加入到 HARDWARE 組下,

然後打開 MMC_SD.H,在該檔案裡面輸入如下代碼:

#ifndef _MMC_SD_H_

#define _MMC_SD_H_

#include "sys.h"

#include

// SD 卡類型定義

#define SD_TYPE_ERR 0X00

#define SD_TYPE_MMC

0X01

#define SD_TYPE_V1 0X02

#define SD_TYPE_V2 0X04

#define SD_TYPE_V2HC 0X06

// SD 卡指令表

#define CMD0 0 //卡複位

#define CMD1 1

#define CMD8 8 //指令 8 ,SEND_IF_COND

#define CMD9 9 //指令 9 ,讀 CSD 資料

#define CMD10 10 //指令 10,讀 CID 資料

#define CMD12 12 //指令 12,停止資料傳輸

#define CMD16 16 //指令 16,設定 SectorSize 應傳回 0x00

#define CMD17 17 //指令 17,讀 sector

#define CMD18 18 //指令 18,讀 Multi sector

#define CMD23 23 //指令 23,設定多 sector 寫入前預先擦除 N 個 block

#define CMD24 24 //指令 24,寫 sector

#define CMD25 25 //指令 25,寫 Multi sector

#define CMD41 41 //指令 41,應傳回 0x00

#define CMD55 55 //指令 55,應傳回 0x01

#define CMD58 58 //指令 58,讀 OCR 資訊

#define CMD59 59 //指令 59,使能/禁止 CRC,應傳回 0x00

//資料寫入回應字意義

#define MSD_DATA_OK

0x05

#define MSD_DATA_CRC_ERROR 0x0B

#define MSD_DATA_WRITE_ERROR 0x0D

#define MSD_DATA_OTHER_ERROR 0xFF

//SD 卡回應标記字

#define MSD_RESPONSE_NO_ERROR 0x00

#define MSD_IN_IDLE_STATE

0x01

#define MSD_ERASE_RESET

0x02

#define MSD_ILLEGAL_COMMAND 0x04

#define MSD_COM_CRC_ERROR 0x08

#define MSD_ERASE_SEQUENCE_ERROR 0x10

#define MSD_ADDRESS_ERROR 0x20

#define MSD_PARAMETER_ERROR 0x40

#define MSD_RESPONSE_FAILURE 0xFF

//這部分應根據具體的連線來修改!

//MiniSTM32 開發闆使用的是 PA3 作為 SD 卡的 CS 腳.

#define SD_CS PAout(3)

//SD 卡片選引腳

u8 SD_SPI_ReadWriteByte(u8 data);

void SD_SPI_SpeedLow(void);

void SD_SPI_SpeedHigh(void);

u8 SD_WaitReady(void);

//等待 SD 卡準備

u8 SD_GetResponse(u8 Response);

//獲得相應

u8 SD_Initialize(void);

//初始化

u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt);

//讀塊

u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt);

//寫塊

u32 SD_GetSectorCount(void);

//讀扇區數

u8 SD_GetCID(u8 *cid_data); //讀 SD 卡 CID

u8 SD_GetCSD(u8 *csd_data); //讀 SD 卡 CSD

#endif

該部分代碼主要是一些指令的宏定義以及函數聲明,在這裡我們設定了 SD 卡的 CS 管腳為

PA3。儲存 MMC_SD.H,就可以在主函數裡面編寫我們的應用代碼了,打開 main.c,輸入如下代

碼:

//讀取 SD 卡的指定扇區的内容,并通過序列槽 1 輸出

//sec:扇區實體位址編号

void SD_Read_Sectorx(u32 sec)

{

u8 *buf;

u16 i;

buf=mymalloc(512);

//申請記憶體

if(SD_ReadDisk(buf,sec,1)==0) //讀取 0 扇區的内容

{

LCD_ShowString(60,190,200,16,16,"USART1 Sending Data...");

printf("SECTOR 0 DATA:");

for(i=0;i<512;i++)printf("%x ",buf[i]);//列印 sec 扇區資料

printf("DATA ENDED");

LCD_ShowString(60,190,200,16,16,"USART1 Send Data Over!");

}

myfree(buf);//釋放記憶體

}

int main(void)

{

u8 key; u8 t=0;

u32 sd_size;

HAL_Init();

//初始化 HAL 庫

Stm32_Clock_Init(RCC_PLL_MUL9); //設定時鐘,72M

delay_init(72);

//初始化延時函數

uart_init(115200);

//初始化序列槽

LED_Init();

//初始化 LED

KEY_Init();

//初始化按鍵

LCD_Init();

//初始化 LCD

mem_init();

//初始化記憶體池

POINT_COLOR=RED;

//設定字型為紅色

LCD_ShowString(60,50,200,16,16,"Mini STM32");

LCD_ShowString(60,70,200,16,16,"SD CARD TEST");

LCD_ShowString(60,90,200,16,16,"[email protected]");

LCD_ShowString(60,110,200,16,16,"2019/11/16");

LCD_ShowString(60,130,200,16,16,"KEY0:Read Sector 0");

while(SD_Initialize())//檢測不到 SD 卡

{

LCD_ShowString(60,150,200,16,16,"SD Card Error!"); delay_ms(500);

LCD_ShowString(60,150,200,16,16,"Please Check! "); delay_ms(500);

LED0=!LED0;//DS0 閃爍

}

POINT_COLOR=BLUE;//設定字型為藍色

//檢測 SD 卡成功

LCD_ShowString(60,150,200,16,16,"SD Card OK ");

LCD_ShowString(60,170,200,16,16,"SD Card Size: MB");

sd_size=SD_GetSectorCount();//得到扇區數

LCD_ShowNum(164,170,sd_size>>11,5,16);//顯示 SD 卡容量

while(1)

{

key=KEY_Scan(0);

if(key==KEY0_PRES)SD_Read_Sectorx(0);//KEY0 按,讀取 SD 卡扇區 0 的内容

delay_ms(10);t++;

if(t==20) { LED0=!LED0; t=0; }

}

}

這裡總共 2 個函數,其中 SD_Read_Sectorx 用于讀取 SD 卡指定扇區的資料,并将讀到的

資料通過序列槽 1 輸出。然後 main 函數則通過 SD_GetSectorCount 函數來得到 SD 卡的扇區數,

間接得到 SD 卡容量,然後在液晶上顯示出來,接着我們通過按鍵 KEY0 控制讀取 SD 卡的扇區 0,

然後把讀到的資料通過序列槽列印出來。另外,我們對上一章學過的記憶體管理小試牛刀,稍微用

了下,以後我們會盡量使用記憶體管理來設計。

最後,我們将 SD_Read_Sectorx 函數加入 USMART 控制,這樣,我們就可以通過序列槽調

試助手,讀取 SD 卡任意一個扇區的資料,友善大家測試。

33.4 下載下傳驗證

在代碼編譯成功之後,我們通過下載下傳代碼到 ALIENTEK MiniSTM32 開發闆上,可以看到

LCD 顯示如圖 33.4.1 所示的内容(預設 SD 卡已經接上了):

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

圖 33.4.1 程式運作效果圖

打開序列槽調試助手,按下 KEY0 就可以看到從開發闆發回來的資料了,如圖 33.4.2 所示:

kotlin讀取sd卡裡的檔案_「正點原子STM32Mini闆資料連載」第三十三章 SD 卡實驗

圖 33.4.2 序列槽收到的 SD 卡扇區 0 内容

這裡請大家注意,不同的 SD 卡,讀出來的扇區 0 是不盡相同的,是以不要因為你讀出來

的資料和圖 33.4.2 不同而感到驚訝。