SPI接口——基於STM32

SPI接口——基於STM32

一、SPI協議【SerialPeripheral Interface】

        串行外圍設備接口,是一種高速全雙工的通信總線。在ADC/LCD等與MCU間通信。

1、SPI信號線

        SPI 包含 4 條總線,SPI 總線包含 4 條總線,分別爲SS 、SCK、MOSI、MISO。

(1)SS(SlaveSelect):片選信號線,當有多個 SPI 設備與 MCU 相連時,每個設備的這個片選信號線是與 MCU 單獨的引腳相連的,而其他的 SCK、MOSI、MISO 線則爲多個設備並聯到相同的 SPI 總線上,低電平有效。

(2)SCK (Serial Clock):時鐘信號線,由主通信設備產生,不同的設備支持的時鐘頻率不一樣,如 STM32 的 SPI 時鐘頻率最大爲 f PCLK /2。

(3)MOSI (Master Output, Slave Input):主設備輸出 / 從設備輸入引腳。主機的數據從這條信號線輸出,從機由這條信號線讀入數據,即這條線上數據的方向爲主機到從機。

(4)MISO(Master Input, Slave Output):主設備輸入 / 從設備輸出引腳。主機從這條信號線讀入數據,從機的數據則由這條信號線輸出,即在這條線上數據的方向爲從機到主機。

2、SPI模式

根據 SPI 時鐘極性(CPOL)和時鐘相位(CPHA) 配置的不同,分爲 4 種 SPI 模式。時鐘極性是指 SPI 通信設備處於空閒狀態時(也可以認爲這是 SPI 通信開始時,即SS 爲低電平時),SCK 信號線的電平信號。CPOL=0 時, SCK 在空閒狀態時爲低電平,CPOL=1 時則相反。時鐘相位是指數據採樣的時刻,當 CPHA=0 時,MOSI 或 MISO 數據線上的信號將會在 SCK 時鐘線的奇數邊沿被採樣。當 CPHA=1 時,數據線在 SCK 的偶數邊沿採樣。

首先,由主機把片選信號線SS 拉低,意爲主機輸出,在SS 被拉低的時刻,SCK 分爲兩種情況,若我們設置爲 CPOL=0,則 SCK 時序在這個時刻爲低電平,若設置爲 CPOL=1,則 SCK 在這個時刻爲高電平。採樣時刻都是在 SCK 的奇數邊沿(注意奇數邊沿有時爲下降沿,有時爲上升沿)。

CPHA=1時,數據信號的採樣時刻爲偶數邊沿。

二、SPI特性及架構

(1)單次傳輸可選擇爲 8 或 16 位。

(2)波特率預分頻係數(最大爲 fPCLK/2) 。

(3)時鐘極性(CPOL)和相位(CPHA)可編程設置 。

(4)數據順序的傳輸順序可進行編程選擇,MSB 在前或 LSB 在前。

(5)可觸發中斷的專用發送和接收標誌。

(6)可以使用 DMA 進行數據傳輸操作。

1、SPI架構

MISO 數據線接收到的信號經移位寄存器處理後把數據轉移到接收緩衝區,然後這個數據就可以由我們的軟件從接收緩衝區讀出了。

當要發送數據時,我們把數據寫入發送緩衝區,硬件將會把它用移位寄存器處理後輸出到 MOSI 數據線。

SCK 的時鐘信號則由波特率發生器產生,我們可以通過波特率控制位(BR)來控制它輸出的波特率。

控制寄存器 CR1 掌管着主控制電路,STM32 的 SPI 模塊的協議設置(時鐘極性、相位等)就是由它來制定的。而控制寄存器 CR2 則用於設置各種中斷使能。

最後爲 NSS 引腳,這個引腳扮演着 SPI 協議中的SS 片選信號線的角色,如果我們把 NSS 引腳配置爲硬件自動控制,SPI 模塊能夠自動判別它能否成爲 SPI 的主機,或自動進入 SPI 從機模式。但實際上我們用得更多的是由軟件控制某些 GPIO 引腳單獨作爲SS信號,這個 GPIO 引腳可以隨便選擇。

 

三、SPI接口讀取Flash

        各信號線相應連接到 Flash(型號 :W25X16/W25Q16)的 CS、CLK、DO 和 DIO 線,實現SPI 通信,對 Flash進行讀寫,其中 W25X16 和 W25Q16 在程序上不同的地方是 FLASH 的ID 不一樣。

        讀取 Flash 的 ID 信息,寫入數據,並讀取出來進行校驗,通過串口打印寫入與讀取出來的數據,輸出測試結果。

        不同的設備都會相應的有不同的指令,如 EEPROM 中會把第一個數據解釋爲存儲矩陣的地址(實質就是指令)。而 Flash 則定義了更多的指令,有寫指令、讀指令、讀ID 指令等。

SPI-FLASH通信:

(1)配置 I/O端口,使能 GPIO。

(2)根據將要進行通信器件的 SPI模式,配置 STM32的 SPI,使能 SPI時鐘。

(3)配置好 SPI後,根據各種 Flash定義的命令控制對它的讀寫。

