1. 程式人生 > >STM32——SPI介面

STM32——SPI介面

STM32——SPI介面

宗旨:技術的學習是有限的,分享的精神是無限的。

一、SPI協議【SerialPeripheral Interface

        序列外圍裝置介面,是一種高速全雙工的通訊匯流排。在ADC/LCD等與MCU間通訊。

1SPI訊號線

        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):主裝置輸入 / 從裝置輸出引腳。主機從這條訊號線讀入資料,從機的資料則由這條訊號線輸出,即在這條線上資料的方向為從機到主機。

2SPI模式

根據 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 進行資料傳輸操作。

1SPI架構

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 的電源

2SPI初始化

#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。

2、讀FLASHID

#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;
}<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>

3、讀取廠商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;
}

4、擦除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();
}

5、向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);
      }
    }
  }
}

6、從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();
}