1. 程式人生 > >基於STC12C5A60S2的SDHC卡的初始化和讀寫過程

基於STC12C5A60S2的SDHC卡的初始化和讀寫過程

最近學習一了下SD卡的驅動,網上程式的版本很多,使用的MCU和SD卡的型號千奇百怪,學起來反而沒有方向,感覺上亂七八糟的,直到現在才知到我們平常說的SD卡實際上有很多中類別。0到2G的SD卡,最普通的卡;2G到32G的SDHC卡,也就是現在最常用的大容量SD卡;還有我沒有見過的SDXC卡,容量好像在32G以上。同時還有手機上的TF卡,實際上也是SD卡 只不過做工不同而已,MMC卡。學習的時候走了很多彎路,SD卡的官方data sheet感覺上寫的相當坑爹,網上的學習資料還是給了很大的幫助,但是由於網上的版本很多,程式流程還是要參考官方相對應的SD卡初始化流程。這兩天閒下來,抓緊時間整理一下筆記。

首先說一下我自己使用的卡,它是SanDisk 4G SDHC Card,速度等級為4,算比較快的一種大容量SD卡。MCU選取了STC12C5A60S2,反饋資訊的顯示採用最常用的1602液晶屏,當然大部分網上的程式碼使用的是串列埠。SD卡有兩種傳輸模式,SD模式和SPI模式,SD模式需要4跟資料線,而我們一般都採用SPI模式,也就是常說的序列通訊模式,這種方式需要的通訊線比較少,一根資料輸入D_IN,一根資料輸出D_OUT,CS片選線,CLK時鐘,此外還有電源3.6V和GND地線,其他的引腳按照說明懸空或者接地即可。

其次,SD卡的初始化過程根據卡的不同有不同的方式,我們按照官方給出的流程來說。關於命令的具體引數和返回值的型別說明放在下一篇筆記中,這裡只記錄流程。


第一步,首先上電,將CS片選訊號拉低,在這個基礎上對CLK操作,給SD卡傳送至少74個時鐘週期,讓SD卡完成自身檢查和初始化,進入空閒狀態(IDLE)。之後,對SD卡傳送CMD0使其進入SPI模式。不論你是什麼卡,第一步的工作都是相同的,這個時候可以觀察一下SD卡從D_OUT線上的返回值,如果是0x01,說明CMD0操作是成功的,此時SD卡還處在IDLE狀態。

第二步,傳送CMD8這一步新的SD卡和老版本的SD卡是有區別的,CMD8是檢測SD卡版本的命令,如果SD卡對此命令不識別,那麼說明你的SD卡為老版本的,如果SD卡對CMD8做出了正確的返回值(前提你命令格式要對),則說明你的SD卡的硬體層版本是2.0的,支援大容量儲存,也就是SDHC卡。我所使用的卡就是SDHC卡,所以有6個位元組的返回值,這個在後面說明。另外要說的一點,我曾經直接跳過了CMD8的傳送,直接進行了下一步命令,SD卡返回了錯誤的資訊,沒有進入正確的讀寫準備狀態。所以這個命令還是按照官方的建議,傳送檢測。

第三步,CMD8有了返回值以後,則需要進一步讓卡從IDLE狀態進入讀寫就緒的狀態,也就是傳送ACMD41命令。這裡要注意的是,SD卡有兩種命令CMD和ACMD,如果直接傳送命令,SD卡會將命令預設為CMD,如果你想傳送ACMD,則要特殊的說明一下,CMD55就是這個功能,它可以提醒SD卡進行接著CMD55後的下一條命令為ACMD。第三步的操作即首先發送CMD55命令,接收到正常的返回值0X01後接著傳送ACMD41,完成卡從IDLE狀態到讀寫狀態的初始化程序。如果操作正常,SD卡退出IDLE狀態,最後的返回值為0X00,此外任何其他的返回值都是不正常的。

第四步,傳送CMD58,讀取OCR暫存器,OCR暫存器記錄了SD卡可識別的電壓範圍;SD卡是否支援大容量儲存,即SDHC;和SD卡上點狀態。傳送了CMD58命令後,SD卡的下一組返回值為R1返回值+OCR暫存器的內容。根據datasheet我們可以得到很多資訊,上面已經提到,具體的位置手冊上很明白。手冊上推薦傳送這個命令,主要功能是你可以知道你的V2.0SD卡是標準版本的,還是大容量的SD卡,大容量的SD卡讀寫操作時按照塊(512BYTE)進行的,所以讀寫地址的方法有所不同。判斷正常的方法,CMD58的返回值型別為R3 ,即R1型別+OCR暫存器內容,如果一切就緒,那麼OCR的最高四位為1100。從這個命令以後,初始化的工作就全部進入了,SD卡進入讀寫準備狀態,接下來就可以任意讀取目標地址,對其進行讀寫操作了。

讀寫資料的過程:
無論讀寫資料還是接收發送CMD,我們都會用到兩個最基本的函式,一個是read_byte(),即從SD卡的DATA_OUT引腳上讀取8bit(1byte)的資料;另一個是write_byte(),向SD卡的DATA_IN引腳寫一個位元組的資料。命令,資料和返回值都是由多位元組組合成的,所以在一個操作中會多次呼叫這兩個基本的函式。如SD_Read_Sector()這個函式的主要功能就是從指定的地址中讀取512位元組的資料,那我們在傳送了讀的命令後相應的要呼叫512次read_byte()函式。

讀寫函式的時序圖:向SD卡寫資料時,時鐘上升沿時資料有效;從SD卡讀資料時,時鐘在高電平時,MCU讀到的資料有效,根據這個寫兩個基本函式就沒有問題。

/****************************************************************************************
** 函式名稱: void write_byte()
** 功能描述: 對SD卡寫一個位元組的資料				
** 輸   入: 要寫入的位元組									
** 輸   出: 無									
****************************************************************************************/
void write_byte( uchar _data)
{
	uchar i;
	for(i=0; i<8; i++)
	{
		SD_CLK_CLR();
		SD_DAIN = (_data & 0x80);			//位操作必須有與運算以免不必要的錯誤
		if(is_init)							//是否進入高速模式,初始化時低速,is_init=1
			DelayMs(4);						//讀寫資料時高速,is_init=0;
		_data <<= 1;						//有的人為了提高速度,會把for迴圈拆開一句一句寫
		SD_CLK_SET();
		if(is_init)
			DelayMs(4);
	}

	SD_CLK_CLR();							//8bit資料傳輸完後拉低時鐘線
	SD_DAIN_SET();	
}

/****************************************************************************************
** 函式名稱: uchar read_byte()
** 功能描述: 從DATA_OUT線上讀取一位元組的資料					
** 輸   入: 無									
** 輸   出: 讀出的一位元組資料									
****************************************************************************************/
uchar read_byte()
{
	uchar _data,i;
	
	SD_DAOUT_SET();							//讀取前先要拉高資料線
	for(i=0; i<8; i++)
	{
		SD_CLK_CLR();
		if(is_init)	
			DelayMs(4);
		SD_CLK_SET();
		if(is_init)
			DelayMs(4);
		_data <<= 1;
		if(SD_DAOUT == 1)
		{
			_data = _data | 0x01;
		}		
	}
	SD_CLK_CLR();
	return _data;	
}