STM32配置SPI通訊功能
一、SPI協(xié)議
串行外圍設(shè)備接口,是一種高速全雙工的通信總線。在ADC/LCD等與MCU間通信。1、SPI信號(hào)線
SPI 包含 4 條總線,SPI 總線包含 4 條總線,分別為SS 、SCK、MOSI、MISO。 (1)SS(SlaveSelect):片選信號(hào)線,當(dāng)有多個(gè) SPI 設(shè)備與 MCU 相連時(shí),每個(gè)設(shè)備的這個(gè)片選信號(hào)線是與 MCU 單獨(dú)的引腳相連的,而其他的 SCK、MOSI、MISO 線則為多個(gè)設(shè)備并聯(lián)到相同的 SPI 總線上,低電平有效。 (2)SCK (Serial Clock):時(shí)鐘信號(hào)線,由主通信設(shè)備產(chǎn)生,不同的設(shè)備支持的時(shí)鐘頻率不一樣,如 STM32 的 SPI 時(shí)鐘頻率最大為 f PCLK /2。 (3)MOSI (Master Output, Slave Input):主設(shè)備輸出 / 從設(shè)備輸入引腳。主機(jī)的數(shù)據(jù)從這條信號(hào)線輸出,從機(jī)由這條信號(hào)線讀入數(shù)據(jù),即這條線上數(shù)據(jù)的方向?yàn)橹鳈C(jī)到從機(jī)。 (4)MISO(Master Input, Slave Output):主設(shè)備輸入 / 從設(shè)備輸出引腳。主機(jī)從這條信號(hào)線讀入數(shù)據(jù),從機(jī)的數(shù)據(jù)則由這條信號(hào)線輸出,即在這條線上數(shù)據(jù)的方向?yàn)閺臋C(jī)到主機(jī)。2、SPI模式
根據(jù) SPI 時(shí)鐘極性(CPOL)和時(shí)鐘相位(CPHA) 配置的不同,分為 4 種 SPI 模式。時(shí)鐘極性是指 SPI 通信設(shè)備處于空閑狀態(tài)時(shí)(也可以認(rèn)為這是 SPI 通信開始時(shí),即SS 為低電平時(shí)),SCK 信號(hào)線的電平信號(hào)。CPOL=0 時(shí), SCK 在空閑狀態(tài)時(shí)為低電平,CPOL=1 時(shí)則相反。時(shí)鐘相位是指數(shù)據(jù)采樣的時(shí)刻,當(dāng) CPHA=0 時(shí),MOSI 或 MISO 數(shù)據(jù)線上的信號(hào)將會(huì)在 SCK 時(shí)鐘線的奇數(shù)邊沿被采樣。當(dāng) CPHA=1 時(shí),數(shù)據(jù)線在 SCK 的偶數(shù)邊沿采樣。 首先,由主機(jī)把片選信號(hào)線SS 拉低,意為主機(jī)輸出,在SS 被拉低的時(shí)刻,SCK 分為兩種情況,若我們?cè)O(shè)置為 CPOL=0,則 SCK 時(shí)序在這個(gè)時(shí)刻為低電平,若設(shè)置為 CPOL=1,則 SCK 在這個(gè)時(shí)刻為高電平。采樣時(shí)刻都是在 SCK 的奇數(shù)邊沿(注意奇數(shù)邊沿有時(shí)為下降沿,有時(shí)為上升沿)。 CPHA=1時(shí),數(shù)據(jù)信號(hào)的采樣時(shí)刻為偶數(shù)邊沿。二、SPI特性及架構(gòu)
(1)單次傳輸可選擇為 8 或 16 位。 (2)波特率預(yù)分頻系數(shù)(最大為 fPCLK/2) 。 (3)時(shí)鐘極性(CPOL)和相位(CPHA)可編程設(shè)置 。 (4)數(shù)據(jù)順序的傳輸順序可進(jìn)行編程選擇,MSB 在前或 LSB 在前。 (5)可觸發(fā)中斷的專用發(fā)送和接收標(biāo)志。 (6)可以使用 DMA 進(jìn)行數(shù)據(jù)傳輸操作。 MISO 數(shù)據(jù)線接收到的信號(hào)經(jīng)移位寄存器處理后把數(shù)據(jù)轉(zhuǎn)移到接收緩沖區(qū),然后這個(gè)數(shù)據(jù)就可以由我們的軟件從接收緩沖區(qū)讀出了。 當(dāng)要發(fā)送數(shù)據(jù)時(shí),我們把數(shù)據(jù)寫入發(fā)送緩沖區(qū),硬件將會(huì)把它用移位寄存器處理后輸出到 MOSI 數(shù)據(jù)線。 SCK 的時(shí)鐘信號(hào)則由波特率發(fā)生器產(chǎn)生,我們可以通過波特率控制位(BR)來控制它輸出的波特率。 控制寄存器 CR1 掌管著主控制電路,STM32 的 SPI 模塊的協(xié)議設(shè)置(時(shí)鐘極性、相位等)就是由它來制定的。 而控制寄存器 CR2 則用于設(shè)置各種中斷使能。 最后為 NSS 引腳,這個(gè)引腳扮演著 SPI 協(xié)議中的SS 片選信號(hào)線的角色,如果我們把 NSS 引腳配置為硬件自動(dòng)控制,SPI 模塊能夠自動(dòng)判別它能否成為 SPI 的主機(jī),或自動(dòng)進(jìn)入 SPI 從機(jī)模式。 但實(shí)際上我們用得更多的是由軟件控制某些 GPIO 引腳單獨(dú)作為SS信號(hào),這個(gè) GPIO 引腳可以隨便選擇。三、SPI接口讀取Flash
各信號(hào)線相應(yīng)連接到 Flash(型號(hào) :W25X16/W25Q16)的 CS、CLK、DO 和 DIO 線,實(shí)現(xiàn)SPI 通信,對(duì) Flash進(jìn)行讀寫,其中 W25X16 和 W25Q16 在程序上不同的地方是 FLASH 的ID 不一樣。 讀取 Flash 的 ID 信息,寫入數(shù)據(jù),并讀取出來進(jìn)行校驗(yàn),通過串口打印寫入與讀取出來的數(shù)據(jù),輸出測(cè)試結(jié)果。不同的設(shè)備都會(huì)相應(yīng)的有不同的指令,如 EEPROM 中會(huì)把第一個(gè)數(shù)據(jù)解釋為存儲(chǔ)矩陣的地址(實(shí)質(zhì)就是指令)。而 Flash 則定義了更多的指令,有寫指令、讀指令、讀ID 指令等。
SPI-FLASH通信: (1)配置 I/O端口,使能 GPIO。 (2)根據(jù)將要進(jìn)行通信器件的 SPI模式,配置 STM32的 SPI,使能 SPI時(shí)鐘。 (3)配置好 SPI后,根據(jù)各種 Flash定義的命令控制對(duì)它的讀寫。 注意,在操作Flash前要進(jìn)行解鎖操作。
int main(void) { /* 配置串口 1 為:115200 8-N-1 */ USART1_Config(); printf("\r\n 這是一個(gè) 2M 串行 flash(W25X16)實(shí)驗(yàn) \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 檢測(cè)到串行 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)測(cè)試成功!\n\r"); } else { printf("\r\n 2M 串行 flash(W25X16)測(cè)試失敗!\n\r"); } }// if (FlashID == sFLASH_ID) else { printf("\r\n 獲取不到 W25X16 ID!\n\r"); } 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 器件的設(shè)備 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ù)對(duì)寫入的數(shù)據(jù)與讀取的數(shù)據(jù)進(jìn)行比較,若寫入的數(shù)據(jù)與讀出的數(shù)據(jù)相同,則把標(biāo)志變量TransferStatus1 賦值為 PASSED(自定義的枚舉變量)。 (6)最后調(diào)用 SPI_Flash_PowerDown()函數(shù)關(guān)閉 Flash 設(shè)備的電源,因?yàn)閿?shù)據(jù)寫入到Flash 后并不會(huì)因斷電而丟失,我們?cè)谑褂盟鼤r(shí)才重新開啟 Flash 的電源。
#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 :主機(jī)模式(SPI_Mode_Master)或從機(jī)模式(SPI_Mode_Slave),這兩個(gè)模式的最大區(qū)別為 SPI 的 SCK 信號(hào)線的時(shí)序,SCK 的時(shí)序是由通信中的主機(jī)產(chǎn)生的。若被配置為從機(jī)模式,STM32 的 SPI 模塊將接受外來的 SCK 信號(hào)。
(2)SPI_DataSize :SPI 每次通信的數(shù)據(jù)大小(稱為數(shù)據(jù)幀)為 8 位還是 16 位。 (3)SPI_CPOL 和 SPI_CPHA :配置SPI的時(shí)鐘極性(CPOL)和時(shí)鐘相位CPHA),這兩個(gè)配置影響到 SPI 的通信模式,該設(shè)置要符合將要互相通信的設(shè)備的要求。CPOL 分別可以取 SPI_CPOL_High(SPI 通信空閑時(shí) SCK 為高電平)和SPI_CPOL_Low(SPI 通信空閑時(shí) 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 片選信號(hào)由硬件自動(dòng)產(chǎn)生,而軟件模式則需要我們親自把相應(yīng)的 GPIO 端口拉高或置低產(chǎn)生非片選和片選信號(hào)。如果外界條件允許,硬件模式還會(huì)自動(dòng)將 STM32 的 SPI 設(shè)置為主機(jī)。我們使用軟件模式,向這個(gè)成員賦值為 SPI_NSS_Soft。 (5)SPI_BaudRatePrescaler:本成員設(shè)置波特率分頻值,分頻后的時(shí)鐘即為 SPI 的 SCK信號(hào)線的時(shí)鐘頻率。這個(gè)成員參數(shù)可設(shè)置為 f PCLK 的 2、4、6、8、16、32、64、128、256 分頻。賦值為 SPI_BaudRatePrescaler_4,即 f PCLK 的 4 分頻。 (6)SPI_FirstBit:所有串行的通信協(xié)議都會(huì)有 MSB 先行(高位數(shù)據(jù)在前)還是 LSB先行(低位數(shù)據(jù)在前)的問題,而 STM32 的 SPI 模塊可以通過這個(gè)結(jié)構(gòu)體成員,對(duì)這個(gè)特性編程控制。據(jù) Flash 的通信時(shí)序,我們向這個(gè)成員賦值為MSB先行(SPI_FirstBit_MSB)。 (7)SPI_CRCPolynomial:這是 SPI 的 CRC 校驗(yàn)中的多項(xiàng)式,若我們使用 CRC 校驗(yàn)時(shí),就使用這個(gè)成員的參數(shù)(多項(xiàng)式)來計(jì)算 CRC 的值。由于本實(shí)驗(yàn)的 Flash 不支持 CRC校驗(yàn),所以我們向這個(gè)結(jié)構(gòu)體成員賦值為 7 實(shí)際上是沒有意義的。 配置完這些結(jié)構(gòu)體成員后,我們要調(diào)用 SPI_Init() 函數(shù)把這些參數(shù)寫入寄存器中,實(shí)現(xiàn)SPI 的初始化,然后調(diào)用 SPI_Cmd() 來使能 SPI1。
#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); // 向從機(jī)發(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; }讀廠商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; }擦除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(); }向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); } } } }
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(); }
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!