STM32——SPI介面

來源:互聯網
上載者:User

標籤:

STM32——SPI介面


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


一、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 器件的裝置識別碼 資訊

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

 

2、讀FLASH的ID

#define Dummy_Byte   0xFFu8 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();}

STM32——SPI介面

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.