2.4G无线芯片NRF24L01 驱动源码及详解
概述
? ? NRF24L01/NRF24L01+ 是挪威NordicVLSI公司出品的2.4G射頻收發器件,主打高速率,低功耗,應用簡單功能。其性能卓越(我說的原廠真貨),深受國內開發者歡迎。原廠芯片價格一直比較貴,加上國內不往優質走就往便宜靠這德性,市場上有各種假冒貨。也有專業模仿的替換芯片,價格親民同時品質較好。
? ? 說簡單易用是相對其它射頻芯片,射頻芯片本身有一定的門檻,小白階段調試時遇到收、發不通問題時,缺乏了調試手段,大多都傻眼了。本文盡管往細里講,希望別人看后少進坑。
硬件
? ? 射頻芯片對硬件要求比較高,而本人是軟件開發,只能簡單講講。
第一個事情是天線要嚴格按照參考電路來,除非你是大牛,否則別亂改。我硬件同事用頻譜分析儀分析過不同的板子,得到的結論就是人家的參考電路還是比較優質的。
第二個事情就是我們用國產的芯片時,射頻連接不穩定,有丟包問題。經過定位是VCC沒有經過電容濾波。
準備知識
? ? 我的項目是兩個NRF24L01芯片分別作為主端和從端,雙向通訊。而芯片同一時間只能收,或者只能發,也是就半雙工。要實現全雙工只能通過軟件分時切換。我沒有使用芯片的自動應答,自動重發功能,都是由上層軟件保障的。更沒有用到Enhanced ShockBurst功能。
開工第一步就是看芯片資料,要了解這個芯頻率范圍,最好設置時能繞過wifi。還有射頻參數及對應寄存器的值。發送及接收緩存,通道個數,中斷接收等等都要了解清楚,否則容易出問題。
開工前要了解SPI接口,如下圖(紅框里面的幾個管腳):
如果是使用MCU的SPI接口,就要先初始化。如果像我這樣直接用GPIO模擬就要初始化管腳的GPIO屬性。都要滿足以下時序圖。
后面的代碼會注釋如何實現這個時序,這里先跳過。像我這樣要使這個芯片TX、RX切換,就一定要了解以下這個圖:
上圖講述了芯片的狀態機,RX、TX狀態切換是需要一點時間的,要遵守。否則會出現問題,我當時軟件也有些bug,兩個問題加在一起出現各種沒收到,發多了塞在rx管道里面,flush還flush不掉之類的詭異現象,說多了都是淚。
下面直接貼代碼,注釋豐富,僅供參考。
/********************************************************************************** 文件描述 : NRF 24L01芯片驅動 作者 : gavinpeng 時間 : 2017.6.8 ***********************************************************************************/// 頭文件,略。。。#define NRF_ADR_WIDTH 5 // 5 bytes TX(RX) address width #define TX_PLOAD_WIDTH 32 // 32 bytes TX payload #define RX_PLOAD_WIDTH 32 // 32 bytes RX payload#define NRF_RX_DR (1 << 6) // Data Ready Rx FIFO interrupt #define NRF_TX_DS (1 << 5) // Data Send Tx FIFO #define NRFW_REG 0x20 // Define write command to register #define RD_RX_PLOAD_W 0x60 #define RD_RX_PLOAD 0x61 // Define RX payload register address #define WR_TX_PLOAD 0xA0 // Define TX payload register address #define FLUSH_TX 0xE1 // Define flush TX register command #define FLUSH_RX 0xE2 // Define flush RX register command #define REUSE_TX_PL 0xE3 // Define reuse TX payload register command #define NOP 0xFF // Define No Operation, might be used to read status register//***************************************************// // SPI(nRF24L01) registers(addresses) #define CONFIG 0x00 // 'Config' register address #define EN_AA 0x01 // 'Enable Auto Acknowledgment' register address #define EN_RXADDR 0x02 // 'Enabled RX addresses' register address #define SETUP_AW 0x03 // 'Setup address width' register address #define SETUP_RETR 0x04 // 'Setup Auto. Retrans' register address #define RF_CH 0x05 // 'RF channel' register address #define RF_SETUP 0x06 // 'RF setup' register address #define STATUS 0x07 // 'Status' register address #define OBSERVE_TX 0x08 // 'Observe TX' register address #define CD 0x09 // 'Carrier Detect' register address #define RX_ADDR_P0 0x0A // 'RX address pipe0' register address #define RX_ADDR_P1 0x0B // 'RX address pipe1' register address #define RX_ADDR_P2 0x0C // 'RX address pipe2' register address #define RX_ADDR_P3 0x0D // 'RX address pipe3' register address #define RX_ADDR_P4 0x0E // 'RX address pipe4' register address #define RX_ADDR_P5 0x0F // 'RX address pipe5' register address #define TX_ADDR 0x10 // 'TX address' register address #define RX_PW_P0 0x11 // 'RX payload width, pipe0' register address #define RX_PW_P1 0x12 // 'RX payload width, pipe1' register address #define RX_PW_P2 0x13 // 'RX payload width, pipe2' register address #define RX_PW_P3 0x14 // 'RX payload width, pipe3' register address #define RX_PW_P4 0x15 // 'RX payload width, pipe4' register address #define RX_PW_P5 0x16 // 'RX payload width, pipe5' register address #define FIFO_STATUS 0x17 // 'FIFO Status Register' register address #define DNYPD 0x1C // 動態包長使能,對應于各個通道 #define NRF_FEATURE 0x1D // 特性#define NRF_RX_EMPTY 0x0E // rXFIFO為空 #define STA_MARK_RX 0X40 #define STA_MARK_TX 0X20 #define STA_MARK_MX 0X10 // Define SPI pins sbit CE = P1^2; // Chip Enable pin signal (output) sbit CSN = P1^1; // Slave Select pin, (output to CSN, nRF24L01) sbit SCK = P1^0; // Serial Clock pin, (output) /* 片選函數 */ #define nrf_cs_enable() (CSN = 0) #define nrf_cs_disable() (CSN = 1)/* CE(去)使能函數 */ #define nrf_ce_enable() (CE = 1) #define nrf_ce_disable() (CE = 0)extern void delay_ms(UINT16 n); extern void delay_us(UINT16 n);/********************************************************** 函數描述 : spi接口 gpio 管腳初始化 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_spi_io_init(void) { P11_PushPull_Mode; // P11 (CS for tx) 推挽輸出 P12_PushPull_Mode; // P12 (CE for tx) 推挽輸出 P10_PushPull_Mode; // P10 (SPCLK) 推挽輸出 P00_PushPull_Mode; // P00 (MOSI) 推挽輸出 P01_Input_Mode; // P01 (MISO) 輸入 P11 = 1; //CS for txP12 = 0; //CE for tx P10 = 0; //SPCLKP00 = 0; //MOSIclr_SPIEN; // 禁止SPI } /********************************************************** 函數描述 : spi接口讀寫一個字節 輸入參數 : val -- 要寫出spi 數據寄存器的值 輸出參數 : 返回值 : spi 數據寄存器讀出數據 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ unsigned char nrf_spi_rw(unsigned char val) {unsigned char i; for ( i = 0; i < 8; i++ ) // 循環8次 { MOSI = (val & 0x80); // byte最高位輸出到MOSI val <<= 1; // 低一位移位到最高位 SCK = 1; // 拉高SCK,nRF24L01從MOSI讀入1位數據,同時從MISO輸出1位數據 val |= MISO; // 讀MISO到byte最低位 SCK = 0; // SCK置低 }return(val); // 返回讀出的一字節 }/********************************************************** 函數描述 : 寫一段寄存器 輸入參數 : reg -- 寄存器地址buf -- 數據首指針num -- 要寫出的數據的字節數 輸出參數 : 返回值 : 狀態值 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_buf_write(unsigned char reg, unsigned char *buf, unsigned char num) {unsigned char i;nrf_cs_enable();/* 寫命令要求最高3個bit的值是001 */(void)nrf_spi_rw(((reg & 0x1F) | NRFW_REG));for ( i = 0; i < num; i++ )(void)nrf_spi_rw(buf[i]);nrf_cs_disable();return; }/********************************************************** 函數描述 : 讀芯片 狀態 寄存器的值 輸入參數 : 輸出參數 : 返回值 : 寄存器值 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ unsigned char nrf_reg_read_status(void) {unsigned char val;nrf_cs_enable();/* 讀狀態寄存器,省略了" & 0x1F" */(void)nrf_spi_rw(STATUS);val = nrf_spi_rw(NOP);nrf_cs_disable();return val; }/********************************************************** 函數描述 : 讀芯片寄存器的值 輸入參數 : reg -- 寄存器地址 輸出參數 : 返回值 : 寄存器值 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ unsigned char nrf_reg_read(unsigned char reg) {nrf_cs_enable();/* 讀命令要求最高3個bit都是0 */(void)nrf_spi_rw(reg & 0x1F);reg = nrf_spi_rw(NOP);nrf_cs_disable();return reg; }/********************************************************** 函數描述 : 寫芯片寄存器的值 輸入參數 : reg -- 寄存器地址, 帶有讀寫標志value -- 要寫入的值 輸出參數 : 返回值 : 寄存器值 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_reg_write(unsigned char reg, unsigned char value) {nrf_cs_enable();/* 寫命令要求最高3個bit的值是001 */(void)nrf_spi_rw(((reg & 0x1F) | NRFW_REG));(void)nrf_spi_rw(value);nrf_cs_disable();return; }/********************************************************** 函數描述 : 讀接收到的數據 輸入參數 : 輸出參數 : buf -- 緩存 返回值 : 高8位是通道號,低8位是字節數 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ unsigned char nrf_payload_read(unsigned char *buf) {unsigned char i;unsigned char len;/* 1. 讀數據長度 */nrf_cs_enable();(void)nrf_spi_rw(RD_RX_PLOAD_W);len = nrf_spi_rw(NOP);nrf_cs_disable();if ( len == 0 )return 0;/* 2. 讀數據 */nrf_cs_enable();(void)nrf_spi_rw(RD_RX_PLOAD); /* 讀payload命令 */for ( i = 0; i < len; i++ )buf[i] = nrf_spi_rw(NOP);nrf_cs_disable();return len; }/********************************************************** 函數描述 : 發送數據出去 輸入參數 : buf -- 緩存num -- 要寫出內容字節數 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_payload_write(unsigned char *buf, unsigned char num) {unsigned char i;nrf_cs_enable(); /* 使能 + 延時 *//* 寫命令 */(void)nrf_spi_rw(WR_TX_PLOAD);/* 寫數據 */for ( i = 0; i < num; i++ )(void)nrf_spi_rw(buf[i]);nrf_cs_disable(); }/********************************************************** 函數描述 : flush rx fifo 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_flush_buf_rx(void) {nrf_cs_enable();(void)nrf_spi_rw(FLUSH_RX);nrf_cs_disable(); }/********************************************************** 函數描述 : flush tx fifo 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_flush_buf_tx(void) {nrf_cs_enable();(void)nrf_spi_rw(FLUSH_TX);nrf_cs_disable(); }/********************************************************** 函數描述 : 將模塊初始化為接收模式 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_init_mode_rx(void) {unsigned char nrf_buf[RX_PLOAD_WIDTH];unsigned char nrf_buf_len = 0;unsigned char status = 0;nrf_ce_disable();nrf_reg_write(CONFIG, 0x7F);// power up ,RX模式nrf_ce_enable();/* 沒有接收標志但是有通道號可能是fifo error了, 要flush */status = nrf_reg_read_status();if ( ((status & 0x0E) == 0) && ((status & 0x40) == 0) ) {nrf_buf_len = nrf_payload_read(nrf_buf);nrf_flush_buf_rx();} }/********************************************************** 函數描述 : 初始化nrf 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ int nrf_init(void) {unsigned char chn = 0;unsigned char addr_rx[5] = {0xA5, 0x5A, 0xA5, 0x01, 0x01}; // 重點:對端的rx,tx地址跟本端交換unsigned char addr_tx[5] = {0xA5, 0x5A, 0xA5, 0x02, 0x02};chn = nrf_chn_get(); // 通道號從別的地方獲取nrf_ce_disable();nrf_buf_write(TX_ADDR, addr_tx, NRF_ADR_WIDTH); // 要發送的目的地址nrf_buf_write(RX_ADDR_P0, addr_rx, NRF_ADR_WIDTH); // 自身(接收)地址// 射頻參數要求收、發雙方要一致nrf_reg_write(EN_AA, 0x0); // 去使能通道0的自動應答nrf_reg_write(EN_RXADDR, 0x01); // Enable Pipe0nrf_reg_write(SETUP_RETR, 0x10); // 500us + 86us, 0 retrans... nrf_reg_write(RF_CH, chn); // 接收頻率 2400 + chn MHznrf_reg_write(RX_PW_P0, TX_PLOAD_WIDTH); // Select same RX payload width as TX Payload widthnrf_reg_write(RF_SETUP, 0x07); // 0x07 TX_PWR:0dBm, Datarate:1Mbps, LNA:HCURRnrf_reg_write(DNYPD,0x01); // 0通道使能動態長度nrf_reg_write(NRF_FEATURE, 0x04); // 特性: 使能動態長度nrf_ce_enable();return 0; }/********************************************************** 函數描述 : 接收從端數據,這個函數會被頻繁調用 輸入參數 : 輸出參數 : 返回值 : 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ void nrf_recv_data(void) {unsigned char nrf_buf[RX_PLOAD_WIDTH];unsigned char status;unsigned char nrf_buf_len;// 讀狀態寄存器,如果有數據就接收處理status = nrf_reg_read_status();if ( status & 0x40 ){nrf_buf_len = nrf_payload_read(nrf_buf);nrf_reg_write(STATUS, status);// 長度檢查。要注意,因為本項目沒有小于2字節的數據幀,所以認為是異常。if ( (nrf_buf_len < 2) || (nrf_buf_len > RX_PLOAD_WIDTH) ){nrf_flush_buf_rx();return;}else{// 這里是處理接收到的數據chan_nrf_recv(nrf_buf, nrf_buf_len);}} }/********************************************************** 函數描述 : 向對端發送數據 輸入參數 : buf -- 緩存len -- 要寫入內容的字節數 輸出參數 : 返回值 : 成功返回0,失敗返回<0 作者/時間: gavinpeng / 2017.6.8 ************************************************************/ int nrf_send_data(unsigned char *buf, unsigned char len) {char ret;unsigned char status = 0;unsigned short count = 0;nrf_ce_disable();/* 數據內容 */nrf_payload_write(buf, len);nrf_reg_write(CONFIG, 0x7E); // power up ,TXnrf_ce_enable();delay_us(10);nrf_ce_disable();// 等發送完成count = 0;ret = -1;while ( ++count < 30000 ){status = nrf_reg_read_status();if ( status & STA_MARK_TX ){nrf_reg_write(STATUS, status);ret = 0;break;}else if ( status & STA_MARK_MX ){nrf_reg_write(STATUS, status);nrf_flush_buf_tx();break;}}return ret; }上面的代碼有要注意的地方。
? ? 一是發送數據后,通過讀狀態寄存器知道發送完成,然后返回。實測是對端在更早一點的時候就已經讀到數據,大概是這個樣子:"一一_____||___一一",紅色部分代表對端已收到,本端延時了一點才有寄存器狀態變更(我沒有使用中斷的方式)。
? ? 二是跟我沒有使用中斷的方式有關,我用的輪詢方式不斷去查是否有收到數據,所以會很頻繁調用nrf_recv_data函數。
打完收工。
自己用SI24R1(國產NRF24L01)芯片做的基于2.4G無線串口USB轉TTL工具:
總結
以上是生活随笔為你收集整理的2.4G无线芯片NRF24L01 驱动源码及详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 亲爱的热爱的CTF
- 下一篇: 英伟达gt和gtx的区别