Перевод статьи Interfacing nRF24L01+ Wireless Module with ArduinoShreepad Prabhu, Last Minute Engineers.

Представьте, что две или больше Arduino общаются между собой без проводов! Это открывает массу возможностей: удалённо снимать показания датчиков, управлять роботами на расстоянии, автоматизировать дом и многое другое.

Если ищете надёжное и при этом дешёвое решение для беспроводной связи, отличный выбор — трансивер nRF24L01+. Такие модули стоят меньше двух долларов, что делает их одним из самых бюджетных беспроводных вариантов для проектов.

В этом руководстве мы настроим nRF24L01+ с Arduino на трёх практических примерах:

  • сначала — простая односторонняя связь: одна Arduino шлёт сообщение другой;
  • затем — передача сразу нескольких значений (температура, влажность и др.) одним пакетом, что идеально для метеостанций;
  • наконец — двусторонняя связь, где обе Arduino одновременно и отправляют, и принимают данные, как рация.

Обзор модуля#

nRF24L01+ — это бюджетное однокристальное беспроводное устройство, которое передаёт и принимает радиосигналы на небольших расстояниях. Работает в диапазоне 2,4–2,5 ГГц (тот же, что у Wi-Fi и Bluetooth) и не требует лицензии. Скорость передачи — 250 кбит/с, 1 Мбит/с или 2 Мбит/с.

Питать модуль нужно напряжением 1,9–3,9 В. Осторожно: 5 В, скорее всего, повредят модуль! Однако его логические выводы толерантны к 5 В — их можно подключать напрямую к 5-вольтовому микроконтроллеру вроде Arduino без преобразователя уровней.

Мощность передачи регулируется: 0, −6, −12 или −18 дБм. На максимуме (0 дБм) при передаче модуль потребляет всего 12 мА — меньше, чем один светодиод! В режиме ожидания — 22 мкА, в режиме power-down — крошечные 900 нА. Такое низкое потребление делает его идеальным для устройств на батарейках.

На открытом пространстве стандартная версия (с антенной на плате) бьёт примерно на 50–100 м, особенно на низких скоростях. Есть версии с усилителем мощности и внешней антенной — до 1000 м при прямой видимости.

Модуль nRF24L01+ с антенной на плате

Стандартный модуль nRF24L01+ с PCB-антенной.

Характеристики#

ПараметрЗначение
Диапазон частот2,4 ГГц (ISM)
Макс. скорость по эфиру2 Мбит/с
МодуляцияGFSK
Макс. выходная мощность0 дБм
Напряжение питания1,9–3,6 В
Макс. рабочий ток13,5 мА
Мин. ток (ожидание)26 мкА
Логические входытолерантны к 5 В
Дальность связи800+ м (прямая видимость)

nRF24L01+ против версии PA/LNA#

Модуль выпускается в двух версиях. Первая — с антенной прямо на плате: компактная, но небольшая антенна не бьёт далеко (до ~100 м на улице без препятствий).

Вторая версия — с разъёмом SMA и внешней антенной, а также с чипом-усилителем RFX2401C, объединяющим усилитель мощности (PA), малошумящий усилитель (LNA) и схему переключения приём/передача. Благодаря этому она бьёт гораздо дальше — до 1000 м.

Обе версии полностью взаимозаменяемы: собрав проект на одной, можно без изменений вставить другую.

Модуль nRF24L01+ PA/LNA с внешней антенной

Версия PA/LNA с внешней антенной и усилителем.

Что такое PA и LNA? PA (Power Amplifier) — усилитель мощности: усиливает исходящий сигнал, чтобы он бил дальше. LNA (Low-Noise Amplifier) — малошумящий усилитель на приёме: усиливает очень слабый входящий сигнал (иногда всего несколько микровольт) до уровня, с которым работают остальные схемы, почти не добавляя шума.

Как работает модуль#

Радиоканалы#

Модуль общается на определённых частотах — каналах. Чтобы два модуля «слышали» друг друга, они должны быть настроены на один канал. Каналы лежат в диапазоне 2,400–2,525 ГГц. Каждый занимает около 1 МГц, а при ширине диапазона 125 МГц это даёт 125 возможных каналов с шагом 1 МГц. То есть в одной зоне могут работать до 125 отдельных групп модулей, не мешая друг другу.

125 радиоканалов nRF24L01+ с шагом 1 МГц в диапазоне 2,4 ГГц

125 каналов с шагом 1 МГц в диапазоне 2,4 ГГц.

Сеть Multiceiver#

У nRF24L01+ есть особая функция «Multiceiver» — Multiple Transmitters, Single Receiver (несколько передатчиков, один приёмник). Каждый радиоканал логически делится на шесть отдельных «труб данных» (data pipes), у каждой свой уникальный адрес. То есть один модуль может одновременно общаться с шестью другими на том же радиоканале.

Схема сети Multiceiver: несколько передатчиков и один приёмник

Сеть Multiceiver: несколько передатчиков, один приёмник.

Протокол Enhanced ShockBurst#

nRF24L01+ использует протокол Enhanced ShockBurst™, который повышает надёжность доставки: буферизует пакеты, подтверждает приём (чтобы отправитель знал о доставке) и автоматически переотправляет потерянные пакеты. Это упрощает жизнь микроконтроллеру — ему не нужно заниматься всем этим самому.

Распиновка#

Распиновка модуля nRF24L01+ с восемью выводами

Распиновка стандартного модуля nRF24L01+.

  • GND — земля (квадратная контактная площадка).
  • VCC — питание, 1,9–3,9 В. Подключайте к 3,3 В Arduino, но никогда к 5 В!
  • CE (Chip Enable) — активный высоким уровнем; включает модуль на приём/передачу (HIGH) или переводит в ожидание (LOW).
  • CSN (Chip Select Not) — активный низким уровнем; обычно HIGH, а при обращении к модулю выставляется в LOW.
  • SCK — тактовый сигнал SPI от Arduino.
  • MOSI — вход SPI от Arduino к модулю.
  • MISO — выход SPI от модуля к Arduino.
  • IRQ — активный низким уровнем выход прерывания; сигнализирует Arduino о важных событиях. Во многих простых проектах не используется.
Распиновка модуля nRF24L01+ PA/LNA с внешней антенной

Распиновка версии PA/LNA.

Подключение к Arduino#

Сначала подключите VCC модуля к 3,3 В Arduino, а GND — к земле. Важно: не подключайте к 5 В — модуль работает от 3,3 В. Пины CSN и CE можно подключить к любым цифровым пинам; в примере используем цифровой пин 8 для CSN и пин 9 для CE. Пины SPI лучше подключать к аппаратным: у Arduino UNO/Nano это 13 (SCK), 12 (MISO), 11 (MOSI), 10 (CS).

nRF24L01+Arduino
GNDGND
VCC3.3V
CE9
CSN8
SCK13
MOSI11
MISO12

Понадобятся две одинаковые схемы — передатчик и приёмник, с одинаковой разводкой.

Схема подключения nRF24L01+ к Arduino UNO

Подключение стандартного модуля к Arduino UNO.

Схема подключения модуля nRF24L01+ PA/LNA к Arduino UNO

Подключение версии PA/LNA к Arduino UNO.

Установка библиотеки#

Библиотек для nRF24L01+ много, но одна из самых популярных — RF24. Установка: откройте менеджер библиотек Arduino IDE, введите «rf24», найдите библиотеку от TmRh20 и нажмите Install.

Установка библиотеки RF24 в Arduino IDE

Установка библиотеки RF24 от TMRh20.

Пример 1 — простая односторонняя связь#

Настроим базовую одностороннюю связь: передатчик непрерывно шлёт классическое «Hello World», а приёмник слушает и выводит сообщения в монитор порта.

Код передатчика#

//Include Libraries
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

// Define pins
#define CE_PIN 9
#define CSN_PIN 8

