image
Картинка Wallpapersafari

С каждым днём количество подключённых к интернету устройств только увеличивается, благодаря чему даже появился термин «интернет вещей». Однако, для того чтобы все эти разрозненные системы могли пересылать телеметрию различного рода или быть дистанционно управляемыми, требуется некий объединяющий элемент, в роли которого выступает связь. Именно об этом мы и поговорим в этой статье.

Я не случайно решил сконцентрироваться на этом элементе, так как любому интересующемуся этой темой понятно, что технических воплощений конечного устройства IoT может быть очень много: мониторинг технического состояния систем с помощью датчиков; обновление прошивки «по воздуху»; сигнализация разного рода; сбор информации о погодных условиях; реализация разнообразных способов удалённого управления устройствами и т.д. и т.п.

Однако мало сгенерировать идею о каком-то компоненте или даже системе, теперь это всё каким-то образом должно быть доступно из удалённой сети! Я сейчас не буду говорить о каких-то сложных промышленных вещах и стандартах, поэтому мы постараемся рассмотреть несколько вариантов, которые доступны практически каждому.

Условно говоря, тип используемой связи и её дальность напрямую зависят от стратегии использования:

  • Будем ли мы передавать большие объёмы данных.
  • Насколько далеко нам необходимо будет их передавать.
  • Требуется ли в процессе повышенная энергоэффективность.

В качестве мозга всей системы я предлагаю использовать широко известный микроконтроллер ESP32. Почему именно его: так как по цене, сходной со стоимостью той же Arduino Nano, мы получаем целую кучу «плюшек», и на данный момент мне видится нерациональной покупка каких-либо ардуин по причине их морального устаревания для многих задач (ну, если только вы не являетесь ревностным фанатом именно этой платформы).

Конечно, для многих задач подобный микроконтроллер может быть слегка избыточным, как с точки зрения размеров (не слишком маленьких), так и с точки зрения энергоэффективности. Однако пока остановимся на нём.

Кстати говоря, здесь было бы очень интересно услышать от знающих, какие максимально компактные и недорогие чипы существуют. Желательно со встроенными возможностями беспроводного сбора информации с этого чипа, мониторингом температуры и показателей давления. Полагаю, что подобная информация была бы крайне полезной для множества читателей, в целях встраивания подобных чипов в будущие устройства IoT.

«Нулевой» радиус


Итак… Говоря о самом ближайшем радиусе, условно говоря «нулевом уровне», можно сказать, что сама ESP32 уже является датчиком! А именно: в плату уже встроен датчик температуры, который может измерять температуру самого чипа. Использовать его для каких-то других полезных применений вряд ли получится, однако он вполне сгодится, чтобы удалённо мониторить температуру чипа (если мы его загружаем какой-то тяжёлой работой, например, задачами из сферы machine learning). Для задействования этого датчика можно воспользоваться кодом вот отсюда:

Код замера температуры чипа
#ifdef __cplusplus
extern "C" {
#endif

uint8_t temprature_sens_read();

#ifdef __cplusplus
}
#endif

uint8_t temprature_sens_read();

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.print("Temperature: ");

  // Convert raw temperature in F to Celsius degrees
  Serial.print((temprature_sens_read() - 32) / 1.8);
  Serial.println(" C");
  delay(1000);
}


Кроме того, микроконтроллер содержит также и датчик Холла, который может применяться для замеров изменения магнитного поля.

image
Картинка Diytech

Датчик является аналоговым. То есть он может измерять расстояние от магнита до микроконтроллера:

image
Картинка Diytech

image
Картинка Diytech

Для производства подобных замеров мы можем воспользоваться вот этим кодом:

Код датчика Холла
const int LED = 2;

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
}

void loop() {
  int sensor = hallRead();  // считываем показания датчика Холла
  Serial.print("Sensor Reading:");
  Serial.println(sensor);

  digitalWrite(LED, (sensor < 0) ? HIGH : LOW); // включаем светодиод при обнаружении магнита
  delay(500);
}


Разумеется, возможно и подключение внешних датчиков, однако в рамках этой статьи это выходит за пределы темы, поэтому мы просто отметим такую возможность.

Ближний радиус


А теперь рассмотрим возможности мониторинга на ближайшем удалении от микроконтроллера. Под ближайшим удалением мы подразумеваем радиус в пределах до 30 метров, и здесь, конечно, уже просится использование BLE или Bluetooth с низким энергопотреблением.

