В этом уроке мы научимся осуществлять беспроводную связь между двумя платами Arduino с помощью модулей приемопередатчика nRF24L01. Он включает в себя все, что нам нужно знать о модуле приемопередатчика nRF24L01: распиновку модуля, принцип работы, схема его подключения к плате Arduino и несколько примеров кода.
Модуль nRF24L01 является очень популярным выбором для беспроводной связи при использовании Arduino. Ранее на нашем сайте мы уже рассматривали ряд проектов с его подключением к плате Arduino:
- пульт дистанционного управления дроном на Arduino;
- радиосвязь между Raspberry Pi и Arduino Uno с помощью модулей nRF24L01;
- радиостанции большого радиуса действия на Arduino и модулях nRF24L01;
- передача данных на смартфон с помощью Arduino, модуля NRF24L01 и Bluetooth (BLE).
Для объяснения беспроводной связи с помощью модулей nRF24L01 мы приведем два примера: первый будет отправлять простое сообщение «Hello World» от одной платы Arduino к другой, а во втором примере у нас будет двунаправленная связь между платами Arduino, где с помощью джойстика на первой плате Arduino мы будем управлять серводвигателем на второй плате Arduino, и наоборот, с помощью кнопки на второй Arduino мы будем управлять светодиодом на первой Arduino.
Необходимые компоненты
- Плата Arduino Uno (купить на AliExpress).
- Модуль nRF24L01 (купить на AliExpress).
- Макетная плата и соединительные провода.
Модуль приемопередатчика nRF24L01
Давайте подробнее рассмотрим модуль трансивера (приемопередатчика, радиочастотного модуля) NRF24L01. Он использует диапазон 2,4 ГГц и может работать со скоростью передачи данных от 250 Кбит/с до 2 Мбит/с. При использовании на открытом пространстве и при более низкой скорости передачи данных его дальность действия может достигать 100 метров.
Характеристики модуля:
Диапазон частот | Диапазон ISM 2,4–2,5 ГГц |
Скорость передачи данных | 250 Кбит/с/1 Мбит/с/2 Мбит/с |
Макс. выходная мощность | 0 дБм |
Рабочее напряжение | 1,9–3,6 В |
Макс. рабочий ток | 12,3 мА |
Ток в режиме ожидания | 22 мкА |
Логические входы | устойчивы к 5 В |
Дальность связи | 100 м (открытое пространство) |
Как видите, весьма неплохие характеристики за те деньги, которые просят за данный модуль.
Как это работает
Модуль может использовать 125 различных каналов, что дает возможность иметь сеть из 125 независимо работающих модемов в одном месте. Каждый канал может иметь до 6 адресов, или каждое устройство может одновременно взаимодействовать с 6 другими устройствами.
Потребляемая мощность этого модуля во время передачи составляет всего около 12 мА, что даже ниже, чем у одного светодиода. Рабочее напряжение модуля составляет от 1,9 до 3,6 В, но хорошо то, что остальные контакты допускают логику 5 В, поэтому мы можем легко подключить его к Arduino без использования каких-либо преобразователей логических уровней.
Три из этих контактов предназначены для связи по интерфейсу SPI, и их необходимо подключить к контактам SPI Arduino, но учтите, что каждая плата Arduino имеет разные контакты SPI. Выводы CSN и CE можно подключить к любому цифровому выводу платы Arduino и они используются для установки модуля в режим ожидания или активный режим, а также для переключения между режимом передачи и режимом команд. Последний вывод — это вывод прерывания, который не обязательно использовать.
Варианты модулей nRF24L01
Существует несколько вариантов модулей NRF24L01. Самый популярный – со встроенной антенной. Это делает модуль более компактным, но с другой стороны снижает дальность передачи до расстояния около 100 метров.
Второй вариант вместо встроенной антенны имеет разъем SMA, к которому можно прикрепить внешнюю антенну для увеличения дальности передачи.
Третий вариант, показанный здесь, помимо внешней антенны, имеет чип RFX2401C, который включает в себя PA (Power Amplifier - усилитель мощности) и LNA (Low-Noise Amplifier - малошумящий усилитель). Это усиливает сигнал модуля NRF24L01 и обеспечивает еще большую дальность передачи - до 1000 метров на открытом пространстве.
Распиновка модуля nRF24L01
Приведена на следующем рисунке.
Оба модуля, NRF24L01 и NRF24L01+ PA/LNA, имеют одинаковую распиновку, поэтому мы можем подключить их в нашей схеме одинаково.
Схема проекта
Схема подключения модуля nRF24L01 к плате Arduino приведена на следующем рисунке
Каждая плата Arduino имеет разные контакты SPI, поэтому имейте это в виду при подключении модулей к плате Arduino.
Arduino | SCK | MISO | MOSI | SS |
Uno | 13 | 12 | 11 | 10 |
Nano | 13 | 12 | 11 | 10 |
Mega | 52 | 50 | 51 | 53 |
Более подробно про работу с интерфейсом SPI в платах Arduino вы можете прочитать в этой статье.
Код Arduino и nRF24L01
Подключив модули NRF24L01 к платам Arduino, мы готовы создавать коды как для передатчика, так и для приемника.
Сначала нам нужно скачать и установить библиотеку RF24, которая существенно упростит нам написание программы для нашего проекта. Мы также можем установить эту библиотеку непосредственно из диспетчера библиотек Arduino IDE. Просто найдите «rf24», а затем найдите и установите файл «TMRh20, Avamander».
Вот два кода для беспроводной связи в нашем проекте (их описание приведено ниже).
Код передатчика
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* * Arduino Wireless Communication Tutorial * Example 1 - Transmitter Code * * by Dejan Nedelkovski, www.HowToMechatronics.com * * Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> RF24 radio(7, 8); // CE, CSN const byte address[6] = "00001"; void setup() { radio.begin(); radio.openWritingPipe(address); radio.setPALevel(RF24_PA_MIN); radio.stopListening(); } void loop() { const char text[] = "Hello World"; radio.write(&text, sizeof(text)); delay(1000); } |
Код приемника
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* * Arduino Wireless Communication Tutorial * Example 1 - Receiver Code * * by Dejan Nedelkovski, www.HowToMechatronics.com * * Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> RF24 radio(7, 8); // CE, CSN const byte address[6] = "00001"; void setup() { Serial.begin(9600); radio.begin(); radio.openReadingPipe(0, address); radio.setPALevel(RF24_PA_MIN); radio.startListening(); } void loop() { if (radio.available()) { char text[32] = ""; radio.read(&text, sizeof(text)); Serial.println(text); } } |
Описание работы кода
Сначала в коде программы нам нужно подключить библиотеку для работы с интерфейсом SPI и недавно установленную библиотеку RF24 и создать объект RF24 для работы с модулем. Двумя аргументами в нем являются контакты платы Arduino, к которым подключены контакты модуля CSN и CE.
1 |
RF24 radio(7, 8); // CE, CSN |
Далее нам нужно создать массив байтов, который будет представлять адрес, или так называемый канал, через который будут взаимодействовать два модуля.
1 |
const byte address[6] = "00001"; |
Мы можем изменить значение этого адреса на любую строку из 5 букв, и это позволяет выбрать, с каким приемником мы будем взаимодействовать, поэтому в нашем случае у нас будет один и тот же адрес как на приемнике, так и на передатчике.
В разделе setup() нам нужно инициализировать радиообъект и с помощью функции radio.openWritingPipe() установить адрес получателя (приемника), которому мы будем отправлять данные, 5-буквенную строку, которую мы установили ранее.
1 |
radio.openWritingPipe(address); |
С другой стороны, на приемнике, используя функцию radio.setReadingPipe(), мы устанавливаем тот же адрес и таким образом разрешаем связь между двумя модулями.
1 |
radio.openReadingPipe(0, address); |
Затем с помощью функции radio.setPALevel() мы устанавливаем уровень усилителя мощности, в нашем случае я устанавливаю его на минимум, так как мои модули расположены очень близко друг к другу.
1 |
radio.setPALevel(RF24_PA_MIN); |
Обратите внимание, что при использовании более высокого уровня рекомендуется использовать развязывающие конденсаторы между контактами GND и 3,3 В модулей, чтобы они имели более стабильное напряжение во время работы.
Далее у нас есть функция radio.stopListening(), которая устанавливает модуль в качестве передатчика, а с другой стороны, у нас есть функция radio.startListening(), которая устанавливает модуль в качестве приемника.
1 2 |
// at the Transmitter radio.stopListening(); |
1 2 |
// at the Receiver radio.startListening(); |
В секции цикла на передатчике мы создаем массив символов, которому присваиваем сообщение «Hello World». Используя функцию radio.write(), мы отправим это сообщение получателю. Первый аргумент здесь — это переменная, которую мы хотим отправить.
1 2 3 4 5 |
void loop() { const char text[] = "Hello World"; radio.write(&text, sizeof(text)); delay(1000); } |
Используя «&» перед именем переменной, мы фактически устанавливаем указание переменной, в которой хранятся данные, которые мы хотим отправить, и используя второй аргумент, мы устанавливаем количество байтов, которые мы хотим взять из этой переменной. В этом случае функция sizeof() получает все байты строки «text». В конце программы мы добавим задержку в 1 секунду.
Используя функцию radio.write(), мы можем отправлять максимум 32 байта за раз.
С другой стороны, на приемнике, в секции цикла с помощью функции radio.available() мы проверяем, есть ли данные для приема. Если это правда, сначала мы создаем массив из 32 элементов, называемый «text», в котором будем сохранять входящие данные.
1 2 3 4 5 6 7 |
void loop() { if (radio.available()) { char text[32] = ""; radio.read(&text, sizeof(text)); Serial.println(text); } } |
Используя функцию radion.read(), мы читаем и сохраняем данные в переменную «text». В конце мы просто печатаем текст на последовательном мониторе. Итак, как только мы загрузим обе программы, мы сможем запустить последовательный монитор на приемнике и заметить, что сообщение «Hello World» печатается каждую секунду.
Поиск неисправностей
Стоит отметить, что шум источника питания является одной из наиболее распространенных проблем, с которыми сталкиваются люди при попытке установить успешную связь с модулями NRF24L01. Как правило, радиочастотные цепи или радиочастотные сигналы чувствительны к помехам источника питания. Поэтому всегда полезно включить в линию питания развязывающий конденсатор. Конденсатор может быть любой емкостью от 10 мкФ до 100 мкФ.
Другая распространенная проблема заключается в том, что вывод 3,3 В плат Arduino не всегда может обеспечить достаточную мощность для модуля NRF24L01. Поэтому питание модуля от внешнего источника питания также является хорошей идеей.
Двунаправленная беспроводная связь с двумя NRF24L01 и Arduino
Давайте посмотрим второй пример: двунаправленную беспроводную связь между двумя платами Arduino. Вот принципиальная схема для этого примера:
Исходный код nRF24L01
Код передатчика
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/* * Arduino Wireless Communication Tutorial * Example 2 - Transmitter Code * * by Dejan Nedelkovski, www.HowToMechatronics.com * * Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #define led 12 RF24 radio(7, 8); // CE, CSN const byte addresses[][6] = {"00001", "00002"}; boolean buttonState = 0; void setup() { pinMode(12, OUTPUT); radio.begin(); radio.openWritingPipe(addresses[1]); // 00002 radio.openReadingPipe(1, addresses[0]); // 00001 radio.setPALevel(RF24_PA_MIN); } void loop() { delay(5); radio.stopListening(); int potValue = analogRead(A0); int angleValue = map(potValue, 0, 1023, 0, 180); radio.write(&angleValue, sizeof(angleValue)); delay(5); radio.startListening(); while (!radio.available()); radio.read(&buttonState, sizeof(buttonState)); if (buttonState == HIGH) { digitalWrite(led, HIGH); } else { digitalWrite(led, LOW); } } |
Код приемника
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
/* * Arduino Wireless Communication Tutorial * Example 2 - Receiver Code * * by Dejan Nedelkovski, www.HowToMechatronics.com * * Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <Servo.h> #define button 4 RF24 radio(7, 8); // CE, CSN const byte addresses[][6] = {"00001", "00002"}; Servo myServo; boolean buttonState = 0; void setup() { pinMode(button, INPUT); myServo.attach(5); radio.begin(); radio.openWritingPipe(addresses[0]); // 00001 radio.openReadingPipe(1, addresses[1]); // 00002 radio.setPALevel(RF24_PA_MIN); } void loop() { delay(5); radio.startListening(); if ( radio.available()) { while (radio.available()) { int angleV = 0; radio.read(&angleV, sizeof(angleV)); myServo.write(angleV); } delay(5); radio.stopListening(); buttonState = digitalRead(button); radio.write(&buttonState, sizeof(buttonState)); } } |
Что здесь отличается от предыдущего примера, так это то, что нам нужно создать два канала или адреса для двунаправленной связи.
1 |
const byte addresses[][6] = {"00001", "00002"}; |
В разделе настройки нам нужно определить оба канала и отметить, что адрес записи на первом Arduino должен быть адресом чтения на втором Arduino, и наоборот, адрес чтения на первом Arduino должен быть адресом записи на втором Arduino.
1 2 3 |
// на передатчике radio.openWritingPipe(addresses[1]); // 00001 radio.openReadingPipe(1, addresses[0]); // 00002 |
1 2 3 |
// на приемнике radio.openWritingPipe(addresses[0]); // 00002 radio.openReadingPipe(1, addresses[1]); // 00001 |
В разделе цикла с помощью функции radio.stopListening() мы устанавливаем первый Arduino в качестве передатчика, считываем и отображаем значение джойстика от 0 до 180 и с помощью функции radio.write() отправляем данные в приемник.
1 2 3 4 |
radio.stopListening(); int potValue = analogRead(A0); int angleValue = map(potValue, 0, 1023, 0, 180); radio.write(&angleValue, sizeof(angleValue)); |
С другой стороны, используя функцию radio.startListening(), мы устанавливаем второй Arduino в качестве приемника и проверяем, есть ли доступные данные. Пока есть доступные данные, мы их прочитаем, сохраним в переменной «angleV», а затем используем это значение для вращения серводвигателя.
1 2 3 4 5 6 7 |
radio.startListening(); if ( radio.available()) { while (radio.available()) { int angleV = 0; radio.read(&angleV, sizeof(angleV)); myServo.write(angleV); } |
Затем в передатчике мы устанавливаем первый Arduino в качестве приемника и с помощью пустого цикла while ждем от второго Arduino данных отправки, а это данные о состоянии кнопки, независимо от того, нажата она или нет. Если кнопка нажата, загорится светодиод. Таким образом, этот процесс постоянно повторяется, и обе платы Arduino постоянно отправляют и получают данные.
Пример 3. Отправка нескольких переменных в одном пакете
Давайте рассмотрим еще один пример кода с использованием модулей NRF24L01. Все остается таким же, как и в предыдущих примерах, за исключением того, как мы структурируем и отправляем дату.
Код передатчика
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/* Arduino Wireless Communication Tutorial Example 1 - Transmitter Code by Dejan Nedelkovski, www.HowToMechatronics.com Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> RF24 radio(7, 8); // CE, CSN const byte address[6] = "00001"; // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte a = 0; byte b = 125; byte c = 255; int d = 1024; float e = 3.141592; String f = "Test"; }; Data_Package data; // Create a variable with the above structure void setup() { radio.begin(); radio.openWritingPipe(address); radio.setPALevel(RF24_PA_MIN); radio.stopListening(); } void loop() { // Send the whole data from the structure to the receiver radio.write(&data, sizeof(Data_Package)); delay(500); } |
Итак, мы можем создать структуру, которая на самом деле представляет собой набор переменных различных типов.
1 2 3 4 5 6 7 8 9 10 11 |
// Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte a = 0; byte b = 125; byte c = 255; int d = 1024; float e = 3.141592; String f = "Test"; }; Data_Package data; // Create a variable with the above structure |
Следует иметь в виду, что максимальный размер данных этой структуры может составлять 32 байта. Здесь мы видим, что я включил три переменные типа byte, одну целочисленную переменную (4 байта), одну переменную с плавающей запятой (4 байта) и одну строку, содержащую четыре символа (4 байта). Это в сумме 15 байт.
Код приемника
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/* Arduino Wireless Communication Tutorial Example 1 - Receiver Code by Dejan Nedelkovski, www.HowToMechatronics.com Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> RF24 radio(7, 8); // CE, CSN const byte address[6] = "00001"; // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte a = 0; byte b = 125; byte c = 255; int d = 1024; float e = 3.141592; String f = "Test"; }; Data_Package data; //Create a variable with the above structure void setup() { Serial.begin(9600); radio.begin(); radio.openReadingPipe(0, address); radio.setPALevel(RF24_PA_MIN); radio.startListening(); } void loop() { // Check whether there is data to be received if (radio.available()) { radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure } Serial.print("a: "); Serial.print(data.a); Serial.print(" b: "); Serial.print(data.b); Serial.print(" c: "); Serial.print(data.c); Serial.print(" d: "); Serial.print(data.d); Serial.print(" e: "); Serial.print(data.e); Serial.print(" f: "); Serial.println(data.f); } |
На стороне получателя (приемника) мы должны определить те же данные структуры, чтобы иметь возможность получать входящие данные. Чтобы проверить правильность работы беспроводной связи, я распечатал каждую переменную на последовательном мониторе.