STM32F767 SPI通信實驗

SPI通信實驗

SPI簡介

串行外設接口(Serial Peripheral Interface Bus,SPI),是一種用于短程通信的同步串行通信接口規范,主要應用于單片機系統中。類似I2C。 這種接口首先被Motorola(摩托羅拉)公司開發,然后發展成了一種行業規范。典型應用包含SD卡和液晶顯示器。 SPI設備之間使用全雙工模式通信,包含一個主機和一個或多個從機。主機產生待讀或待寫的幀數據,多個從機通過一個片選線路 決定哪個來響應主機的請求。 有時SPI接口被稱作四線程接口,SPI準確來講稱為同步串行接口,但是與同步串行接口協議(SSI)不同,SSI是一個四線程 同步通信協議,但是使用差分信號輸入同時僅提供一個單工通信信道。

SPI總線規定了4個保留邏輯信號接口:

  • SCLK(Serial Clock):串行時鐘,由主機發出
  • MOSI(Master Output,Slave Input):主機輸出從機輸入信號,由主機發出
  • MISO(Master Input,Slave Output):主機輸入從機輸出信號,由從機發出
  • SS(Slave Selected):選擇信號,由主機發出,低電位有效

SPI總線:單一master對單一slave

單對單

SPI總線:單一master對復合slave

一對多

雖然上述名稱是現在廣泛在使用的,但是在有些情況下,比如舊的IC,可能與插圖上描述的引腳口名稱不同:

  • SCLK:SCK

  • MOSI:SIMO, SDO, DI, DIN, SI, MTSR

  • MISO:SOMI, SDI, DO, DOUT, SO, MRST

  • SDIO:SIO

  • SS: S?S?, SSEL, CS, C?S?, CE, nSS, /SS, SS#

MOSI / MISO慣例要求,在使用備用名稱的設備上,主機上的SDI連接到從機上的SDI,反之亦然。從選擇與芯片選擇相同的功能,而不是尋址概念。引腳名稱始終大寫,如從選擇,串行時鐘和主輸出從輸入。

SPI Operation

SPI總線可以與單個主器件以及一個或多個從器件一起工作。

如果使用單個從器件,如果從器件允許,SS引腳可能會固定為邏輯低電平。有些器件需要下降邊沿的片選信號來啟動一個動作。一個例子是Maxim MAX1242 ADC,它在高→低轉換時開始轉換。對于多個從設備,每個從設備都需要從主站獨立的SS信號。

大多數從器件具有三態輸出,因此當未選擇器件時,其MISO信號變為高阻抗(邏輯斷開)。沒有三態輸出的器件不能與其他器件共享SPI總線段; 只有一個這樣的從設備可以跟主設備說話。

數據傳輸

要開始通信,總線主機使用從設備支持的頻率配置時鐘,通常高達幾MHz。然后主機在選擇行上選擇邏輯電平為0的從設備。如果需要等待時間,例如進行模數轉換,則在發出時鐘周期之前,主機必須至少等待一段時間。

在每個SPI時鐘周期期間,發生全雙工數據傳輸。主機在MOSI線路上發送一個位,從機讀取它,而從機在MISO線路上發送一個位,主器件讀取它。即使僅需要單向數據傳輸,也保持該序列。

傳輸通常涉及一些給定字大小的兩個移位寄存器,例如8位,主器件中有1位,從器件中有1位。它們以虛擬環形拓撲連接。數據通常以最高有效位首先移出,同時將新的最低有效位移位到同一寄存器。同時,來自對方的數據被移入最低有效位寄存器。在寄存器位被移出和進入后,主器件和從器件已經交換了寄存器值。如果需要更換數據,則重新裝入移位寄存器,重復該過程。傳輸可以持續任何數量的時鐘周期。完成后,主站停止切換時鐘信號,通常取消選擇從站。

傳輸通常由8位字組成。然而,其他字大小也是常見的,例如,用于觸摸屏控制器或音頻編解碼器的16位字,例如德州儀器公司的TSC2101 或12位字,用于許多數模轉換器或模數轉換器轉換器。

總線上未使用芯片選擇線激活的每個從器件都必須忽略輸入時鐘和MOSI信號,而不能驅動MISO。

典型的硬件設置使用兩個移位寄存器來形成一個片間循環緩沖區,如下圖:


時鐘極性和相位

除了設置時鐘頻率外,主機還必須配置相對于數據的時鐘極性和相位。摩托羅拉SPI指南將這兩個選項分別命名為CPOL和CPHA,大多數廠商都采用了這一慣例。

時序圖如下圖所示。 該時序在下面進一步描述并適用于主設備和從設備。

顯示時鐘極性和相位的時序圖。紅線表示時鐘前沿,藍線,后沿。


  • CPOL確定時鐘的極性。極性可以用簡單的逆變器轉換
    • CPOL = 0是空閑0的時鐘,每個周期由1的脈沖組成。也就是說,前沿是上升沿,后沿是下降沿。
    • CPOL = 1是空閑1的時鐘,每個周期由0的脈沖組成。也就是說,前沿是下降沿,后沿是上升沿。
  • CPHA確定數據位相對于時鐘脈沖的定時。在兩種形式之間進行轉換并不是微不足道的。
    • 對于CPHA = 0,“輸出”側在前一時鐘周期的后沿更改數據,而“in”側在時鐘周期的前沿捕獲(或不久之后)數據。外側保持數據有效直到當前時鐘周期的下降沿。對于第一個周期,第一個位必須位于MOSI線前面的前沿時鐘邊沿。
    • 考慮它的另一種方法是說,CPHA = 0周期由時鐘空閑的半周期組成,隨后是時鐘被斷言的半周期。
    • 對于CPHA = 1,“輸出”側在當前時鐘周期的前沿更改數據,而“in”側在時鐘周期的后沿捕獲(或不久之后)數據。外側保持數據有效直到下一個時鐘周期的前沿。對于上一個周期,從機將MISO線保持有效,直到從機選擇被解除。
    • 考慮它的另一種方法是說,CHPA = 1周期由一個半周期組成,時鐘被置位,隨后是一個半周期,時鐘空閑。

MOSI和MISO信號通常在半個周期內穩定(在其接收點),直到下一個時鐘轉換。SPI主從設備可能會在該半周期的不同點對數據進行采樣。

這為主站和從站之間的通信通道增加了更多的靈活性。

模式

極性和相位的組合通常被稱為根據以下約定通常編號的模式,CPOL為高位,CPHA為低位位:

對于“Microchip PIC”/“ARM”微控制器(注意,NCPHA是CPHA的反轉):


極性