Устройство стандарта Bluetooth с низким энергопотреблением обычно находится в спящем состоянии, именно поэтому ему требуется довольно мало энергии. Кстати говоря, именно поэтому на базе этого стандарта строятся разнообразные системы мониторинга, например, фитнес-трекеры, системы навигации и т.д.

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

Говоря о скоростях передачи, можно сказать, что этот протокол больше оптимизирован для задач малой энергоёмкости, чем для высокой пропускной способности.

Однако, несмотря на это, даже ранние протоколы, вплоть до Bluetooth 4.2, допускают передачу на скоростях до 1 мбит/сек.; говоря же о последнем Bluetooth 5 и более поздних, можно отметить, что там скорость передачи зависит больше от настроек физического уровня и может достигать 2 мбит/сек.

В одной из предыдущих статей мы уже разбирали стандарт BLE и сейчас можем воспользоваться кодом оттуда для клиента и сервера BLE-подключения. Логика работы кода в двух словах: клиент, подключаясь к серверу, обновляет характеристику сервиса BLE на нём.

Код BLE-сервера
/*
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp
    Ported to Arduino ESP32 by Evandro Copercini
    updates by chegewara
*/

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>

// Генератор UUID:
// https://www.uuidgenerator.net/

#define SERVICE_UUID        "4fafc201-1fb5-459e-8fcc-c5c9c331914b"
#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8"

void setup() {
  Serial.begin(115200);
  Serial.println("Начало работы BLE!");

  BLEDevice::init("Eto moye imya");
  BLEServer *pServer = BLEDevice::createServer();
  BLEService *pService = pServer->createService(SERVICE_UUID);
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(
                                         CHARACTERISTIC_UUID,
                                         BLECharacteristic::PROPERTY_READ |
                                         BLECharacteristic::PROPERTY_WRITE
                                       );

  pCharacteristic->setValue("Всем привет!");
  pService->start();
  // BLEAdvertising *pAdvertising = pServer->getAdvertising();  // this still is working for backward compatibility
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  BLEDevice::startAdvertising();
  Serial.println("Характеристика установлена! Теперь вы можете прочитать её на телефоне!");
}

void loop() {
  delay(2000);
}


Код BLE-клиента
/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

// The remote service we wish to connect to.
static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b");
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
 
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    String newValue = "Time since boot: " + String(millis()/1000);
    Serial.println("Setting new characteristic value to \"" + newValue + "\"");
    
    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }else if(doScan){
    BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
  }
 
  delay(1000); // Delay a second between loops.
} // End of loop


Средний радиус


Если же рассмотреть возможность управления мониторингом на значительном удалении, но всё ещё не слишком далёком (в пределах 100 м.), то здесь мы можем использовать следующие возможности: Wi-Fi связь и радиомодули NRF24L01+.

WiFi-подключение


На подобном расстоянии вполне применимо стандартное Wi-Fi соединение, которое позволяет сочетать в себе как хорошую дальность, так и высокую скорость передачи данных. Конечно, есть и свои минусы. Например, Wi-Fi не относится к энергоэффективным способам.

Если говорить о возможной дальности, то стандартная дальность измеряется десятками метров. При этом скорость передачи данных может достигать 150 Мбит/сек, если мы говорим о ESP32. Наибольшая известная мне дальность, достигнутая одним из любителей, составляет порядка 80 метров.

Здесь я сразу хочу оговориться, что не имею в виду некие экстремальные варианты соединения, где используются разнообразные выносные и специальные направленные антенны, благодаря чему дальность резко подпрыгивает вплоть до десятков километров.

Стандартный способ соединения в таком случае выглядит как подключение клиента Wi-Fi к точке доступа, причём и на точке доступа, и на клиенте запущены post-серверы, прослушивающие входящие запросы. Таким образом, и клиент, и сервер могут обмениваться информацией друг с другом — перекрёстно. Понятно, что это не единственный возможный вариант, но один из вероятных. Его мы также рассматривали ранее.

Код WiFi-сервера и клиента можно скачать здесь. Там также понадобится библиотека ESPAsyncWebServer.

Подключение с помощью модулей NRF24L01+


Подобные модули широко известны любителям и также некоторыми из них «широко прокляты». Мне известно множество случаев, даже на популярных форумах, когда их мастодонты начинали распродавать свои запасы модулей, купленных впрок в большом количестве, так как не смогли их запустить (справедливости ради — всё вышесказанное в меньшей степени относятся к модулям со встроенной антенной и больше относится к версии с внешней выносной антенной). Подобные импульсивные решения я считаю поспешными, так как модули достаточно интересные, и как их «правильно готовить, чтобы не было несварения», я опишу чуть ниже, в секции про модули с усилителем и выносной антенной.

