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):時鐘信號線,由主通信設備產(chǎn)生,不同的設備支持的時鐘頻率不一樣,如 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ù)線接收到的信號經(jīng)移位寄存器處理后把數(shù)據(jù)轉移到接收緩沖區(qū),然后這個數(shù)據(jù)就可以由我們的軟件從接收緩沖區(qū)讀出了。
當要發(fā)送數(shù)據(jù)時,我們把數(shù)據(jù)寫入發(fā)送緩沖區(qū),硬件將會把它用移位寄存器處理后輸出到 MOSI 數(shù)據(jù)線。
SCK 的時鐘信號則由波特率發(fā)生器產(chǎn)生,我們可以通過波特率控制位(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ù)解釋為存儲矩陣的地址(實質(zhì)就是指令)。而 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("rn?這是一個?2M?串行?flash(W25X16)實驗?rn"); ??/*?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("rn?FlashID?is?0x%X,?Manufacturer?Device?ID?is?0x%Xrn",??FlashID,?DeviceID); ??/*?Check?the?SPI?Flash?ID?*/ ??if?(FlashID?==?sFLASH_ID)???/*?#define?sFLASH_ID?0xEF3015?*/ ??{ ????printf("rn?檢測到串行?flash?W25X16?!rn"); ????SPI_FLASH_SectorErase(FLASH_SectorToErase); ????SPI_FLASH_BufferWrite(Tx_Buffer,?FLASH_WriteAddress,??BufferSize); ????printf("rn?寫入的數(shù)據(jù)為:%s?rt",?Tx_Buffer); ????SPI_FLASH_BufferRead(Rx_Buffer,?FLASH_ReadAddress,?BufferSize); ????printf("rn?讀出的數(shù)據(jù)為:%s?rn",?Tx_Buffer); ????/*?檢查寫入的數(shù)據(jù)與讀出的數(shù)據(jù)是否相等?*/ ????TransferStatus1?=?Buffercmp(Tx_Buffer,?Rx_Buffer,?BufferSize); ????if?(?PASSED?==?TransferStatus1?) ????{ ??????printf("rn?2M?串行?flash(W25X16)測試成功!nr"); ????} ????else ????{ ??????printf("rn?2M?串行?flash(W25X16)測試失敗!nr"); ????} ??}//?if?(FlashID?==?sFLASH_ID) ??else ??{ ????printf("rn?獲取不到?W25X16?ID!nr"); ??} ??SPI_Flash_PowerDown(); ??while?(1); }
(1)調(diào)用 USART1Confi g() 初始化串口。
(2)調(diào)用 SPI_FLASH_Init() 初始化 SPI 模塊。
(3)調(diào)用 SPI_FLASH_ReadDeviceID() 讀取 Flash 器件生產(chǎn)廠商的 ID 信息。
(4)調(diào)用 SPI_FLASH_ReadID() 讀取 Flash 器件的設備 ID 信息
(5)若讀取得的ID正確, 則調(diào)用 SPI_FLASH_SectorErase()把 Flash 的內(nèi) 容擦除,擦除后調(diào)用SPI_FLASH_BufferWrite() 向Flash 寫入數(shù)據(jù),然后再調(diào)用SPI_FLASH_BufferRead()從剛剛寫入的地址中讀出數(shù)據(jù)。最后調(diào)用 Buffercmp() 函數(shù)對寫入的數(shù)據(jù)與讀取的數(shù)據(jù)進行比較,若寫入的數(shù)據(jù)與讀出的數(shù)據(jù)相同,則把標志變量TransferStatus1 賦值為 PASSED(自定義的枚舉變量)。
(6)最后調(diào)用 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 的時序是由通信中的主機產(chǎn)生的。若被配置為從機模式,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 片選信號由硬件自動產(chǎn)生,而軟件模式則需要我們親自把相應的 GPIO 端口拉高或置低產(chǎn)生非片選和片選信號。如果外界條件允許,硬件模式還會自動將 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 實際上是沒有意義的。
配置完這些結構體成員后,我們要調(diào)用 SPI_Init() 函數(shù)把這些參數(shù)寫入寄存器中,實現(xiàn)SPI 的初始化,然后調(diào)用 SPI_Cmd() 來使能 SPI1。
?
2、讀FLASH的ID
#define?Dummy_Byte???0xFF u8?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; }
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內(nèi)容
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寫數(shù)據(jù)——分頁
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(); }