另一種常用的符號表示作為(CPOL,CPHA)元組的模式; 例如,值'(0,1)'將指示CPOL = 0和CPHA = 1。

中斷

SPI器件有時使用另一條信號線將中斷信號發送到主機CPU。實例包括從觸摸屏傳感器落筆中斷,從溫度傳感器熱限制警報,由實時時鐘芯片,發出警報SDIO,和耳機插孔插入從在蜂窩電話的聲音編解碼器。中斷不在SPI標準的范圍內; 它們的使用既不被禁止也不被標準規定。

SPI接口

STM32F7 的 SPI 功能很強大, SPI 時鐘最高可以到 54Mhz,支持 DMA,可以配置為 SPI
協議或者 I2S 協議(支持全雙工 I2S)。
實驗中,我們將使用 STM32F767 的 SPI 來驅動 NRF24L01 無線模塊。這里對 SPI 我們只簡
單介紹一下 SPI 的使用, STM32F767 的 SPI 詳細介紹請參考《 STM32F7 中文參考手冊》第 965頁, 32 章。

我們使用 STM32F767 的 SPI2 來驅動 NRF24L01, HAL 庫中 SPI 相關函數定義分布
在源文件stm32f7xx_hal_spi.c和對應的頭文件stm32f7xx_hal_spi.h 中 。 下面就來看看STM32F767 的 SPI2 主模式配置步驟:

配置相關引腳的復用功能,使能SPI2時鐘

我們要使用SPI2,第一步就是要使能SPI2時鐘,SPI2的時鐘通過APB1ENR的第14位來設置。其次要設置SPI2的相關引腳為復用(AF5)輸出,這樣才會連接到SPI2上。這里我們使用的是PB13,14,15這3個(SCK, MISO, MOSI, CS使用軟件管理方式),所以設置這三個為復用IO,復用功能為AF5。

HAL庫中,SPI2時鐘使能方法:

__HAL_RCC_SPI2_CLK_ENABLE();    //使能SPI2時鐘

IO口的復用功能設置前面我們已經多次提到,這里就不累贅了。和串口等其他外設一樣,HAL庫同樣提供了SPI的初始化回調函數,用來編寫與MCU相關的配置。我們只需要復現void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi);

初始化SPI2,設置SPI2工作模式等

這一步全部是通過SPI2_CR1寄存器來設置,我們設置SPI2為主機模式,設置數據格式為8位,然后通過CPOL和CPHA位來設置SCK時鐘極性及采樣方式。并設置SPI2的時鐘頻率(最大54MHz),以及數據的格式。在庫函數中,初始化SPI的函數為:HAL_StatusTypeDef HAL_SPI_Init(SPI_HandleTypeDef *hspi);
該函數只有一個入口參數hspi,為SPI_HandleTypeDef結構體指針類型,該結構體定義如下:

typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef         *Instance;          //外設寄存器基地址
SPI_InitTypeDef     Init;           //初始化結構體
uint8_t             *pTxBuffPtr;            //發送緩存
uint16_t            TxXferSize;             //發送數據大小
uint16_t            TxXferCount;            //還剩余多少個數據要發送
uint8_t             *pRxBuffPtr;            //接收緩存
uint16_t            RxXferSize;             //接收數據大小
uint16_t            RxXferCount;            //還剩余多少個數據要接收
DMA_HandleTypeDef   *hdmatx;        //DMA 發送句柄
DMA_HandleTypeDef   *hdmarx;        //DMA 接收句柄
void                (*RxISR)(struct __SPI_HandleTypeDef * hspi);
void                (*TxISR)(struct __SPI_HandleTypeDef * hspi);
HAL_LockTypeDef     Lock;
__IO HAL_SPI_StateTypeDef   State;
__IO uint32_t       ErrorCode;
}SPI_HandleTypeDef;

該結構體成員變量較多。成員變量 Instance 用來設置外設寄存器基地址,對于 SPI2,我們
設置為宏定義標識符 SPI2 即可。成員變量 pTxBuffPtr, TxXferSize 和 TxXferCount 用來設置 SPI發送緩存,發送數據量和發送剩余數據量。成員變量 pRxBuffPtr, RxXferSize 和 RxXferCount用來設置接收緩存,接收數據量和接收剩余數據量。hdmatx和 hdmarx 是 DMA 處理句柄。 RxISR 和 TxISR 是函數指針,用來指向 SPI 的接收和發送中斷處理函數。這里我們著重講解第二個成員變量 Init,該成員變量用來初始化 SPI時序和工作模式等,Init 成員變量是 SPI_InitTypeDef 結構體類型,該結構體定義如下:

typedef struct
{
uint32_t        Mode; // 模式:主( SPI_MODE_MASTER),從( SPI_MODE_SLAVE)
uint32_t        Direction; //方式: 只接受模式, 單線雙向通信數據模式,全雙工
uint32_t        DataSize; //8 位還是 16 位幀格式選擇項
uint32_t        CLKPolarity; //時鐘極性
uint32_t        CLKPhase; //時鐘相位
uint32_t        NSS; //SS 信號由硬件( NSS 管腳)還是軟件控制
uint32_t        BaudRatePrescaler; //設置 SPI 波特率預分頻值
uint32_t        FirstBit; //起始位是 MSB 還是 LSB
uint32_t        TIMode; //幀格式 SPI motorola 模式還是 TI 模式
uint32_t        CRCCalculation; //硬件 CRC 是否使能
uint32_t        CRCPolynomial; //CRC 多項式
uint32_t        CRCLength;
uint32_t        NSSPMode;
}SPI_InitTypeDef;

