STM32——SPI接口
STM32——SPI接口
宗旨:技術的學習是有限的,分享的精神是無限的。
一、SPI協(xié)議【SerialPeripheral Interface】
? ? ? ? 串行外圍設備接口,是一種高速全雙工的通信總線。在ADC/LCD等與MCU間通信。
1、SPI信號線
? ? ? ? SPI 包含 4 條總線,SPI 總線包含 4 條總線,分別為SS 、SCK、MOSI、MISO。
(1)SS(SlaveSelect):片選信號線,當有多個 SPI 設備與 MCU 相連時,每個設備的這個片選信號線是與 MCU 單獨的引腳相連的,而其他的 SCK、MOSI、MISO 線則為多個設備并聯(lián)到相同的 SPI 總線上,低電平有效。
(2)SCK (Serial Clock):時鐘信號線,由主通信設備產生,不同的設備支持的時鐘頻率不一樣,如 STM32 的 SPI 時鐘頻率最大為 f PCLK /2。
(3)MOSI (Master Output, Slave Input):主設備輸出 / 從設備輸入引腳。主機的數(shù)據(jù)從這條信號線輸出,從機由這條信號線讀入數(shù)據(jù),即這條線上數(shù)據(jù)的方向為主機到從機。
(4)MISO(Master Input, Slave Output):主設備輸入 / 從設備輸出引腳。主機從這條信號線讀入數(shù)據(jù),從機的數(shù)據(jù)則由這條信號線輸出,即在這條線上數(shù)據(jù)的方向為從機到主機。
2、SPI模式
根據(jù) SPI 時鐘極性(CPOL)和時鐘相位(CPHA) 配置的不同,分為 4 種 SPI 模式。時鐘極性是指 SPI 通信設備處于空閑狀態(tài)時(也可以認為這是 SPI 通信開始時,即SS 為低電平時),SCK 信號線的電平信號。CPOL=0 時, SCK 在空閑狀態(tài)時為低電平,CPOL=1 時則相反。時鐘相位是指數(shù)據(jù)采樣的時刻,當 CPHA=0 時,MOSI 或 MISO 數(shù)據(jù)線上的信號將會在 SCK 時鐘線的奇數(shù)邊沿被采樣。當 CPHA=1 時,數(shù)據(jù)線在 SCK 的偶數(shù)邊沿采樣。
首先,由主機把片選信號線SS 拉低,意為主機輸出,在SS 被拉低的時刻,SCK 分為兩種情況,若我們設置為 CPOL=0,則 SCK 時序在這個時刻為低電平,若設置為 CPOL=1,則 SCK 在這個時刻為高電平。采樣時刻都是在 SCK 的奇數(shù)邊沿(注意奇數(shù)邊沿有時為下降沿,有時為上升沿)。
CPHA=1時,數(shù)據(jù)信號的采樣時刻為偶數(shù)邊沿。
?
二、SPI特性及架構
(1)單次傳輸可選擇為 8 或 16 位。
(2)波特率預分頻系數(shù)(最大為 fPCLK/2) 。
(3)時鐘極性(CPOL)和相位(CPHA)可編程設置 。
(4)數(shù)據(jù)順序的傳輸順序可進行編程選擇,MSB 在前或 LSB 在前。
(5)可觸發(fā)中斷的專用發(fā)送和接收標志。
(6)可以使用 DMA 進行數(shù)據(jù)傳輸操作。
1、SPI架構
MISO 數(shù)據(jù)線接收到的信號經移位寄存器處理后把數(shù)據(jù)轉移到接收緩沖區(qū),然后這個數(shù)據(jù)就可以由我們的軟件從接收緩沖區(qū)讀出了。
當要發(fā)送數(shù)據(jù)時,我們把數(shù)據(jù)寫入發(fā)送緩沖區(qū),硬件將會把它用移位寄存器處理后輸出到 MOSI 數(shù)據(jù)線。
SCK 的時鐘信號則由波特率發(fā)生器產生,我們可以通過波特率控制位(BR)來控制它輸出的波特率。
控制寄存器 CR1 掌管著主控制電路,STM32 的 SPI 模塊的協(xié)議設置(時鐘極性、相位等)就是由它來制定的。而控制寄存器 CR2 則用于設置各種中斷使能。
最后為 NSS 引腳,這個引腳扮演著 SPI 協(xié)議中的SS 片選信號線的角色,如果我們把 NSS 引腳配置為硬件自動控制,SPI 模塊能夠自動判別它能否成為 SPI 的主機,或自動進入 SPI 從機模式。但實際上我們用得更多的是由軟件控制某些 GPIO 引腳單獨作為SS信號,這個 GPIO 引腳可以隨便選擇。
?
三、SPI接口讀取Flash
? ? ? ? 各信號線相應連接到 Flash(型號 :W25X16/W25Q16)的 CS、CLK、DO 和 DIO 線,實現(xiàn)SPI 通信,對 Flash進行讀寫,其中 W25X16 和 W25Q16 在程序上不同的地方是 FLASH 的ID 不一樣。
? ? ? ? 讀取 Flash 的 ID 信息,寫入數(shù)據(jù),并讀取出來進行校驗,通過串口打印寫入與讀取出來的數(shù)據(jù),輸出測試結果。
? ? ? ? 不同的設備都會相應的有不同的指令,如 EEPROM 中會把第一個數(shù)據(jù)解釋為存儲矩陣的地址(實質就是指令)。而 Flash 則定義了更多的指令,有寫指令、讀指令、讀ID 指令等。
SPI-FLASH通信:
(1)配置 I/O端口,使能 GPIO。
(2)根據(jù)將要進行通信器件的 SPI模式,配置 STM32的 SPI,使能 SPI時鐘。
(3)配置好 SPI后,根據(jù)各種 Flash定義的命令控制對它的讀寫。
注意在寫操作前要先進行存儲扇區(qū)的擦除操作,擦除操作前也要先發(fā)出“寫使能”命令
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 寫入的數(shù)據(jù)為:%s \r\t", Tx_Buffer);SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("\r\n 讀出的數(shù)據(jù)為:%s \r\n", Tx_Buffer);/* 檢查寫入的數(shù)據(jù)與讀出的數(shù)據(jù)是否相等 */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 寫入數(shù)據(jù),然后再調用SPI_FLASH_BufferRead()從剛剛寫入的地址中讀出數(shù)據(jù)。最后調用 Buffercmp() 函數(shù)對寫入的數(shù)據(jù)與讀取的數(shù)據(jù)進行比較,若寫入的數(shù)據(jù)與讀出的數(shù)據(jù)相同,則把標志變量TransferStatus1 賦值為 PASSED(自定義的枚舉變量)。
(6)最后調用 SPI_Flash_PowerDown()函數(shù)關閉 Flash 設備的電源,因為數(shù)據(jù)寫入到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),這兩個模式的最大區(qū)別為 SPI 的 SCK 信號線的時序,SCK 的時序是由通信中的主機產生的。若被配置為從機模式,STM32 的 SPI 模塊將接受外來的 SCK 信號。
(2)SPI_DataSize : SPI 每次通信的數(shù)據(jù)大小(稱為數(shù)據(jù)幀)為 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 的奇數(shù)邊沿采集數(shù)據(jù)) 和 SPI_CPHA_2Edge(在 SCK偶數(shù)邊沿采集數(shù)據(jù))。
(4)SPI_NSS :配置NSS引腳的使用模式,硬件模式(SPI_NSS_Hard)與軟件模式(SPI_NSS_Soft),在硬件模式中的 SPI 片選信號由硬件自動產生,而軟件模式則需要我們親自把相應的 GPIO 端口拉高或置低產生非片選和片選信號。如果外界條件允許,硬件模式還會自動將 STM32 的 SPI 設置為主機。我們使用軟件模式,向這個成員賦值為 SPI_NSS_Soft。
(5)SPI_BaudRatePrescaler:本成員設置波特率分頻值,分頻后的時鐘即為 SPI 的 SCK信號線的時鐘頻率。這個成員參數(shù)可設置為 f PCLK 的 2、4、6、8、16、32、64、128、256 分頻。賦值為 SPI_BaudRatePrescaler_4,即 f PCLK 的 4 分頻。
(6)SPI_FirstBit:所有串行的通信協(xié)議都會有 MSB 先行(高位數(shù)據(jù)在前)還是 LSB先行(低位數(shù)據(jù)在前)的問題,而 STM32 的 SPI 模塊可以通過這個結構體成員,對這個特性編程控制。據(jù) Flash 的通信時序,我們向這個成員賦值為MSB先行(SPI_FirstBit_MSB)。
(7)SPI_CRCPolynomial:這是 SPI 的 CRC 校驗中的多項式,若我們使用 CRC 校驗時,就使用這個成員的參數(shù)(多項式)來計算 CRC 的值。由于本實驗的 Flash 不支持 CRC校驗,所以我們向這個結構體成員賦值為 7 實際上是沒有意義的。
配置完這些結構體成員后,我們要調用 SPI_Init() 函數(shù)把這些參數(shù)寫入寄存器中,實現(xiàn)SPI 的初始化,然后調用 SPI_Cmd() 來使能 SPI1。
?
2、讀FLASH的ID
#define Dummy_Byte 0xFFu8 SPI_FLASH_SendByte(u8 byte) {// 等待發(fā)送數(shù)據(jù)寄存器清空while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);SPI_I2S_SendData(SPI1, byte); // 向從機發(fā)送數(shù)據(jù)while (SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET); // 等待接收數(shù)據(jù)寄存器非空return SPI_I2S_ReceiveData(SPI1); // 獲取接收寄存器中的數(shù)據(jù) } 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); // 0x9FTemp0 = 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); // 06HSPI_FLASH_CS_HIGH(); }void SPI_FLASH_WaitForWriteEnd(void) {u8 FLASH_Status = 0;SPI_FLASH_CS_LOW();SPI_FLASH_SendByte(W25X_ReadStatusReg); // 05Hdo{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); // 20HSPI_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寫數(shù)據(jù)——分頁
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite) {SPI_FLASH_WriteEnable();SPI_FLASH_CS_LOW();SPI_FLASH_SendByte(W25X_PageProgram); // 02HSPI_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); // 03HSPI_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接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mysql的高阶用法_MySQL的经典用
- 下一篇: cdr软件百度百科_什么是CDR软件?