// Create RF24 radio object
RF24 radio(CE_PIN, CSN_PIN);

//address through which two modules communicate.
const byte address[6] = "00001";

void setup() {
  while (!Serial)
    ;
  Serial.begin(9600);

  radio.begin();

  //set the address
  radio.openWritingPipe(address);

  //Set module as transmitter
  radio.stopListening();
}
void loop() {
  //Send message to receiver
  const char text[] = "Hello World";
  radio.write(&text, sizeof(text));

  Serial.print("Data sent: ");
  Serial.println(text);

  delay(1000);
}

Разбор кода#

Подключаем библиотеки: SPI.h для связи по SPI, а nRF24L01.h и RF24.h — для управления модулем. Затем создаём объект RF24, передавая ему номера пинов CE и CSN.

RF24 radio(9, 8);  // CE, CSN

Создаём массив байтов с адресом «трубы», по которой общаются два модуля. Адрес не обязан быть «00001» — подойдёт любая 5-символьная строка вроде «node1», главное — чтобы передатчик и приёмник использовали один и тот же.

const byte address[6] = "00001";

В setup() инициализируем радио вызовом begin(), задаём адрес передатчика через openWritingPipe() и переводим модуль в режим передатчика через stopListening().

В loop() отправляем сообщение функцией write(), которая принимает данные и их размер. Важно: за раз можно передать до 32 байт — это максимальный размер пакета nRF24L01+. write() возвращает bool: TRUE, если данные дошли до приёмника, иначе FALSE.

const char text[] = "Hello World";
radio.write(&text, sizeof(text));

Код приёмника#

//Include Libraries
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

// Define pins
#define CE_PIN 9
#define CSN_PIN 8

// Create RF24 radio object
RF24 radio(CE_PIN, CSN_PIN);

//address through which two modules communicate.
const byte address[6] = "00001";

void setup()
{
  while (!Serial);
    Serial.begin(9600);
  
  radio.begin();
  
  //set the address
  radio.openReadingPipe(0, address);
  
  //Set module as receiver
  radio.startListening();
}

void loop()
{
  //Read the data if available in buffer
  if (radio.available())
  {
    char text[32] = {0};
    radio.read(&text, sizeof(text));
    Serial.print("Data received: ");
    Serial.println(text);
  }
}

Разбор кода#

Код почти такой же, как у передатчика. Функцией openReadingPipe() задаём тот же адрес трубы. Первый аргумент — номер трубы для чтения (0–5, одновременно можно открыть до шести), второй — 40-битный адрес.

radio.openReadingPipe(0, address);

Функция startListening() переводит модуль в режим приёма. В loop() метод available() проверяет, есть ли данные; если да — читаем их методом read() в массив из 32 нулей и выводим в монитор порта.

Монитор порта: приём сообщений Hello World

Приём «Hello World» в мониторе порта.

Пример 2 — передача нескольких значений разом#

Часто нужно передать сразу несколько величин. Например, в метеостанции к передатчику подключён датчик DHT11, и мы хотим отправить и температуру, и влажность одним пакетом. Упакуем оба значения в один пакет с помощью struct.

Подключение#

Разводка как в примере 1, плюс датчик DHT11 на стороне передатчика: VCC к 5 В, GND к земле, вывод данных — к цифровому пину 2. Между выводом данных и VCC нужен подтягивающий резистор 10 кОм (если DHT11 на модуле-breakout — он уже встроен).

Схема примера 2 с датчиком DHT11 на стороне передатчика

Схема примера 2: передатчик с датчиком DHT11.

Установите библиотеку DHTlib (менеджер библиотек → «dhtlib» → библиотека от Rob Tillaart).

Установка библиотеки DHTlib в Arduino IDE

Установка DHTlib для датчика DHT11.

Код передатчика#

//Include Libraries
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <dht.h>

// Define pins
#define CE_PIN 9
#define CSN_PIN 8
#define DHTPin 2

// Create RF24 radio object
RF24 radio(CE_PIN, CSN_PIN);