結構體成員變量比較多, 接下來我們簡單講解一下:

  • 參數 Mode 用來設置 SPI 的主從模式,這里我們設置為主機模式 SPI_MODE_MASTER,當然有
    需要你也可以選擇為從機模式 SPI_MODE_SLAVE。
  • 參數 Direction 用來設置 SPI 的通信方式,可以選擇為半雙工,全雙工,以及串行發和串行收方式,這里我們選擇全雙工模式 SPI_DIRECTION_2LINES。
  • 參數DataSize為 8位還是 16位幀格式選擇項,這里我們是 8位傳輸,選擇SPI_DATASIZE_8BIT。
  • 參數 CLKPolarity 用來設置時鐘極性,我們設置串行同步時鐘的空閑狀態為高電平所以我們選
    擇 SPI_POLARITY_HIGH。
  • 參數 CLKPhase 用來設置時鐘相位, 也就是選擇在串行同步時鐘的第幾個跳變沿(上升或下降)數據被采樣,可以為第一個或者第二個條邊沿采集,這里我們選擇第二個跳變沿,所以選擇SPI_PHASE_2EDGE。
  • 參數 NSS 設置 NSS 信號由硬件( NSS 管腳)還是軟件控制,這里我們通過軟件控制 NSS 關鍵,而不是硬件自動控制,所以選擇 SPI_NSS_SOFT。
  • 參數 SPI_ BaudRatePrescaler 很關鍵,就是設置 SPI 波特率預分頻值也就是決定 SPI 的時鐘的參數 , 從2分頻到256分頻8個可選值,初始化的時候我們選擇256分頻值SPI_BAUDRATEPRESCALER_256, 傳輸速度為 108M/256=421.875KHz。
  • 參 數 FirstBit 設置數據傳輸順序是MSB位在前還是LSB位在前,這里我們選擇SPI_FIRSTBIT_MSB 高位在前。
  • 參數 TIMode 用來設置 TI 模式使能還是禁止,這里我們禁止即可。
  • 參數 CRCCalculation, CRCPolynomial 和 CRCLength 分別用來設置使能/禁止 CRC 校驗, CRC校驗多項式以及 CRC 校驗的長度。
  • 參數 NSSPMode 用來設置在連續傳輸時,是否允許 SPI 在兩個連續數據間產生 NSS 脈沖。

這些參數設置好后,就可以調用初始化函數了,設置示例如下:

SPI2_Handler.Instance=SPI2;             //SP2
SPI2_Handler.Init.Mode=SPI_MODE_MASTER; //設置 SPI 工作模式,設置為主模式
SPI2_Handler.Init.Direction=SPI_DIRECTION_2LINES;// SPI 設置為雙線模式
SPI2_Handler.Init.DataSize=SPI_DATASIZE_8BIT; // SPI 發送接收 8 位幀結構
SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_HIGH; //時鐘的空閑狀態為高電平
SPI2_Handler.Init.CLKPhase=SPI_PHASE_2EDGE; //同步時鐘的第二個跳變沿數據采樣
SPI2_Handler.Init.NSS=SPI_NSS_SOFT;     //內部 NSS 信號有 SSI 位控制
SPI2_Handler.Init.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_256;//波特率 256 分頻
SPI2_Handler.Init.FirstBit=SPI_FIRSTBIT_MSB; //數據傳輸從 MSB 位開始
SPI2_Handler.Init.TIMode=SPI_TIMODE_DISABLE; //關閉 TI 模式
SPI2_Handler.Init.CRCCalculation=SPI_CRCCALCULATION_DISABLE;//關閉 CRC 校驗
SPI2_Handler.Init.CRCPolynomial=7;      //CRC 值計算的多項式

HAL_SPI_Init(&SPI2_Handler);            //初始化 SPI2

上面的代碼備注有很詳細的解釋,前面也對這些參數進行了介紹。

使能SPI2

這一步通過SPI2_CR1的bit6來設置,以啟動SPI2,在啟動之后,我們就可以通過SPI通訊了,庫函數使能SPI2的方法為:__HAL_SPI_ENABLE(&SPI2_Handler);

SPI傳輸數據

通信接口當然需要有發送數據和接收數據的函數,HAL庫提供的發送/接收數據函數原型為:

HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout);

這兩個函數非常好理解,就是向SPIx數據寄存器中寫/讀數據Data,從而實現發送/接收。

前面我們簡述了SPI的通信原理,因為SPI是全雙工,發送一個字節同時接收一個字節,發送和接收同時完成,所有HAL庫提供了一個發送接收統一函數:HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size, uint32_t Timeout);
該函數發送一個字節的同時接收一個字節。

SPI中斷處理

SPI1 和 SPI2 中斷服務函數分別為 SPI1_IRQHandler 和 SPI2_IRQHandler,和串口中斷處理
過程一樣, HAL 庫同樣提供了 SPI 中斷通用處理入口函數 HAL_SPI_IRQHandler,同時提供了
多個中斷處理回調函數,通信過程各種中斷最終都會通過相應的回調函數來處理。 SPI 相關回
調函數如下:

void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);//發送完成
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi);//接收完成
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi);//發送接收完成
void HAL_SPI_TxHalfCpltCallback(SPI_HandleTypeDef *hspi);//發送過半
void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi);//接收過半
void HAL_SPI_TxRxHalfCpltCallback(SPI_HandleTypeDef *hspi);//發送接收過半
void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi);//傳輸錯誤

中斷的使能,及編程過程,這里就不累贅了,前面已經說的太多了。

SPI在HAL庫當中的使用,就介紹到這里,接下來我們看下NRF24L01無線模塊。

NRF24L01 無線模塊簡介

nRF24.L01是一款新型單片射頻收發器件,工作于2.4 GHz~2.5 GHz ISM頻段。內置頻率合成器、功率放大器、晶體振蕩器、調制器等功能模塊,并融合了增強型ShockBurst技術,其中輸出功率和通信頻道可通過程序進行配置。nRF24L01功耗低,在以-6 dBm的功率發射時,工作電流也只有9 mA;接收時,工作電流只有12.3 mA,多種低功率工作模式,工作在100mw時電流為160mA,在數據傳輸方面實現相對WiFi距離更遠,但傳輸數據量不如WiFi(掉電模式和空閑模式)使節能設計更方便。

功能介紹

  • 2.4Ghz 全球開放ISM 頻段免許可證使
  • 最高工作速率2Mbps,高效GFSK調制,抗干擾能力強,特別適合工業控制場合
  • 126 頻道,滿足多點通信和跳頻通信需要
  • 內置硬件CRC 檢錯和點對多點通信地址控制
  • 低功耗1.9-3.6V 工作,待機模式下狀態為22uA;掉電模式下為900nA
  • 內置2.4Ghz 天線,體積小巧 15mmX29mm
  • 模塊可軟件設地址,只有收到本機地址時才會輸出數據(提供中斷指示),可直接接各種單片機使用,軟件編程非常方便
  • 內置專門穩壓電路,使用各種電源包括DC/DC 開關電源均有很好的通信效果
  • 2.54MM間距接口, DIP封裝
  • 工作于EnhancedShockBurst 具有Automaticpackethandling,Auto packettransactionhandling,具有可選的內置包應答機制,極大的降低丟包率

硬件與接口

nRF24L01芯片

接口圖示

接口介紹

nRF24L01模塊

模塊
  • VCC腳接電壓范圍為1.9V~3.6V之間,不能在這個區間之外,超過3.6V將會燒毀模塊。推薦電壓3.3V左右。
  • 除電源VCC和接地端,其余腳都可以直接和普通的5V單片機IO口直接相連,無需電平轉換。當然對3V左右的單片機更加適用了。
  • 硬件上面沒有SPI的單片機也可以控制本模塊,用普通單片機IO口模擬SPI也可以。

