天天看點

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

一、環境介紹

小車主要MCU: STM32F103ZET6

STM32程式開發IDE: keil5

STM32程式風格:  采用寄存器方式開發,注釋齊全,執行效率高,友善移植

硬體包含:  一塊STM32F103ZET6系統闆、一個2.8寸TFT電阻觸摸顯示屏、一個SD卡卡槽(SPI接口)、一張SD卡(存放字庫和小說檔案)

工程完整源碼下載下傳位址:

https://download.csdn.net/download/xiaolong1126626497/19628524

二、功能介紹

這是基于ST32F103ZET6設計的小說閱讀器,雖然對于真實的小說閱讀器産品來講,實用性和功能方面還差很多,但是對于剛入門的STM32、單片機開發工程師來講,這裡面設計到的技術才是最有價值的。

是以這篇文章的小說閱讀器主要是用來作為嵌入式單片機工程師入門練手項目、大學生的課程設計等。目的不在于小說閱讀器,而是以小說閱讀器為例子,學習相關的技術:  SD卡、序列槽通信、SPI通信、8080時序、觸摸屏校準原理、FATFS檔案系統使用、語音播報子產品使用等等。

該閱讀器支援正常閱小說讀器具備的基本功能:

1.  支援選擇指定的小說進行檢視閱讀,可以通過觸摸屏上的按鈕進行切換。

2.  支援切換字型大小

3.  支援切換字型顔色、背景顔色

4. 标題欄顯示目前閱讀器檢視的小說檔案名稱

5.  支援翻頁、上一頁、下一頁

6. 支援語音自動閱讀,發聲接近正常真人發聲,非常強大。

語音方案可以選擇兩種:  (1). 宇音SYN6658  (2). 科大訊飛SYN5152。    這兩款晶片都是通過序列槽通信,程式設計十分簡單。

内部程式設計思路介紹:

小說閱讀器的字型是存放在SD卡上的,SD卡采用SPI接口的卡槽與STM32相連接配接,STM32配合FATFS檔案系統對SD卡上的檔案進行操作;為了提高通路效率、在第一次上電的時候會将SD卡上的字庫檔案拷貝到闆載W25Q64晶片内。小說檔案還是存放在SD卡上,每次翻頁的時候從SD卡上擷取文本檔案,渲染到LCD顯示屏上。

該顯示屏是2.8寸的電阻觸摸顯示屏,驅動晶片是ILI9341(相容:9325,9328),LCD的引腳接線相容正點原子的2.8寸LCD顯示屏;電阻屏的驅動晶片是XPT2046,,是很常見的組合,這個XPT2046就是個ADC晶片,最終要完成觸摸屏上坐标點定位,還需要自己寫校準算法進行換算。 ILI9341驅動晶片支援8080時序操作,可以采用IO模拟方式驅動、也可以采用STM32的FSMC接口驅動。 STM32增強版支援FSMC功能的,其他沒有FSMC接口的晶片,可以采用模拟8080時序方式驅動,效果一樣,隻是效率上差點,無法實作高速刷屏,隻要不進行高速刷屏,湊合使用是沒什麼問題的。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
三、所用到的硬體介紹(都是淘寶買的)

3.1 STM32F103ZET6最小系統闆

   這是在淘寶上買的硬體詳情,開發闆和LCD用哪一款都可以的,程式設計思路都是一樣。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

開發闆的闆載資源如下:

CPU:STM32F103ZET6,LQFP144,FLASH:512K,SRAM:64K;

外擴SPI FLASH:W25Q32,8M位元組;

1個電源訓示燈;

2個狀态訓示燈;

一個EEPROM晶片,24C02,容量256位元組(注意:不同産地标号不一,但都是24C02晶片,經測試無誤)

1個光敏傳感器;

1個無線子產品接口,可接NRF24L01/RFID/CC01子產品;

1路CAN接口,采用TJA1050晶片;

1路485接口,采用SP485晶片;

1個标準的2.4/2.8/3.5/4.3/7寸LCD接口,支援觸摸屏;

一個USB序列槽,可用于程式下載下傳和代碼調試(USMART調試);

1個USB SLAVE接口,用于USB通信;