// Create DHT object
dht DHT;

// Struct for transmitting data
struct SensorData {
  float temperature;
  float humidity;
};

// Create a variable with the above structure
SensorData data;

//address through which two modules communicate.
const byte address[6] = "00001";

void setup()
{
  while (!Serial);
    Serial.begin(9600);
  
  radio.begin();
  
  //set the address
  radio.openWritingPipe(address);
  
  //Set module as transmitter
  radio.stopListening();
}
void loop()
{
  // Read DHT sensor  
  DHT.read11(DHTPin);

  data.humidity = DHT.humidity;
  data.temperature = DHT.temperature;

  Serial.print("Sending Data - Temperature: ");
  Serial.print(data.temperature);
  Serial.print("°C, Humidity: ");
  Serial.print(data.humidity);
  Serial.println("%");

  //Send message to receiver
  radio.write(&data, sizeof(data));

  delay(1000);
}

Код приёмника#

//Include Libraries
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

// Define pins
#define CE_PIN 9
#define CSN_PIN 8

// Create RF24 radio object
RF24 radio(CE_PIN, CSN_PIN);

// Struct for receiving data
struct SensorData {
  float temperature;
  float humidity;
};

//address through which two modules communicate.
const byte address[6] = "00001";

void setup()
{
  while (!Serial);
    Serial.begin(9600);
  
  radio.begin();
  
  //set the address
  radio.openReadingPipe(0, address);
  
  //Set module as receiver
  radio.startListening();
}

void loop()
{
  //Read the data if available in buffer
  if (radio.available())
  {
    SensorData receivedData;
    radio.read(&receivedData, sizeof(receivedData));
    
    Serial.print("Received Data - Temperature: ");
    Serial.print(receivedData.temperature);
    Serial.print("°C, Humidity: ");
    Serial.print(receivedData.humidity);
    Serial.println("%");
  }
}

Разбор кода#

Помимо SPI, nRF24L01 и RF24 подключаем библиотеку DHT. Поскольку значений несколько, простую строку не отправишь — на помощь приходит struct: пользовательский тип, объединяющий связанные переменные в одну единицу. Создаём структуру SensorData с температурой и влажностью.

struct SensorData {
  float temperature;
  float humidity;
};

В loop() читаем значения с DHT11 и кладём их в поля структуры (доступ через точку .), затем отправляем всю структуру целиком.

radio.write(&data, sizeof(data));

На приёмнике читаем структуру SensorData и извлекаем значения.

Монитор порта: приём температуры и влажности

Приём температуры и влажности в реальном времени.

Пример 3 — двусторонняя связь#

Иногда нужно, чтобы устройства общались в обе стороны — как рации. Настроим двустороннюю связь между двумя Arduino: у каждой будет кнопка и светодиод. Нажатие кнопки на одной плате переключает светодиод на другой, и наоборот.

Подключение#

Разводка как в примере 1, плюс кнопка и светодиод на каждой схеме. Одну ногу кнопки — к пину 2, другую — к GND (используем внутреннюю подтяжку, внешний резистор не нужен). Светодиод: анод (длинная нога) — к пину 3 через токоограничивающий резистор 220 Ом, катод — к GND.

Схема Arduino №1 для двусторонней связи

Схема Arduino №1.

Схема Arduino №2 для двусторонней связи

Схема Arduino №2.

Код (одинаковый для обеих плат)#

Заливаем один и тот же скетч в обе Arduino — ведь теперь каждая и отправляет, и принимает данные.

//Include Libraries
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

// Define pins
#define CE_PIN 9
#define CSN_PIN 8
#define SWITCH_PIN 2
#define LED_PIN 3

// Create RF24 radio object
RF24 radio(CE_PIN, CSN_PIN);

//address through which two modules communicate.
const byte address[6] = "00001";

int ledState = HIGH;          // the current state of the output pin
int buttonState;              // the current reading from the input pin
bool lastButtonState = HIGH;  // the previous reading from the input pin

