基於STM32F1與NRF24L01模組的SPI簡單通訊
一、前言
1.簡介:
本文是基於STM32F1,將資料傳送至NRF模組的暫存器,並將資料重新讀取,通過串列埠傳送出來的簡單SPI單通訊。
2.SPI簡介:
調過STM8的都已經對SPI有所瞭解,調法都一致,這裡就不做詳細的講解。
3.準備工作:
軟體層:
Keil5 連結: 點選下載 提取碼:wrt9
STMCubeMX5.1.0版本 連結: 點選下載 提取碼:20xs
硬體層:
1. STM32F1ZE開發板 (什麼型號的都可以,只要能在CUbe配置好型號就行)
2.NRF24L01模組
二、SPI詳解
SPI是序列外設介面(Serial Peripheral Interface)的縮寫。是 Motorola 公司推出的一 種同步序列介面技術,是一種高速的,全雙工,同步的通訊匯流排。因為其沒有指定的流控制,沒有應答機制確認是否接收到資料,所以跟IIC匯流排協議比較在資料可靠性上有一定的缺陷。
1.硬體接線:
VCC、GND不用講。下面的四條線是完成通訊的最基本線,作用已簡略給出,需要進一步瞭解的可以詳看資料手冊。大家可以按照相應線用示波器觀察對應波形,看時序是否正確。
CSN:從裝置使能訊號,由主裝置控制。
SCK:時鐘訊號,由主裝置產生。
MISO:主裝置資料輸入,從裝置資料輸出。(主要用於讀取資料)
MOSI:主裝置資料輸出,從裝置資料輸入。(主要用於寫入資料)
(在這裡提示用示波器看的同學,記得看讀取到資料的波形時,切換到MISO線,在MOSI線上是觀測不到讀取回來的資料 的)
2.Cube配置問題:
一開始除錯時用的TIM7來配置Cube,但是一直無法正確讀到波形,通過看資料手冊和部分歷程,我將時鐘配置成TIM2,就能正常收發了。其他配置跟平常串列埠配置一樣就行了,沒有特別需要注意的。
3.暫存器地址和驅動讀寫問題:
不同於IIC讀取時鐘模組,這裡只需要暫存器的地址,同時要查詢到讀指令和寫指令來驅動裝置讀與寫的功能。
#define CONFIG 0x00 //配置暫存器地址; //暫存器讀寫驅動命令: #define READ_REG_NRF 0x00 //讀配置暫存器,低5位為暫存器地址 #define WRITE_REG_NRF 0x20 //寫配置暫存器,低5位為暫存器地址
4.本文部分程式碼:
最後貼出一些主要的程式碼,這些都是最基礎的程式碼,看著資料手冊寫就行了。但是這裡我在讀取與寫入暫存器函式中並沒有使用延時,但是也能成功輸出。
SPI_test.c
void delay2ms() //開機延時2MS,實測 { unsigned char a,b,c; for(c=5;c>0;c--) for(b=68;b>0;b--) for(a=31;a>0;a--); } //這個函式是將讀取/寫入一個位元組合二為一的基礎函式程式碼。 uint32_t uSPI_RW_Byte(uint32_t uByte) { uint32_t uBits; for(uBits=0; uBits<8; uBits++) //8次迴圈 { if(uByte & 0x80) PIN_MOSI_H; //該位為1則置1 else PIN_MOSI_L; //該位為0則置0 uByte <<= 1; //左移一位,可讀取1位,並輸出下一位 PIN_SCK_H; //拉高時序線,開始傳送資料 if(MISO) //判斷MISO電平 uByte|=0x01; //若為1則賦值到相應的位上 PIN_SCK_L; //結束該Byte資料的傳送 } return uByte; }
如果看不懂這個函式,想分開的函式,可以用下面這兩個 /* ** 函式名 : SPI_Read_OneByte ** 返回值 : temp--SPI讀取的一位元組資料 ** 參 數 : None ** 描 述 : 下降沿讀資料,每次讀取 1 bit */ uint32_t SPI_Read_OneByte(void) { uint32_t i; uint32_t temp = 0; for(i=0;i<8;i++) { temp <<= 1; //讀取MISO 8次輸入的值,存入temp。 //讀取最後1byte的最後一位(即LSB)之後,不能再左移了 PIN_SCK_H; if(MISO) //讀取最高位,儲存至最末尾,通過左移位完成讀整個位元組 temp |= 0x01; else temp &= ~0x01; PIN_SCK_L; //下降沿來了(SCK從1-->0),MISO上的資料將發生改變,穩定後讀取存入temp } return temp; } /* ** 函式名 : SPI_Write_OneByte ** 返回值 : None ** 參 數 : u8_writedata--SPI寫入的一位元組資料 ** 描 述 : 上升沿寫資料,每次寫入 1 bit */ void SPI_Write_OneByte(uint32_t u8_writedata) { uint32_t i; for(i=0;i<8;i++) { if(u8_writedata & 0x80) //判斷最高位,總是傳送最高位 PIN_MOSI_H; //MOSI輸出1,資料匯流排準備資料1 else PIN_MOSI_L; //MOSI輸出0,資料匯流排準備資料0 PIN_SCK_H; //上升沿來了(SCK從0-->1),資料匯流排上的資料寫入到器件 u8_writedata <<= 1; //左移拋棄已經輸出的最高位 PIN_SCK_L; //拉低SCK訊號,初始化為0 } } /* ** 函式名: nRF24L01_WriteReg ** 返回值: None ** 參 數 : (1)uint8 addr--暫存器地址 ** (2)uint8 value--寫入值 ** 說 明 : nRF24L01暫存器寫函式 */ void nRF24L01_WriteReg(uint32_t addr, uint32_t value) { PIN_CSN_L; //CS片選拉低 uSPI_RW_Byte(WRITE_REG_NRF+addr); uSPI_RW_Byte(value); PIN_CSN_H; //CS片選拉高 } /* ** 函式名: nRF24L01_ReadReg ** 返回值: value--讀取暫存器值 ** 參 數 : addr--暫存器地址 ** 說 明 : nRF24L01暫存器讀函式 */ uint32_t nRF24L01_ReadReg(uint8_t addr) { uint8_t value; PIN_CSN_L; //CS片選拉低 uSPI_RW_Byte(READ_REG_NRF+addr); //SPI讀資料 value=uSPI_RW_Byte(0); PIN_CSN_H; //CS片選拉高 return value; }
以下貼出的是SPI_test.h的部分程式碼:是對上面的一些定義函式的補充
SPI_test.h #define PIN_CSN_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET) #define PIN_CSN_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET) #define PIN_MISO_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET) #define PIN_MISO_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET) #define PIN_SCK_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET) #define PIN_SCK_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET) #define PIN_MOSI_H HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET) #define PIN_MOSI_L HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET) #define MISO HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) //判斷0或1
最後貼出主函式的呼叫函式,這裡是用呼叫HAL庫函式的串列埠直接傳送的方法:如圖
5.輸出結果驗證:
驗證是否讀取到數值,可以直接在示波器看波形,也可以採用直接在串列埠偵錯程式看。但是個人建議先觀察示波器波形,比較容易找到問題所在。
三、總結:
相對於STM8,在STM32上來除錯時,最主要是要注意時鐘配置問題,其他都比較方便,也沒有特別需要注意的問題。有不對的地方可以留言指出或提