Сам модуль выглядит приблизительно так:

image
Картинка Howtomechatronics

Скорость передачи достигает 2 мбит/сек. Передача данных осуществляется на определённой частоте, которая носит название канала. Ширина полосы канала составляет порядка 1 МГц, что даёт возможность передавать на 125 возможных каналах.

Для работы с Arduino или ESP32 используется известная библиотека RF24.

Один из примеров кода приёмника и передатчика приведён здесь и выглядит следующим образом:

Код передатчика
/*
* 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);
}


Код приёмника
/*
* 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);
  }
}


Дальний радиус


WiFi LR


И вот мы наконец-то вступаем в одну из самых интересных зон — способы дальней передачи данных. И самым первым, на мой взгляд, нужно упомянуть весьма интересное решение, случайно найденное мной в интернете.

Если кратко: один из поклонников микроконтроллера ESP32 старался добиться стабильности Wi-Fi на каких-либо дальних расстояниях. Это ему никак не удавалось. Тогда он провёл весьма интересный эксперимент: переключил передатчик микроконтроллера на стандарт Wi-Fi LR (Long Range). То есть, другими словами, Wi-Fi дальнего радиуса. После чего связь у него стала весьма стабильной, несмотря на то, что точка доступа и клиент были разделены сложной средой. Стабильной связь оставалась вплоть до дистанции в 200м (и это несмотря на то, что по спецификации стандарт предназначен для работы на расстояниях до 50 метров). По его прикидкам стабильная связь возможна вплоть до 240 метров. К сожалению, мне не удалось найти конкретные значения пропускной способности при таких расстояниях. Однако, им же самим было упомянуто, что стабильная связь на таких расстояниях достигается за счёт некоторого понижения пропускной способности.

Свой тестовый код он выложил здесь, который я и привожу:

Код master-устройства
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiUdp.h>

#include <esp_wifi.h>

#include "peripherals.h"
#include "credentials.h"


Led redLed( GPIO_NUM_19 );
Led greenLed( GPIO_NUM_21 );

WiFiUDP udp;

const char *toStr( wl_status_t status ) {
    switch( status ) {
    case WL_NO_SHIELD: return "No shield";
    case WL_IDLE_STATUS: return "Idle status";
    case WL_NO_SSID_AVAIL: return "No SSID avail";
    case WL_SCAN_COMPLETED: return "Scan compleded";
    case WL_CONNECTED: return "Connected";
    case WL_CONNECT_FAILED: return "Failed";
    case WL_CONNECTION_LOST: return "Connection lost";
    case WL_DISCONNECTED: return "Disconnected";
    }
    return "Unknown";
}

void setupAp() {
    greenLed.set();
    WiFi.begin();
    delay( 500 ); // If not used, somethimes following command fails
    ESP_ERROR_CHECK( esp_wifi_set_protocol( WIFI_IF_AP, WIFI_PROTOCOL_LR ) );
    WiFi.mode( WIFI_AP );

    Serial.println( WiFi.softAP( ssid, password ) );
    Serial.println( WiFi.softAPIP() );
    delay( 1000 );
    greenLed.reset();
}

void setup() {
    Serial.begin( 115200 );
    Serial.println( "Master" );
    setupAp();
    udp.begin( 8888 );
}

void loop() {
    udp.beginPacket( { 192, 168, 4, 255 }, 8888 );
    udp.write( 'b' );
    redLed.invert();
    if ( !udp.endPacket() )
        ESP.restart(); // When the connection is bad, the TCP stack refuses to work
    delay( 100 );
}


Код slave-устройства
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <esp_wifi.h>

#include "peripherals.h"
#include "credentials.h"

Led redLed( GPIO_NUM_19 );
Led greenLed( GPIO_NUM_21 );

WiFiUDP udp;

const char *toStr( wl_status_t status ) {
    switch( status ) {
    case WL_NO_SHIELD: return "No shield";
    case WL_IDLE_STATUS: return "Idle status";
    case WL_NO_SSID_AVAIL: return "No SSID avail";
    case WL_SCAN_COMPLETED: return "Scan compleded";
    case WL_CONNECTED: return "Connected";
    case WL_CONNECT_FAILED: return "Failed";
    case WL_CONNECTION_LOST: return "Connection lost";
    case WL_DISCONNECTED: return "Disconnected";
    }
    return "Unknown";
}

void setup() {
    Serial.begin( 115200 );
    Serial.println( "Slave" );

    WiFi.begin();
    delay( 500 );
    ESP_ERROR_CHECK( esp_wifi_set_protocol( WIFI_IF_STA, WIFI_PROTOCOL_LR ) );
    WiFi.mode( WIFI_STA );

    udp.begin( 8888 );
}

void loop() {
    if ( WiFi.status() != WL_CONNECTED ) {
        Serial.println( "|" );
        int tries = 0;
        WiFi.begin( ssid, password );
        while( WiFi.status() != WL_CONNECTED ) {
            tries++;
            if ( tries == 5 )
                return;
            Serial.println( toStr( WiFi.status() ) );
            greenLed.set();
            delay( 10 );
            greenLed.reset();
            delay( 500 );
        }
        Serial.print( "Connected " );
        Serial.println( WiFi.localIP() );
    }
    int size = udp.parsePacket();
    if ( size == 0 )
        return;
    char c = udp.read();
    if ( c == 'b' )
        greenLed.invert();
    udp.flush();
}


Кстати говоря, вы вполне можете самостоятельно провести тест пропускной способности, «допилив» код выше и выведя показатели скорости передачи данных.

NRF24L01+PA+LNA


Этот тип радиомодулей фактически идентичен рассмотренному выше радиомодулю со встроенной антенной и отличается от него только тем, что у него в составе имеется усилитель мощности малошумящего типа.

Подобный апгрейд позволяет этому устройству обеспечивать высокоскоростную связь на расстоянии вплоть до 1000 метров. Конечно, при таком расстоянии скорость передачи уже падает и достигает на максимальном удалении порядка 250 кбит/с. Соответственно, чем ближе находятся два общающихся друг с другом модуля, тем выше скорость. Максимально она равна возможностям модуля (2 мбит/сек).



Почему он получил такое распространение и известность у любителей? Потому что модуль является одним из самых недорогих и высокоскоростных на рынке для дальней радиосвязи.

Но все, кто начинает работу с этим устройством, сталкиваются с одной его особенностью, которую не все могут преодолеть в силу недостатка опыта или терпения. Проблема заключается в том, что этот модуль потребляет достаточно много энергии, особенно в момент начальной инициализации.

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

Как же бороться с этой бедой? Можно не беспокоиться, всё уже решено до вас, вам всего лишь нужно:

  • Воспользоваться стандартным стабилизатором напряжения, который может преобразовать входящие 5 вольт в 3,3 вольт, требующиеся для питания радиомодуля.



  • Для сглаживания пульсаций питания припаять конденсатор ёмкостью от 10 до 100 мкФ на пины питания модуля. В моём случае помогло припаивание конденсатора на 100 мкФ / 63 вольта.



  • Также рекомендуется обмотать модуль фольгой, чтобы экранировать его от возможных радиопомех, так как он достаточно чувствителен к этому. В моём случае я поступил так: обмотал его малярным скотчем и поверх него в несколько оборотов навернул кулинарную фольгу (естественно, стараясь расположить её так, чтобы случайно не замкнуть нигде контакты).



И только после этого он у меня успешно завёлся. Причём моментально!

Нужно ещё отметить такой момент, что я его достаточно долго и безуспешно пытался запустить от Arduino Ethernet и Arduino Mega 2560, а от ESP32 модуль завёлся просто моментально и без всяких бубнов! Вот такая вот загадка…

Для соединения модуля и ESP32 через интерфейс SPI я использовал следующую схему:

image
Картинка Zen.yandex

image
Картинка Zen.yandex

Код приёмника и передатчика, который у меня заработал, я выкладываю ниже:

Код приёмника
/*
* 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>  // Подключаем библиотеку для работы с SPI-интерфейсом
#include <nRF24L01.h> // Подключаем файл конфигурации из библиотеки RF24
#include <RF24.h> // Подключаем библиотеку для работы с модулем NRF24L01
#define PIN_LED 3  // Номер пина Arduino, к которому подключён светодиод
#define PIN_CE  4  // Номер пина Arduino, к которому подключён вывод CE радиомодуля
#define PIN_CSN 5 // Номер пина Arduino, к которому подключён вывод CSN радиомодуля
RF24 radio(PIN_CE, PIN_CSN); // Создаём объект radio с указанием выводов CE и CSN

const byte address[6] = "00001";

void setup() {
  Serial.begin(115200);
  pinMode(PIN_LED, OUTPUT); // Настраиваем на выход пин светодиода
  radio.begin();  // Инициализация модуля NRF24L01
  radio.setChannel(5); // Обмен данными будет вестись на пятом канале (2,405 ГГц)
  radio.setDataRate (RF24_1MBPS); // Скорость обмена данными 1 Мбит/сек
  radio.setPALevel(RF24_PA_HIGH); // Выбираем высокую мощность передатчика (-6dBm)
  radio.openReadingPipe (1, 0x7878787878LL); // Открываем трубу ID передатчика
  radio.startListening(); // Начинаем прослушивать открываемую трубу
}

void loop() {
  if (radio.available()) {
    char text[14]="";
    radio.read(&text, sizeof(text));
    Serial.println(text);
  }
}


Код передатчика
/*
* 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>  // Подключаем библиотеку для работы с SPI-интерфейсом
#include <nRF24L01.h> // Подключаем файл конфигурации из библиотеки RF24
#include <RF24.h> // Подключаем библиотеку для работы с модулем NRF24L01
#define PIN_POT A7  // Номер пина Arduino, к которому подключён потенциометр
#define PIN_CE  4  // Номер пина Arduino, к которому подключён вывод CE радиомодуля
#define PIN_CSN 5 // Номер пина Arduino, к которому подключён вывод CSN радиомодуля
RF24 radio(PIN_CE, PIN_CSN); // Создаём объект radio с указанием выводов CE и CSN


const byte address[6] = "00001";

void setup() {
  radio.begin();  // Инициализация модуля NRF24L01
  radio.setChannel(5); // Обмен данными будет вестись на пятом канале (2,405 ГГц)
  radio.setDataRate (RF24_1MBPS); // Скорость обмена данными 1 Мбит/сек
  radio.setPALevel(RF24_PA_HIGH); // Выбираем высокую мощность передатчика (-6dBm)
  radio.openWritingPipe(0x7878787878LL); // Открываем трубу с уникальным ID
}

void loop() {
  const char text[] = "Привет!";
  radio.write(&text, sizeof(text));
  delay(1000);
}


Для работы модуля я скачал самую последнюю версию библиотеки RF24 с её официального github-а.

Тут стоит отметить интересный момент: необходимо использовать только самые последние версии библиотек.

Почему: когда я пытался использовать старые версии библиотек, например, 2016 года, скачав один из тестовых скетчей с интернета (который тестирует совместимость библиотеки и конкретного радиомодуля), библиотека у меня выдавала сообщение «неопознанный радиомодуль» и не узнавала устройство, более новое, чем сама библиотека. Поэтому следует использовать только самые последние версии.

Сверхдальний радиус


И здесь, конечно же, в голову приходит один из самых известных вариантов осуществления дальней радиосвязи в рамках интернета вещей — это так называемая LoRa или «Long Range». Скорость соединения находится приблизительно в диапазоне до 27 кбит/сек. (но это сильно громко сказано и это максимум), и типичные скорости могут лежать в пределах 3-5 кбит/с.

Уже по характеристикам понятно, что подобный тип связи не предназначен для передачи больших и тяжёлых пакетов. Его назначение в другом: гарантированно передать на большое расстояние малый объём информации. Длина волны устройства (433 МГц или 868 МГц) легко огибает препятствия, и датчики могут считываться на расстояниях вплоть до 10 км. Хотя согласен, вопрос достаточно спорный и зависит от устройства антенны. Известны случаи осуществления радиосвязи даже на расстоянии порядка 200 км. Хотя — это скорее экстремальный вариант…

Для построения сетей на базе LoRa можно использовать модуль RFM95. Тогда код приёмника и передачи для этого случая будут выглядеть следующим образом:

Код передатчика
/*********
  Modified from the examples of the Arduino LoRa library
  More resources: https://randomnerdtutorials.com
*********/