注意在寫操作前要先進行存儲扇區的擦除操作,擦除操作前也要先發出“寫使能”命令

1、 main.c

int main(void)
{
  /* 配置串口 1 爲:115200 8-N-1 */
  USART1_Config();
  printf("\r\n 這是一個 2M 串行 flash(W25X16)實驗 \r\n");
 
  /* 2M 串行 flash W25Q16 初始化 */
  SPI_FLASH_Init();
 
  /* Get SPI Flash Device ID */
  DeviceID = SPI_FLASH_ReadDeviceID();
 
  Delay( 200 );
 
  /* Get SPI Flash ID */
  FlashID = SPI_FLASH_ReadID();
 
  printf("\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n",  FlashID, DeviceID);
 
  /* Check the SPI Flash ID */
  if (FlashID == sFLASH_ID)   /* #define sFLASH_ID 0xEF3015 */
  {
    printf("\r\n 檢測到串行 flash W25X16 !\r\n");
 
    SPI_FLASH_SectorErase(FLASH_SectorToErase);
    SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress,  BufferSize);
    printf("\r\n 寫入的數據爲:%s \r\t", Tx_Buffer);
 
    SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
    printf("\r\n 讀出的數據爲:%s \r\n", Tx_Buffer);
    /* 檢查寫入的數據與讀出的數據是否相等 */
    TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);
 
    if ( PASSED == TransferStatus1 )
    {
      printf("\r\n 2M 串行 flash(W25X16)測試成功!\n\r");
    }
    else
    {
      printf("\r\n 2M 串行 flash(W25X16)測試失敗!\n\r");
    }
  }// if (FlashID == sFLASH_ID)
  else
  {
    printf("\r\n 獲取不到 W25X16 ID!\n\r");
 
  }
 
  SPI_Flash_PowerDown();
  while (1);
}

(1)調用 USART1Confi g() 初始化串口。

(2)調用 SPI_FLASH_Init() 初始化 SPI 模塊。

(3)調用 SPI_FLASH_ReadDeviceID() 讀取 Flash 器件生產廠商的 ID 信息。

(4)調用 SPI_FLASH_ReadID() 讀取 Flash 器件的設備 ID 信息

(5)若讀取得的ID正確, 則調用 SPI_FLASH_SectorErase()把 Flash 的內 容擦除,擦除後調用SPI_FLASH_BufferWrite() 向Flash 寫入數據,然後再調用SPI_FLASH_BufferRead()從剛剛寫入的地址中讀出數據。最後調用 Buffercmp() 函數對寫入的數據與讀取的數據進行比較,若寫入的數據與讀出的數據相同,則把標誌變量TransferStatus1 賦值爲 PASSED(自定義的枚舉變量)。

(6)最後調用 SPI_Flash_PowerDown()函數關閉 Flash 設備的電源,因爲數據寫入到Flash 後並不會因斷電而丟失,我們在使用它時才重新開啓 Flash 的電源

 

2、SPI初始化

#define SPI_FLASH_CS_HIGH() GPIO_SetBits(GPIOA, GPIO_Pin_4)
#define SPI_FLASH_CS_LOW() GPIO_ResetBits(GPIOA, GPIO_Pin_4)
 
void SPI_FLASH_Init(void)
{
  SPI_InitTypeDef  SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOD, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
 
  /* SCK */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  /* MISO */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  /* MOS */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  /* CS  */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOA, &GPIO_InitStructure);
 
  SPI_FLASH_CS_HIGH();
 
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_Init(SPI1, &SPI_InitStructure);
 
  SPI_Cmd(SPI1, ENABLE);
}

(1)SPI_Mode :主機模式(SPI_Mode_Master)或從機模式(SPI_Mode_Slave),這兩個模式的最大區別爲 SPI 的 SCK 信號線的時序,SCK 的時序是由通信中的主機產生的。若被配置爲從機模式,STM32 的 SPI 模塊將接受外來的 SCK 信號。
(2)SPI_DataSize : SPI 每次通信的數據大小(稱爲數據幀)爲 8 位還是 16 位。

(3)SPI_CPOL 和 SPI_CPHA :配置SPI的時鐘極性(CPOL)和時鐘相位CPHA),這兩個配置影響到 SPI 的通信模式,該設置要符合將要互相通信的設備的要求。CPOL 分別可以取 SPI_CPOL_High(SPI 通信空閒時 SCK 爲高電平)和SPI_CPOL_Low(SPI 通信空閒時 SCK 爲低電平)。CPHA 則可以取 SPI_CPHA_1Edge(在 SCK 的奇數邊沿採集數據) 和 SPI_CPHA_2Edge(在 SCK偶數邊沿採集數據)。

(4)SPI_NSS :配置NSS引腳的使用模式,硬件模式(SPI_NSS_Hard)與軟件模式(SPI_NSS_Soft),在硬件模式中的 SPI 片選信號由硬件自動產生,而軟件模式則需要我們親自把相應的 GPIO 端口拉高或置低產生非片選和片選信號。如果外界條件允許,硬件模式還會自動將 STM32 的 SPI 設置爲主機。我們使用軟件模式,向這個成員賦值爲 SPI_NSS_Soft。