各管腳位置和定義

  1. vcc:接1.9V~3.6V間的電壓
  2. GND:接地
  3. CE:芯片的模式控制線。在 CSN 為低的情況下,CE 協同NRF24L01 的CONFIG 寄存器共同決定NRF24L01 的狀態
  4. CSN:為芯片的片選線 CSN 為低電平芯片工作
  5. SCK:為芯片控制的時鐘線(SPI時鐘)
  6. MOSI:為芯片控制數據線,主設備數據輸出 從設備數據輸入
  7. MISO:芯片控制數據線,主設備數據輸入 從設備數據輸出
  8. IRQ:中斷信號引腳。中斷時變為低電平,即NRF24L01內部發生中斷時IRQ引腳從高電平變為低電平。引腳會在以下三種情況變低:(1)Tx FIFO 發完并且收到ACK(使能ACK情況下);(2)Rx FIFO 收到數據;(3)達到最大重發次數。

工作模式

工作模式設定

工作模式
  • PWR_UP: 上電
  • PRIM_RX: 掉電
  • CE: 芯片使能

PWR_UP和PRIM_RX 在配置寄存器(CONFIG)中設置位0和位1:


寄存器

收發模式

收發模式有Enhanced(增強型) ShockBurstTM收發模式、ShockBurstTM收發模式和直接收發模式三種。

ShockBurstTM 模式

ShockBurstTM收發模式下,使用片內的先入先出堆棧區,數據低速從微控制器送入,但高速(1Mbps)發射,這樣可以盡量節能,因此,使用低速的微控制器也能得到很高的射頻數據發射速率。與射頻協議相關的所有高速信號處理都在片內進行,這種做法有三大好處:盡量節能;低的系統費用(低速微處理器也能進行高速射頻發射);數據在空中停留時間短,抗干擾性高。nRF2401 的 ShockBurstTM 技術同時也減小了整個系統的平均工作電流。

在 ShockBurstTM 收發模式下,nRF2401 自動處理字頭和 CRC 校驗碼。在接收數據時,自動把字頭和CRC 校驗碼移去。在發送數據時,自動加上字頭和 CRC 校驗碼,當發送過程完成后,數據準備好引腳通知微處理器數據發射完畢。

ShockBurstTM收發模式可以與Nrf2401a,02,E1及E2兼容

Enhanced(增強型) ShockBurstTM 模式

增強型ShockBurst TM 典型的雙鏈方式為:發送方要求終端設備在接收到數據后有應答信號,以便發送方檢測有無數據丟失,一旦丟失則重發數據。重發數據設置在地址為 04 的數據重發設置寄存器 用于設置其重發次數及設置在未收到應答信號后等待重發的時間。

數據重發設置寄存器(SETUP_RETR):


數據重發設置寄存器

nRF24L01 在接收模式下可以接收6 路不同通道的數據。
每一個數據通道使用不同的地址,但是共用相同的頻道。

也就是說6 個不同的nRF24L01 設置為發送模式后可以與同一個設置為接收模式的nRF24L01 進行通訊,而設置為接收模式的nRF24L01 可以對這6 個發射端進行識別。

數據通道0 是唯一的一個可以配置為40 位自身地址的數據通道。1~5 數據通道都為8 位自身地址和32 位公用地址。所有的數據通道都可以設置為增強型ShockBurst 模式。

NRF24L01在確認收到數據后記錄地址,并以此地址為目標地址發送應答信號,在發送端,數據通道0被用作接收應答信號,因此屬通道0 的接收地址要與發送地址端地址相等,以確保接收到正確的應答信號

傳輸示意圖

當MCU控制NRF24L01發送數據時,NRF24L01就會啟動發送數據,發送完后NRF24L01就會轉到接收模式并等待終端的應答信號。

如果沒有收到應答信號,NRF24L01就會重發數據包,直到收到應答信號,或達到重發次數寄存器設定的最大值為止,如果重發次數超過了設定值則產生MAX_RT(最大重發次數中斷)(應該在該中斷沒有被屏蔽的情況下的時候才會發生)

只要收到確認信號,nRF24L01 就認為最后一包數據已經發送成功(接收方已經收到數據),把TX FIFO 中的數據清除掉并產生TX_DS中斷(數據發送完中斷)(IRQ 引腳置高)。

Enhanced ShockBurstTM發射流程
  1. 配置CONFIG寄存器位PRIM_RX 為低,進入發送模式

  2. 當MCU 有數據要發送時,接收節點地址(TX_ADDR)和有效數據(TX_PLD)通過SPI 接口寫入nRF24L01。發送數據的長度以字節計數從MCU 寫入TX FIFO。當CSN 為低時數據被不斷的寫入。發送端發送完數據后,將通道0 設置為接收模式來接收應答信號,其接收地址(RX_ADDR_P0)與接收端地址(TX_ADDR)相同。

    例:在上圖 中數據通道5 的發送端(TX5)及接收端(RX)地址設置如下:
    TX5:TX_ADDR=0xB3B4B5B605
    TX5:RX_ADDR_P0=0xB3B4B5B605
    RX:RX_ADDR_P5=0xB3B4B5B605

  3. 把CE置高,最小時間為10us,激發nRF24L01進行Enhanced ShockBurstTM 發射

  4. nRF24L01 Enhanced ShockBurstTM 模式:

    • 無線系統上電
    • 頻數據打包(加字頭、 CRC校驗碼)
    • 高速發射數據包(由MCU 設定為1Mbps 或2Mbps)
  5. 如果啟動了自動應答模式(自動重發計數器不等于0,ENAA_P0=1),無線芯片立即進入接收模式。如果在有效應答時間范圍內收到應答信號,則認為數據成功發送到了接收端,此時狀態寄存器的TX_DS 位置高并把數據從TX FIFO 中清除掉。如果在設定時間范圍內沒有接收到應答信號,則重新發送數據。如果自動重發計數器(ARC_CNT)溢出(超過了編程設定的值),則狀態寄存器的MAX_RT 位置高。不清除TX FIFO 中的數據。當MAX_RT 或TX_DS 為高電平時IRQ 引腳產生中斷。IRQ 中斷通過寫狀態寄存器來復位(見中斷章節)。如果重發次數在達到設定的最大重發次數時還沒有收到應答信號的話,在MAX_RX 中斷清除之前不會重發數據包。數據包丟失計數器(PLOS_CNT)在每次產生MAX_RT 中斷后加一。也就是說:重發計數器ARC_CNT 計算重發數據包次數,PLOS_CNT 計算在達到最大允許重發次數時仍沒有發送成功的數據包個數。

  6. 如果CE 置低,則系統進入待機模式I。如果不設置CE 為低,則系統會發送TX FIFO 寄存器中下一包數據。如果TX FIFO 寄存器為空并且CE 為高則系統進入待機模式II.

  7. 如果系統在待機模式II,當CE 置低后系統立即進入待機模式I.

