1.1 實(shí)驗描述及工程文件清單
實(shí)驗描述:MicroSD卡(SDIO模式)測試實(shí)驗,采用4bit數據線(xiàn)模式。沒(méi)有跑文件系統,只是單純地讀block并將測試信息通過(guò)串口1在電腦的超級終端上 打印出來(lái)。
硬件連接:MicroSD卡(SDIO模式)測試實(shí)驗,采用4bit數據線(xiàn)模式。沒(méi)有跑文件系統,只是單純地讀block并將測試信息通過(guò)串口1在電腦的超級終端上 打印出來(lái)。
用到的庫文件:startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.cFWlib/stm32f10x_usart.cFWlib/ stm32f10x_sdio.cFWlib/ stm32f10x_dma.cFWlib/ misc.c
用戶(hù)編寫(xiě)的文件:USER/main.cUSER/stm32f10x_it.cUSER/usart1.cUSER/ sdio_sdcard.c
野火STM32開(kāi)發(fā)板 MicroSD卡硬件原理圖:
1.2 SDIO簡(jiǎn)介
野火STM32開(kāi)發(fā)板的CPU ( STM32F103VET6 )具有一個(gè)SDIO接口。SD/SDIO/MMC主機接口可以支持MMC卡系統規范4.2版中的3個(gè)不同的數據總線(xiàn)模式:1位(默認)、4位和8位。在8位模式下,該接口可以使數據傳輸速率達到48MHz,該接口兼容SD存儲卡規范2.0版。SDIO存儲卡規范2.0版支持兩種數據總線(xiàn)模式:1位(默認)和4位。
目前的芯片版本只能一次支持一個(gè)SD/SDIO/MMC 4.2版的卡,但可以同時(shí)支持多個(gè)MMC 4.1版或之前版本的卡。除了SD/SDIO/MMC,這個(gè)接口完全與CE-ATA數字協(xié)議版本1.1兼容。
1.3 SD協(xié)議
大多數人原來(lái)沒(méi)有了解過(guò)SD協(xié)議,又看到SDIO的驅動(dòng)有2000多行,感覺(jué)無(wú)從下手。所以野火重新寫(xiě)了這個(gè)文檔進(jìn)行詳細的解釋?zhuān)瑤椭蠹腋斓乜邕^(guò)這道檻。
附資料:《Simplified_Physical_Layer_Spec.pdf》,這個(gè)資料包含了SDIO協(xié)議中SD存儲卡的部分。
下面野火結合STM32的SDIO,分析SD協(xié)議,讓大家對它先有個(gè)大概了解,更具體的說(shuō)明在代碼中展開(kāi)。
SDIO接口圖
一.從SDIO的時(shí)鐘說(shuō)起。
SDIO_CK時(shí)鐘是通過(guò)PC12引腳連接到SD卡的,是SDIO接口與SD卡用于同步的時(shí)鐘。
SDIO選配器掛載到AHB總線(xiàn)上,通過(guò)HCLK二分頻輸入到適配器得到SDIO_CK的時(shí)鐘,這時(shí)SDIO_CK = HCLK/(2+CLKDIV)。其中CLKDIV是SDIO_CLK(寄存器)中的CLKDIV位。
另外,SDIO_CK也可以由SDIOCLK通過(guò)設置bypass模式直接得到,這時(shí)SDIO_CK = SDIOCLK=HCLK。
通過(guò)下面的庫函數來(lái)配置時(shí)鐘:
SDIO_Init(&SDIO_InitStructure);
對SD卡的操作一般是大吞吐量的數據傳輸,所以采用DMA來(lái)提高效率,SDIO采用的是DMA2中的通道4。在數據傳輸的時(shí)候SDIO可向DMA發(fā)出請求。
二.講解SDIO的命令、數據傳輸方式。
SDIO的所有命令及命令響應,都是通過(guò)SDIO-CMD引腳來(lái)傳輸的。
命令只能由host即STM32的SDIO控制器發(fā)出。SDIO協(xié)議把命令分成了11種,包括基本命令,讀寫(xiě)命令還有ACMD系列命令等。其中,在發(fā)送ACMD命令前,要先向卡發(fā)送編號為CMD55的命令。
參照下面的命令格式圖,其中的start bit,transmission bit ,crc7,endbit,都是由STM32中的SDIO硬件完成,我們在軟件上配置的時(shí)候只需要設置command index和命令參數argument。Command index就是命令索引(編號),如CMD0,CMD1…被編號成0,1...。有的命令會(huì )包含參數,讀命令的地址參數等,這個(gè)參數被存放在argument段。
SD卡命令格式
可以通過(guò)下面的函數來(lái)配置、發(fā)送命令:
SDIO_SendCommand(&SDIO_CmdInitStructure); //發(fā)送命令
SD卡對host的各種命令的回復稱(chēng)為響應,除了CMD0命令外,SD卡在接收到命令都會(huì )返回一個(gè)響應。對于不同的命令,會(huì )有不同的響應格式,共7種,分為長(cháng)響應型(136bit)和短響應型(48bit)。以下圖,響應6(R6)為例:
SD卡命令響應格式(R6)
SDIO通過(guò)CMD接收到響應后,硬件去除頭尾的信息,把command index 保存到SDIO_RESPCMD寄存器,把argument field內容保存存儲到SDIO_RESPx寄存器中。這兩個(gè)值可以分別通過(guò)下面的庫函數得到。
SDIO_GetCommandResponse(); //卡返回接收到的命令
SDIO_GetResponse(SDIO_RESP1); //卡返回的argument field內容
數據寫(xiě)入,讀取。請看下面的寫(xiě)數據時(shí)序圖,在軟件上,我們要處理的只是讀忙。另外,我們的實(shí)驗中用的是Micro SD卡,有4條數據線(xiàn),默認的時(shí)候SDIO采用1條數據線(xiàn)的傳輸方式,更改為4條數據線(xiàn)模式要通過(guò)向卡發(fā)送命令來(lái)更改。
SD卡的多塊寫(xiě)入時(shí)序圖
三.卡的種類(lèi)。
STM32的SDIO支持SD存儲卡,SD I/O卡 ,MMC卡。
其中SDI/O卡與SD存儲卡是有區別的,SDI/O卡實(shí)際上就是利用SDIO接口的一些模塊,插入SD的插槽中,擴展設備的功能,如:SDI/O wifi, SDI/O cmos相機等。而SD存儲卡就是我們平時(shí)常見(jiàn)的單純用于存儲數據的卡。
可使用SDIO接口類(lèi)型的卡
本實(shí)驗中使用的Micro SD卡屬于SDSC(標準容量,最大兩G)卡。介紹卡的種類(lèi)是因為SD協(xié)議中的命令也支持這三種類(lèi)型的卡,因此對STM32中的SDIO接口進(jìn)行初始化后,上電后就要對接入的卡進(jìn)行檢測、分類(lèi),這個(gè)過(guò)程是通過(guò)向卡發(fā)送一系列不同的命令,根據卡不同的響應來(lái)進(jìn)行分類(lèi)。
下面進(jìn)入代碼展開(kāi)具體講解。
1.4 代碼分析
首先要添加用的庫文件,在工程文件夾下Fwlib下我們需添加以下庫文件:
FWlib/stm32f10x_gpio.c
FWlib/stm32f10x_rcc.c
FWlib/stm32f10x_usart.c
FWlib/stm32f10x_sdio.c
FWlib/stm32f10x_dma.c
FWlib/misc.c
還要在 stm32f10x_conf.h中把相應的頭文件添加進(jìn)來(lái):
#include "stm32f10x_dma.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_sdio.h"
#include "stm32f10x_usart.h"
#include "misc.h"
保持良好的習慣,從main函數開(kāi)始分析:
int main(void)
{
/*進(jìn)入到main函數前,啟動(dòng)文件startup(startup_stm32f10x_xx.s)已經(jīng)調用了在
system_stm32f10x.c中的SystemInit(),配置好了系統時(shí)鐘,在外部晶振8M的條件下,
設置HCLK = 72M */
/* Interrupt Config */
NVIC_Configuration();
/* USART1 config */
USART1_Config();
/*------------------------------ SD Init ---------------------------------- */
Status = SD_Init();
printf( "\r\n 這是一個(gè)MicroSD卡實(shí)驗(沒(méi)有跑文件系統).........\r\n " );
if(Status == SD_OK) //檢測初始化是否成功
{
printf( " \r\n SD_Init 初始化成功 \r\n " );
}
else
{
printf("\r\n SD_Init 初始化失敗 \r\n" );
printf("\r\n 返回的Status的值為: %d \r\n",Status );
}
printf( " \r\n CardType is :%d ", SDCardInfo.CardType );
printf( " \r\n CardCapacity is :%d ", SDCardInfo.CardCapacity );
printf( " \r\n CardBlockSize is :%d ", SDCardInfo.CardBlockSize );
printf( " \r\n RCA is :%d ", SDCardInfo.RCA);
printf( " \r\n ManufacturerID is :%d \r\n", SDCardInfo.SD_cid.ManufacturerID );
SD_EraseTest(); //擦除測試
SD_SingleBlockTest(); //單塊讀寫(xiě)測試
SD_MultiBlockTest(); //多塊讀寫(xiě)測試
while (1)
{}
}
main函數的流程簡(jiǎn)單明了:
1、用NVIC_Configuration()初始化好SDIO的中斷;
2、用USART1_Config()配置好用于返回調試信息的串口,SD_Init()開(kāi)始進(jìn)行SDIO的初始化;
3、最后分別用SD_EraseTest()、SD_SingleBlockTest()、SD_MultiBlockTest()進(jìn)行擦除,單數據塊讀寫(xiě),多數據塊讀寫(xiě)測試。
下面我們先進(jìn)入SDIO驅動(dòng)函數的大頭——SD_Init()進(jìn)行分析:
/*
* 函數名:SD_Init
* 描述 :初始化SD卡,使卡處于就緒狀態(tài)(準備傳輸數據)
* 輸入 :無(wú)
* 輸出 :-SD_Error SD卡錯誤代碼
* 成功時(shí)則為 SD_OK
* 調用 :外部調用
*/
SD_Error SD_Init(void)
{
/*重置SD_Error狀態(tài)*/
SD_Error errorstatus = SD_OK;
/* SDIO 外設底層引腳初始化 */
GPIO_Configuration();
/*對SDIO的所有寄存器進(jìn)行復位*/
SDIO_DeInit();
/*上電并進(jìn)行卡識別流程,確認卡的操作電壓 */
errorstatus = SD_PowerON();
/*如果上電,識別不成功,返回“響應超時(shí)”錯誤 */
if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/*卡識別成功,進(jìn)行卡初始化 */
errorstatus = SD_InitializeCards();
if (errorstatus != SD_OK) //失敗返回
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/*!< Configure the SDIO peripheral
上電識別,卡初始化都完成后,進(jìn)入數據傳輸模式,提高讀寫(xiě)速度
速度若超過(guò)24M要進(jìn)入bypass模式
!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz
!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising; //上升沿采集數據
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; //時(shí)鐘頻率若超過(guò)24M,要開(kāi)啟此模式
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; //若開(kāi)啟此功能,在總線(xiàn)空閑時(shí)關(guān)閉sd_clk時(shí)鐘
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; //1位模式
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; //硬件流,若開(kāi)啟,在FIFO不能進(jìn)行發(fā)送和接收數據時(shí),數據傳輸暫停
SDIO_Init(&SDIO_InitStructure);
if (errorstatus == SD_OK)
{
/*----------------- Read CSD/CID MSD registers ------------------*/
errorstatus = SD_GetCardInfo(&SDCardInfo); //用來(lái)讀取csd/cid寄存器
}
if (errorstatus == SD_OK)
{
/*----------------- Select Card --------------------------------*/
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16)); //通過(guò)cmd7 ,rca選擇要操作的卡
}
if (errorstatus == SD_OK)
{
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b); //開(kāi)啟4bits模式
}
return(errorstatus);
}
先從整體上了解這個(gè)SD_Init()函數:
1.用 GPIO_Configuration()進(jìn)行SDIO的端口底層配置
2.分別調用了SD_PowerON()和SD_InitializeCards()函數,這兩個(gè)函數共同實(shí)現了上面提到的卡檢測、識別流程。
3.調用SDIO_Init(&SDIO_InitStructure)庫函數配置SDIO的時(shí)鐘,數據線(xiàn)寬度,硬件流(在讀寫(xiě)數據的時(shí)候,開(kāi)啟硬件流是和很必要的,可以減少出錯)
4. 調用SD_GetCardInfo(&SDCardInfo)獲取sd卡的CSD寄存器中的內容,在main函數里輸出到串口的數據就是這個(gè)時(shí)候從卡讀取得到的。
5. 調用SD_SelectDeselect()選定后面即將要操作的卡。
6.調用SD_EnableWideBusOperation(SDIO_BusWide_4b)開(kāi)啟4bit數據線(xiàn)模式
如果SD_Init()函數能夠執行完整個(gè)流程,并且返回值是SD_OK的話(huà)則說(shuō)明初始化成功,就可以開(kāi)始進(jìn)行擦除、讀寫(xiě)的操作了。
下面進(jìn)入SD_PowerON()函數,分析完這個(gè)函數大家就能了解SDIO如何接收、發(fā)送命令了。
/*
* 函數名:SD_PowerON
* 描述 :確保SD卡的工作電壓和配置控制時(shí)鐘
* 輸入 :無(wú)
* 輸出 :-SD_Error SD卡錯誤代碼
* 成功時(shí)則為 SD_OK
* 調用 :在 SD_Init() 調用
*/
SD_Error SD_PowerON(void)
{
SD_Error errorstatus = SD_OK;
uint32_t response = 0, count = 0, validvoltage = 0;
uint32_t SDType = SD_STD_CAPACITY;
/*!< Power ON Sequence -----------------------------------------------------*/
/*!< Configure the SDIO peripheral */
/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV) */
/*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */
/*!< SDIO_CK for initialization should not exceed 400 KHz */
/*初始化時(shí)的時(shí)鐘不能大于400KHz*/
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV; /* HCLK = 72MHz, SDIOCLK = 72MHz, SDIO_CK = HCLK/(178 + 2) = 400 KHz */
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable; //不使用bypass模式,直接用HCLK進(jìn)行分頻得到SDIO_CK
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; // 空閑時(shí)不關(guān)閉時(shí)鐘電源
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b; //1位數據線(xiàn)
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;//硬件流
SDIO_Init(&SDIO_InitStructure);
/*!< Set Power State to ON */
SDIO_SetPowerState(SDIO_PowerState_ON);
/*!< Enable SDIO Clock */
SDIO_ClockCmd(ENABLE);
/*下面發(fā)送一系列命令,開(kāi)始卡識別流程*/
/*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/
/*!< No CMD response required */
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE; //cmd0
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No; //無(wú)響應
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; //則CPSM在開(kāi)始發(fā)送命令之前等待數據傳輸結束。
SDIO_SendCommand(&SDIO_CmdInitStructure); //寫(xiě)命令進(jìn)命令寄存器
errorstatus = CmdError();//檢測是否正確接收到cmd0
if (errorstatus != SD_OK) //命令發(fā)送出錯,返回
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/*!< CMD8: SEND_IF_COND ----------------------------------------------------*/
/*!< Send CMD8 to verify SD card interface operating condition */
/*!< Argument: - [31:12]: Reserved (shall be set to '0')
- [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)
- [7:0]: Check Pattern (recommended 0xAA) */
/*!< CMD Response: R7 */
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN; //接收到命令sd會(huì )返回這個(gè)參數
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND; //cmd8
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r7
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; //關(guān)閉等待中斷
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/*檢查是否接收到命令*/
errorstatus = CmdResp7Error();
if (errorstatus == SD_OK) //有響應則card遵循sd協(xié)議2.0版本
{
CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 ,先把它定義會(huì )sdsc類(lèi)型的卡*/
SDType = SD_HIGH_CAPACITY; //這個(gè)變量用作acmd41的參數,用來(lái)詢(xún)問(wèn)是sdsc卡還是sdhc卡
}
else //無(wú)響應,說(shuō)明是1.x的或mmc的卡
{
/*!< CMD55 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
}
/*!< CMD55 */ //為什么在else里和else外面都要發(fā)送CMD55?
//發(fā)送cmd55,用于檢測是sd卡還是mmc卡,或是不支持的卡
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); //是否響應,沒(méi)響應的是mmc或不支持的卡
/*!< If errorstatus is Command TimeOut, it is a MMC card */
/*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch)
or SD card 1.x */
if (errorstatus == SD_OK) //響應了cmd55,是sd卡,可能為1.x,可能為2.0
{
/*下面開(kāi)始循環(huán)地發(fā)送sdio支持的電壓范圍,循環(huán)一定次數*/
/*!< SD CARD */
/*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */
while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
{
//因為下面要用到ACMD41,是ACMD命令,在發(fā)送ACMD命令前都要先向卡發(fā)送CMD55
/*!< SEND CMD55 APP_CMD with RCA as 0 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; //CMD55
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD); //檢測響應
if (errorstatus != SD_OK)
{
return(errorstatus);//沒(méi)響應CMD55,返回
}
//acmd41,命令參數由支持的電壓范圍及HCS位組成,HCS位置一來(lái)區分卡是SDSc還是sdhc
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType; //參數為主機可供電壓范圍及hcs位
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r3
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp3Error(); //檢測是否正確接收到數據
if (errorstatus != SD_OK)
{
return(errorstatus); //沒(méi)正確接收到acmd41,出錯,返回
}
/*若卡需求電壓在SDIO的供電電壓范圍內,會(huì )自動(dòng)上電并標志pwr_up位*/
response = SDIO_GetResponse(SDIO_RESP1); //讀取卡寄存器,卡狀態(tài)
validvoltage = (((response >> 31) == 1) ? 1 : 0); //讀取卡的ocr寄存器的pwr_up位,看是否已工作在正常電壓
count++; //計算循環(huán)次數
}
if (count >= SD_MAX_VOLT_TRIAL) //循環(huán)檢測超過(guò)一定次數還沒(méi)上電
{
errorstatus = SD_INVALID_VOLTRANGE; //SDIO不支持card的供電電壓
return(errorstatus);
}
/*檢查卡返回信息中的HCS位*/
if (response &= SD_HIGH_CAPACITY) //判斷ocr中的ccs位 ,如果是sdsc卡則不執行下面的語(yǔ)句
{
CardType = SDIO_HIGH_CAPACITY_SD_CARD; //把卡類(lèi)型從初始化的sdsc型改為sdhc型
}
}/*!< else MMC Card */
return(errorstatus);
}
卡的上電,識別流程:
截圖來(lái)自《Simplified_Physical_Layer_Spec.pdf》 page27
代碼中所有的判斷語(yǔ)句都是根據這個(gè)圖的各個(gè)識別走向展開(kāi)的,最終把卡分為1.0版的SD存儲卡,2.0版的SDSC卡和2.0版的SDHC卡。
在這個(gè)代碼流程中有兩點(diǎn)要注意一下:
1.初始化的時(shí)鐘。SDIO_CK的時(shí)鐘分為兩個(gè)階段,在初始化階段SDIO_CK的頻率要小于400KHz,初始化完成后可把SDIO_CK調整成高速模式,高速模式時(shí)超過(guò)24M要開(kāi)啟bypass模式,對于SD存儲卡即使開(kāi)啟bypass,最高頻率不能超過(guò)25MHz。
2.CMD8命令。
CMD8命令格式。
CMD8命令中的VHS是用來(lái)確認主機SDIO是否支持卡的工作電壓的。Check pattern部分可以是任何數值,若SDIO支持卡的工作電壓,卡會(huì )把接收到的check pattern數值原樣返回給主機。
CMD8命令的響應格式R7:
在驅動(dòng)程序中調用了CmdResp7Error()來(lái)檢驗卡接收命令后的響應。
3.ACMD41命令。
這個(gè)命令也是用來(lái)進(jìn)一步檢查SDIO是否支持卡的工作電壓的,協(xié)議要它在調用它之前必須先調用CMD8,另外還可以通過(guò)它命令參數中的HCS位來(lái)區分卡是SDHC卡還是SDSC卡。
確認工作電壓時(shí)循環(huán)地發(fā)送ACMD41,發(fā)送后檢查在SD卡上的OCR寄存器中的pwr_up位,若pwr_up位置為1,表明SDIO支持卡的工作電壓,卡開(kāi)始正常工作。
同時(shí)把ACMD41中的命令參數HCS位置1,卡正常工作的時(shí)候檢測OCR寄存器中的CCS位,若CCS位為1則說(shuō)明該卡為SDHC卡,為零則為SDSC卡。
因為ACMD41命令屬于A(yíng)CMD命令,在發(fā)送ACMD命令前都要先發(fā)送CMD55.
ACMD41命令格式
ACMD41命令的響應(R3),返回的是OCR寄存器的值
OCR寄存器的內容
SD卡上電確認成功后,進(jìn)入SD_InitializeCards()函數:
/*
* 函數名:SD_InitializeCards
* 描述 :初始化所有的卡或者單個(gè)卡進(jìn)入就緒狀態(tài)
* 輸入 :無(wú)
* 輸出 :-SD_Error SD卡錯誤代碼
* 成功時(shí)則為 SD_OK
* 調用 :在 SD_Init() 調用,在調用power_on()上電卡識別完畢后,調用此函數進(jìn)行卡初始化
*/
SD_Error SD_InitializeCards(void)
{
SD_Error errorstatus = SD_OK;
uint16_t rca = 0x01;
if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)//判斷卡的類(lèi)型
{
/*!< Send CMD2 ALL_SEND_CID */
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
/*下面開(kāi)始SD卡初始化流程*/
if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
|| (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) //使用的是2.0的卡
{
/*!< Send CMD3 SET_REL_ADDR with argument 0 */
/*!< SD Card publishes its RCA. */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR; //cmd3
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r6
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca); //把接收到的卡相對地址存起來(lái)。
if (SD_OK != errorstatus)
{
return(errorstatus);
}
}
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
RCA = rca;
/*!< Send CMD9 SEND_CSD with argument as card's RCA */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
errorstatus = SD_OK; /*!< All cards get intialized */
return(errorstatus);
}
這個(gè)函數向卡發(fā)送了CMD2和CMD3命令
1.CMD2
CMD2命令是要求卡返回它的CID寄存器的內容。
命令的響應格式(R2)。
因為命令格式是136位的,屬于長(cháng)響應。軟件接收的信息有128位。在長(cháng)響應的時(shí)候通過(guò)SDIO_GetResponse(SDIO_RESP4);中的不同參數來(lái)獲取CID中的不同數據段的數據。
2.CMD3
CMD3命令是要求卡向主機發(fā)送卡的相對地址。在接有多個(gè)卡的時(shí)候,主機要求接口上的卡重新發(fā)一個(gè)相對地址,這個(gè)地址跟卡的實(shí)際ID不一樣。比如接口上接了5個(gè)卡,這5個(gè)卡的相對地址就分別為1,2,3,4,5.以后主機SDIO對這幾個(gè)卡尋址就直接使用相對地址。這個(gè)地址的作用就是為了尋址更加簡(jiǎn)單。
接下來(lái)我們回到SD_Init()函數。分析到這里大家應該對SDIO的命令發(fā)送和響應比較清楚了。在SD_InitializeCards()之后的SD_GetCardInfo(&SDCardInfo)、 SD_SelectDeselect()和
SD_EnableWideBusOperation(SDIO_BusWide_4b)的具體實(shí)現就不再詳細分析了,實(shí)際就是發(fā)送相應的命令,對卡進(jìn)行相應的操作。
接下來(lái)分析main函數中的SD_MultiBlockTest()多塊數據讀寫(xiě)函數,讓大家了解SDIO是怎樣傳輸數據的。
/*
* 函數名:SD_MultiBlockTest
* 描述 : 多數據塊讀寫(xiě)測試
* 輸入 :無(wú)
* 輸出 :無(wú)
*/
void SD_MultiBlockTest(void)
{
/*--------------- Multiple Block Read/Write ---------------------*/
/* Fill the buffer to send */
Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);
if (Status == SD_OK)
{
/* Write multiple block of many bytes on address 0 */
Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
/* Check if the Transfer is finished */
Status = SD_WaitWriteOperation();
while(SD_GetStatus() != SD_TRANSFER_OK);
}
if (Status == SD_OK)
{
/* Read block of many bytes from address 0 */
Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
/* Check if the Transfer is finished */
Status = SD_WaitReadOperation();
while(SD_GetStatus() != SD_TRANSFER_OK);
}
/* Check the correctness of written data */
if (Status == SD_OK)
{
TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
}
if(TransferStatus2 == PASSED)
printf("\r\n 多塊讀寫(xiě)測試成功! " );
else
printf("\r\n 多塊讀寫(xiě)測試失??! " );
}
把這個(gè)函數拿出來(lái)分析最重要的一點(diǎn)就是讓大家注意在調用了SD_WriteMultiBlocks()這一類(lèi)讀寫(xiě)操作的函數后,一定要調用SD_WaitWriteOperation()[在讀數據時(shí)調用SD_WaitReadOperation]和SD_GetStatus()來(lái)確保數據傳輸已經(jīng)結束再進(jìn)行其它操作。其中的SD_WaitWriteOperation()是用來(lái)等待DMA把緩沖的數據傳輸到SDIO的FIFO的;而SD_GetStatus()是用來(lái)等待卡與SDIO之間傳輸數據完畢的。
最后進(jìn)入SD_WriteMultiBlocks()函數分析:
/*
* 函數名:SD_WriteMultiBlocks
* 描述 :從輸入的起始地址開(kāi)始,向卡寫(xiě)入多個(gè)數據塊,
只能在DMA模式下使用這個(gè)函數
注意:調用這個(gè)函數后一定要調用
SD_WaitWriteOperation()來(lái)等待DMA傳輸結束
和 SD_GetStatus() 檢測卡與SDIO的FIFO間是否已經(jīng)完成傳輸
* 輸入 :
* @param WriteAddr: Address from where data are to be read.
* @param writebuff: pointer to the buffer that contain the data to be transferred.
* @param BlockSize: the SD card Data block size. The Block size should be 512.
* @param NumberOfBlocks: number of blocks to be written.
* 輸出 :SD錯誤類(lèi)型
*/
SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)
{
SD_Error errorstatus = SD_OK;
__IO uint32_t count = 0;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 1;
SDIO->DCTRL = 0x0;
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
{
BlockSize = 512;
WriteAddr /= 512;
}
/*******************add,沒(méi)有這一段容易卡死在DMA檢測中*************************************/
/*!< Set Block Size for Card,cmd16,若是sdsc卡,可以用來(lái)設置塊大小,若是sdhc卡,塊大小為512字節,不受cmd16影響 */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
/*********************************************************************************/
/*!< To improve performance */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; // cmd55
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/*!< To improve performance */// pre-erased,在多塊寫(xiě)入時(shí)可發(fā)送此命令進(jìn)行預擦除
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks; //參數為將要寫(xiě)入的塊數目
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT; //cmd23
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/*!< Send CMD25 WRITE_MULT_BLOCK with argument data address */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize;
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
SDIO_DataConfig(&SDIO_DataInitStructure);
SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);
SDIO_DMACmd(ENABLE);
SD_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize));
return(errorstatus);
}
寫(xiě)操作在發(fā)送正式的多塊寫(xiě)入命令CMD25前調用了CMD23進(jìn)行預寫(xiě),這樣有利于提高寫(xiě)入的速度。在代碼的最后調用了SDIO_ITConfig(),SDIO的數據傳輸結束中斷就是這個(gè)時(shí)候開(kāi)啟的,數據傳輸結束時(shí),就進(jìn)入到stm32f10x_it.c文件中的中斷服務(wù)函數SDIO_IRQHandler()中處理了,中斷服務(wù)函數主要就是負責清中斷。
最后講一下官方原版的驅動(dòng)中的一個(gè)bug。
在官方原版的SDIO驅動(dòng)的SD_ReadBlock()、SD_ReadMultiBlocks()、SD_WriteBlock()和SD_WriteMultiBlocks()這幾個(gè)函數中,發(fā)送讀寫(xiě)命令前,漏掉了發(fā)送一個(gè)CMD16命令,這個(gè)命令用于設置讀寫(xiě)SD卡的塊大小。缺少這個(gè)命令很容易導致程序運行時(shí)卡死在循環(huán)檢測DMA傳輸結束的代碼中,網(wǎng)上很多人直接移植ST官方例程時(shí),用3.5版庫函數和這個(gè)4.5版的SDIO驅動(dòng)移植失敗,就是缺少了這段用CMD16設置塊大小的代碼。
到這里,終于講解完畢啦!這個(gè)講解如果能讓你從對SDIO一無(wú)所知到大概了解的話(huà),我的目標就達到啦,想要更深入了解還是要好好地配合這個(gè)例程中我在代碼中的注釋和附帶資料SD2.0協(xié)議《Simplified_Physical_Layer_Spec.pdf》好好研究一番! ^_^
注意:這個(gè)例程是沒(méi)有跑文件系統的,而是直接就去讀卡的block,這樣的話(huà)就會(huì )破壞卡的分區,在實(shí)驗完成之后,你再把卡插到電腦上時(shí),電腦會(huì )提示你要重新初始化卡,這是正常想象,并不是本實(shí)驗把你的卡弄壞了,如果卡原來(lái)有資料的請先把數據備份了再進(jìn)行測試。但跑文件系統時(shí)就不會(huì )出現這種問(wèn)題,有關(guān)文件系統的操作將在下一講的教程中講解。
1.5實(shí)驗現象
將野火STM32開(kāi)發(fā)板供電(DC5V),插上JLINK,插上串口線(xiàn)(兩頭都是母的交叉線(xiàn)),插上MicroSD卡( 我用的是1G ,經(jīng)測試,本驅動(dòng)也適用于2G以上的卡(sdhc卡)),打開(kāi)超級終端,配置超級終端為115200 8-N-1,將編譯好的程序下載到開(kāi)發(fā)板,即可看到超級終端打印出如下信息:







