nRF24L01 是一款工作在 2.4~2.5GHz 世界通用 ISM 頻段的單片無線收發機晶片。無線收發機包括:頻率發生器、增強型 SchockBurstTM 模式控制器、功率放大器、晶體振盪器、調製器、解調器。輸出功率頻道選擇和協定的設置可以通過 SPI 介面進行設置。
方便的設置以及極低的功耗使得它被廣泛地應用於無線滑鼠、鍵盤;無線門禁、各種智慧物聯網設備通訊等方面。
參數
- 供電電壓:1.9 V~3.6V;
- 最大發射功率:0 dBm;
- 最大資料傳輸率:2000 kbps;
- 發射模式下電流消耗(0dBm時):11.3 mA;
- 接收模式下電流消耗(2000kbps):12.3 mA;
- 接收模式資料傳輸率為 1000kbps 下的靈敏度:-85 dBm;
- 掉電模式下電流消耗:900 nA。
連結
這裡我們借用論壇裡的照片來說一下接線:
- VCC <-> 3.3V
- GND <-> GND
- CE <-> D9
- CSN <-> D10
- MOSI<-> D11
- MISO<-> D12
- SCK <-> D13
- IRQ <-> 不接
實驗
由於 nRF24L01 是全雙工的通信模組,所以每一個模組既可以做發送端也可以做接收端。這裡我們先去下載 Mirf 庫備用。
首先是初始化配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
String code; void setup() { Serial.begin(9600); code = "A message from another B!!!";//准备要发送的字符串 Mirf.cePin = 9; //设置CE引脚为D9 Mirf.csnPin = 10; //设置CE引脚为D10 Mirf.spi = &MirfHardwareSpi; Mirf.init(); //初始化nRF24L01 Mirf.setTADDR((byte *)"A");//发送地址 A Mirf.setRADDR((byte *)"B");//接收地址 B Mirf.payload = 32;//窗口大小,最大32. 也就是一次收发的数组长度,两端需要一致 //发送通道,可以填0~128,收发必须一致。 Mirf.channel = 0; Mirf.config(); Serial.println("I'm Sender B..."); } |
當然,這是發送端,這裡接收端也是類似的,只不過位址要對應的改變:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void setup() { Serial.begin(9600); Mirf.cePin = 9; //设置CE引脚为D9 Mirf.csnPin = 10; //设置CE引脚为D10 Mirf.spi = &MirfHardwareSpi; Mirf.init(); //初始化nRF24L01 Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B Mirf.payload = 32;//窗口大小,最大32. 也就是一次收发的数组长度,两端需要一致 //发送通道,可以填0~128,收发必须一致。 Mirf.channel = 0; Mirf.config(); Serial.println("I'm Reciver A..."); } |
Mirf.init()一旦被調用,前邊的設置即不可更改,不過發送接收的名稱、視窗大小,頻道等都是可以即時改變的,這裡我們就不做代碼演示了,現在,我們來寫一個簡單的 echo ——即發送端 B 來發送我們預先寫好的字串,A 在接收到字串之後立即把內容發回給 B。
這裡我們先列舉一下 Mirf 庫常用到的方法:
- Mirf.isSending() 返回一個布林量顯示當前晶片是否正在發送資訊;
- Mirf.send() 發送 byte 類型陣列,即字串必須經過轉換才能發送;
- Mirf.getData() 從晶片裡獲取收到的資訊,同樣也是 byte 類型陣列;
- Mirf.dataReady() 判斷晶片是否有收到資訊。
由於發送接收資料有些複雜要轉換資料類型而不是直接發送字串,這裡我們封裝兩個函數來完成這個功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
String reciveDataString() {//从芯片里接收收到的信息 byte data[32];//缓存 String tmp; //字符串缓存 Mirf.getData(data); //接收数据到data数组 for (int i = 0; i < sizeof(data); ++i ) { //遍历data数组转换字符 tmp += char(data[i]); if(char(data[i]) == '\0')break;//拼接字符串直到完成 } return tmp; } void sendDataString(String str) {//发送数据,直接接受字符串形式参数 byte data[32]; //缓存 str.getBytes(data,32); //把字符串转换为byte数组,最大长度不超过32 //实际上这里应该手动实现超长字符串自动分片,但一般也用不到那么长吧…… Mirf.send(data);//调用类方法来发送数据 while(Mirf.isSending()) {}//判断是否发送完毕,如果还在发送就一直等待 } |
這樣,這個收發函數就封裝好了,現在我們來實現功能,首先是發送端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void loop() { sendDataString(code); String tmp; if(Mirf.dataReady()) //等待接收数据准备好 { tmp = reciveDataString(); Serial.println(tmp); } delay(1000); } |
然後是接收(echo)端:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void loop() { String tmp; if(Mirf.dataReady()) //等待接收数据准备好 { tmp = reciveDataString(); Serial.println(tmp); Serial.println("start sending!"); sendDataString(tmp); Serial.println("sent!"); } delay(200); } |
接下來則是把這兩套不同的代碼分別刷在兩個不同的 Arduino 開發板上,同時通電,你就可以在串口終端看到結果了!
特性
這裡有一點我們要提一下,這個 nRF24L01 無線模組是支援混合互聯的,也就是說,它是屬於那種「喊話」模式,即多個模組使用同一個接收標識也不會有問題,如果你這麼做,它們就會各自收到一條一模一樣的資訊。只要視窗大小相同通道一致,那麼對應位址的模組就會收到資訊而不存在衝突。
它沒有實現任何上層功能,除了最基本的比特發送和接收外,其他任何高級功能都需要你自行完成(比如分片、重傳以及其他類似 TCP/IP 的功能)。
說起傳輸距離,使用模組自帶小天線的話,確實不怎麼遠。
玄學
這位同學給出了一個解決辦法,但我沒有條件進行測試了,希望對大家有幫助。 :)
最後補充一句,收發模組的發送位址和接收位址的代碼執行順序不能相同,比如這樣:
1 2 3 4 5 |
<del> Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B ———————— Mirf.setRADDR((byte *)"B");//接收地址 B Mirf.setTADDR((byte *)"A");//发送地址 A</del> |
那麼最終的結果就是兩個模組無法互通——你必須把他們的順序對換,像這樣:
1 2 3 4 5 |
<del> Mirf.setRADDR((byte *)"A");//接收地址 A Mirf.setTADDR((byte *)"B");//发送地址 B ———————— Mirf.setTADDR((byte *)"A");//发送地址 A Mirf.setRADDR((byte *)"B");//接收地址 B</del> |
本文由 落格博客 原創撰寫:落格博客 » nRF24L01 無線收發模塊 Arduino
轉載請保留出處和原文鏈接:https://www.logcg.com/archives/1823.html
博主你好,我试验过你的代码,关于玄学部分,其实把两个地址名称定义长一点问题就决解了,而且可以不必在初始化时定义接收端地址,只要在执行子函数前定义接收端地址,这样能做到一对多通讯
謝謝,不過我手頭沒設備了也沒法測試,我加入到正文中吧:)
請問這式用什麼編譯程式寫的?
C ++
您好,很高興看到您的博客,我現在是浮點型的資料通過arduino和nrf24l01發送,請問浮點數據比如發送「21.23」,我是用這個浮點數據乘以100變成整形之後發送還是可以直接將21.23轉換成字串發送?
都可以,理論上浮點數也可以直接與string互換,你可以用C++試試看,如果可以互相轉換,就不必轉換整形。不過有一點,如果你確實需要固定格式的浮點數,那還是處理一下再轉字串比較好。