1個複位按鍵;

2個獨立按鍵;

1個SD卡座,用來接SD卡;

1個RTC後備電池座;

1個标準的JTAG/SWD仿真下載下傳調試接口;

1路5V轉3.3V電路;

晶片引腳144個腳全部引出,友善外接擴充實驗;

1個電源開關,用來開關USB的電源;

3.2 SD卡卡槽

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
3.3 SYN6658語音合成晶片
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

功能特點:

•  晶片支援任意中文文本的合成,可以采用GB2312、GBK、BIG5 和Unicode 四種編碼方式;

•  晶片具有文本智能分析處理功能,對常見的數值、電話号碼、時間日期、度量衡符号等格式的文本;

•  晶片可以自動對文本進行分析,判别文本中多音字的讀法并合成正确的讀音;

•  晶片可實作10級數字音量控制,音量更大,更廣;

•  晶片内內建了77首聲音提示音和14首和弦音樂;

•  提供兩男、兩女、一個效果器和一個女童聲共6個中文發音人;

•  支援多種文本控制标記,提升文本處理的正确率;

•  支援多種控制指令,包括:合成、停止、暫停合成、繼續合成、改變波特率等;

•  支援多種方式查詢晶片的工作狀态;

•  兩種通訊模式:晶片支援UART、SPI兩種通訊方式;

•  晶片支援Power Down 模式。使用控制指令可以使晶片進入Power Down 模式;

•  晶片支援的通訊波特率:4800bps,9600bps,57600bps、115200bps;

•  晶片各項名額均滿足室外嚴酷環境下的應用;

應用範圍:

•  車載資訊終端語音播報,車載排程,車載導航

•  停車場收費系統/誘導系統

•  公交報站器 ,考勤機

•  手機,固定電話

•  排隊叫号機,收銀收費機

•  自動售貨機,資訊機, POS 機

•  智能儀器儀表 ,氣象預警機,智能變壓器

•  智能玩具,智能手表

•  電動自行車

•  語音電子書,彩屏故事書,語音電子詞典,語音電子導遊

•  短消息播放 ,新聞播放

•  電子地圖

四、操作說明

4.1 程式下載下傳

開發闆支援Jlink下載下傳、也支援序列槽下載下傳。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

4.2 螢幕操作說明

目前實作的功能:

1.    小說翻頁:支援點選觸摸屏按鈕翻下一頁顯示

2.    換小說:點選觸摸屏按鈕“下一本”,可以切換小說。

3.    換顔色:點選觸摸屏按鈕“顔色調整”,可以切換顔色,支援12種字型顔色切換。

4.    換字型:點選觸摸屏按鈕“字型調整”,可以切換字型,目前支援兩種字型(16X16  24X24)。

思路說明:

程式裡移植了FATFS檔案系統,字型檔案和小說檔案都是存放在SD卡,通過檔案系統讀取SD卡裡的小說檔案進行顯示。

操作的過程在序列槽調試助手上也會同步輸出資訊。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

4.3 校準說明

第一次使用,需要校準螢幕,否則觸摸屏沒有反應。

如果發現螢幕不靈敏,可以強制進行校準,按下按鍵K2再按下複位鍵即可進行強制校準。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
依次點選螢幕上4個紅圈。
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

4.4 SD卡上存放的檔案 

SD卡上有兩個目錄:font目錄和txt目錄。

font目錄:存放字庫檔案。有兩個字庫字型。

txt目錄:存放小說檔案,内置了3篇小說。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)

五、核心代碼

代碼采用Keil5編寫,下載下傳即可編譯,測試,學習。

基于STM32設計的小說閱讀器(翻頁、字型切換、顔色切換、語音播報)
5.1  main.c  主函數代碼

#include "stm32f10x.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include <string.h>
#include <stdio.h>
#include "iic.h"
#include "at24c08.h"
#include "w25q64.h"
#include "nt35310_lcd.h"
#include "xpt2046.h"
#include "sdcard.h"
#include "ff.h"  //FATFS檔案系統的頭檔案
 
//更新字庫---從SD卡讀取字庫到W25Q64
void FontUpdate_to_W25Q64();
    