unsigned long lastDebounceTime = 0;      // the last time the output pin was toggled
const unsigned long debounceDelay = 50;  // the debounce time

void setup() {
  pinMode(SWITCH_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);

  Serial.begin(9600);

  radio.begin();

  //set the address
  radio.openWritingPipe(address);
  radio.openReadingPipe(1, address);

  //Set module as receiver
  radio.startListening();
}
void loop() {
  // Debounce the switch
  bool reading = digitalRead(SWITCH_PIN);
  if (reading != lastButtonState) {
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      // only toggle the LED if the new button state is HIGH
      if (buttonState == HIGH) {
        ledState = !ledState;  // Toggle LED state

        radio.stopListening();  // Stop listening to send data
        radio.write(&ledState, sizeof(ledState));
        radio.startListening();  // Resume listening

        Serial.print("Data sent: ");
        Serial.println(ledState);
      }
    }
  }

  lastButtonState = reading;

  if (radio.available()) {
    bool receivedState;
    radio.read(&receivedState, sizeof(receivedState));

    digitalWrite(LED_PIN, receivedState ? HIGH : LOW);

    Serial.print("Data received: ");
    Serial.println(receivedState);
  }
}

Разбор кода#

Как и раньше, подключаем библиотеки и задаём пины радио (CE_PIN, CSN_PIN), кнопки (SWITCH_PIN) и светодиода (LED_PIN). Переменные ledState, buttonState и lastButtonState следят за состоянием светодиода и кнопки, а lastDebounceTime и debounceDelay нужны для устранения дребезга кнопки.

В setup() задаём кнопку как INPUT_PULLUP (по умолчанию HIGH), а светодиод — как выход. Затем открываем и «трубу записи», и «трубу чтения» с одним адресом, чтобы модуль мог и отправлять, и принимать, и включаем приём вызовом startListening().

radio.openWritingPipe(address);
radio.openReadingPipe(1, address);
radio.startListening();

В loop() проверяем нажатие кнопки с учётом дребезга. Если кнопка действительно нажата — переключаем состояние светодиода. Перед отправкой ненадолго прекращаем приём (stopListening()), ведь модуль не может одновременно слушать и передавать, отправляем новое состояние и снова включаем приём. Затем проверяем, пришли ли данные от другого модуля, и обновляем свой светодиод.

Монитор порта: двусторонний обмен данными между Arduino

Двусторонний обмен в мониторе порта.

Как увеличить дальность#

Снизить шум по питанию#

Схема, генерирующая радиочастотный сигнал, крайне чувствительна к шуму по питанию — он заметно снижает достижимую дальность. Если питание не от отдельной батареи, вероятно, оно шумит. Рекомендуется поставить фильтрующий конденсатор 10 мкФ по линии питания как можно ближе к модулю. Самый простой способ — использовать дешёвый адаптер-переходник для nRF24L01+ со встроенным стабилизатором 3,3 В и фильтрующими конденсаторами: тогда модуль можно безопасно питать от 5 В.

Адаптер питания для nRF24L01+ со стабилизатором 3,3 В

Адаптер со стабилизатором 3,3 В и фильтрацией питания.

Сменить канал#

Внешняя среда — тоже источник помех, особенно если соседние сети на том же канале. Поскольку Wi-Fi занимает в основном нижние каналы, используйте старшие 25 каналов nRF24L01+, чтобы уйти от помех.

Снизить скорость передачи#

При 250 кбит/с чувствительность приёмника максимальна — −94 дБм, а при 2 Мбит/с падает до −82 дБм. То есть на 250 кбит/с приёмник примерно в десять раз чувствительнее и «слышит» вдесятеро более слабый сигнал. Снижение скорости заметно увеличивает дальность, а 250 кбит/с хватает для большинства проектов.

Поднять выходную мощность#

Максимальная выходная мощность тоже расширяет дальность. nRF24L01+ поддерживает 0, −6, −12 и −18 дБм: 0 дБм даёт более сильный сигнал, но и потребляет больше.