天天看點

卡函數or1200基于simplespi的SD卡驅動

每日一貼,天今的内容關鍵字為卡函數

        這篇blog來說說基于simple-spi這個ipcore編寫spi模式的SD Card裸機的驅動程式,植移依附分不清什麼SD卡啊,micro SD啊,miniSD,MMC,SDIO啊,SDHC啊等等一大堆的觀點,天今抽了點時光百科和wiki掃盲去了,順便把結總的貼出來,留自己後以回想~

        MMC:Multimedia Card(多媒體記憶卡),基于NAND-Flash技巧,衍生版有出RS-MMC(小尺寸的多媒體卡)、雙電壓小尺寸多媒體卡(DV-RS-MMC)。4.x準标引入更新版MMC plus,MMC4卡和RS-MMC4(移動式MMC,老式RS-MMC的盜窟),并且引入secureMMC準标

        SD:Secure Digital Memory Card,基于MMC卡格式展發而來,同樣是NAND-Flash技巧,SD裝置相容MMC卡存取,而MMC裝置不能存取SD卡(因為卡槽不一緻,不能互插)。

        miniSD:個人解理是小型的SD卡,相容SD卡,電器性屬略微有點區分。

        microSD:原名Trans-flash Card(TF卡),04年名更microSD Card,比miniSD尺寸更小的SD卡。

        SDHC:Secure Digital High Capacity(高容量SD卡4G~32G),判定終了(本人機手用的也隻microSD咧,SDHC用在什麼方面?求釋解),劃定用使FAT32檔案系統。

        SDIO:Secure Digital Input and Output Card(安全字數輸入輸出卡),在SD準标上定義的一種外設接口。(接口?我能不能解理成似類USB一樣,不過就是把外設的接口做成SDIO,那有USB了為毛還要用這個接口,求大神釋解)

        現在SD卡差不多代取完MMC卡了,不過SD的卡槽可以卡得進MMC,是以是相容MMC的,也因為這樣MMC卡,因為用MMC卡可以不交SD議協的稅版,是以還在用,至于miniSD和microSD套進去插到SD卡槽就能夠用了,SDHC沒見過,釋解不了了,SDIO的裝置也沒用過,可以去寶淘YY一下,SDIO接口的wifi,藍牙等等的,但是比起USB接口的來得貴,大神,求釋解啊,應用合場是什麼?

        這界世貌似多好西東我不能夠解理啊~除了坑爹還是坑爹~

        再說說那個作操接口和時序準标的問題

        (轉一下wiki面下關于各種技巧比對的圖,很然了目一)

        SD卡比起MMC多了2pins,都是用來做資料線的;

        miniSD比起SD又多了2pins,當初隻做預留,沒什麼用途,其他和SD卡一樣

        microSD比SD少了1pin?貌似比對我手上的那些原理圖是少了個VSS?

        因為曆史因原,SPI時序基本在這面裡都市支撐,還有SD總線的1bit mode是支撐的,但是SD卡用于高速情況是一般我們擇選是4bit mode,我自己是沒用過SD總線的1 bit mode,平日我會擇選跑高速資料擇選SD總線的4 bit mode,低速跑SPI。

        有這個SD 1 bit mode為毛還要SPI mode咧?是以這個界世除了坑爹還是坑爹,但是像我們這些讀過一點點書的人都道知:“存在就是理合”這個堡碉了的名言。

        1.你見過大部分SOC(特别是低端)成集SPI controller還是支撐SD Bus 和controller多

                2.寫過SD卡驅動的友朋們處置CRC校驗你耗資源多不多(件硬無CRC校驗)

        3.SD的CMD線與DATA線之間有可能同時生産資料,對沒有SD件硬子產品的機主支撐起來難度較高(引用别人blog的原話)

        挑點重點來講講,也因為我隻道知這麼多了,見笑見笑!!!

        面下就是講講SD/MMC準标SPI時序的SD卡寫讀程序了,至于SD 4bit mode就先不細說了,後以有時光編寫程式再發個blog~

        至于官方的SD specs我沒怎麼看過,太長了~心真看不動~google一下有沒有别人結總過的驗經了,就算把官方的specs翻譯成中文也懶得看了~

        SD展發了這麼多頭年,官方結總的資料我認為比起看官方的spces來得際實很多,至于關于SD卡的資料推薦《ALIENTEK戰艦STM32開發闆》面裡的盤光,這裡絕對我不是托,隻是友朋再用這款開發闆,而面裡整理好關于SD卡的資料我心真認為不錯。

        到此,根據寫好的基于simple-spi這個core的SD驅動,結總最簡略的流程

        一、Initialize:

        1.開發闆上電

        2.延時>74clocks

        3.寫CMD0,複位SD卡

        4.寫CMD8,檢查版本

        5.寫CMD58,查詢OCR,獲得卡供電情況

        6.寫CMD1,激活卡

        7.8 clocks後,止禁SD的CS

         二,read block:

        1.寫CMD17 

        2.等到R1格式的令命應答

        3.取讀始起令牌0xFE

        4.讀512 bytes

        5.棄丢2 bytes的CRC校驗bytes

        6.8 clocks後,止禁SD的CS

        三、write block:

        1.寫CMD24

        2.等到R1格式的令命應答

        3.插入幹若clocks

        4.寫始起令牌0xFE

        5.寫512 bytes

        6.寫2 bytes CRC校驗bytes(dummy bytes)

        7.收接響應資料0x05

        8.8 clocks後,止禁SD的CS

        四、write blocks:

        1.寫CMD25

        2.等到R1格式的令命應答

        3.取讀始起令牌0xFC

        4.寫512 bytes

        5.寫2 bytes CRC校驗bytes(dummy bytes)

        6.收接響應資料0x05

        7.等待SD card閑暇

        8.重複第2步

        至于關于SD卡的令命集google一下一大堆,解了一下經常使用的令命就OK啦,或者參看官方的SD specs 2.x的第7 chapter關于SPI Mode的述描,面裡有關于令命集合時序圖。

        空話不多了,關于SD的基礎知識和SPI的作操釋解到這裡,至于友朋們還須要更多的SD的知識可以去wiki關于SD的詳解。

        http://en.wikipedia.org/wiki/Secure_Digital

        面下就根據SD卡SPI的時序圖和simple-spi這個core的spces來敲SD card的driver

        還是來講講編寫好的simple-spi的幾個封裝函數先了,這些函數咧,是opencore區社的牛牛寫好的,我就不另外自己去編了~省點力精~

        首先在高速寫讀SD卡的時候咧,用輪詢方法比用斷中方法去寫讀SD卡效率要高(真的嗎?我自己沒究研過),但是我輕信了這句話,是以沒用斷中去寫SD的driver,是以這裡僅僅紹介用到的API啦~

        這個驅動檔案在u-boot/driver/spi中可以找到oc_simple_spi.c~

        至于對應的simple-spi的代碼我用的是orpsocv2面裡的,orpsocv2修改了simple-spi支撐slave的擇選功能,這份驅動就是按照修過改的core編寫的,上代碼了~

        隻貼出來用到的函數,體具參考simple-spi的specs把寄存器置配都略微看懂。

void 
spi_core_enable(int core)
{
  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) |= SIMPLESPI_SPCR_SPE;
}

void 
spi_core_disable(int core)
{
  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) &= ~SIMPLESPI_SPCR_SPE;
}

void 
spi_core_clock_setup(int core, char polarity, char phase, char rate,
			  char ext_rate)
{
  char spcr = REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR));

  if (polarity)
    spcr |= SIMPLESPI_SPCR_CPOL;
  else
    spcr &= ~SIMPLESPI_SPCR_CPOL;

  if (phase)
    spcr |= SIMPLESPI_SPCR_CPHA;
  else
    spcr &= ~SIMPLESPI_SPCR_CPHA;

  spcr = (spcr & ~SIMPLESPI_SPCR_SPR) | (rate & SIMPLESPI_SPCR_SPR);

  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPCR)) = spcr;

  char sper = REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPER));
  
  sper = (sper & ~SIMPLESPI_SPER_ESPR) | (ext_rate & SIMPLESPI_SPER_ESPR);

  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPER)) = sper;

}

// No decode on slave select lines, so assert correct bit to select slave
void 
spi_core_slave_select(int core, char slave_sel_dec)
{  
  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SSPU)) = slave_sel_dec;
}

int 
spi_core_data_avail(int core)
{
  return !!!(REG8((SPI_BASE_ADR[core]+SIMPLESPI_SPSR))&SIMPLESPI_SPSR_RFEMPTY);
}

int 
spi_core_write_avail(int core)
{
  return !!!(REG8((SPI_BASE_ADR[core]+SIMPLESPI_SPSR))&SIMPLESPI_SPSR_WFFULL);
}

// Should call spi_core_write_avail() before calling this, we don't check
void 
spi_core_write_data(int core, char data)
{
  REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPDR)) = data;
}

char 
spi_core_read_data(int core)
{
  return REG8((SPI_BASE_ADR[core] + SIMPLESPI_SPDR));
}      

        然後略微解理用到的最平日的寫讀函數,就能夠轉到SD卡driver的編寫了~驅動參考振南兄的znFAT裡SD驅動流程編寫

        好,第一個,reset程序函數SDReset(),對着時序圖來談話

        按照面下述描過的初始化流程,送74clocks,cs拉低,送令命CMD0(0x4000000095),距離8*clock*n後,等待從裝置的0x1應答,cs再次拉高

        必須注意的是,reset和初始化時時鐘率速必須下降,至于率速降到多少貌似還沒有定論,我初始化的時候時降到了300K閣下,貌似400K一下都可以作操。

unsigned char SDReset(void){		
	unsigned char times, temp, i;
	unsigned char pcmd[] = {0x40,0x00,0x00,0x00,0x00,0x95};	// CMD0

	// send SDCard 74 clocks
	spi_core_slave_select(SD, SDDisable);
	SetSDClockInitRate(SD);		// slow down sdcard clock speed
	for(i=0; i<0x0f; i++){		
		spi_write_ignore_read(SD, 0xff);	// 8*15 = 120 clocks
	}

	// send CMD0
	spi_core_slave_select(SD, SDEnable);
		
	times = 0;
	do{
		temp = SDWriteCmd(pcmd);
		times++;
		if(times == TRY_TIME){
			return INIT_CMD0_ERROR;
		}
	}while(temp!=0x01);

	spi_core_slave_select(SD, SDDisable);
	spi_write_ignore_read(SD, 0xff);
	
	return 0;
}      

        初始化SD卡時序:cs拉低,送令命CMD0(0x41000000ff),距離8*clock*n後,等待從裝置的0x0應答,cs再次拉高

        初始化代碼:

unsigned char SDInit(void){
	unsigned char times, temp;
	unsigned char pcmd[] = {0x41,0x40,0x00,0x00,0x00,0xff};

	spi_core_slave_select(SD, SDEnable);
	times = 0;
	do{
		temp = SDWriteCmd(pcmd);
		times++;
		if(times==TRY_TIME){
			return INIT_CMD1_ERROR;
		}
	}while(temp!=0x00);

	SetSDClockTransferRate(SD);		//set sdcrad clock as transfre clock
	spi_core_slave_select(SD, SDDisable);
	spi_write_ignore_read(SD, 0xff);
	return 0;
}      

        至于CID和CSD的取讀,程式裡沒有實作,有興緻的友朋可以根據CMD0和CMD1的時序自己敲個代碼上去彌補完全

    每日一道理

人生是潔白的畫紙,我們每個人就是手握各色筆的畫師;人生也是一條看不到盡頭的長路,我們每個人則是人生道路的遠足者;人生還像是一塊神奇的土地,我們每個人則是手握農具的耕耘者;但人生更像一本難懂的書,我們每個人則是孜孜不倦的讀書郎。

        在認默情況下,SD卡的寫讀block巨細都是512bytes,是以就按照最平日的block巨細行進寫讀作操。

        block讀作操:cs拉低,送令命CMD17(0x51000000ff),距離8*clock*n後,等待從裝置的0x0應答,再次距離距離8*clock*n,取讀開始位元組(0xfe),取讀512bytes,棄丢2bytes的CRC,cs再次拉高

        寫讀一個section的代碼:

unsigned char SDReadSector(unsigned long addr,unsigned char *buffer){
	unsigned char temp,times;
	unsigned char i;
	unsigned char pcmd[]={0x51,0x00,0x00,0x00,0x00,0xff};	// CMD17

	addr<<=9;
	pcmd[1]=addr>>24;
	pcmd[2]=addr>>16;
	pcmd[3]=addr>>8;
	pcmd[4]=addr;

	spi_core_slave_select(SD, SDEnable);
	times = 0;
	do{
		temp = SDWriteCmd(pcmd);
		times++;
		if(times==TRY_TIME){
			return READ_BLOCK_ERROR;
		}
	}while(temp!=0x00);
	
	// check if datas ready ,then recevie datas and two bytes CRC(ignored)
	do{
		temp = spi_read_ignore_write(SD);
	}while(temp!=0xfe);
	
	for(i=0; i<64; i++){
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
		*(buffer++) = spi_read_ignore_write(SD);
	}
	spi_read_ignore_write(SD);
	spi_read_ignore_write(SD);

	spi_core_slave_select(SD, SDDisable);
	spi_write_ignore_read(SD, 0xff);
	return 0;	
}      

        block寫作操:cs拉低,送令命CMD24(0x58000000ff),距離8*clock*n後,等待從裝置的0x0應答,再次距離距離8*clock*n,入寫開始位元組(0xfe),入寫512bytes,寫2bytes的dummy CRC,取讀應答位元組0x05,等待SD忙狀态束結,cs再次拉高。

        寫作操代碼:

unsigned char SDWriteSector(unsigned long addr,unsigned char *buffer){
	unsigned char temp,times;
	unsigned char i;
	unsigned char pcmd[] = {0x58,0x00,0x00,0x00,0x00,0xff};	// CMD24

	addr<<=9;
	// *((unsigned long *)(pcmd+1))=addr;
	pcmd[1]=addr>>24;
	pcmd[2]=addr>>16;
	pcmd[3]=addr>>8;
	pcmd[4]=addr;

	spi_core_slave_select(SD, SDEnable);
	times = 0;
	do{
		temp = SDWriteCmd(pcmd);
		times++;
		if(times==TRY_TIME){
			return temp;
		}
	}while(temp!=0x00);

	// insert some clocks
	for(i=0; i<10; i++){
		spi_read_ignore_write(SD);
	}

	// write 512 bytes to SD Card ,and two bytes CRC(ignored)
	spi_write_ignore_read(SD, 0xfe);
	for(i=0; i<64; i++){
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
		spi_write_ignore_read(SD, *buffer++);
	}
	spi_write_ignore_read(SD, 0xff);
	spi_write_ignore_read(SD, 0xff);

	// check if datas have been written to SD Card
	temp = spi_read_ignore_write(SD);
	if((temp & 0x1F)!=0x05){
		spi_core_slave_select(SD, SDDisable);
		return WRITE_BLOCK_ERROR;
	}
	
	do{
		temp = spi_read_ignore_write(SD);
	}while(temp!=0xff);	
	
	spi_core_slave_select(SD, SDDisable);
	spi_write_ignore_read(SD, 0xff);
	return 0;
}      

        對于多個blocks的連續寫作操,直接參考代碼吧,隻是用連續寫作操令命CMD18,令命的數參段位入寫datas時的address,好吧,有了寫作操程序連續寫的程序也不難,上代碼咯~

        連續寫n個sections代碼:

unsigned char SDWritenSector(unsigned long nsec,unsigned long addr,unsigned char *buffer){
	unsigned char temp,times;
	unsigned long i, j;
	unsigned char pcmd[] = {0x59,0x00,0x00,0x00,0x00,0xff};

	unsigned char *temp_buf = buffer;

	if(sd_ver==0x05 || !addr_mode) addr<<=9;
	pcmd[1]=addr>>24;
	pcmd[2]=addr>>16;
	pcmd[3]=addr>>8;
	pcmd[4]=addr;

	spi_core_slave_select(SD, SDEnable);

	times = 0;
	do{
		temp = SDWriteCmd(pcmd);
		times++;
		if(times==TRY_TIME){
			return temp;
		}
	}while(temp!=0x00);

	// insert some clocks
	for(i=0; i<10; i++){
		spi_read_ignore_write(SD);
	}

	// write datas to sections
	for(j=0; j<nsec; j++){	
		// write 512 bytes to SD Card ,and two bytes CRC(ignored)
		spi_write_ignore_read(SD, 0xfc);	
		for(i=0; i<64; i++){
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
			spi_write_ignore_read(SD, *buffer++);
		}
		spi_write_ignore_read(SD, 0xff);
		spi_write_ignore_read(SD, 0xff);
		
		// check if datas have been write to SD Card
		temp = spi_read_ignore_write(SD);
		if((temp & 0x1F)!=0x05){
			spi_core_slave_select(SD, SDDisable);
			return WRITE_BLOCK_ERROR;
		}
		while(spi_read_ignore_write(SD)!=0xff);
		buffer=temp_buf;	
	}

	spi_write_ignore_read(SD, 0xfd);
	while(spi_read_ignore_write(SD)!=0xff);
	spi_core_slave_select(SD, SDDisable);
	spi_write_ignore_read(SD, 0xff);
	return 0;	
}      

        OK,到這裡SD卡的驅動就基本可以用了,部全的碼源的話可以有興緻的友朋郵件我,接下來就是還有一些用到的函數都講講吧。

        SD驅動的寫讀函數

void spi_write_ignore_read(int core, char dat){
	spi_core_write_data(core, dat);
  	while (!(spi_core_data_avail(core))); // Wait for the transaction (should generate a byte)
  	spi_core_read_data(core);
}

char spi_read_ignore_write(int core){
	spi_core_write_data(core, 0xff);
	while (!(spi_core_data_avail(core))); // Wait for the transaction (should generate a byte)
	return spi_core_read_data(core);
}      

        Oc-simple-spi的初始化函數

void SpiCoreInit(int core){
	// disable spi core, and deselect sdcard
	spi_core_enable(core);
	spi_core_slave_select(core, SDDisable);

	// clear read buffer
	while (spi_core_data_avail(core)){
		spi_core_read_data(core);
	}

	// setup default clock = sysclk/128
	spi_core_clock_setup(core, 0, 0, 0x01, 0x02);

	// enable spi core
	spi_core_slave_select(core, SDEnable);
	spi_core_enable(core);
}      

        Oc-simple-spi的時鐘率速切換函數

void SetSDClockInitRate(int core){
	spi_core_disable(core);

	// set sdcard clock = sysclk/128
	spi_core_clock_setup(core, 0, 0, 0x01, 0x02);

	spi_core_enable(core);		
}

void SetSDClockTransferRate(int core){
	spi_core_disable(core);

	// set sdcard clock = sysclk/2
	spi_core_clock_setup(core, 0, 0, 0x00, 0x00);

	spi_core_enable(core);
}      

        以上那些函數都和體具的oc-simple-spi core相幹,可以參考這個core的specs來看

        SD驅動的初始化函數,這個函數用于下一節提到的在植移znFat時須要駁接的SD卡複位函數

unsigned char SDReady(void){	
	SDReset();
	SDCheckVersion();
	SDGetAddrMode();
	return SDInit();
}      

        好,沒了,驅動寫到這裡就OK了,下節根據znFat的植移教程植移這個檔案系統

文章結束給大家分享下程式員的一些笑話語錄: 開發時間

  項目經理: 如果我再給你一個人,那可以什麼時候可以完工?程式員: 3個月吧!項目經理: 那給兩個呢?程式員: 1個月吧!

項目經理: 那100呢?程式員: 1年吧!

項目經理: 那10000呢?程式員: 那我将永遠無法完成任務.