#######相關寄存器:

自動應答寄存器:


自動應答寄存器

狀態寄存器:


狀態寄存器

發送檢測寄存器:


發送檢測寄存器
Enhanced ShockBurstTM接收流程
  1. 配置CONFIG寄存器位PRIM_RX 為高,進入接收模式準備接收數據的通道必須被使能(EN_RXADDR 寄存器),所有工作在增強型ShockBurstTM 模式下的數據通道的自動應答功能是由(EN_AA 寄存器)來使能的,有效數據寬度是由RX_PW_Px 寄存器來設置的。地址的建立過程見增強型ShockBurstTM 發送章節。
  2. 接收模式由設置CE 為高來啟動。
  3. 130us 后nRF24L01 開始檢測空中信息。
  4. 接收到有效的數據包后(地址匹配、CRC 檢驗正確),數據存儲在RX_FIFO 中,同時RX_DR 位置高,并產生中斷。狀態寄存器中RX_P_NO 位顯示數據是由哪個通道接收到的。
  5. 如果使能自動確認信號,則發送確認信號。
  6. MCU 設置CE 腳為低,進入待機模式I(低功耗模式)。
  7. MCU 將數據以合適的速率通過SPI 口將數據讀出。
  8. 芯片準備好進入發送模式、接收模式或掉電模式。

#######相關寄存器

接收數據使能寄存器:


接收數據使能寄存器

接收數據通道有效數據寬度設置寄存器:


接收數據通道有效數據寬度設置寄存器
兩種數據雙方向的通訊方式

如果想要數據在雙方向上通訊,PRIM_RX 寄存器必須緊隨芯片工作模式的變化而變化。
處理器必須保證PTX 和PRX 端的同步性。在RX_FIFO 和TX_FIFO 寄存器中可能同時存有數據。

數據包識別和CRC校驗應用于增強型ShockBurstTM模式下

每一包數據都包括兩位的PID(數據包識別)來識別接收的數據是新數據包還是重發的數據包。

PID識別可以防止接收端同一數據包多次送入MCU。

在發送方每從MCU取得一包新數據后PID值加一。

PID和CRC校驗應用在接收方識別接收的數據是重發的數據包還是新數據包。

如果鏈接中有一些數據丟失了,則PID值與上一包數據的PID值相同。

接收方對新接收的數據包進行比較:如果一包數據擁有與上一包數據相同的PID值,nRF24L01將對兩包數據的CRC值進行比較。如果CRC值也相同的話就認為后面一包是前一包的重發數據包而被舍棄。

數據通道

nRF24L01 配置為接收模式時可以接收 6 路不同地址相同頻率的數據。每個數據通道擁有自己的地址并且可以通過寄存器來進行分別配置。

數據通道是通過寄存器EN_RXADDR來設置的,默認狀態下只有數據通道 0 和數據通道 1 是開啟狀態的。

每一個數據通道的地址是通過寄存器RX_ADDR_Px來配置的。通常情況下不允許不同的數據通道設置完全相同的地址。

數據通道040位可配置地址。數據通道1-5的地址為:32位共用地址+各自的地址(最低字節)。

下圖所示的是數據通道1-5的地址設置方法舉例。所有數據通道可以設置為多達40位,但是1-5數據通道的最低位必須不同。

當從一個數據通道接收到數據,并且此數據通道設置為應答方式的話,則nRF24L01 在收到數據后 產生應答信號,此應答信號的目標地址為接收通道地址

數據通道1-5的配置方式

待機模式

待機模式1在保證快速啟動的同時減少系統平均消耗電流。

在待機模式1下,晶振正常工作。

在待機模式2下部分時鐘緩沖器處在工作模式。

當發送端TX FIFO寄存器位空并且CE為高電平時進入待機模式2。

在待機模式期間,寄存器配置字內容保持不變。

掉電模式

在掉電模式下,nRF24L01各功能關閉,保持電流消耗最小。

進入掉電模式后,nRF24L01停止工作,但寄存器內容保持不變。

掉電模式由寄存器中PWR_UP位來控制。

涉及硬件

SPI實驗涉及到的硬件就是前面介紹的SPI以及我們的無線NRF24L01模塊,實驗開始先檢測無線模塊,然后根據KEY0和KEY1按鍵來設置決定模塊的工作模式,在設定好工作模式后,就會不停的發送/接收數據,需要的硬件如下:

  1. LED燈提示系統運行正常
  2. KEY0和KEY1按鍵
  3. LCD模塊
  4. SPI2
  5. NRF24L01

NRF24L01屬于外接模塊,這里我們僅介紹下開發板上的模塊接口和STM32F767的連接情況,他們的連接關系如下圖:


連接原理圖

這里無線模塊連接的是SPI2,連接在PB13,14,15這3個IO口上,看圖上可以看到 NRF_IRQ和GBC_KEY共用了PI11, NRF_CE和SPDIF_RX共用PG12 所以,他們不能同時使用,需要分時復用。由于無線模塊需要雙向通信,所有至少需要兩個模塊同時工作,所以這里需要大家用兩套開發板和無線模塊來測試。

軟件設計

打開工作,我們添加spi.c以及nrf24l01.c兩個文件,編寫我們的SPI以及NRF24L01的底層驅動函數,當然,還要添加對應的頭文件。首先來編寫spi.c。

spi.c

SPI_HandleTypeDef SPI2_Handler;         //定義一個全局的SPI2句柄