FATFS fatfs; //檔案系統注冊工作區需要使用
 
u16 select_color[]={WHITE,BLACK,BLUE,RED,YELLOW,BROWN,BRRED,GRAY,DARKBLUE,LIGHTBLUE,GRAYBLUE,LIGHTGREEN};
u8 read_text_buf[4096+1];
int main()
{  
    u32 x;u32 y;u32 size=16;u8 *p;
    u8 color_select_cnt=0; //12個
    FIL text_file;
    u16 br=0;
    u8 r_data=10;
    u32 read_cnt=0;
    DIR dir;
    FRESULT res; 
    FILINFO fno; //存放讀取的檔案資訊
    char *abs_path=NULL;  
    char path[]="0:/txt";
    u32 cnt=0;
    USART_X_Init(USART1,72,115200);
    
    NT35310_LcdInit();
    NT35310_Clear(WHITE);
    
    IIC_Init(); //IIC總線初始化
    W25Q64_Init(); //初始化W25Q64
    
    TOUCH_Init();    //觸摸屏初始化
    TOUCH_CheckXY(); //觸摸屏校準程式
    
    RCC->APB2ENR|=1<<5;
    GPIOD->CRH&=0xFF0FFFFF;
    GPIOD->CRH|=0x00300000;
    while(SDCardDeviceInit()!=0)
    {
        printf("SDCard_DeviceInit 錯誤.\r\n");
        PDout(13)=!PDout(13);
        delay_ms(100);
    }
    
    f_mount(&fatfs,"0:",0); //注冊檔案系統的工作區
 
    //設計界面
    LCD_color_1=RED;
    LCD_color_2=LIGHTBLUE;
    NT35310_DisplayString(16,0,16,"基于STM32的小說閱讀器設計");
    NT35310_DrawLine(0,16,239,16,DARKBLUE);
    
    //繪制按鍵
    NT35310_DrawRectangle(0,319-80,239,319,RED);
    NT35310_DrawLine(0,319-40,239,319-40,DARKBLUE);
    NT35310_DrawLine(239/2,319-80,239/2,319,DARKBLUE);
    
    LCD_color_2=WHITE;
    NT35310_DisplayString(32,319-70,16,"下一頁");
    NT35310_DisplayString(239/2+32,319-70,16,"下一本");
    NT35310_DisplayString(32,319-30,16,"字型調整");
    NT35310_DisplayString(239/2+32,319-30,16,"顔色調整");
    
     /*1. 打開目錄*/    
    res=f_opendir(&dir,path);
    if(res!=FR_OK)return res;
    
    res=f_readdir(&dir,&fno);
    printf("檔案名稱: %s,檔案大小: %ld 位元組\r\n",fno.fname,fno.fsize);
    
    LCD_color_1=BLACK;
    NT35310_DisplayString(0,17,16,fno.fname);
    
    if(abs_path)
    {
         free(abs_path);
         abs_path=NULL;
    }
    
    //申請存放檔案名稱的長度
    abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
    
    strcpy(abs_path,path);
    strcat(abs_path,"/");
    strcat(abs_path,fno.fname);
 
    printf("abs_path=%s\n",abs_path);
    
 
    NT35310_DisplayString(0,17+16,16,"第1卷\
第一回 甄士隐夢幻識通靈 賈雨村風塵懷閨秀\
此開卷第一回也。作者自雲:因曾曆過一番夢幻之後,故将真事隐去,\
    而借“通靈”之說,撰此<<石頭記>>一書也。故曰“甄士隐”雲雲。\
    但書中所記何事何人?自又雲:“今風塵碌碌,一事無成,忽念及當日所有之女子,\
    一一細考較去,覺其行止見識,皆出于我之上。何我堂堂須眉,誠不若彼裙钗哉?");
    
    while(1)
    {
        if(TOUCH_PEN==0) //判斷觸摸屏是否按下
        {
            //判斷是否讀取到XY坐标
            if(TOUCH_ReadXY())
            {   
               // printf("x=%d,y=%d\r\n",touch_info.x,touch_info.y);
 
                //判斷範圍
                if((touch_info.x>=0 && touch_info.x<=239/2)&&
                (touch_info.y>=319-80 && touch_info.y<=319-40))
                {
                    LCD_color_2=BLUE;
                    //填充顔色
                    NT35310_Fill(0+1,319-80+1,239/2-1,319-40-1,BLUE);
                    //顯示字元串
                    NT35310_DisplayString(32,319-70,16,"下一頁");
                    
                    //等待觸摸屏松開
                    while(TOUCH_PEN==0){}
                    
                    //填充顔色--清屏
                    NT35310_Fill(0,18+16,239,319-80-1,WHITE);
                        
                    LCD_color_2=WHITE;
                    if(read_cnt>=br)
                    {
                        read_cnt=0;
                    }
                    if(read_cnt==0)
                    {
                        if(br!=4096)
                        {
                            res=f_open(&text_file,(const TCHAR*)abs_path,FA_READ);//打開檔案     
                            if(res!=0)
                            {
                                printf("%s檔案打開失敗!\r\n",abs_path);   
                                return 1;  //檔案打開失敗
                            }        
                            printf("%s檔案打開成功!\n",abs_path); 
                        }
                      //執行代碼                        
                        res=f_read(&text_file,read_text_buf,4096,(UINT*)&br);//讀出4096個位元組
                        read_text_buf[br]='\0';
                        printf("br=%d\r\n",br);
                        if(br!=4096)
                        {
                            f_close(&text_file);
                        }
                    }
                   
                    //字型大小
                    
                    x=0;     //坐标起始位置
                    y=17+16; //坐标起始位置
                    p=read_text_buf+read_cnt;
                    while(*p!='\0')
                    {
                            if(*p>0x80) //判斷是否是中文-編碼規則從 0X8140 開始
                            {
                                    read_cnt+=2;
                                    if(x+size>239)
                                    {
                                            x=0; //橫坐歸0
                                            y+=size; //換行
                                            if(y+size>=319-80-1)break;
                                    }
                                    NT35310_DisplayGBKData(x,y,size,p);//顯示一個中文
                                    x+=size;
                                    p+=2; //偏移兩個位元組
                            }
                            else if(*p>=' ' && *p<='~')  //常用的ASCII碼
                            {
                                    read_cnt+=1;
                                    if(x+size/2>239)
                                    {
                                            x=0; //橫坐歸0
                                            y+=size; //換行
                                            if(y+size>=319-80-1)break;
                                    }
                                    if(size==16)
                                    {
                                        //顯示英文字母
                                        NT35310_DisplayData(x,y,size/2,size,(u8*)ASCII_8_16[*p-' ']);
                                    }
                                    else if(size==24)
                                    {
                                        //顯示英文字母
                                        NT35310_DisplayData(x,y,size/2,size,(u8*)asc2_2412[*p-' ']);
                                    }
                                   
                                    p+=1;
                                    x+=size/2;
                            }
                            else if(*p=='\n')
                            {
                                    x=0;
                                    y+=size;
                                    p+=1; //偏移指針
                                    read_cnt+=1;
                                    if(y+size>=319-80-1)break;
                            }
                            else 
                            {
                                    read_cnt+=1;
                                    p+=1; //偏移指針
                            }
                    }
                        
                    //填充顔色
                    NT35310_Fill(0+1,319-80+1,239/2-1,319-40-1,WHITE);
                    LCD_color_2=WHITE;
                    //顯示字元串
                    NT35310_DisplayString(32,319-70,16,"下一頁");   
                }
 
                 //判斷範圍
                if((touch_info.x>=239/2 && touch_info.x<=239)&&
                (touch_info.y>=319-80 && touch_info.y<=319-40))
                {
                    LCD_color_2=BLUE;
                    //填充顔色
                    NT35310_Fill(239/2+1,319-80+1,239-1,319-40-1,BLUE);
                    //顯示字元串
                    NT35310_DisplayString(239/2+32,319-70,16,"下一本");
                    
                    //等待觸摸屏松開
                    while(TOUCH_PEN==0){}
                    LCD_color_2=WHITE;
                    
                     //關閉原來的檔案
                    f_close(&text_file);
                        
                    //觸發新的頁
                    read_cnt=0;
                    br=0;
                    //執行代碼                        
                    res=f_readdir(&dir,&fno);
                    if(fno.fname[0] == 0 || res!=0)
                    {
                        /*3. 關閉目錄*/
                        f_closedir(&dir);
                        
                         /*1. 打開目錄*/    
                        res=f_opendir(&dir,path);
                        if(res!=FR_OK)return res;
                        
                        res=f_readdir(&dir,&fno);
                    }
                        
                    printf("檔案名稱: %s,檔案大小: %ld 位元組\r\n",fno.fname,fno.fsize);
                    
                    LCD_color_1=BLACK;
                    NT35310_DisplayString(0,17,16,fno.fname);
                    
                    if(abs_path)
                    {
                         free(abs_path);
                         abs_path=NULL;
                    }
                    //申請存放檔案名稱的長度
                    abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
                    
                    strcpy(abs_path,path);
                    strcat(abs_path,"/");
                    strcat(abs_path,fno.fname);
 
                    printf("abs_path=%s\n",abs_path);
                        
                    //填充顔色
                    NT35310_Fill(239/2+1,319-80+1,239-1,319-40-1,WHITE);
                    
                    //顯示字元串
                    NT35310_DisplayString(239/2+32,319-70,16,"下一本");   
                }
                
                //判斷範圍
                if((touch_info.x>=0 && touch_info.x<=239/2)&&
                (touch_info.y>=319-40 && touch_info.y<=319))
                {
                    LCD_color_2=BLUE;
                    //填充顔色
                    NT35310_Fill(0+1,319-40+1,239/2-1,319-1,BLUE);
                    //顯示字元串
                     NT35310_DisplayString(32,319-30,16,"字型調整");
                    
                    //等待觸摸屏松開
                    while(TOUCH_PEN==0){}
                    
                     if(size==16)size=24;
                     else size=16;
                        
                    //執行代碼                        
                        
                    //填充顔色
                    NT35310_Fill(0+1,319-40+1,239/2-1,319-1,WHITE);
                    LCD_color_2=WHITE;
                    //顯示字元串
                     NT35310_DisplayString(32,319-30,16,"字型調整");   
                }
                
                
                 //判斷範圍
                if((touch_info.x>=239/2 && touch_info.x<=239)&&
                (touch_info.y>=319-40 && touch_info.y<=319))
                {
                    LCD_color_2=BLUE;
                    //填充顔色
                    NT35310_Fill(239/2+1,319-40+1,239-1,319-1,BLUE);
                    //顯示字元串
                    NT35310_DisplayString(239/2+32,319-30,16,"顔色調整");
                    
                    //等待觸摸屏松開
                    while(TOUCH_PEN==0){}
                    
                    //執行代碼  
                    //前景字型顔色切換
                    LCD_color_1=select_color[color_select_cnt++];                      
                    if(color_select_cnt>=12)
                    {
                        color_select_cnt=0;
                    }
                       
                    //填充顔色
                    NT35310_Fill(239/2+1,319-40+1,239-1,319-1,WHITE);
                    LCD_color_2=WHITE;
                    //顯示字元串
                    NT35310_DisplayString(239/2+32,319-30,16,"顔色調整");   
                }
            }   
        }
    }
}
 