(5)SPI_BaudRatePrescaler:本成員設置波特率分頻值,分頻後的時鐘即爲 SPI 的 SCK信號線的時鐘頻率。這個成員參數可設置爲 f PCLK 的 2、4、6、8、16、32、64、128、256 分頻。賦值爲 SPI_BaudRatePrescaler_4,即 f PCLK 的 4 分頻。

(6)SPI_FirstBit:所有串行的通信協議都會有 MSB 先行(高位數據在前)還是 LSB先行(低位數據在前)的問題,而 STM32 的 SPI 模塊可以通過這個結構體成員,對這個特性編程控制。據 Flash 的通信時序,我們向這個成員賦值爲MSB先行(SPI_FirstBit_MSB)。

(7)SPI_CRCPolynomial:這是 SPI 的 CRC 校驗中的多項式,若我們使用 CRC 校驗時,就使用這個成員的參數(多項式)來計算 CRC 的值。由於本實驗的 Flash 不支持 CRC校驗,所以我們向這個結構體成員賦值爲 7 實際上是沒有意義的。

配置完這些結構體成員後,我們要調用 SPI_Init() 函數把這些參數寫入寄存器中,實現SPI 的初始化,然後調用 SPI_Cmd() 來使能 SPI1。

3、讀FLASH的ID

#define Dummy_Byte   0xFF
 
u8 SPI_FLASH_SendByte(u8 byte)
{
  // 等待發送數據寄存器清空
  while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);
 
  SPI_I2S_SendData(SPI1, byte); // 向從機發送數據
  while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET); // 等待接收數據寄存器非空
 
  return SPI_I2S_ReceiveData(SPI1); // 獲取接收寄存器中的數據
}
u32 SPI_FLASH_ReadDeviceID(void)
{
  u32 Temp = 0;
 
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_DeviceID);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_SendByte(Dummy_Byte);
  Temp = SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_CS_HIGH();
 
  return Temp;
}

4、讀取廠商ID

u32 SPI_FLASH_ReadID(void)
{
  u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
 
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_JedecDeviceID); // 0x9F
  Temp0 = SPI_FLASH_SendByte(Dummy_Byte);
  Temp1 = SPI_FLASH_SendByte(Dummy_Byte);
  Temp2 = SPI_FLASH_SendByte(Dummy_Byte);
  SPI_FLASH_CS_HIGH();
 
  Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
  return Temp;
}

5、擦除FLASH內容

void SPI_FLASH_WriteEnable(void)
{
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_WriteEnable); // 06H
  SPI_FLASH_CS_HIGH();
}
 
void SPI_FLASH_WaitForWriteEnd(void)
{
  u8 FLASH_Status = 0;
 
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_ReadStatusReg); // 05H
  do
  {
    FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);
  }
  while ((FLASH_Status & WIP_Flag) == SET);
  SPI_FLASH_CS_HIGH();
}
 
void SPI_FLASH_SectorErase(u32 SectorAddr)
{
  SPI_FLASH_WriteEnable();
  SPI_FLASH_WaitForWriteEnd();
 
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_SectorErase); // 20H
  SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(SectorAddr & 0xFF);
  SPI_FLASH_CS_HIGH();
 
  SPI_FLASH_WaitForWriteEnd();
}

6、向Flash寫數據——分頁

void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  SPI_FLASH_WriteEnable();
 
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_PageProgram); // 02H
  SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(WriteAddr & 0xFF);
 
  if(NumByteToWrite > SPI_FLASH_PerWritePageSize)
  {
    NumByteToWrite = SPI_FLASH_PerWritePageSize;
  }
 
  while (NumByteToWrite--)
  {
    SPI_FLASH_SendByte(*pBuffer);
    pBuffer++;
  }
  SPI_FLASH_CS_HIGH();
 
  SPI_FLASH_WaitForWriteEnd();
}
 
 
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
 
  Addr = WriteAddr % SPI_FLASH_PageSize;
  count = SPI_FLASH_PageSize - Addr;
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
 
  if (Addr == 0)
  {
    if (NumOfPage == 0)
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else
    {
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
 
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
  else
  {
    if (NumOfPage == 0)
    {
      if (NumOfSingle > count)
      {
        temp = NumOfSingle - count;
 
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
        WriteAddr +=  count;
        pBuffer += count;
 
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else
    {
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
 
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
      WriteAddr +=  count;
      pBuffer += count;
 
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
 
      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

7、從Flash讀

void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{
  SPI_FLASH_CS_LOW();
  SPI_FLASH_SendByte(W25X_ReadData); // 03H
  SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);
  SPI_FLASH_SendByte((ReadAddr & 0xFF00) >> 8);
  SPI_FLASH_SendByte(ReadAddr & 0xFF);
  while (NumByteToRead--) 
  {
    *pBuffer = SPI_FLASH_SendByte(Dummy_Byte);
    pBuffer++;
  }
  SPI_FLASH_CS_HIGH();
}

轉自STM32——SPI接口

發佈了36 篇原創文章 · 獲贊 183 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章