#include <SPI.h>
#include <LoRa.h>

//define the pins used by the transceiver module
#define ss 5
#define rst 14
#define dio0 2

int counter = 0;

void setup() {
  //initialize Serial Monitor
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Sender");

  //setup LoRa transceiver module
  LoRa.setPins(ss, rst, dio0);
 
  //replace the LoRa.begin(---E-) argument with your location's frequency
  //433E6 for Asia
  //866E6 for Europe
  //915E6 for North America
  while (!LoRa.begin(866E6)) {
    Serial.println(".");
    delay(500);
  }
   // Change sync word (0xF3) to match the receiver
  // The sync word assures you don't get LoRa messages from other LoRa transceivers
  // ranges from 0-0xFF
  LoRa.setSyncWord(0xF3);
  Serial.println("LoRa Initializing OK!");
}

void loop() {
  Serial.print("Sending packet: ");
  Serial.println(counter);

  //Send LoRa packet to receiver
  LoRa.beginPacket();
  LoRa.print("hello ");
  LoRa.print(counter);
  LoRa.endPacket();

  counter++;

  delay(10000);
}


Код приёмника
/*********
  Modified from the examples of the Arduino LoRa library
  More resources: https://randomnerdtutorials.com
*********/

#include <SPI.h>
#include <LoRa.h>