u32 gbk32_32_addr=1024*0;
u8 font_buffer[4096];
 
//更新字庫---從SD卡讀取字庫到W25Q64
void FontUpdate_to_W25Q64()
{
    u32 w_cnt=0;
    FILINFO fno;
    FIL fp;
    UINT br,res;
    /*1. 打開字庫*/
    f_open(&fp,"0:/font/gbk16.DZK",FA_READ);
    
    /*2. 循環讀取字庫更新到W25Q64*/
    f_stat("0:/font/gbk16.DZK",&fno);
    printf("檔案的大小:%d\r\n",fno.fsize);
    while(1)
    {
         /*3. 讀取字庫檔案*/
         res=f_read(&fp,font_buffer,4096,&br);
         /*4. 寫入到W25Q64裡*/
         W25Q64_WriteData(gbk32_32_addr,br,font_buffer);
         gbk32_32_addr+=br;
         w_cnt+=br;
         printf("font16:%.f%%\r\n",(w_cnt*1.0/fno.fsize)*100);
        
         /*5. 判斷檔案是否結束*/
         if(res!=FR_OK||br!=4096)break;
    }
    /*6. 關閉字庫檔案*/
    f_close(&fp);
}
       

5.2 sdcard.c  SD卡驅動代碼

#include "sdcard.h"            
static u8  SD_Type=0;  //存放SD卡的類型
 
