這篇文章來源于DevicePlus.com英語網站的翻譯稿。
在上一個教程中,我們介紹了ESP8266-01(ESP8266 設置教程)。這是一款小巧的WiFi模塊,能夠讓用戶輕松地為他們的項目添加WiFi功能。今天,我們將討論nRF24L01+ RF 模塊。該模塊是ESP8266 ESP-01的姊妹模塊,旨在讓用戶在項目中集成無線射頻通信功能。nRF24L01+和ESP8266 ESP-01具有相似的外形尺寸和引腳布局(乍一看甚至完全相同)。但是兩者的控制方式和功能完全不同。在本教程中,我們將介紹使用該RF模塊所需的基礎知識,同時還會介紹該模塊如何與其他RF模塊和微控制器通信。就本教材而言,我們將演示該模塊如何與Arduino Uno微控制器相連。
nRF24L01+基于Nordic Semiconductor nRF24L01+,是一種“用于2.4GHz ISM(工業、科學和醫療)頻段的RF收發器IC”。
2.4GHz ISM 頻段運行
Vcc標稱電壓為3.3V(耐壓輸入5V)
片上穩壓
無線傳輸速率為250kbps、1Mbps、2Mbps
超低運行功耗
6個數據通道
首先,我們來介紹以下該模塊的硬件部分。與ESP-01類似,該RF模塊配有4×2公頭接口。然而,模塊的實際引腳布局與ESP-01模塊不同,這是因為該RF模塊利用不同的通信協議——利用不同的與其他設備通信。如果您想了解有關SPI協議的更多信息,請查看我們的Arduino Arduino通信協議教程!
RF模塊的引腳布局如下圖所示。此信息來源于Addicore。
圖1.nRF24L01+ RF模塊的引腳說明/ ?Addicore
根據設置,該RF模塊屬于SPI從器件,這意味著它只能與具有專用SPI通信線路的器件一起工作。因此,圖中顯示的SPI MOSI、MISO和SCK(時鐘)引腳必須連接到微控制器上的相應引腳。在Arduino上,這些引腳的定義如下:
MOSI: Arduino D11
MISO: Arduino D12
SCK: Arduino D13
CE和CSN引腳可以連到Arduino上的任何輸出GPIO引腳。在軟件中,SPI通信初始化時系統會指定這些引腳。
RF模塊與Arduino之間的連接示例如下:
圖2.CE和CSN引腳(圖中的黃色和綠色線)可以連至任意兩個未使用的數字引腳。在軟件中,您應該在構造RF模塊對象時指定這些引腳。
為了連接Arduino與該模塊,我們將使用TMRh20的RF24庫。該庫能夠方便地將RF模塊和MCU之間的低級通信打包成一個易于使用的C++類。
開始使用該模塊之前,我們將首先介紹一些背景基礎知識。在美國,射頻設備能夠使用的頻率僅限于FCC分配的頻率范圍。ISM頻段是為科學和醫療儀器保留的一個頻率范圍,我們的RF模塊將使用該ISM范圍內的頻率進行通信。使用RF模塊時,我們無需了解這些頻率的細節或者通信是如何在這些頻率上發生的。我們將專注于可以控制的無線RF通信內容。
如果滾動瀏覽RF24庫文件,您會注意到里面有許多參數可以設置。關鍵參數如下所示:
信道:通信發生的特定頻率信道(頻率映射為0到125之間的整數)
讀取通道:讀取通道是指一個唯一的24位、32位或40位地址,模塊從該地址讀取數據
寫入通道:寫入通道是模塊寫入數據的唯一地址
功放(PA)級別:PA級別負責設定芯片的功耗,從而設定傳輸功率。就本教程而言(與Arduino一起使用),我們將使用最小功率設置。
RF24庫文檔頁面提供了一些入門的優秀示例代碼。示例項目的地址如下:https://tmrh20.github.io/RF24/examples.html
接下來,我們在“Getting Started”Arduino代碼中查看一下上文列出的參數是如何初始化的。“Getting Started”(入門)代碼的地址如下:
https://tmrh20.github.io/RF24/GettingStarted_8ino-example.html
GettingStarted.ino
/* * Getting Started example sketch for nRF24L01+ radios * This is a very basic example of how to send data from one node to another * Updated: Dec 2014 by TMRh20 */ #include #include "RF24.h" /****************** User Config ***************************/ /*** Set this radio as radio number 0 or 1 ***/ bool radioNumber = 0; /* Hardware configuration: Set up nRF24L01 radio on SPI bus plus pins 7 & 8 */ RF24 radio(7,8); /**********************************************************/ byte addresses[][6] = {"1Node","2Node"}; // Used to control whether this node is sending or receiving bool role = 0; void setup() { Serial.begin(115200); Serial.println(F("RF24/examples/GettingStarted")); Serial.println(F("*** PRESS 'T' to begin transmitting to the other node")); radio.begin(); // Set the PA Level low to prevent power supply related issues since this is a // getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default. radio.setPALevel(RF24_PA_LOW); // Open a writing and reading pipe on each radio, with opposite addresses if(radioNumber){ radio.openWritingPipe(addresses[1]); radio.openReadingPipe(1,addresses[0]); }else{ radio.openWritingPipe(addresses[0]); radio.openReadingPipe(1,addresses[1]); } // Start the radio listening for data radio.startListening(); } void loop() { /****************** Ping Out Role ***************************/ if (role == 1) { radio.stopListening(); // First, stop listening so we can talk. Serial.println(F("Now sending")); unsigned long start_time = micros(); // Take the time, and send it. This will block until complete if (!radio.write( &start_time, sizeof(unsigned long) )){ Serial.println(F("failed")); } radio.startListening(); // Now, continue listening unsigned long started_waiting_at = micros(); // Set up a timeout period, get the current microseconds boolean timeout = false; // Set up a variable to indicate if a response was received or not while ( ! radio.available() ){ // While nothing is received if (micros() - started_waiting_at > 200000 ){ // If waited longer than 200ms, indicate timeout and exit while loop timeout = true; break; } } if ( timeout ){ // Describe the results Serial.println(F("Failed, response timed out.")); }else{ unsigned long got_time; // Grab the response, compare, and send to debugging spew radio.read( &got_time, sizeof(unsigned long) ); unsigned long end_time = micros(); // Spew it Serial.print(F("Sent ")); Serial.print(start_time); Serial.print(F(", Got response ")); Serial.print(got_time); Serial.print(F(", Round-trip delay ")); Serial.print(end_time-start_time); Serial.println(F(" microseconds")); } // Try again 1s later delay(1000); } /****************** Pong Back Role ***************************/ if ( role == 0 ) { unsigned long got_time; if( radio.available()){ // Variable for the received timestamp while (radio.available()) { // While there is data ready radio.read( &got_time, sizeof(unsigned long) ); // Get the payload } radio.stopListening(); // First, stop listening so we can talk radio.write( &got_time, sizeof(unsigned long) ); // Send the final one back. radio.startListening(); // Now, resume listening so we catch the next packets. Serial.print(F("Sent response ")); Serial.println(got_time); } } /****************** Change Roles via Serial Commands ***************************/ if ( Serial.available() ) { char c = toupper(Serial.read()); if ( c == 'T' && role == 0 ){ Serial.println(F("*** CHANGING TO TRANSMIT ROLE -- PRESS 'R' TO SWITCH BACK")); role = 1; // Become the primary transmitter (ping out) }else if ( c == 'R' && role == 1 ){ Serial.println(F("*** CHANGING TO RECEIVE ROLE -- PRESS 'T' TO SWITCH BACK")); role = 0; // Become the primary receiver (pong back) radio.startListening(); } } } // Loop
首先,我們需要注意文件頂部的兩個C++ #include指令:一個用于包含Arduino SPI庫(前文提到RF模塊使用SPI與Arduino通信),另一個用于包含RF24庫。
#include
#include “RF24.h”
接下來,我們需要注意構造RF24對象的這行代碼:RF24 radio(7,8); 傳遞給構造函數的兩個參數是連至模塊的CE和CSN數字引腳。MOSI、MISO和SCK引腳必須分別是數字引腳11、12和13,可是CE和CSN引腳可以連接任意兩個數字引腳!
接下來,我們來看一下讀寫通道地址。您可能猜到了,寫入和讀取通道地址在兩個彼此通信的無線設備之間是互換的,因為一個無線設備的寫入通道是另一個的讀取通道。無線通訊地址的大小為24位、32位或40位。在示例代碼中,這些地址是從C++字符串文字轉換而來,但您也可以以二進制或十六進制格式指定它們。比如,指定為十六進制的40位地址可以是0xF0F0F0F0F0。存儲寫入和讀取通道地址時,一個良好的編程實踐是將兩個值放在一個數組中。在示例代碼中,寫入和讀取通道的地址存儲在名為“地址”的字節數組中。
在void setup()方法中,我們需要說明如何使用地址通道參數和其他參數初始化無線設備。
首先,系統調用RF24::begin()方法。針對radio對象調用其它RF24庫之方法之前,必須先調用begin() ,這是因為該方法負責初始化RF芯片的運行。
接下來,系統調用RF24::setPALevel() 方法,以設定功放(PA)級別。為了指定功放級別,RF24庫提供了不同的常數值。更高的PA級別意味著模塊可以實現更長距離的通信,但是在運行期間會消耗更多的電流。作為入門程序,我們將RF_24_LOW常數作為一個參數傳遞給setPALevel()方法,因為兩個通信模塊之間的距離不會很大。一般來講,該RF模塊與Arduino板配合使用時,我們應該保持PA級別盡可能地低,從而減少Arduino穩壓電源的電流消耗。
接下來,我們將討論如何初始化寫入和讀取通道。我們已經將寫入和讀取通道定義為一些字節值。現在,我們必須將這些定義傳遞給radio對象,這樣它才會知道寫入和讀取通道地址。系統用openWritingPipe() 方法設定寫入通道;用openReadingPipe()方法設定讀取通道。打開寫入和讀取通道的示例如下:
radio.openWritingPipe(addresses[1]);
radio.openReadingPipe(1, addresses[0]);
請注意,我們還必須為openReadingPipe()方法傳遞一個額外的整數參數,指明初始化哪個讀取通道。這是因為RF模塊在給定時間最多可以打開6個讀取通道!
示例代碼通過一個role布爾值來適當分配讀取和寫入通道值。根據role的值,程序確定RF模塊是ping設備還是pong設備。您的其他項目也可以使用類似代碼。但是,要確保兩個設備的讀寫地址互換,否則無法實現數據傳輸或讀取!
調用RF24::startListening()方法之后,無線模塊開始偵聽。需要注意的是,指示RF模塊開始監聽數據之前必須初始化讀取通道(即調用startListening()方法之前必須先調用openReadingPipe()方法!)
類似地,RF24類還提供了一個stopListening()方法,無線模塊開始寫入之前必須首先調用該方法。
在示例代碼中,您可能會注意到程序利用RF24::available()方法告知無線電模塊檢查傳入的數據。這與我們之前見過的Serial::available()和SoftwareSerial::available() 方法類似——如果RF連接上的數據可用,那么available()方法返回真,然后就可以讀取數據了。
最后,RF24類提供了實際數據寫入和讀取的方法。RF24::write()與RF24::read()方法的參數包括(1)指向與傳輸數據類型相同的變量的指針,以及(2)傳輸數據的大小。在read()方法中,指針指向的變量負責接收正在讀取的數據。在write()方法中,指針指向的變量負責保存正在寫入的數據。在這兩種方法中,我們必須確保指針指向與傳輸數據類型相同的變量,并且傳遞給方法的大小實際上反映了數據的大小。將不正確的類型或大小值傳遞給read()和write()方法可能會產生不希望的值截斷,從而導致傳輸的數據無用。在“Getting Started”代碼中,需要傳輸的數據是一個unsigned long類型。因此,傳遞給read()和write()方法的指針參數也應該指向一個unsigned long型的變量。很明顯,傳輸數據的大小始終是unsigned long類型變量的大小。在這種情況下,大小并不需要用一個整數進行傳遞,大小(size)參數可以簡單地寫為sizeof(unsigned long)。
“Getting Started”(入門)代碼中唯一沒有涉及的參數是信道(communication channel)。如果需要指定具體信道(比如您有多個RF網絡,不希望彼此干擾),那么可以將一個8位的整數參數傳遞給RF24::setChannel() 方法,以設置信道。其代碼示例如下:radio.setChannel(10);
請將兩個RF模塊連到兩個獨立的Arduino板上,并且都上傳“Getting Started”(入門)代碼(您必須將其中一塊板的role布爾值改為1)。您現在應該能夠根據相應的ping時間發送消息并且接收返回的消息了!下圖是“ping”和“pong”兩個串口監視器的并排截圖:
圖3.左:“ping”監視器語句(設備1);右:“pong”監視器語句(設備0)
恭喜您完成nRF24L01+教程的學習!您現在已經掌握了使用這些漂亮RF模塊研發相關項目的技能和知識了!要了解涉及這些RF模塊的項目,您可以瀏覽Device Plus博客!
審核編輯:湯梓紅
-
微控制器
+關注
關注
48文章
7562瀏覽量
151503 -
通信協議
+關注
關注
28文章
886瀏覽量
40316 -
RF
+關注
關注
65文章
3055瀏覽量
167079 -
nRF24L01
+關注
關注
17文章
330瀏覽量
69492
發布評論請先 登錄
相關推薦
評論