Радиопередача на NRF24l01 — радиосвязь между двумя Ардуинами
*двумя Ардуинами. Ардуина — это имя нарицательное, так называется любая плата с программируемым микроконтроллером в кругу ардуинщиков. Здесь имеются ввиду платы Arduino Uno/Nano и ESP32 Dev Module.
Всем привет! В этой статье поделюсь своим опытом по созданию радиоаппаратуры на программируемых микроконтроллерах и радиомодулях NRF24l01. Так что приятного чтения =)

Цель создания радиоуправления
Радиоаппаратуру я захотел сделать для самодельного радиоуправляемого самолета (картинка 1), а самому делать, потому что в заводских аппаратурах особо не настраивается как мне нужно и стоят аппаратуры дорого. А самодельную можно настроить на что угодно и стоит дешевле, но нужно потратить время.
Конечно, заводские аппаратуры красивее и компактнее, но мне все равно =)
Скачайте необходимую библиотеку для радиомодулей с GitHub автора. А также библиотека NRF24, если первой будет недостаточно.
Покупка первых радиомодулей и первое подключение

Первый шаг в создании радиоаппаратуры — покупка и настройка радиомодулей. Я выбрал очень популярные и прикольные радиомодули NRF24l01, самую простую версию без усилителей. Ещё я купил Arduino Nano, которая должна была быть приёмником, но что-то она сломалась и не хочет работать. Поэтому пришлось её заменить на плату ESP32 Dev Module, обзор которой доступен по этой ссылке.
Эти радиомодули имеют такое свойство, что у них все контакты собраны в группу 2*4, и в макетную плату такая группа не вставляется. Значит, нужны провода с гнездом «мама». У меня таких не хватало на оба модуля, хватало только на один, поэтому для второго надо было наколхозить что-нибудь, либо ждать с AliExpress новые провода.
Сначала я решил наколхозить, результат колхоза смотрите на картинке 2. У меня оставались пины «мама» от платы Wemos D1 Mini. Железочки из пинов я вытащил, установил пластиковые корпуса на пины радиомодуля и воткнул с обратной стороны провода. Вроде крепко держались, но все равно не работало.
ФАТАЛЬНАЯ ОШИБКА — как я испортил радиомодуль (или два)

Короче, вроде как-то приколхозил провода. Начал подключать к ардуинкам. Прочитал в интернете, что ардуиновских 3.3В радиомодулям не хватит. Нашел понижайку LM2596S, настроил её на 3.3В на выходе, четко выдает 3.3В. Все подключил по схеме из интернета, запускаю. Обе ардуинки пишут в порт что, мол, «Модуль не найден». Я начал смотреть в интернете, что же может быть. Везде говорили что неправильное подключение значит. Проверил подключение — оказывается я у одного из модулей (или обоих, не помню) перепутал плюс и минус =) Ну, блин, молодец….
От неправильной полярности они даже нагрелись, довольно ощутимо. Так что повнимательнее там в подключении =)
На этом история этих двух малышей закончилась…

Новые радиомодули
Пошел на AliExpress купил новые радиомодули, уже с усилителями, и заодно комплект проводов 40 штук папа-мама. Прошло 2-3 недели, забрал, потом ещё сколько-то времени искал схемы, скетчи, потом ещё что-то. Наконец, недавно дошли руки и наконец всё собрал и заработало.
Итак, знакомьтесь — новые радиомодули NRF24l01+PA/LNA (картинка 4), дальность связи до 1 км на открытой местности (в реальности около 1,5-2 км на открытой местности).
Стоят эти радиомодули с антеннами не сильно дороже, около 150-200 рублей за штуку. Так что сразу берем их =) Если питания хватит =))))
Ниже будут представлены схемы подключения и скетчи. Так что обязательно дочитайте до конца!
Теперь перейдём к распиновке и схеме подключения.

Схемы подключения
На картинке 5 представлена распиновка радиомодуля NRF24l01. Распиновка на разных версиях этих модулей одинаковая — как без усилителя, так и с усилителем. Для стабильной работы модулей желательно обеспечить стабильный ток 400-500 мА 3.3В.
Радиомодуль использует для связи с микроконтроллером интерфейс SPI (от него пины CSN, MOSI, MISO, SCK). Пин IRQ не подключается. Пин CE это управляющий пин, на который подаётся сигнал для включения (подаётся 1) и выключения (подаётся 0) радиомодуля. Вернее — не выключения, а отправки в режим ожидания с самым низким энергопотреблением.
Если вы будете запитывать схему с радиомодулями от батарей/аккумуляторов, то перед каждой отправкой сигнала нужно включать модуль, а после отправки выключать. Для этого в библиотеке предусмотрены специальные функции.
Если вы делаете летающую или ездящую модель с постоянным управлением, то необязательно выключать передатчик, либо можно сделать кэширование информации на приёмнике, и выключать передатчик при бездействии.
ВНИМАНИЕ! Крайне рекомендуется добавить в схему 2 электролитических конденсатора (обычные цилиндрические, черные/синие) на 10 мкФ (напряжение не важно, важна емкость), максимально близко к контактам радиомодулей, по одному на каждый модуль, на пины плюса питания и земли, во избежание скачков напряжения питания. Там, где белая полоска на корпусе конденсатора, там минус, подключаем к земле радиомодуля.
Подключаются радиомодули по такой схеме:
| Пин NRF24l01 | Arduino Uno/Nano (передатчик TX) | ESP32 (приёмник RX) |
|---|---|---|
| VCC | Через понижайку до 3.3V подключаем на пин 5V | Запитать можно от встроенного преобразователя на 3.3В, у меня так работает. |
| GND | GND | GND |
| CSN | D8 | GPIO12 |
| CE | D7 | GPIO14 |
| SCK | D13 | GPIO26 |
| MOSI | D11 | GPIO27 |
| MISO | D12 | GPIO25 |
| IRQ | Не подключаем | Не подключаем |
Аналоговый джойстик подключается по классике — питание 5 вольт, земля, ось X на аналоговый вход А0, ось Y на аналоговый вход А1, пин кнопки на цифровой пин Arduino D2.
ВИДЕО ДЕМОНСТРАЦИЯ РАБОТЫ РАДИОАППАРАТУРЫ на Arduino + серво
С момента написания статьи я доработал схему, добавил сервопривод к ESP32 и доработал код. Доработанный код также есть внизу. Вот видео демонстрация управления серво по радио:
Смотреть на YouTube
Смотреть на Rutube
Перейдите на Rutube по этой ссылке, так как встроить видео Shorts с Rutube невозможно. а также смотрите на Rutube видео про радиоуправляемую машинку.
Скетчи и инструкция по загрузке
ЧТО ДЕЛАЮТ ЭТИ СКЕТЧИ?
В будущем можно доработать скетч для масштабирования показаний с джойстика и вывода на сервы/моторы, подключить к приёмнику сервопривод, драйвер с моторами и сделать полноценную радиоуправляемую модель! Советую начинать с наземных моделей — машин. В случае неисправности машинка просто остановится, или, на худой конец, продолжит ехать, упрётся во что-нибудь и всё. А летающая модель упадёт и разобьётся.
Используйте простенькие корпуса для тестов, чтобы не жалко было поцарапать/сломать, например, сделайте корпус из картона.
Видео демонстрацию работы этих скетчей можете посмотреть в моём телеграм канале по этой ссылке.
Передатчик: принимает значения с осей джойстика X и Y, а также кнопки джойстика. Собирает в массив из 3 элементов, передаёт массив по радио на канале, размер сообщения — 6 байт (2Б ось X, 2Б ось Y, 1Б кнопка, 1Б контрольная сумма). Также во избежание получения случайных или искаженных сигналов передаётся контрольная сумма. Адрес «трубы» общения радиомодулей — 1. Мощность — низкая. Скорость — 250 кБит в секунду. Скорость монитора порта — 9600 бод.
Если радиомодуль не подключен или не исправен, либо плохой контакт в соединениях, в монитор порта выйдет сообщение «NRF24L01 не найден!».
Приёмник: получает сигнал по радио, декодирует в массив из 3 чисел, выводит их в монитор порта. Адрес, мощность, скорость — такие же. Скорость порта 115200 бод для ESP32 и так же 9600 для Arduino. Пауза в получении и выводе информации в порт — 50 миллисекунд (0.05 секунды, для стабильности). Если радиомодуль не подключен или не исправен, либо плохой контакт в соединениях, в монитор порта выйдет сообщение «NRF24L01 не найден!».
На случай, если вы будете использовать вместо ESP32 плату Arduino Uno/Nano, я доработал скетч приёмника под Arduino Uno/Nano и добавил после основных двух скетчей.

