STM32學習筆記(八) SPI總線(操作外部flash)


1. SPI總線簡介

  SPI全稱串行外設接口,是一種高速,全雙工,同步的外設總線;它工作在主從方式,常規需要至少4根線才能夠正常工作。SPI作為基本的外設接口,在FLASH,EPPROM和一些數字通訊中,具有廣泛的應用。SPI總線由四個接口構成:

CS  片選端,由主設備控制

MISO主設備輸入,從設備輸出

MOSI主設備輸出,從設備輸入

SCK 時鍾信號

  其中SCK僅能由主設備提供,且接收和發送和同時產生的,因此在主設備接收數據時也要先發送數據從而為從設備提供時鍾;根據SPI時鍾信號配置相關說明,SPI的時鍾相位和極性由CPOL和CPHA兩位控制共有四種不同的工作時序。

   

其中CPOL:0 空閑狀態低電平  1 空閑時候高電平

     CPHA: 0 第一個邊沿采樣  1 第二個邊沿采樣

2. 工作原理圖

  了解了SPI總線,下面就開始進入正題,通過SPI總線操作外部flash(W25X16)。首先確定開發板原理圖對應的端口連接:

    

從上可以得出 CS:PB9     SCK:PA5

       MISO:PA6   MOSI:PA7

不過因為開發板的資源有限,SD卡和外部flash共用SPI總線,因此在讀取SPI FLASH之前要關閉SD卡的片選端,避免出現總線沖突。

了解了這些,就可以開始SPI_FLASH驅動硬件部分的編寫了。

3. SPI硬件驅動

SPI端口配置比較簡單,主要包含端口時鍾啟動,端口功能配置,初始化即可

 

GPIO_InitTypeDef  GPIO_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB                                             |RCC_APB2Periph_GPIOD | RCC_APB2Periph_AFIO, ENABLE);
    
/*SD_CS Disable PD11*/
GPIO_InitStructure.GPIO_Pin  = SD_CS_Pin;
GPIO_InitStructure.GPIO_Speed= GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(SD_CS_Port, &GPIO_InitStructure);
GPIO_SetBits(SD_CS_Port, SD_CS_Pin);
    
/*SPI1_CS 端口配置*/
GPIO_InitStructure.GPIO_Pin  = SPI1_CS_Pin;
GPIO_Init(SPI1_CS_Port, &GPIO_InitStructure);
SPI1_CS_Disable();
    
/*SPI1_SCK 端口配置*/
GPIO_InitStructure.GPIO_Pin  = SPI1_SCK_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(SPI1_SCK_Port, &GPIO_InitStructure);
    
/*SPI1_MISO 端口配置*/
GPIO_InitStructure.GPIO_Pin  = SPI1_MISO_Pin;
GPIO_Init(SPI1_MISO_Port, &GPIO_InitStructure);
    
/*SPI1_MOSI 端口配置*/
GPIO_InitStructure.GPIO_Pin  = SPI1_MOSI_Pin;
GPIO_Init(SPI1_MOSI_Port, &GPIO_InitStructure);

 

SPI功能配置主要包含上面我提到的主從設備,時鍾相位和極性,發送數據長度和順序(STM32本身集成功能,與SPI本身關系不大)等,具體配置如下:

SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1 | RCC_APB2Periph_AFIO, ENABLE); SPI_InitStructure.SPI_Direction =    SPI_Direction_2Lines_FullDuplex;    //SPI工作在雙向雙線模式
SPI_InitStructure.SPI_Mode      = SPI_Mode_Master;                       //CPU的SPI工作在主機模式
SPI_InitStructure.SPI_DataSize  = SPI_DataSize_8b;                       //SPI傳輸數據幀長度為8字節
SPI_InitStructure.SPI_CPOL      = SPI_CPOL_High;                         //空閑時SCK高電平 MODE3模式
SPI_InitStructure.SPI_CPHA      = SPI_CPHA_2Edge;                       //第二個下降沿接收/發送數據 
SPI_InitStructure.SPI_NSS       = SPI_NSS_Soft;                          //啟用軟件從設備管理
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;       //波特率為Pclk2/4
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;                       //高位先發送
SPI_InitStructure.SPI_CRCPolynomial = 7;                                 //默認CRC校驗多項式 x^2+x+1 SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);                              

   STM32因為SPI總線已經集成在CPU內部,因此配置起來十分簡單,僅修改部分寄存器就可以實現對於SPI總線的配置,用於操作外部設備,不過涉及到外部設備的通訊並沒有這么簡單,這涉及讀取和操作芯片的時序和指令,下面我以開發板上的W25X16外部flash為例,講解SPI總線的實際運用。

4. SPI總線操作W25X16

    外部flash的操作比較簡單,總結起來僅讀寄存器,寫寄存器,讀數據,寫數據,擦除數據,讀ID這6種工作模式,如W25X16指令表如下:

   

參照該表,程序中就可以有如下flash操作指令定義