void SPI2_Init(void)                    //SPI2初始化,并配置為主機模式
{
    SPI2_Handler.Instance   = SPI2;     //SPI2
    SPI2_Handler.Init.Mode  = SPI_MODE_MASTER;  //設置SPI2為主模式
    SPI2_Handler.Init.Direction = SPI_DIRECTION_2LINES; //SPI設置為雙線模式
    SPI2_Handler.Init.DataSize  = SPI_DATASIZE_8BIT;    //SPI發送接收8bit結構
    SPI2_Handler.Init.CLKPolarity   = SPI_POLARITY_HIGH;    //同步時鐘空閑狀態為高電平
    SPI2_Handler.Init.CLKPhase      = SPI_PHASE_2EDGE;      //同步時鐘第二個跳變沿采樣數據
    SPI2_Handler.Init.NSS           = SPI_NSS_SOFT;         //內部NSS型號有SSI位控制
    SPI2_Handler.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;    //定義波特率預分頻值為256
    SPI2_Handler.Init.FirstBit  = SPI_FIRSTBIT_MSB;         //數據傳輸從MSB位開始
    SPI2_Handler.Init.TIMode    = SPI_TIMODE_DISABLE;       //關閉TI模式
    SPI2_Handler.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;   //關閉硬件CRC檢測
    SPI2_Handler.Init.CRCPolynomial     = 7;                //CRC值計算的多項式
    HAL_SPI_Init(&SPI2_Handler);
    __HAL_SPI_ENABLE(&SPI2_Handler);                    //使能SPI2
    SPI2_ReadWriteBtye(0xff);                           //啟動傳輸
}

這份代碼在上邊以上已經介紹了一邊,給句柄用宏賦值,本質就是在配置SPI的功能寄存器,這個前面已經說的太多了,這里就不累贅了,配置完了以后調用HAL_SPI_Init()來初始化,并且要使能SPI,然后開啟傳輸。

那么我們知道HAL_SPI_Init()函數在內部會調用一個回調函數,我們需要來實現他,配置引腳以及時鐘使能:

void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOB_CLK_ENABLE();           //使能 GPIOB 時鐘
__HAL_RCC_SPI2_CLK_ENABLE();            //使能 SPI2 時鐘

//PB13,14,15
GPIO_Initure.Pin=GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
GPIO_Initure.Mode=GPIO_MODE_AF_PP;      //復用推挽輸出
GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST;     //快速
GPIO_Initure.Alternate=GPIO_AF5_SPI2;   //復用為 SPI2
HAL_GPIO_Init(GPIOB,&GPIO_Initure);     //初始化
}

代碼非常簡單,就不多做解釋了,STM32F767上面的SPI的分頻系數是2~256,那么我們來封裝一個函數,隨需要動態調整SPI的通信速度,SPI速度 = fAPB1 / f分頻系數,HAL庫中對分頻系數的宏定義是SPI_BAUDRATEPRESCALER_2~SPI_BAUDRATEPRESCALER_256,fAPB1 時鐘一般為 54Mhz,代碼如下:

void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{
assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判斷有效性
__HAL_SPI_DISABLE(&SPI2_Handler);               //關閉 SPI
SPI2_Handler.Instance->CR1&=0XFFC7;             //位 3-5 清零,用來設置波特率
SPI2_Handler.Instance->CR1|=SPI_BaudRatePrescaler;  //設置 SPI 速度
__HAL_SPI_ENABLE(&SPI2_Handler);                //使能 SPI
}

函數入口處,使用了斷言函數,來確定參數的合法性,如果不符合HAL庫定義的參數取值范圍,就會自動返回,不會繼續執行,這個前面也已經說過很多次了。如果要通過SPI讀寫,再封裝一個函數來調用HAL庫函數即可,代碼如下:

u8 SPI2_ReadWriteByte(u8 TxData)
{
u8 Rxdata;
HAL_SPI_TransmitReceive(&SPI2_Handler,&TxData,&Rxdata,1, 1000);
return Rxdata;              //返回收到的數據
}

SPI2_ReadWriteByte 函數主要是通過調用 HAL 庫中 SPI 發送接收函數 HAL_SPI_TransmitReceive 來實現數據的發送和接收。spi.c的底層函數就這么多了,接下來我們來看下nrf24l01的底層驅動函數封裝。

nrf24l01.c

const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //發送地址
const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //接收地址

首先定義兩個全局const修飾的數組,來存放發送和接收地址。然后針對我們的NRF24L01來修改我們的spi2驅動,代碼如下:

void NRF24L01_SPI_Init(void)
{
__HAL_SPI_DISABLE(&SPI2_Handler);               //先關閉 SPI2
SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //同步時鐘的空閑狀態為低電平
SPI2_Handler.Init.CLKPhase=SPI_PHASE_1EDGE;     //同步時鐘第 1 個跳變沿采樣數據
HAL_SPI_Init(&SPI2_Handler);
__HAL_SPI_ENABLE(&SPI2_Handler);                //使能 SPI2
}

我們的NRF24L01需要8根線來連接,除過電源線VCC和GND外,之前SPI通信已經初始化過了SCK,MOSI,MISO三根線,要讓NRF24L01通信還需要連接其他3根(CE,CSN,IRQ),具體代碼如下:

void NRF24L01_Init(void)
{
GPIO_InitTypeDef GPIO_Initure;
__HAL_RCC_GPIOG_CLK_ENABLE(); //開啟 GPIOG 時鐘
__HAL_RCC_GPIOI_CLK_ENABLE(); //開啟 GPIOI 時鐘

GPIO_Initure.Pin=GPIO_PIN_10|GPIO_PIN_12; //PG10,12
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽輸出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOG,&GPIO_Initure); //初始化

GPIO_Initure.Pin=GPIO_PIN_11; //PI11
GPIO_Initure.Mode=GPIO_MODE_INPUT; //輸入
HAL_GPIO_Init(GPIOI,&GPIO_Initure); //初始化

SPI2_Init(); //初始化 SPI2
NRF24L01_SPI_Init(); //針對 NRF 的特點修改 SPI 的設置
NRF24L01_CE(0); //使能 24L01
NRF24L01_CSN(1); //SPI 片選取消
}

通過前面的連接圖,我們知道這3根線連接的是PG10,12以及PI11,按照HAL庫IO口初始化套路設置就行,然后調用了SPI2_Init()函數,初始化SPI2的IO口,并且調用NRF24L01_SPI_Init()函數,針對NRF修改SPI設置,后面調用了2個宏函數,定義在頭文件當中,具體定義如下:

#define NRF24L01_CE(n) (n?HAL_GPIO_WritePin(GPIOG,GPIO_PIN_12,\
GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOG,GPIO_PIN_12,GPIO_PIN_RESET))  
//24L01 片選信號
#define NRF24L01_CSN(n) (n?HAL_GPIO_WritePin(GPIOG,GPIO_PIN_10,\
GPIO_PIN_SET):HAL_GPIO_WritePin(GPIOG,GPIO_PIN_10,GPIO_PIN_RESET))  
//SPI 片選信號

依然通過三目運算符來控制CE線以及CSN輸出高低電壓。這些初始化完成后,之前介紹過了,NRF24L01芯片內部還有他自己的寄存器,需要通過SPI來設置,那么還需要封裝2個函數來對NRF24L01芯片內部寄存器來進行讀寫,具體代碼如下:

u8 NRF24L01_Write_Reg(u8 reg,u8 value)
{
u8 status;
NRF24L01_CSN(0);                    //使能 SPI 傳輸
status =SPI2_ReadWriteByte(reg);    //發送寄存器號
SPI2_ReadWriteByte(value);          //寫入寄存器的值
NRF24L01_CSN(1);                    //禁止 SPI 傳輸
return(status);                     //返回狀態值
}

u8 NRF24L01_Read_Reg(u8 reg)
{
u8 reg_val;
NRF24L01_CSN(0);                    //使能 SPI 傳輸
SPI2_ReadWriteByte(reg);            //發送寄存器號
reg_val=SPI2_ReadWriteByte(0XFF);   //讀取寄存器內容
NRF24L01_CSN(1);                    //禁止 SPI 傳輸
return(reg_val);                    //返回狀態值
}

這2個函數就非常簡單了,調用了HAL庫內部的SPI操作函數,先開啟SPI傳輸,然后發送要讀寫內容的寄存器的地址,然后再次調用SPI_ReadWriteByte()函數,就可以得到該寄存器的值,用一個變量取接返回值就行,然后關閉SPI傳輸。

要使用NRF24L01通信當然是發送數據了,那么封裝兩個函數在指定位置(寄存器)讀寫指定長度的數據,代碼如下:

u8 NRF24L01_Read_Buf(u8 reg,u8 *pBuf,u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN(0);                    //使能 SPI 傳輸
status=SPI2_ReadWriteByte(reg);     //發送寄存器值(位置),并讀取狀態值
for(u8_ctr=0;u8_ctr<len;u8_ctr++) {
    pBuf[u8_ctr] = SPI2_ReadWriteByte(0XFF);    //讀出數據
}
NRF24L01_CSN(1);                    //關閉 SPI 傳輸
return status;                      //返回讀到的狀態值
}

u8 NRF24L01_Write_Buf(u8 reg, u8 *pBuf, u8 len)
{
u8 status,u8_ctr;
NRF24L01_CSN(0);                    //使能 SPI 傳輸
status = SPI2_ReadWriteByte(reg);   //發送寄存器值(位置),并讀取狀態值
for(u8_ctr=0; u8_ctr<len; u8_ctr++) {
    SPI2_ReadWriteByte(*pBuf++);    //寫入數據
}
NRF24L01_CSN(1);                    //關閉 SPI 傳輸
return status;                      //返回讀到的狀態值
}

兩個函數長的非常相像,都是先通過SPI發送需要讀/寫的位置(寄存器),然后再調用一次SPI2_ReadWriteByte()函數,就可以讀/寫數據了。

基本工作做完了,現在可以調用上面封裝的函數來編寫函數發送/接收一次數據了,代碼如下:

u8 NRF24L01_TxPacket(u8 *txbuf)
{
u8 sta;
SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8);     //spi 速度為 6.75Mhz
NRF24L01_CE(0);
NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);/
NRF24L01_CE(1);                             //啟動發送
while(NRF24L01_IRQ!=0);                     //等待發送完成
sta=NRF24L01_Read_Reg(STATUS);              //讀取狀態寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta);
if(sta&MAX_TX)  {                           //達到最大重發次數
    NRF24L01_Write_Reg(FLUSH_TX,0xff);      //清除 TX FIFO 寄存器
    return MAX_TX;
}
if(sta&TX_OK) {                             //發送完成
    return TX_OK;
}
return 0xff;                                //其他原因發送失敗
}

u8 NRF24L01_RxPacket(u8 *rxbuf)
{
u8 sta;
SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8);
sta=NRF24L01_Read_Reg(STATUS);              //讀取狀態寄存器的值
NRF24L01_Write_Reg(NRF_WRITE_REG+STATUS,sta);
if(sta&RX_OK) {                             //接收到數據
    NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);    //讀取數據
    NRF24L01_Write_Reg(FLUSH_RX,0xff);      //清除 RX FIFO 寄存器
    return 0;
}
return 1;                                   //沒收到任何數據
}

在頭文件中,有如下定義:

//NRF24L01寄存器操作命令
#define NRF_READ_REG    0x00  //讀配置寄存器,低5位為寄存器地址
#define NRF_WRITE_REG   0x20  //寫配置寄存器,低5位為寄存器地址
#define RD_RX_PLOAD     0x61  //讀RX有效數據,1~32字節
#define WR_TX_PLOAD     0xA0  //寫TX有效數據,1~32字節
#define FLUSH_TX        0xE1  //清除TX FIFO寄存器.發射模式下用
#define FLUSH_RX        0xE2  //清除RX FIFO寄存器.接收模式下用
#define REUSE_TX_PL     0xE3  //重新使用上一包數據,CE為高,數據包被不斷發送.
#define NOP             0xFF  //空操作,可以用來讀狀態寄存器   

//SPI(NRF24L01)寄存器地址
#define CONFIG          0x00  //配置寄存器地址;bit0:1接收模式,0發射模式;bit1:電選擇;bit2:CRC模式;bit3:CRC使能;
//bit4:中斷MAX_RT(達到最大重發次數中斷)使能;bit5:中斷TX_DS使能;bit6:中斷RX_DR使能
#define EN_AA           0x01  //使能自動應答功能  bit0~5,對應通道0~5
#define EN_RXADDR       0x02  //接收地址允許,bit0~5,對應通道0~5
#define SETUP_AW        0x03  //設置地址寬度(所有數據通道):bit1,0:00,3字節;01,4字節;02,5字節;
#define SETUP_RETR      0x04  //建立自動重發;bit3:0,自動重發計數器;bit7:4,自動重發延時 250*x+86us
#define RF_CH           0x05  //RF通道,bit6:0,工作通道頻率;
#define RF_SETUP        0x06  //RF寄存器;bit3:傳輸速率(0:1Mbps,1:2Mbps);bit2:1,發射功率;bit0:低噪聲放大器增益
#define STATUS          0x07  //狀態寄存器;bit0:TX FIFO滿標志;bit3:1,接收數據通道號(最大:6);bit4,
//達到最多次重發;bit5:數據發送完成中斷;bit6:接收數據中斷;

#define MAX_TX          0x10  //達到最大發送次數中斷
#define TX_OK           0x20  //TX發送完成中斷
#define RX_OK           0x40  //接收到數據中斷