Скетч для Arduino Uno/Nano (передатчик TX)
#include <SPI.h> // Библиотека для интерфейса связи платы с радиомодулем
#include <nRF24L01.h> // Основная библиотека для радиомодуля
#include <RF24.h> // Дополнительная библиотека для радиомодуля
// Пины для NRF24L01. Остальные пины указывать в данном случае не нужно, они стандартные для SPI (11, 12, 13)
#define CE_PIN 7
#define CSN_PIN 8
// Пины джойстика
#define JOYSTICK_X_PIN A0
#define JOYSTICK_Y_PIN A1
#define JOYSTICK_BUTTON_PIN 2
// Создаем объект радио
RF24 radio(CE_PIN, CSN_PIN);
// Адрес трубы
const byte address[6] = "00001";
// Буфер для передачи данных (массив байтов)
byte dataBuffer[6]; // 2 байта X + 2 байта Y + 1 байт кнопка + 1 байт контрольная сумма
void setup() {
// Старт монитора порта для отладки
Serial.begin(9600);
// Инициализация пинов джойстика
pinMode(JOYSTICK_X_PIN, INPUT);
pinMode(JOYSTICK_Y_PIN, INPUT);
pinMode(JOYSTICK_BUTTON_PIN, INPUT_PULLUP);
// Инициализация NRF24L01
if (!radio.begin()) {
Serial.println("NRF24L01 не найден!");
while (1);
}
// Создаём новую "трубу" для связи, адрес 00001
// Доступные номера "труб" - от 00001 до 00005.
// Номер 00000 занят, это специальный канал для приёма ACK-пакетов.
radio.openWritingPipe(address);
// Мощность ставим низкую,
// чтобы слишком много не потребляли модули,
// и для теста на столе этого достаточно
radio.setPALevel(RF24_PA_LOW);
// Скорость ставим 250 килобит в секунду, этого тоже достаточно для теста.
// Доступные варианты:
// 1. RF24_250KBPS - низкая скорость, высокая стабильность, дальность и качество сигнала.
// 2. RF24_1MBPS - средняя скорость, в 4 раза больше, стабильность и дальность ниже, но всё ещё нормально.
// 3. RF24_2MBPS - высокая скорость, в 8 раз больше первой, но не стабильная и сигнал может потеряться. Дальность маленькая.
radio.setDataRate(RF24_250KBPS);
// Отключаем режим приёмника, так как мы передатчик
radio.stopListening();
// Отчитаемся, что мы готовы передавать данные, всё настроено
Serial.println("Arduino Uno - Пульт готов!");
}
void loop() {
// Чтение данных с джойстика
int xValue = analogRead(JOYSTICK_X_PIN);
int yValue = analogRead(JOYSTICK_Y_PIN);
bool buttonState = !digitalRead(JOYSTICK_BUTTON_PIN);
// Упаковываем данные в массив байтов
dataBuffer[0] = highByte(xValue); // Старший байт X
dataBuffer[1] = lowByte(xValue); // Младший байт X
dataBuffer[2] = highByte(yValue); // Старший байт Y
dataBuffer[3] = lowByte(yValue); // Младший байт Y
dataBuffer[4] = buttonState; // Состояние кнопки
dataBuffer[5] = dataBuffer[0] ^ dataBuffer[1] ^ dataBuffer[2] ^ dataBuffer[3] ^ dataBuffer[4]; // Контрольная сумма, состоит из связки всех показаний
// Отчёт об отправке в Serial
Serial.print("Отправка - X: ");
Serial.print(xValue);
Serial.print(" Y: ");
Serial.print(yValue);
Serial.print(" Button: ");
Serial.println(buttonState);
// Отправка данных
bool success = radio.write(dataBuffer, sizeof(dataBuffer));
// Если данные успешно отправлены, похвастаемся =)
if (success) {
Serial.println("Данные отправлены успешно!");
} else { // Если не отправлены, сообщим:
Serial.println("Ошибка отправки!");
}
delay(100); // Пауза
}
Скетч для ESP32 (приёмник RX)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
// Пины для NRF24L01 на ESP32
#define CE_PIN 12
#define CSN_PIN 14
#define SCK_PIN 26
#define MISO_PIN 25
#define MOSI_PIN 27
// Создаем объект радио
RF24 radio(CE_PIN, CSN_PIN);
// Адрес трубы
const byte address[6] = "00001";
// Буфер для приема данных
byte dataBuffer[6];
// Настройка SPI с пользовательскими пинами
SPIClass spi = SPIClass(HSPI);
void setup() {
Serial.begin(115200);
// Инициализация SPI с пользовательскими пинами
spi.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CSN_PIN);
// Инициализация NRF24L01
if (!radio.begin(&spi)) {
Serial.println("NRF24L01 не найден!");
while (1);
}
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_250KBPS);
radio.startListening();
Serial.println("ESP32 - Приемник готов!");
Serial.println("Ожидание данных...");
}
void loop() {
if (radio.available()) {
// Чтение данных
radio.read(dataBuffer, sizeof(dataBuffer));
// Проверка контрольной суммы
byte checksum = dataBuffer[0] ^ dataBuffer[1] ^ dataBuffer[2] ^ dataBuffer[3] ^ dataBuffer[4];
if (checksum == dataBuffer[5]) {
// Распаковываем данные
int xValue = (dataBuffer[0] << 8) | dataBuffer[1];
int yValue = (dataBuffer[2] << 8) | dataBuffer[3];
bool buttonState = dataBuffer[4];
// Вывод в монитор порта
Serial.print("Получено - X: ");
Serial.print(xValue);
Serial.print(" Y: ");
Serial.print(yValue);
Serial.print(" Button: ");
Serial.println(buttonState);
} else {
Serial.println("Ошибка контрольной суммы!");
}
}
delay(50);
}
Скетч для ESP32 с подключенным сервоприводом (приёмник RX)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <ESP32Servo.h>
// Пины для NRF24L01 на ESP32
#define CE_PIN 12
#define CSN_PIN 14
#define SCK_PIN 26
#define MISO_PIN 25
#define MOSI_PIN 27
// Пин для сервопривода
#define SERVO_PIN 13
// Создаем объекты
Servo myServo;
RF24 radio(CE_PIN, CSN_PIN);
// Адрес трубы
const byte address[6] = "00001";
// Буфер для приема данных
byte dataBuffer[6];
// Настройка SPI с пользовательскими пинами
SPIClass spi = SPIClass(HSPI);
// Переменные для калибровки джойстика
int joyMinX = 0; // Минимальное значение X с джойстика
int joyMaxX = 1023; // Максимальное значение X с джойстика
int deadZone = 50; // Мертвая зона в центре
void setup() {
Serial.begin(115200);
// Разрешаем использование всех таймеров
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
// Инициализация сервопривода
myServo.setPeriodHertz(50); // Стандартная частота сервопривода
myServo.attach(SERVO_PIN, 500, 2400); // Минимальный и максимальный импульс в микросекундах
myServo.write(90);
Serial.println("Сервопривод инициализирован с ESP32Servo");
// Инициализация SPI с пользовательскими пинами
spi.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CSN_PIN);
// Инициализация NRF24L01
if (!radio.begin(&spi)) {
Serial.println("NRF24L01 не найден!");
while (1);
}
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_250KBPS);
radio.startListening();
Serial.println("ESP32 - Приемник с сервоприводом готов!");
Serial.println("Ожидание данных...");
delay(1000);
}
void loop() {
if (radio.available()) {
// Чтение данных
radio.read(dataBuffer, sizeof(dataBuffer));
// Проверка контрольной суммы
byte checksum = dataBuffer[0] ^ dataBuffer[1] ^ dataBuffer[2] ^ dataBuffer[3] ^ dataBuffer[4];
if (checksum == dataBuffer[5]) {
// Распаковываем данные
int xValue = (dataBuffer[0] << 8) | dataBuffer[1];
int yValue = (dataBuffer[2] << 8) | dataBuffer[3];
bool buttonState = dataBuffer[4];
// Вывод в монитор порта
Serial.print("Получено - X: ");
Serial.print(xValue);
Serial.print(" Y: ");
Serial.print(yValue);
Serial.print(" Button: ");
Serial.print(buttonState);
// Управление сервоприводом по оси X
controlServo(xValue);
Serial.println();
} else {
Serial.println("Ошибка контрольной суммы!");
}
}
delay(50);
}
void controlServo(int joyX) {
// Применяем мертвую зону
if (abs(joyX - 512) < deadZone) {
// В мертвой зоне - серво в центре
myServo.write(90);
Serial.print(" | Сервo: 90° (центр)");
return;
}
// Масштабируем значение джойстика в угол сервопривода (0-180°)
int servoAngle = map(joyX, joyMinX, joyMaxX, 0, 180);
// Ограничиваем значения
servoAngle = constrain(servoAngle, 0, 180);
// Устанавливаем угол сервопривода
myServo.write(servoAngle);
// Вывод информации о серво
Serial.print(" | Сервo: ");
Serial.print(servoAngle);
Serial.print("°");
}
Скетч для Arduino Uno/Nano (приёмник RX)
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
// Пины для NRF24L01
#define CE_PIN 7
#define CSN_PIN 8
// Создаем объект радио
RF24 radio(CE_PIN, CSN_PIN);
// Адрес трубы
const byte address[6] = "00001";
// Буфер для приема данных
byte dataBuffer[6];
// Настройка SPI с пользовательскими пинами
SPIClass spi = SPIClass(HSPI);
void setup() {
Serial.begin(115200);
// Инициализация SPI с пользовательскими пинами
spi.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CSN_PIN);
// Инициализация NRF24L01
if (!radio.begin(&spi)) {
Serial.println("NRF24L01 не найден!");
while (1);
}
radio.openReadingPipe(0, address);
radio.setPALevel(RF24_PA_LOW);
radio.setDataRate(RF24_250KBPS);
radio.startListening();
Serial.println("ESP32 - Приемник готов!");
Serial.println("Ожидание данных...");
}
void loop() {
if (radio.available()) {
// Чтение данных
radio.read(dataBuffer, sizeof(dataBuffer));
// Проверка контрольной суммы
byte checksum = dataBuffer[0] ^ dataBuffer[1] ^ dataBuffer[2] ^ dataBuffer[3] ^ dataBuffer[4];
if (checksum == dataBuffer[5]) {
// Распаковываем данные
int xValue = (dataBuffer[0] << 8) | dataBuffer[1];
int yValue = (dataBuffer[2] << 8) | dataBuffer[3];
bool buttonState = dataBuffer[4];
// Вывод в монитор порта
Serial.print("Получено - X: ");
Serial.print(xValue);
Serial.print(" Y: ");
Serial.print(yValue);
Serial.print(" Button: ");
Serial.println(buttonState);
} else {
Serial.println("Ошибка контрольной суммы!");
}
}
delay(50);
}
Прошивка Arduino Uno/Nano: просто создайте новый скетч в Arduino IDE, вставьте код, приведённый выше, сохраните скетч, подключите плату к компьютеру, выберите плату в меню Arduino IDE, выберите порт, нажмите Загрузка. Дождитесь загрузки скетча, отключите плату и приступите к прошивке ESP32!
Прошивка ESP32: убедитесь, что в вашей среде Arduino IDE есть платы ESP32. Если их нет, прочитайте статью по добавлению плат в Arduino IDE (нажмите сюда). Если есть, ЗАЖМИТЕ КНОПКУ BOOT НА ПЛАТЕ, подключите плату к компьютеру, ОТПУСТИТЕ КНОПКУ BOOT (для ввода платы в режим прошивки), выберите плату ESP32 Dev Module, выберите порт. Создайте новый скетч, вставьте код сверху, сохраните, нажмите Загрузка. Если появится сообщение ошибки, что не удалось загрузить прошивку из-за истечения времени ожидания, снова нажмите Загрузка и нажимайте и отпускайте кнопку BOOT на плате, когда статус загрузки перейдёт в «Загрузка…».
Спасибо за внимание! Удачи в повторении проекта!
(поставь 5 звёзд, чё ты =)))) спасибо! =)