//define the pins used by the transceiver module
#define ss 5
#define rst 14
#define dio0 2

void setup() {
  //initialize Serial Monitor
  Serial.begin(115200);
  while (!Serial);
  Serial.println("LoRa Receiver");

  //setup LoRa transceiver module
  LoRa.setPins(ss, rst, dio0);
 
  //replace the LoRa.begin(---E-) argument with your location's frequency
  //433E6 for Asia
  //866E6 for Europe
  //915E6 for North America
  while (!LoRa.begin(866E6)) {
    Serial.println(".");
    delay(500);
  }
   // Change sync word (0xF3) to match the receiver
  // The sync word assures you don't get LoRa messages from other LoRa transceivers
  // ranges from 0-0xFF
  LoRa.setSyncWord(0xF3);
  Serial.println("LoRa Initializing OK!");
}

void loop() {
  // try to parse packet
  int packetSize = LoRa.parsePacket();
  if (packetSize) {
    // received a packet
    Serial.print("Received packet '");

    // read packet
    while (LoRa.available()) {
      String LoRaData = LoRa.readString();
      Serial.print(LoRaData);
    }

    // print RSSI of packet
    Serial.print("' with RSSI ");
    Serial.println(LoRa.packetRssi());
  }
}


Подытоживая этот рассказ, хотелось бы сказать, что, на мой взгляд, большую часть потребностей закроют возможности Wi-Fi соединения, которые позволят обеспечить высокоскоростную надёжную связь на ближайшем радиусе и на среднем радиусе, если мы будем использовать Wi-Fi LR. Хотя, как вариант, можно использовать и BLE, если энергозатратность не стоит у вас на первом месте.