#define OBSERVE_TX      0x08  //發送檢測寄存器,bit7:4,數據包丟失計數器;bit3:0,重發計數器
#define CD              0x09  //載波檢測寄存器,bit0,載波檢測;
#define RX_ADDR_P0      0x0A  //數據通道0接收地址,最大長度5個字節,低字節在前
#define RX_ADDR_P1      0x0B  //數據通道1接收地址,最大長度5個字節,低字節在前
#define RX_ADDR_P2      0x0C  //數據通道2接收地址,最低字節可設置,高字節,必須同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P3      0x0D  //數據通道3接收地址,最低字節可設置,高字節,必須同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P4      0x0E  //數據通道4接收地址,最低字節可設置,高字節,必須同RX_ADDR_P1[39:8]相等;
#define RX_ADDR_P5      0x0F  //數據通道5接收地址,最低字節可設置,高字節,必須同RX_ADDR_P1[39:8]相等;
#define TX_ADDR         0x10  //發送地址(低字節在前),ShockBurstTM模式下,RX_ADDR_P0與此地址相等
#define RX_PW_P0        0x11  //接收數據通道0有效數據寬度(1~32字節),設置為0則非法
#define RX_PW_P1        0x12  //接收數據通道1有效數據寬度(1~32字節),設置為0則非法
#define RX_PW_P2        0x13  //接收數據通道2有效數據寬度(1~32字節),設置為0則非法
#define RX_PW_P3        0x14  //接收數據通道3有效數據寬度(1~32字節),設置為0則非法
#define RX_PW_P4        0x15  //接收數據通道4有效數據寬度(1~32字節),設置為0則非法
#define RX_PW_P5        0x16  //接收數據通道5有效數據寬度(1~32字節),設置為0則非法
#define NRF_FIFO_STATUS 0x17  //FIFO狀態寄存器;bit0,RX FIFO寄存器空標志;bit1,RX FIFO滿標志;bit2,3,保留;
//bit4,TX FIFO空標志;bit5,TX FIFO滿標志;bit6,1,循環發送上一數據包.0,不循環;

#define NRF24L01_IRQ HAL_GPIO_ReadPin(GPIOI,GPIO_PIN_11)        //IRQ 主機數據輸入

這里定義非常多的宏,看著很麻煩,但是在工程當中,這樣做是非常有必要的,可以節省大量的代碼量,后期在RAM開發中,大家就會遇到,基本每個模塊都需要在頭文件中去這樣來定義。這里還通過 TX_PLOAD_WIDTH 和 RX_PLOAD_WIDTH 決定了發射和接收的數據寬度,也就是我們每次發射和接受的有效字節數。 NRF24L01 每次最多傳輸 32 個字節,再多的字節傳輸則需要多次傳送。

還需要有兩個函數來對REF24L01的模式來進行切換,代碼如下:

void NRF24L01_RX_Mode(void)
{
NRF24L01_CE(0);
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,
RX_ADR_WIDTH);                                      //寫 RX 節點地址
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);       //使能通道 0 的自動應答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);   //使能通道 0 的接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);         //設置 RF 通信頻率
NRF24L01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);
//選擇通道 0 的有效數據寬度
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);    //設置 TX 發射參數,0db 增益,2Mbps,低噪聲增益開啟
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);     //配置基本工作模式的參數;PWR_UP,EN_CRC,16BIT_CRC,接收模式
NRF24L01_CE(1);                                     //CE 為高,進入接收模式
}

void NRF24L01_TX_Mode(void)
{
NRF24L01_CE(0);
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,
TX_ADR_WIDTH);                                      //寫 TX 節點地址
NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);  //設置 TX 節點地址,主要為了使能 ACK
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);       //使能通道 0 的自動應答
NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);   //使能通道 0 接收地址
NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);  //設置自動重發間隔時間:500us + 86us;最大自動重發次數:10 次
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);         //設置 RF 通道為 40
NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);    //設置 TX 發射參數,0db 增益,2Mbps,低噪聲增益開啟
NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);      //配置基本工作模式的參數;PWR_UP,EN_CRC,16BIT_CRC,接收模式,開啟所有中斷
NRF24L01_CE(1);                                     //CE 為高,10us 后啟動發送
}

這個兩個函數都是根據NRF24L01的設定來配置寄存器,初始化NRF24L01到接收或者發送模式,并相應的設置其他配置。

然后再封裝一個函數來動態監測NRF24L01是否存在,代碼如下:

u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI2_SetSpeed(SPI_BAUDRATEPRESCALER_8);             //spi 速度為 6.75Mhz
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);    //寫入 5 個字節的地址.
NRF24L01_Read_Buf(TX_ADDR,buf,5);                   //讀出寫入的地址
for(i=0;i<5;i++) {
    if(buf[i]!=0XA5)break;
}
if(i!=5) return 1;                                  //檢測 24L01 錯誤

return 0;                                           //檢測到 24L01
}

以上就是24l01的底層驅動代碼了,完成了對NRF24L01的初始化,模式設置(發送/接收),數據讀寫等操作。在這里強調一個要注意的地方,在NRF24L01_Init()函數里面,我們調用了SPI2_Init()函數,該函數設置的是SCK空閑時為高,但是NRF24L01的SPI通信時序如下圖:

NRF24L01時序圖

上圖中Cn代表指令位,Sn代表狀態寄存器位,Dn代表數據位。從圖中可以看出,SCK空閑的時候是低電平,而數據在SCK的上升沿被讀寫。所有,我們在NRF24L01_Init()函數里面又單獨添加了將CPOL和CPHA設置為0的函數NRF24L01_SPI_Init()。這里主要是修改了下面兩行代碼:

SPI2_Handler.Init.CLKPolarity=SPI_POLARITY_LOW; //串行同步時鐘的空閑狀態為低電平
SPI2_Handler.Init.CLKPhase=SPI_PHASE_1EDGE; //同步時鐘的第 1 個跳變沿數據被采樣
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • ???本文主要介紹嵌入式系統的一些基礎知識,希望對各位有幫助。 嵌入式系統基礎 1、嵌入式系統的定義 (1)定義:...
    OpenJetson閱讀 3,352評論 0 13
  • 1、嵌入式系統的定義 (1)定義:以應用為中心,以計算機技術為基礎,軟硬件可裁剪,適應應用系統對功能、可靠性、成本...
    榮卓然閱讀 1,859評論 0 5
  • 什么是嵌入式 IEEE(Institute of Electrical and Electronics Engin...
    Leon_Geo閱讀 3,768評論 1 20
  • 1 說明 在不同的平臺,會使用不同的網絡組件,形成具有集成不同功能種類的網絡協議。 在Linux上,一般會有很齊全...
    wit_yuan閱讀 4,321評論 0 3