外设驱动库开发笔记26:nRF24L01无线通讯驱动
現在無線在我們的生活中無處不在。而我們開發的物聯網產品也大量使用無線通訊。在這一篇文章中,我們將討論nRF24L01無線通訊模塊驅動程序的開發與實現。
1、功能概述
nRF24L01是一款工作在2.4~2.5GHz世界通用ISM 頻段的單片無線收發器芯片無線收發器包括:頻率發生器、增強型SchockBurst模式控制器、功率放大器、晶體振蕩器、調制器、解調器。輸出功率、頻道選擇和協議的設置可以通過SPI 接口進行設置。其封裝及引腳定義如下:
1.1、工作模式
nRF24L01無線通訊模塊可以設置為多種不同的工作模式:待機模式、掉電模式、數據包處理方式。各模式的功能及操作如下:
1.1.1、待機模式
待機模式I在保證快速啟動的同時減少系統平均消耗電流。在待機模式I下,晶振正常工作。在待機模式II下部分時鐘緩沖器處在工作模式。當發送端TX FIFO寄存器為空并且CE為高電平時進入待機模式II。在待機模式期間,寄存器配置字內容保持不變。
1.1.2、掉電模式
在掉電模式下,nRF24L01各功能關閉,保持電流消耗最小。進入掉電模式后,nRF24L01停止工作,但寄存器內容保持不變。掉電模式由寄存器中PWR_UP位來控制。
1.1.3、數據包處理方式
nRF24L01數據包處理方式包括ShockBurst模式和增強型ShockBurst模式。
ShockBurst模式下nRF24L01可以與成本較低的低速MCU相連。高速信號處理是由芯片內部的射頻協議處理的,nRF24L01提供SPI接口,數據率取決于單片機本身接口速度。ShockBurst模式通過允許與單片機低速通信而無線部分高速通信,減小了通信的平均消耗電流。
增強型ShockBurst模式可以使得雙向鏈接協議執行起來更為容易、有效。典型的雙向鏈接為:發送方要求終端設備在接收到數據后有應答信號,以便于發送方檢測有無數據丟失。一旦數據丟失,則通過重新發送功能將丟失的數據恢復。增強型的ShockBurstTM模式可以同時控制應答及重發功能而無需增加MCU工作量。
1.2、數據通訊
?
1.2.1、通訊指令及數據包
nRF24L01所有配置都在配置寄存器中。所有寄存器都是通過SPI口進行配置的。SPI接口采用標準的SPI接口,其最大的數據傳輸率為10Mbps。指令格式采用命令字加數據字節的格式。其中命令字由高位到低位(每字節);數據字節從低字節到高字節,每一字節高位在前。nRF24L01支持的指令如下:
R_REGISTER和W_REGISTER寄存器可能操作單字節或多字節寄存器。當訪問多字節寄存器時首先要讀/寫的是最低字節的高位。在所有多字節寄存器被寫完之前可以結束寫SPI操作,在這種情況下沒有寫完的高字節保持原有內容不變。例如RX_ADDR_P0寄存器的最低字節可以通過寫一個字節給寄存器RX_ADDR_P0來改變。在CSN狀態由高變低后可以通過 MISO 來讀取狀態寄存器的內容。
nRF24L01在增強型ShockBurst模式下和ShockBurst模式下的數據包格式略有不同。
增強型ShockBurst模式下的數據包形式如下:
ShockBurst模式下的數據包形式如下:
在數據包中,前導碼用來檢測0和1。芯片在接收模式下去除前導碼,在發送模式下加入前導碼。地址內容為接收機地址。地址寬度可以是3、4或5字節寬度。地址可以對接收通道及發送通道分別進行配置。從接收的數據包中自動去除地址。標志位就是PID數據包識別號,后兩位會在每次接收到新的數據包后加,前7位保留。CRC校驗是可選的,0-2字節寬度的CRC校驗。若采用8位CRC校驗,則其特征多項式是:X8 +X2 +X+1;若采用16位CRC校驗,則其特征多項式是:X16+X12+X5 +1。
1.2.2、數據通道
nRF24L01配置為接收模式時可以接收6路不同地址相同頻率的數據。每個數據通道擁有自己的地址并且可以通過寄存器來進行分別配置。數據通道是通過寄存器EN_RXADDR來設置的,默認狀態下只有數據通道0和數據通道1是開啟狀態的。每一個數據通道的地址是通過寄存器RX_ADDR_Px來配置的。通常情況下不允許不同的數據通道設置完全相同的地址。數據通道0有40位可配置地址。數據通道1~5的地址為32位共用地址+各自的地址(最低字節)。如下所示:
2、驅動設計與實現
我們已經了解了nRF24L01無線通訊模塊的功能及操作方式,接下來我們將設計并實現nRF24L01無線通訊模塊的驅動程序。
2.1、對象定義
在使用一個對象之前我們需要獲得一個對象。同樣的我們想要nRF24L01無線通訊模塊就需要先定義nRF24L01無線通訊模塊的對象。
2.1.1、對象的抽象
我們要得到nRF24L01無線通訊模塊對象,需要先分析其基本特性。一般來說,一個對象至少包含兩方面的特性:屬性與操作。接下來我們就來從這兩個方面思考一下nRF24L01無線通訊模塊的對象。
先來考慮屬性,作為屬性肯定是用于標識或記錄對象特征的東西。我們來考慮nRF24L01無線通訊模塊對象屬性。nRF24L01有一些寄存器用于配置工作狀態,所以我們將這些寄存器狀態作為對象的屬性。
接著我們還需要考慮nRF24L01無線通訊模塊對象的操作問題。我們通過nRF24L01來收發數據就需要讀寫SPI接口,而這與特定的硬件平臺相關,所以我們將其作為對象的操作。而片選信號和使能信號以及中斷輸入信號也都與具體的操作平臺有關,所以我們也將其作為對象的操作。在進行相關操作時,我們需要控制時序,則需要使用延時操作,但延時處理總是依賴于具體的軟硬件平臺,所以我們將延時處理作為對象的操作。
根據上述我們對nRF24L01無線通訊模塊的分析,我們可以定義nRF24L01無線通訊模塊的對象類型如下:
/* 定義NRF24L01對象類型 */ typedef struct NRF24L01Object {uint8_t reg[8];//記錄前8個配置寄存器uint8_t (*ReadWriteByte)(uint8_t TxData);//聲明向nRF24L01讀寫一個字節的函數void (*ChipSelect)(NRF24L01CSType cs);//聲明片選操作函數void (*ChipEnable)(NRF24L01CEType en);//聲明使能及模式操作函數uint8_t (*GetIRQ)(void);//聲明中斷獲取函數void (*Delayms)(volatile uint32_t nTime);?????? //毫秒延時操作指針 }NRF24L01ObjectType;2.1.2、對象初始化
我們知道,一個對象僅作聲明是不能使用的,我們需要先對其進行初始化,所以這里我們來考慮nRF24L01無線通訊模塊對象的初始化函數。一般來說,初始化函數需要處理幾個方面的問題。一是檢查輸入參數是否合理;二是為對象的屬性賦初值;三是對對象作必要的初始化配置。據此我們設計nRF24L01無線通訊模塊對象的初始化函數如下:
/*nRF24L01對象初始化函數*/ NRF24L01ErrorType NRF24L01Initialization(NRF24L01ObjectType *nrf,???? //nRF24L01對象NRF24L01ReadWriteByte spiReadWrite,?? //SPI讀寫函數指針NRF24L01ChipSelect cs, //片選信號操作函數指針NRF24L01ChipEnable ce,????? //使能信號操作函數指針NRF24L01GetIRQ irq,?????????????????? //中斷信號獲取函數指針NRF24L01Delayms delayms? //毫秒延時) {int retry=0;if((nrf==NULL)||(spiReadWrite==NULL)||(ce==NULL)||(irq==NULL)||(delayms==NULL)){return NRF24L01_InitError;}nrf->ReadWriteByte=spiReadWrite;nrf->ChipEnable=ce;nrf->GetIRQ=irq;nrf->Delayms=delayms;if(cs!=NULL){nrf->ChipSelect=cs;}else{nrf->ChipSelect=NRF24L01CSDefault;}while(NRF24L01Check(nrf)&&(retry<5)){nrf->Delayms(300);retry++;}if(retry>=5){return NRF24L01_Absent;}for(int i=0;i<8;i++){nrf->reg[i]=0;}SetNRF24L01Mode(nrf,NRF24L01RxMode);return NRF24L01_NoError; }2.2、對象操作
我們已經完成了nRF24L01無線通訊模塊對象類型的定義和對象初始化函數的設計。但我們的主要目標是獲取對象的信息,接下來我們還要實現面向nRF24L01無線通訊模塊的各類操作。
2.2.1、讀操作
nRF24L01無線通訊模塊有很多的寄存器,所謂讀操作就是對這些寄存器的讀取過程。這個過程就是使用前面我們介紹的命令去獲取不同寄存器的數值。具體的時序過程如下所示:
根據上述時序圖以及各寄存器的定義,我們將讀nRF24L01無線通訊模塊寄存器的方式分為兩類:一類是讀普通的單字節寄存器,這些寄存器主要與配置和狀態有關;另一類是讀多字節寄存器,這些寄存器與數據通訊相關。具體的實現如下:
/*讀取寄存器值*/ static uint8_t NRF24L01ReadRegigster(NRF24L01ObjectType *nrf,uint8_t reg) {uint8_t reg_val;???? ???nrf->ChipSelect(NRF24L01CS_Enable);???????????? //使能SPI傳輸????????????nrf->ReadWriteByte(reg);?????????? //發送寄存器號reg_val=nrf->ReadWriteByte(0XFF);? //讀取寄存器內容nrf->ChipSelect(NRF24L01CS_Disable);??????????? //禁止SPI傳輸return(reg_val);???????????????? ?????//返回狀態值 }/*在指定位置讀出指定長度的數據*/ static uint8_t NRF24L01ReadBuffer(NRF24L01ObjectType *nrf,uint8_t reg,uint8_t *pBuf,uint8_t len) {uint8_t status; ??????nrf->ChipSelect(NRF24L01CS_Enable);????????????? //使能SPI傳輸status=nrf->ReadWriteByte(reg);??? //發送寄存器值(位置),并讀取狀態值for(int i=0;i<len;i++){pBuf[i]=nrf->ReadWriteByte(0XFF);//讀出數據}nrf->ChipSelect(NRF24L01CS_Disable);??????????? //關閉SPI傳輸return status;??????????????????????? //返回讀到的狀態值 }2.2.2、寫操作
nRF24L01無線通訊模塊有很多的寄存器,所謂寫操作就是向這些寄存器寫值的過程。在寫寄存器之前一定要進入待機模式或掉電模式。雖然寄存器的位數等存在差異,但其操作過程基本是一樣的。具體的時序過程如下所示:
同樣的,根據上述時序圖以及各寄存器的定義,我們將寫nRF24L01無線通訊模塊寄存器的方式分為兩類:一類是寫普通的單字節寄存器,這些寄存器主要與配置和狀態有關;另一類是寫多字節寄存器,這些寄存器與數據通訊相關。具體的實現如下:
/*寫寄存器*/ static uint8_t NRF24L01WriteRegister(NRF24L01ObjectType *nrf,uint8_t reg,uint8_t value) {uint8_t status;nrf->ChipSelect(NRF24L01CS_Enable);???????????? //使能SPI傳輸status =nrf->ReadWriteByte(reg);?? //發送寄存器號nrf->ReadWriteByte(value);???????? //寫入寄存器的值nrf->ChipSelect(NRF24L01CS_Disable);??????????? //禁止SPI傳輸return(status);?????????????????????? //返回狀態值 }/*在指定位置寫指定長度的數據*/ static uint8_t NRF24L01WriteBuffer(NRF24L01ObjectType *nrf,uint8_t reg, uint8_t *pBuf, uint8_t len) {uint8_t status;nrf->ChipSelect(NRF24L01CS_Enable);???????????? //使能SPI傳輸status = nrf->ReadWriteByte(reg);? //發送寄存器值(位置),并讀取狀態值for(int i=0; i<len; i++){nrf->ReadWriteByte(pBuf[i]);???? //寫入數據?????}nrf->ChipSelect(NRF24L01CS_Disable);??????????? //關閉SPI傳輸return status;??????????????????????? //返回讀到的狀態值 }3、驅動的使用
前面我們已經設計并實現了nRF24L01無線通訊模塊的驅動程序,我們還需要驗證這一驅動程序的設計是否符合要求,所以在這一節中我們將基于nRF24L01無線通訊模塊的驅動程序設計一驗證應用。
3.1、聲明并初始化對象
使用基于對象的操作我們需要先得到這個對象,所以我們先要使用前面定義的nRF24L01無線通訊模塊類型聲明一個nRF24L01無線通訊模塊對象變量,具體操作格式如下:
NRF24L01ObjectType nrf;
聲明了這個對象變量并不能立即使用,我們還需要使用驅動中定義的初始化函數對這個變量進行初始化。這個初始化函數所需要的輸入參數如下:
NRF24L01ObjectType *nrf,nRF24L01對象
NRF24L01ReadWriteByte spiReadWrite,SPI讀寫函數指針
NRF24L01ChipSelect cs,片選信號操作函數指針
NRF24L01ChipEnable ce,使能信號操作函數指針
NRF24L01GetIRQ irq,中斷信號獲取函數指針
NRF24L01Delayms delayms,毫秒延時
對于這些參數,nRF24L01對象變量我們已經定義了。余下的參數是一些函數指針,這是我們需要定義的,并將函數指針作為參數。這幾個函數的類型如下:
//聲明向nRF24L01讀寫一個字節的函數 typedef uint8_t (*NRF24L01ReadWriteByte)(uint8_t TxData); //聲明片選操作函數 typedef void (*NRF24L01ChipSelect)(NRF24L01CSType cs); //聲明使能及模式操作函數 typedef void (*NRF24L01ChipEnable)(NRF24L01CEType en); //聲明中斷獲取函數 typedef uint8_t (*NRF24L01GetIRQ)(void); //毫秒延時操作指針 typedef void (*NRF24L01Delayms)(volatile uint32_t nTime);對于這幾個函數我們根據樣式定義就可以了,具體的操作可能與使用的硬件平臺有關系。片選操作函數用于多設備需要軟件操作時,如采用硬件片選可以傳入NULL即可。具體函數定義如下:
/* 基于HAL庫的SPI讀寫字節函數 */ static uint8_t NRF24L01ReadWrite(uint8_t txData) {uint8_t rxData=0;HAL_SPI_TransmitReceive(&nrf24l01hspi,&txData,&rxData,1,1000);return rxData; }/*實現片選*/ static void NRF24L01ChipSelectf(NRF24L01CSType cs) {if(NRF24L01CS_Enable==cs){HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(GPIOF, GPIO_PIN_4, GPIO_PIN_SET);} }/*實現使能*/ static void NRF24L01ChipEnablef(NRF24L01CEType en) {if(NRF24L01CE_Enable==en){HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_RESET);}else{HAL_GPIO_WritePin(GPIOF, GPIO_PIN_5, GPIO_PIN_SET);} }/*實現Ready狀態監視*/ static uint8_t NRF24L01GetIRQf(void) {return HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_0); }對于延時函數我們可以采用各種方法實現。我們采用的STM32平臺和HAL庫則可以直接使用HAL_Delay()函數。于是我們可以調用初始化函數如下:
NRF24L01Initialization(&nrf,NRF24L01ReadWrite,NRF24L01ChipSelectf,NRF24L01ChipEnablef,NRF24L01GetIRQf,HAL_Delay);
3.2、基于對象進行操作
我們定義了對象變量并使用初始化函數給其作了初始化。接著我們就來考慮操作這一對象獲取我們想要的數據。我們在驅動中已經將獲取數據并轉換為轉換值的比例值,接下來我們使用這一驅動開發我們的應用實例。
/*NRF24L01數據通訊*/ void NRF24L01DataExchange(void) {uint8_t txDatas[32]={0xAA};uint8_t rxDatas[32]={0x00};NRF24L01TransmitPacket(&nrf,txDatas);HAL_Delay(1);NRF24L01ReceivePacket(&nrf,rxDatas); }4、應用總結
我們已經設計并實現了nRF24L01無線通訊模塊的驅動程序,并且在次驅動程序的基礎上開發了簡單的測試應用。經測試,這一驅動的設計基本上是正確的。
在使用驅動時需注意,采用SPI接口的器件需要考慮片選操作的問題。如果片選信號是通過硬件電路來實現的,我們在初始化時給其傳遞NULL值。如果是軟件操作片選則傳遞我們編寫的片選操作函數。
在使用驅動時,驅動中修改接收和發送模式時采用的是直接寫入數值。其他的寄存器配置也基本都是直接寫入數值,如果需要修改則需要在源碼中修改。事實上,需要經常修改的可能性并不大,這也是我們寫固定值的原因。另外,驅動中配置的是CRC-16校驗,如果需要修改也是在源碼中修改數值。
歡迎關注:
?
總結
以上是生活随笔為你收集整理的外设驱动库开发笔记26:nRF24L01无线通讯驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Socket通用TCP通信协议设计及实现
- 下一篇: 外设驱动库开发笔记3:AD527x系列数