/*
函數功能:SD卡底層接口,通過SPI時序向SD卡讀寫一個位元組
函數參數:data是要寫入的資料
返 回 值:讀到的資料
說明:時序是第二個上升沿采集資料
*/
u8 SDCardReadWriteOneByte(u8 DataTx)
{        
  u8 DataRx;                 
  u8 i;
  for(i=0;i<8;i++)
    {
        SDCardSCLK(0);  
        if(DataTx&0x80){SDCardOut(1);}
        else {SDCardOut(0);}
        DataTx<<=1; 
        SDCardSCLK(1);//第二個上升沿采集資料
        DataRx<<=1;
        if(SDCardInput)DataRx|=0x01;
    }
    return DataRx;
}
 
/*
函數功能:底層SD卡接口初始化
本程式SPI接口如下:
PC11  片選 SDCardCS
PC12  時鐘 SDCardSCLK
PD2   輸出 SPI_MOSI--主機輸出從機輸入
PC8   輸入 SPI_MISO--主機輸入從機輸出
*/
void SDCardSpiInit(void)
{
    RCC->APB2ENR|=1<<5;         //使能PORTD時鐘
    RCC->APB2ENR|=1<<4;         //使能PORTC時鐘
    
    GPIOD->CRL&=0XFFFFF0FF; 
    GPIOD->CRL|=0X00000300;   //PD2     
    GPIOD->ODR|=1<<2;           //PD2
 
    GPIOC->CRH&=0XFFF00FF0;   
    GPIOC->CRH|=0X00033008;
    
    GPIOC->ODR|=0X3<<11;
    GPIOC->ODR|=1<<8;
    SDCardCS(1);
}
 
 
/*
函數功能:取消選擇,釋放SPI總線
*/
void SDCardCancelCS(void)
{
    SDCardCS(1);
    SDCardReadWriteOneByte(0xff);//提供額外的8個時鐘
}
 