Говоря же о дальнем радиусе, мне видится наиболее применимым использование модулей NRF24L01+ со встроенным усилителем и внешней антенной, так как именно они позволят осуществлять высокоскоростную передачу на большом расстоянии по весьма приемлемой цене.

Здесь же нельзя не отметить возможность построения на них весьма интересной архитектуры сети, которая может быть полезна для множества применений, например, для Multiceiver (Multiple Transmitters Single Receiver").

При такой архитектуре физический радиочастотный канал внутри ещё подразделяется на шесть каналов логических. Таким образом, на одной и той же частоте возможно осуществлять связь с шестью различными устройствами, находящимися в пределах действия радиосети. Другими словами, можно получить сеть из 6х125 = 750 slave-устройств!

image
Картинка Joyta

С подробным описанием (как оно работает на аппаратном уровне), можно ознакомиться вот здесь.

Конкретные варианты построения сетей (с примерами кода), можно посмотреть вот здесь.

Подробная документация по модулям NRF24L01+ находится по этому адресу, а API библиотеки можно найти по этой ссылке.

Подобный кейс (Multiceiver) является весьма частым, и мне приходилось слышать даже о построении сети на подобных устройствах для мониторинга погружного оборудования для добычи нефти, расположенного в ближайшем окружении в северных условиях.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (3)


  1. Ivanii
    13.07.2022 11:26

    NRF24L01+PA+LNA глушит сама себя, нужно грамотно монтировать в металлический корпус и/или выносить антенну.


  1. iliasam
    13.07.2022 12:04

    А еще можно использовать спутниковую связь: https://hackaday.com/2021/09/07/review-hands-on-with-the-swarm-satellite-network-eval-kit/


  1. Dynamometr
    14.07.2022 07:01

    Есть еще RFM69. Отличаются от RFM95 отсутствием поддержки LoRa модуляции, цена модулей раза в два ниже.