/*外部flash相關指令*/
#define Flash_WriteEnable            0x06 
#define Flash_WriteDisable            0x04 
#define Flash_ReadStatusReg            0x05 
#define Flash_WriteStatusReg        0x01 
#define Flash_ReadData                0x03 
#define Flash_FastReadData            0x0B 
#define Flash_FastReadDual            0x3B 
#define Flash_PageProgram            0x02 
#define Flash_BlockErase            0xD8 
#define Flash_SectorErase            0x20 
#define Flash_ChipErase                0xC7 
#define Flash_PowerDown            0xB9 
#define Flash_ReleasePowerDown            0xAB 
#define Flash_ManufactDeviceID            0x90 
#define Flash_JedecDeviceID            0x9F 
#define Flash_NoBusy                0xA5            
flash Instruction

可以看出外部flash主要包含擦除,讀寄存器,寫入寄存器,讀數據,寫數據,讀ID這幾種方式。

(1). flash單字節收發

  根據上面SPI總線的說明,SPI的寫入和讀出是同時發生的,且時鍾只能由主設備提供,因此SPI總線的收發由同一個函數完成。如下:

 /*等待SPI發送數據寄存器為空時,發送1字節數據*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) { } SPI_I2S_SendData(SPI1, byte); /*在發送數據同時,SPI_MISO引腳會讀取管腳數據,等待讀取寄存器非空*/
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) { } return SPI_I2S_ReceiveData(SPI1);

 當然,實際項目中在while循環內部需要添加超時時鍾,避免因為可能出現的SPI硬件出錯而導致整個系統停止的問題。

(2). flash擦除

  外部flash的擦除主要包含sector(扇區)擦除,block(塊)擦除,chip(整片)擦除。其中扇區擦除4kb, 塊擦除64kb, flash的擦除按照芯片資料上要求,擦除需要3步:

      1.寫入允許(0x06)

      2.擦除指令(0x20)

      3. 寫入擦除地址(24bit), 分三次發送

      

由時序可知,擦除片代碼如下:

void SPI_EraseSector(u32 SectorAddress) { SectorAddress = (SectorAddress>>12)<<12; //確定擦除塊的首地址
    SPI_Write_Enable();                      //允許寫入 SPI1_CS_Enable(); SPI_SendWrite_Byte(Flash_SectorErase); //寫入擦除指令
    
    /*寫入帶擦除的扇區*/ SPI_SendWrite_Byte((SectorAddress&0xFF0000)>>16); SPI_SendWrite_Byte((SectorAddress&0xFF00)>>8); SPI_SendWrite_Byte(SectorAddress&0xFF); SPI1_CS_Disable(); SPI_WaitWriteEnd(); }

 

(3). 數據讀取

 數據讀取包含3步,1.寫入讀取指令 2.寫入待讀取數據地址 3.讀取flash內部數據。如此便完成外部flash的讀取。

void SPI_Read(u8 *pBuffer,u32 ReadAddress,u16 ReadByteNum) { SPI1_CS_Enable(); SPI_SendWrite_Byte(Flash_ReadData); //寫入讀取指令
    SPI_SendWrite_Byte(ReadAddress >> 16); //寫入待讀取數據地址
    SPI_SendWrite_Byte(ReadAddress >> 8); SPI_SendWrite_Byte(ReadAddress); /*讀取數據*/
    while(ReadByteNum--) { *pBuffer = SPI_SendWrite_Byte(Flash_NoBusy); pBuffer++; } SPI1_CS_Disable(); }

 

(4)數據寫入

 數據寫入包含4步,1.寫入允許  2.寫入數據寫入指令 3.寫入數據存儲地址 4.寫入數據。如此便完成外部flash的寫入。

void SPI_PageWrite(u8 *pBuffer, u32 PageAddress,u16 WriteByteNum) { SPI_Write_Enable(); //寫入允許 SPI1_CS_Enable(); SPI_SendWrite_Byte(Flash_PageProgram); //寫入存儲指令
    SPI_SendWrite_Byte(PageAddress >> 16);    //寫入存儲地址
    SPI_SendWrite_Byte(PageAddress >> 8); SPI_SendWrite_Byte(PageAddress); if(WriteByteNum > 256) { WriteByteNum = 256; printf("\n\r Err: SPI_PageWrite too large!"); } /*寫入數據*/
    while(WriteByteNum--) { SPI_SendWrite_Byte(*pBuffer); pBuffer++; } SPI1_CS_Disable(); SPI_WaitWriteEnd(); }

 

(5)ID讀取

 ID讀取比較簡單,主要用來測試硬件是否成功,具體代碼如下

u32 SPI_ReadID(void) { u32 ID_Temp,temp_h,temp_m,temp_l; SPI1_CS_Enable(); SPI_SendWrite_Byte(Flash_JedecDeviceID); temp_h = SPI_SendWrite_Byte(Flash_NoBusy); temp_m = SPI_SendWrite_Byte(Flash_NoBusy); temp_l = SPI_SendWrite_Byte(Flash_NoBusy); SPI1_CS_Disable(); ID_Temp = (temp_h << 16)|(temp_m << 8)|temp_l; return ID_Temp; } 

上面便是SPI總線基本操作了,具體可參考代碼:

http://files.cnblogs.com/files/zc110747/6.SPI-Flash.7z

 根據代碼中設計以及通過串口輸出如下圖,可以判斷成功實現了SPI-flash的讀,寫和擦除的工作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM