SD 卡有兩個可選的通訊協議:SD 模式和 SPI模式 SD 模式是SD 卡標準的讀寫方式,但是在選用SD 模式時,往往需要選擇帶有SD 卡控制器接口的 MCU,或者必須加入額外的SD卡控制單元以支持SD 卡的讀寫 然而,大多數MCU都沒有集成SD 卡控制器接口,若選用SD 模式通訊就無形中增加了產品的硬件成本。
在SD卡數據讀寫時間要求不是很嚴格的情況下, 選用 SPI模式可以說是一種最佳的解決方案 因為在 SPI模式下,通過四條線就可以完成所有的數據交換,并且目前市場上很多MCU都集成有現成的SPI接口電路,采用 SPI模式對 SD卡進行讀寫操作可大大簡化硬件電路的設計。
一、硬件連接
SD_CS接STM32的PD2
SD_MOSI接STM32的SPI2_MOSI
SD_MISO接STM32的SPI2_MISO
SD_SCK接STM32的SPI2_SCK
SD卡座都連了一個47K的上拉電阻
二、程序
1、初始化函數 SD_Initialize(void)
//SPI硬件層初始化
void SD_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GP IOG, ENABLE); //使能PB端口時鐘
//設置硬件上與SD卡相關聯的控制引腳輸出
//避免NRF24L01/W25Q32等的影響
//這里PB12和PG7拉高,是為了防止影響FLASH的燒寫。
//因為他們共用一個SPI口。
//PG7,PD2接的NRF CS和SD CS
//他們和SPI FLAS共用一個SPI,所以得分時復用!!
//這就是為什么要設置PG7,PD2了,禁止NRF和SD卡,從而SPI FLASH可以獨占SPI。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //PB12 推挽
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_12); //PB12上拉
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PD2 推挽
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_SetBits(GPIOD,GPIO_Pin_2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //PG7 推挽
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_SetBits(GPIOG,GPIO_Pin_7);
SPI2_Init(); //SPI2初始化
SD_CS=1; //不選中SD卡
}
//SD卡初始化的時候,需要低速,SD卡初始化的時候時鐘不能大于400K。
void SD_SPI_SpeedLow(void)
{
SPI2_SetSpeed(SPI_BaudRatePrescaler_256);//設置到低速模式
}
//向SD卡發送一個命令
//SD卡的命令是48位,命令索引8位,命令參數32位,CRC校驗值8位
//輸入: u8 cmd 命令索引,比如 CMD0,CMD8,CMD17,CMD24等
// u32 arg 命令參數
// u8 crc crc校驗值,高7位有效,最低位恒為1.
//返回值:SD卡返回的響應
u8 SD_SendCmd(u8 cmd, u32 arg, u8 crc)
{
u8 r1;
u8 Retry=0;
SD_DisSelect();//取消上次片選
if(SD_Select())return 0XFF;//片選失效
//發送
SD_SPI_ReadWriteByte(cmd | 0x40); //分別寫入命令,命令的最高2位是01,也就是0x40,cmd是6位有效
SD_SPI_ReadWriteByte(arg 》》 24); //發送參數,從高位開始
SD_SPI_ReadWriteByte(arg 》》 16);
SD_SPI_ReadWriteByte(arg 》》 8);
SD_SPI_ReadWriteByte(arg);
SD_SPI_ReadWriteByte(crc); //最后發送CRC,CRC的最低位恒為1.
if(cmd==CMD12)SD_SPI_ReadWriteByte(0xff);//Skip a stuff byte when stop reading
//等待響應,或超時退出
Retry=0X1F;
do
{
r1=SD_SPI_ReadWriteByte(0xFF);
}while((r1&0X80) && Retry--);
//返回狀態值 r1
return r1;
}
//等待卡準備好 ,SD卡寫的時候,會拉低MISO=0,直到數據寫入完成,MISO才變為1.
//返回值:0,準備好了;其他,錯誤代碼
u8 SD_WaitReady(void)
{
u32 t=0;
do {
if(SD_SPI_ReadWriteByte(0XFF)==0XFF)return 0;//寫入完成,OK
t++;
}while(t《0XFFFFFF);//MISO一直是0的時候等待 ,直到超時。
return 1;
}
R7響應的格式
//初始化 SD 卡
u8 SD_Initialize(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 狀態,是否進入空閑狀態,r1=0x01.
}while((r1!=0X01) && retry--);
SD_Type=0;//默認無卡
if(r1==0X01) //如果SD卡進入空閑狀態。
{
if(SD_SendCmd(CMD8,0x1AA,0x87)==1)//SD V2.0 //發送CMD8區分是不是2.0的卡
//如果有響應,就是V2.0的卡
//CMD8是R7響應
{
for(i=0;i《4;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF); //連續讀4個字節,得到R7響應的值
//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); //發送ACMD41命令前要先發送 CMD55
r1=SD_SendCmd(CMD41,0x40000000,0X01);//發送 ACMD41,設置HCS位=1.表示主機支持SDHC卡
}while(r1&&retry--); //r1響應最低位是1,說明SD卡在空閑狀態,如果SD卡進入正常工作狀態,r1最低位是0
//一直等掃r1是0,表示SD卡成功接收到了指令,可以開始下一步操作。
if(retry&&SD_SendCmd(CMD58,0,0X01)==0)//判斷CCS位,鑒別 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,如果是1,表示是SDHC的卡
else SD_Type=SD_TYPE_V2; //如果是0,表示卡是標準的2.0版本SD卡
}
}
}else//SD V1.x/ MMC V3 //如果不支持CMD8指令,表示卡是V1.xx版本的SD卡或MMC卡
{
SD_SendCmd(CMD55,0,0X01); //發送 CMD55
r1=SD_SendCmd(CMD41,0,0X01); //發送 ACMD41
if(r1《=1) //如果支持ADMD41指令,表示是V1.xx的卡
{
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
{
SD_Type=SD_TYPE_MMC;//MMC V3,不支持ACMD41指令表示是MMC卡
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;
//錯誤的卡,如果是1.xx或MMC卡要設置塊大小,為512字節,如果設置失敗,定義卡錯誤
}
}
SD_DisSelect(); //取消片選
SD_SPI_SpeedHigh(); //高速,把SPI接口設置為高速,為36M,實際上stm32f1的SPI最高為18M,所以是超頻使用,覺得不可接收的話可以把分頻系數設置為4.設置為2也可以工作
if(SD_Type)return 0; //如果SD_Type有效,是正確的SD卡或MMC卡,表示初始化成功
else if(r1)return r1;
return 0xaa; //其他錯誤
}
2. 讀卡函數 :u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
//從sd卡讀取一個數據包的內容
//buf:數據緩存區
//len:要讀取的數據長度。
//返回值:0,成功;其他,失敗;
u8 SD_RecvData(u8*buf,u16 len)
{
if(SD_GetResponse(0xFE))return 1;//不停的讀MISO,等待SD卡發回數據起始令牌0xFE
while(len--)//開始接收數據
{
*buf=SPI2_ReadWriteByte(0xFF);
buf++;
}
//下面是2個偽CRC(dummy CRC),多發16個時鐘,把2個CRC移出來,然后不用
SD_SPI_ReadWriteByte(0xFF);
SD_SPI_ReadWriteByte(0xFF);
return 0;//讀取成功
}
//讀SD卡
//buf:數據緩存區
//sector:扇區 注意這里是扇區地址,不是字節地址
//cnt:扇區數,由于是u8類型,所以最多讀255個扇區
//返回值:0,ok;其他,失敗。
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
//先判斷是不是SDHC卡,如果不是SDHC卡,先把扇區地址轉換為字節地址,左移9位就是乘以512
if(SD_Type!=SD_TYPE_V2HC)sector 《《= 9;//轉換為字節地址
if(cnt==1) //如果cnt是1,就是單個扇區的讀寫
{
r1=SD_SendCmd(CMD17,sector,0X01);//讀命令
if(r1==0)//指令發送成功
{
r1=SD_RecvData(buf,512);//接收512個字節到buf中
}
}else
{
r1=SD_SendCmd(CMD18,sector,0X01);//連續讀命令
do
{
r1=SD_RecvData(buf,512);//每次接收512個字節
buf+=512;
}while(--cnt && r1==0); //直到cnt為0
SD_SendCmd(CMD12,0,0X01); //發送停止命令
}
SD_DisSelect();//取消片選
return r1;//
}
3. 寫SD卡函數:u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
//向sd卡寫入一個數據包的內容 512字節
//buf:數據緩存區
//cmd:指令
//返回值:0,成功;其他,失敗;
u8 SD_SendBlock(u8*buf,u8 cmd)
{
u16 t;
if(SD_WaitReady())return 1;//等待準備失效,等待MISO為高電平1.
SD_SPI_ReadWriteByte(cmd); //發送CMD的數據起始令牌0xFE
if(cmd!=0XFD)//不是結束指令
{
for(t=0;t《512;t++)SPI2_ReadWriteByte(buf[t]);//提高速度,減少函數傳參時間
SD_SPI_ReadWriteByte(0xFF);//忽略crc
SD_SPI_ReadWriteByte(0xFF);
t=SD_SPI_ReadWriteByte(0xFF);//接收響應,SD卡在接收到512字節后有個響應
//(t&0x1F)=0x05表示響應正確
if((t&0x1F)!=0x05)return 2;//響應錯誤
}
return 0;//寫入成功
}
//寫SD卡
//buf:數據緩存區
//sector:起始扇區 ,這里注意是扇區地址,不是字節地址
//cnt:扇區數 ,由于是u8類型,所以最多讀255個扇區
//返回值:0,ok;其他,失敗。
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
u8 r1;
if(SD_Type!=SD_TYPE_V2HC)sector *= 512;//轉換為字節地址
if(cnt==1)
{
r1=SD_SendCmd(CMD24,sector,0X01);//讀命令
if(r1==0)//指令發送成功
{
r1=SD_SendBlock(buf,0xFE);//寫512個字節
}
}else
{
if(SD_Type!=SD_TYPE_MMC)
{
SD_SendCmd(CMD55,0,0X01);
SD_SendCmd(CMD23,cnt,0X01);//發送指令,如果不是MMC卡,先預先擦除要寫入的扇區
//這樣可以提高寫入數據的速度
}
r1=SD_SendCmd(CMD25,sector,0X01);//連續寫命令
if(r1==0)
{
do
{
r1=SD_SendBlock(buf,0xFC);//接收512個字節,多塊寫的令牌是0xFC
buf+=512;
}while(--cnt && r1==0);
r1=SD_SendBlock(0,0xFD);//發送多塊寫完成令牌0xFD,不帶數據塊
}
}
SD_DisSelect();//取消片選
return r1;//
}
4. 獲取SD卡總扇區數的函數:u32 SD_GetSectorCount(void)
//獲取SD卡的CSD信息,包括容量和速度信息
//輸入:u8 *cid_data(存放CID的內存,至少16Byte)
//返回值:0:NO_ERR
// 1:錯誤
u8 SD_GetCSD(u8 *csd_data)
{
u8 r1;
r1=SD_SendCmd(CMD9,0,0x01);//發CMD9命令,讀CSD,CSD寄存器為128位寄存器
if(r1==0)
{
r1=SD_RecvData(csd_data, 16);//接收16個字節的數據 即128位
}
SD_DisSelect();//取消片選
if(r1)return 1;
else return 0;
}
//獲取SD卡的總扇區數(扇區數) ,扇區數*512就是容量
//返回值:0: 取容量出錯
// 其他:SD卡的容量(扇區數/512字節)
//每扇區的字節數必為512,因為如果不是512,則初始化不能通過。
u32 SD_GetSectorCount(void)
{
u8 csd[16];
u32 Capacity;
u8 n;
u16 csize;
//取CSD信息,如果期間出錯,返回0
if(SD_GetCSD(csd)!=0) return 0;
//如果為SDHC卡,按照下面方式計算
if((csd[0]&0xC0)==0x40) //V2.00的卡
{
csize = csd[9] + ((u16)csd[8] 《《 8) + 1;
Capacity = (u32)csize 《《 10;//得到扇區數
}else//V1.XX的卡
{
n = (csd[5] & 15) + ((csd[10] & 128) 》》 7) + ((csd[9] & 3) 《《 1) + 2;
csize = (csd[8] 》》 6) + ((u16)csd[7] 《《 2) + ((u16)(csd[6] & 3) 《《 10) + 1;
Capacity= (u32)csize 《《 (n - 9);//得到扇區數
}
return Capacity;
}
評論