標籤:
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介面