/*
函數 功 能:選擇sd卡,并且等待卡準備OK
函數傳回值:0,成功;1,失敗;
*/
u8 SDCardSelectCS(void)
{
    SDCardCS(0);
    if(SDCardWaitBusy()==0)return 0;//等待成功
    SDCardCancelCS();
    return 1;//等待失敗
}
 
 
/*
函數 功 能:等待卡準備好
函數傳回值:0,準備好了;其他,錯誤代碼
*/
u8 SDCardWaitBusy(void)
{
    u32 t=0;
    do
    {
        if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
        t++;          
    }while(t<0xFFFFFF);//等待 
    return 1;
}
 
 
/*
函數功能:等待SD卡回應
函數參數:
                    Response:要得到的回應值
返 回 值:
                    0,成功得到了該回應值
                    其他,得到回應值失敗
*/
u8 SDCardGetAck(u8 Response)
{
    u16 Count=0xFFFF;//等待次數                           
    while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到準确的回應     
    if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回應失敗   
    else return SDCard_RESPONSE_NO_ERROR;//正确回應
}
 
 
/*
函數功能:從sd卡讀取一個資料包的内容
函數參數:
                buf:資料緩存區
                len:要讀取的資料長度.
傳回值:
            0,成功;其他,失敗; 
*/
u8 SDCardRecvData(u8*buf,u16 len)
{                 
    if(SDCardGetAck(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE
    while(len--)//開始接收資料
    {
        *buf=SDCardReadWriteOneByte(0xFF);
        buf++;
    }
    //下面是2個僞CRC(dummy CRC)
    SDCardReadWriteOneByte(0xFF);
    SDCardReadWriteOneByte(0xFF);                                                           
    return 0;//讀取成功
}
 
 
/*
函數功能:向sd卡寫入一個資料包的内容 512位元組
函數參數:
                    buf 資料緩存區
                    cmd 指令
返 回 值:0表示成功;其他值表示失敗;
*/
u8 SDCardSendData(u8*buf,u8 cmd)
{   
    u16 t;            
    if(SDCardWaitBusy())return 1;  //等待準備失效
    SDCardReadWriteOneByte(cmd);
    if(cmd!=0XFD)//不是結束指令
    {
        for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,減少函數傳參時間
        SDCardReadWriteOneByte(0xFF); //忽略crc
        SDCardReadWriteOneByte(0xFF);
          t=SDCardReadWriteOneByte(0xFF); //接收響應
        if((t&0x1F)!=0x05)return 2;   //響應錯誤                                                            
    }                                                                                   
    return 0;//寫入成功
}
 
 
 
/*
函數功能:向SD卡發送一個指令
函數參數:
                u8 cmd   指令 
                u32 arg  指令參數
                u8 crc   crc校驗值 
傳回值:SD卡傳回的響應
*/                                                
u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc)
{
    u8 r1;  
    u8 Retry=0; 
        
    SDCardCancelCS();               //取消上次片選
    if(SDCardSelectCS())return 0XFF;//片選失效 
    //發送資料
    SDCardReadWriteOneByte(cmd | 0x40);//分别寫入指令
    SDCardReadWriteOneByte(arg >> 24);
    SDCardReadWriteOneByte(arg >> 16);
    SDCardReadWriteOneByte(arg >> 8);
    SDCardReadWriteOneByte(arg);      
    SDCardReadWriteOneByte(crc); 
    if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
    Retry=0X1F;
 
    do
    {
        r1=SDCardReadWriteOneByte(0xFF);
    }while((r1&0X80) && Retry--);     //等待響應,或逾時退出
   return r1;   //傳回狀态值
}   
 
 
/*
函數功能:擷取SD卡的CID資訊,包括制造商資訊
函數參數:u8 *cid_data(存放CID的記憶體,至少16Byte)      
返 回 值:
                    0:成功,1:錯誤               
*/
u8 GetSDCardCISDCardOutnfo(u8 *cid_data)
{
    u8 r1;     
    //發SDCard_CMD10指令,讀CID
    r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
    if(r1==0x00)
      {
            r1=SDCardRecvData(cid_data,16);//接收16個位元組的資料  
    }
    SDCardCancelCS();//取消片選
    if(r1)return 1;
    else return 0;
}   
 
 
/*
函數說明:
                    擷取SD卡的CSD資訊,包括容量和速度資訊
函數參數:
                    u8 *cid_data(存放CID的記憶體,至少16Byte)     
返 回 值:
                    0:成功,1:錯誤   
*/
u8 GetSDCardCSSDCardOutnfo(u8 *csd_data)
{
    u8 r1;   
    r1=SendSDCardCmd(SDCard_CMD9,0,0x01);    //發SDCard_CMD9指令,讀CSD
    if(r1==0)
    {
        r1=SDCardRecvData(csd_data, 16);//接收16個位元組的資料 
    }
    SDCardCancelCS();//取消片選
    if(r1)return 1;
    else return 0;
}  
 
 
/*
函數功能:擷取SD卡的總扇區數(扇區數)   
返 回 值:
                0表示容量檢測出錯,其他值表示SD卡的容量(扇區數/512位元組)
說   明:
                每扇區的位元組數必為512位元組,如果不是512位元組,則初始化不能通過.  
*/
u32 GetSDCardSectorCount(void)
{
    u8 csd[16];
    u32 Capacity;  
    u8 n;
      u16 csize;                        
    if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;   //取CSD資訊,如果期間出錯,傳回0
    if((csd[0]&0xC0)==0x40)         //V2.00的卡,如果為SDHC卡,按照下面方式計算
    {   
            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;
}
 
/*
函數功能: 初始化SD卡
返 回 值: 非0表示初始化失敗!
*/
u8 SDCardDeviceInit(void)
{
  u8 r1;      // 存放SD卡的傳回值
  u16 retry;  // 用來進行逾時計數
  u8 buf[4];  
    u16 i;
    SDCardSpiInit();        //初始化底層IO口
    
    for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //發送最少74個脈沖
    retry=20;
    do
    {
        r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//進入IDLE狀态 閑置
    }while((r1!=0X01) && retry--);
    SD_Type=0;   //預設無卡
    if(r1==0X01)
    {
        if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1)  //SD V2.0
        {
            for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);    //Get trailing return value of R7 resp
            if(buf[2]==0X01&&buf[3]==0XAA)    //卡是否支援2.7~3.6V
            {
                retry=0XFFFE;
                do
                {
                    SendSDCardCmd(SDCard_CMD55,0,0X01);     //發送SDCard_CMD55
                    r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//發送SDCard_CMD41
                }while(r1&&retry--);
                if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鑒别SD2.0卡版本開始
                {
                    for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
                    if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC;    //檢查CCS
                    else SD_Type=SDCard_TYPE_V2;   
                }
            }
        }
        else//SD V1.x/ MMC  V3
        {
            SendSDCardCmd(SDCard_CMD55,0,0X01);     //發送SDCard_CMD55
            r1=SendSDCardCmd(SDCard_CMD41,0,0X01);  //發送SDCard_CMD41
            if(r1<=1)
            {       
                SD_Type=SDCard_TYPE_V1;
                retry=0XFFFE;
                do //等待退出IDLE模式
                {
                    SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
                    r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41
                }while(r1&&retry--);
            }
            else//MMC卡不支援SDCard_CMD55+SDCard_CMD41識别
            {
                SD_Type=SDCard_TYPE_MMC;//MMC V3
                retry=0XFFFE;
                do //等待退出IDLE模式
                {                                               
                    r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//發送SDCard_CMD1
                }while(r1&&retry--);  
            }
            if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//錯誤的卡
        }
    }
    SDCardCancelCS();       //取消片選
    if(SD_Type)return 0;  //初始化成功傳回0
    else if(r1)return r1; //傳回值錯誤值     
    return 0xaa;          //其他錯誤
}
 
 
/*
函數功能:讀SD卡
函數參數:
                buf:資料緩存區
                sector:扇區
                cnt:扇區數
傳回值:
                0,ok;其他,失敗.
說  明:
                SD卡一個扇區大小512位元組
*/
u8 SDCardReadData(u8*buf,u32 sector,u32 cnt)
{
    u8 r1;
    if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//轉換為位元組位址
    if(cnt==1)
    {
        r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//讀指令
        if(r1==0)                                                 //指令發送成功
        {
            r1=SDCardRecvData(buf,512);         //接收512個位元組     
        }
    }else
    {
        r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//連續讀指令
        do
        {
            r1=SDCardRecvData(buf,512);//接收512個位元組    
            buf+=512;  
        }while(--cnt && r1==0);     
        SendSDCardCmd(SDCard_CMD12,0,0X01); //發送停止指令
    }   
    SDCardCancelCS();//取消片選
    return r1;//
}
 
/*
函數功能:向SD卡寫資料
函數參數:
                buf:資料緩存區
                sector:起始扇區
                cnt:扇區數
傳回值:
                0,ok;其他,失敗.
說  明:
                SD卡一個扇區大小512位元組
*/
u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt)
{
    u8 r1;
    if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//轉換為位元組位址
    if(cnt==1)
    {
        r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//讀指令
        if(r1==0)//指令發送成功
        {
            r1=SDCardSendData(buf,0xFE);//寫512個位元組      
        }
    }
    else
    {
        if(SD_Type!=SDCard_TYPE_MMC)
        {
            SendSDCardCmd(SDCard_CMD55,0,0X01); 
            SendSDCardCmd(SDCard_CMD23,cnt,0X01);//發送指令 
        }
        r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//連續讀指令
        if(r1==0)
        {
            do
            {
                r1=SDCardSendData(buf,0xFC);//接收512個位元組   
                buf+=512;  
            }while(--cnt && r1==0);
            r1=SDCardSendData(0,0xFD);//接收512個位元組 
        }
    }   
    SDCardCancelCS();//取消片選
    return r1;//
}   
 
       

繼續閱讀