Ранее в своих статьях я рассказывал, как подключать модули EBYTE LoRa к Raspberry Pi и Repka Pi (ссылки в конце этой статьи). Но такие микрокомпьютеры потребляют довольно много электроэнергии и не «проживут» долго на аккумуляторе. 

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

В этой статье на примере метеостанции я расскажу о том, как создавать узлы сбора данных LoRa на базе микроконтроллера ESP32-S3, установленного в модуле Heltec Automation HTIT‑WB32LAF V3. Микроконтроллер может переходить в состояние глубокого сна, когда работают только часы реального времени и память RTC, а вся периферия отключается. В результате узел сбора данных может работать очень долго, питаясь от аккумулятора.

Модуль Heltec Automation HTIT‑WB32LAF

Модуль Heltec Automation HTIT‑WB32LAF V3, объединяющий микроконтроллер ESP32-S3FN8 с LoRa‑чипом SX1262, поставляется в комплекте с антенной, гребенками для самостоятельной распайки платы и проводками с разъёмом для подключения аккумулятора (рис. 1).

Рис. 1. Модуль Heltec Automation HTIT-WB32LAF V3
Рис. 1. Модуль Heltec Automation HTIT‑WB32LAF V3

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

На плате Heltec HTIT‑WB32LAF V3 установлен микроконтроллер ESP32-S3 с широким набором встроенной периферии ввода‑вывода:

  • АЦП (ADC) — два 12-битных SAR АЦП, до 20 входных каналов.

  • ШИМ (PWM) — контроллер LEDC (8 независимых каналов) и контроллер MCPWM для управления моторами.

  • Сенсорные входы (Touch) — до 14 ёмкостных сенсорных каналов.

  • Цифровые GPIO — выводы общего назначения с настраиваемой нагрузочной способностью и прерываниями.

  • Аппаратные интерфейсы: 4× SPI (из них два доступны пользователю), 3× UART, 2× I²C, 2× I²S, USB OTG с поддержкой Serial/JTAG.

  • Четыре универсальных 54-битных таймера, сторожевые таймеры, системный таймер.

  • Встроенный ЦАП (DAC) отсутствует в отличие от классического ESP32.

Из внешней периферии на плате есть:

  • OLED‑монитор 0,96″ SSD1306, подключенный по шине I²C.

  • Радиомодуль LoRa SX1262, подключенный по SPI.

  • Светодиод, подключенный к одному из GPIO.

  • Кнопка PRG, совмещённая с GPIO0. Она используется для загрузки прошивки.

  • Кнопка RST для перезагрузки микроконтроллера.

  • Контроллер заряда LiPo, позволяющий заряжать подключенный аккумулятор через USB.

Чип SX1262, встроенный в модуль Heltec Automation HTIT‑WB32LAF V3, может выдать в антенну до 21–22 дБм выходной мощности в диапазоне 863–928 МГц при высокой чувствительности до -137 дБм. Также есть версия модуля HTIT‑WB32LAF‑N для диапазонов 433/470–510 МГц с мощностью до 21 дБм.

Протокол LoRa позволяет принимать сильно зашумлённые сигналы. Об этом и других особенностях LoRa я рассказал в своей статье «Дальняя связь на модулях LoRa: принимаем сигнал ниже уровня шума».

На модулях имеются перемычки LF и HF (рис. 2). 

Рис. 2. Перемычки LF и HF для выбора частотного диапазона
Рис. 2. Перемычки LF и HF для выбора частотного диапазона

Перепайка этих перемычек не приведёт к изменению частотного диапазона, но я не пробовал. В данном случае модуль работает в диапазоне 863–928 МГц.

На плате установлен микроконтроллер ESP32-S3FN8 (рис. 3). 

Рис. 3. Микроконтроллер ESP32-S3FN8 в модуле HTIT-WB32LAF
Рис. 3. Микроконтроллер ESP32-S3FN8 в модуле HTIT‑WB32LAF

Это версия с 8 МБ встроенной Flash‑памяти и без внешней PSRAM.

Кроме разъёма USB Type‑C, встроенного модуля LoRa и разъёма GPIO для подключения различных внешних устройств, Heltec Automation HTIT‑WB32LAF V3 поддерживает Wi‑Fi b/g/n (802.11b/g/n), Bluetooth 5 (LE). Если рядом есть интернет, на такой плате можно собрать шлюз и передавать результаты измерений в сеть.

Очень удобно, что этот модуль оснащён OLED‑монитором, полезным при отладке и для отображения результатов измерений.

Подробную информацию о модуле Heltec Automation HTIT‑WB32LAF V3 вы найдёте в этом даташите

Есть обновлённая версия Heltec Automation HTIT‑WB32LAF V4. В ней используется микроконтроллер ESP32-S3R2 с 2 МБ встроенной PSRAM и 16 МБ внешней Flash‑памяти, добавлены разъём для солнечной панели, а также интерфейс GNSS для подключения внешнего модуля GNSS. Выходная мощность передатчика может достигать 28 дБм в старшей версии модуля.

Модуль Heltec Automation HTIT‑WB32LAF V3 можно программировать в Arduino IDE — её несложно освоить. Профессионалы, возможно, предпочтут Espressif IDF Extension в связке с Visual Studio Code или фреймворк ESP‑IDF (Espressif IoT Development Framework).

Установка Arduino IDE

Установку Arduino IDE много раз описывали в интернете, однако если создавать с её помощью программы (скетчи) для Heltec Automation HTIT‑WB32LAF V3, нужна дополнительная настройка. Я расскажу об этой процедуре для Windows 11.

Загрузка и запуск установочного файла

Прежде всего, необходимо зайти на сайт arduino.cc и скачать установочный файл Arduino IDE для Windows. Установочный файл Arduino IDE можно загрузить в одном из магазинов, которые торгуют микроконтроллерами, например, здесь. Но, к сожалению, хотя вы сможете установить полученный таким способом файл, вам не удастся выполнить необходимые настройки для работы с LoRa. Вам потребуется Arduino IDE версии 2.3.10 (доступна сейчас на сайте разработчика) или новее. Файл может называться arduino‑ide_2.3.10_Windows_64bit.exe или иначе, в зависимости от версии.

Получив этот файл, запустите его. Довольно подробное руководство по установке Arduino IDE можно найти в статье «Установка/настройка программной оболочки Arduino IDE и поддержки Metro ESP-32 для Windows» и «Как установить arduino ide на windows». Я же остановлюсь подробнее на настройках, необходимых для работы с модулями LoRa.

Настройка Arduino IDE для работы с Heltec Automation HTIT‑WB32LAF V3

После установки Arduino IDE подключите к порту USB компьютера модуль HTIT‑WB32LAF V3. ОС Windows автоматически установит для него драйверы. 

Если установка прошла успешно, вы увидите в диспетчере устройств названия вида Silicon Labs CP210x USB to UART Bridge. На рис. 4 показана ситуация, когда я подключил к одному компьютеру сразу два таких устройства — к портам COM5 и COM7.

Рис. 4. Диспетчер устройств
Рис. 4. Диспетчер устройств

Если с автоматической установкой драйвера CP210x возникли проблемы, скачайте архив установщика драйвера с сайта Silicon Labs, запустите программу установки и следуйте инструкциям.

Далее запустите Arduino IDE для Windows и откройте File → Preferences.

В поле Additional boards manager URLs введите такую строку (рис. 5):
https://raw.githubusercontent.com/espressif/arduino‑esp32/gh‑pages/package_esp32_index.json и щёлкните кнопку OK.

Рис. 5. Ввод дополнительного URL для менеджера плат
Рис. 5. Ввод дополнительного URL для менеджера плат

Далее откройте меню Tools → Board → Boards manager и введите в поиске строку «esp32». В списке найдётся пакет esp32 от Espressif Systems (рис. 6).

Рис. 6. Установлен пакет esp32 от автора Espressif Systems
Рис. 6. Установлен пакет esp32 от автора Espressif Systems

Для установки пакета воспользуйтесь кнопкой INSTALL. На рис. 6 пакет уже установлен.

Установка библиотеки Heltec ESP32 Dev‑Boards

Теперь установим библиотеку с дополнительными функциями для плат Heltec. Откройте SketchInclude LibraryManage Libraries, введите в поиске «Heltec ESP32» и установите библиотеку Heltec ESP32 Dev‑Boards.

Для проверки что все получилось откройте File → Examples → Heltec_ESP32. В самом низу списка, в разделе Examples from Custom Libraries, должен быть раздел Heltec_ESP32 Dev‑Boards (рис. 7).

Рис. 7. Раздел Heltec_ESP32 Dev-Boards в примерах программ
Рис. 7. Раздел Heltec_ESP32 Dev‑Boards в примерах программ

Выбор нужной платы

Откройте раздел Tools → Board → esp32 и выберите в списке Heltec WiFi LoRa 32(V3), как показано на рис. 8.

Рис. 8. Выбор платы Heltec WiFi LoRa 32(V3)
Рис. 8. Выбор платы Heltec WiFi LoRa 32(V3)

Выбор порта COM

Теперь нужно выбрать порт COM, к которому подключена ваша плата, открыв раздел Tools → Port. Я подключил сразу две платы, одну к порту COM5, а другую — к порту COM7. Чтобы определить, какой плате какой порт назначен, вы можете подключать их по очереди, сверяясь с информацией диспетчера устройств Windows.

Передача текстовой строки

Для передачи текстовой строки через эфир я переделал программы LoRaSender и LoRaReceiver из установленных примеров библиотеки Heltec ESP32. Первая из них отправляет в эфир текстовую строку, а вторая — принимает её. На OLED‑мониторах передатчика и приёмника отображаются передаваемая строка и другая информация.

Программа для приёмника LoRaReceiverOLED

Для приёма данных, полученных через эфир модулем Heltec Automation HTIT‑WB32LAF, вы можете воспользоваться программой (скетчем) LoRaReceiverOLED.ino.

Эта программа написана на основе библиотеки Heltec LoRaWan_APP/ESP32_LoRaWAN. Описание использованных функций можно найти в руководстве Radio Driver Reference Guide.

Подключение библиотек

В начале программы оператор include подключает нужные библиотеки. 

Файл LoRaWan_APP.h — заголовок библиотеки ESP32_LoRaWAN; он даёт доступ к глобальному объекту Radio, структуре RadioEvents_t, объекту Mcu и константам режимов модуляции (MODEM_LORA и другим).

Файл Wire.h нужен для работы со стандартной библиотекой Arduino для шины I²C, с помощью которой подключён OLED‑монитор. 

Также для работы с OLED‑монитором модуля Heltec необходим файл HT_SSD1306Wire.h, предоставляющий доступ к библиотеке ThingPulse esp8266-oled‑ssd1306.

Глобальные переменные

В программе определены такие глобальные переменные:

  • txpacket и rxpacket — буферы размером 30 байт для передаваемого и принятого пакета;

  • RadioEvents — структура для функций обратного вызова радиодрайвера;

  • rssi, lastSnr — переменные для хранения уровня сигнала и отношения сигнал/шум последнего пакета;

  • rxCount — счётчик принятых пакетов;

  • lora_idle — флаг «радио свободно и готово к новому приёму»

Функции и макросы

Рассмотрим кратко назначение функций и макросов, используемых в этой программе.

setup

Функция setup выполняет инициализацию:

void setup() {
    Serial.begin(115200);
    Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);    
    VextON();
    delay(100);
    display.init();
    display.setFont(ArialMT_Plain_10);
    rssi = 0;    RadioEvents.RxDone = OnRxDone;
    Radio.Init( &RadioEvents );
    Radio.SetChannel( RF_FREQUENCY );
    Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH,
 		LORA_SPREADING_FACTOR,
 		LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
           LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
 		0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
    drawWaitingScreen();
}

В самом начале функция устанавливает скорость передачи данных для отладочного порта. Далее метод Mcu.begin инициализирует платформу Heltec. 

Макросы HELTEC_BOARD и SLOW_CLK_TPYE, передаваемые в Mcu.begin, не нужно задавать в программе, так как они передаются автоматически Arduino IDE при выборе платы. 

Макрос SLOW_CLK_TPYE сообщает библиотеке, установлен ли на плате внешний часовой кварц 32,768 кГц для тактирования RTC. У большинства плат Heltec WiFi LoRa 32 такого кварца нет, поэтому указывают 0.

В начале программы определен конструктор display:

static SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);

Первый и второй параметры задают адрес OLED‑монитора на шине I²C и частоту этой шины в Гц, соответственно.

Макросы SDA_OLED и SCL_OLED задают номера GPIO‑пинов для линий данных I²C — SDA и SCL. Параметр GEOMETRY_128_64 задаёт разрешение экрана OLED‑монитора. И, наконец, параметр RST_OLED задаёт номер GPIO‑пина для аппаратного сброса монитора. 

Далее для инициализации OLED‑монитора вызывается функция VextON, затем идёт задержка, вызывается display.init и устанавливается шрифт по умолчанию. Можно задавать три шрифта — ArialMT_Plain_10, ArialMT_Plain_16 и ArialMT_Plain_24. Они отличаются размером и не содержат символов кириллицы.

После инициализации OLED‑монитора обнуляется переменная RSSI:

rssi = 0;

Далее выполняется инициализация радиочипа и регистрируется функция обратного вызова OnRxDone:

RadioEvents.RxDone = OnRxDone;
Radio.Init( &RadioEvents );

Эта функция получает управление после приёма данных из эфира.

Radio.SetChannel и Radio.SetRxConfig

Функции Radio.SetChannel и Radio.SetRxConfig устанавливают номер канала и параметры приёмника:

Radio.SetChannel( RF_FREQUENCY );
Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                   LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                   LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                   0, true, 0, 0, LORA_IQ_INVERSION_ON, true );

Рабочая частота задаётся в герцах:

#define RF_FREQUENCY 868900000 // Hz

Частота 868,9 МГц попадает в российский поддиапазон ГКРЧ 868,7–869.2 МГц. Для чипа SX1262 можно задавать частоту с шагом меньше 1 Гц. 

Параметров у SetRxConfig много, но по смыслу они делятся на несколько групп:

  • Тип модема и физика канала. Первый аргумент, MODEM_LORA, указывает драйверу работать в режиме LoRa, а не FSK. Дальше идут три параметра, которые определяют саму CSS‑модуляцию:

    • LORA_BANDWIDTH — ширина полосы канала (0 = 125 кГц, 1 = 250 кГц, 2 = 500 кГц);

    • LORA_SPREADING_FACTOR — фактор расширения спектра (SF7…SF12), то есть сколько чирпов приходится на один символ и, соответственно, дальность связи и время передачи пакета;

    • LORA_CODINGRATE — скорость помехоустойчивого кодирования, отношение полезных бит к избыточным (1 = 4/5, 2 = 4/6, 3 = 4/7, 4 = 4/8). Чем больше избыточность, тем выше устойчивость к помехам, но ниже полезная скорость.

  • Параметр, оставшийся от FSK. Идущий следом ноль — это bandwidthAfc, полоса Automatic Frequency Correction. Он актуален только для FSK‑режима, а для LoRa игнорируется, поэтому здесь всегда 0.

  • Синхронизация и приём. LORA_PREAMBLE_LENGTH задаёт длину преамбулы в символах — она нужна приёмнику для синхронизации перед приёмом заголовка и данных. LORA_SYMBOL_TIMEOUT — тайм‑аут приёма в символах: сколько ждать, прежде чем считать приём несостоявшимся.

  • Формат пакета. Параметр fixLen определяет режим заголовка LoRa‑пакета:

    • true (LORA_FIX_LENGTH_PAYLOAD_ON) — режим implicit header (фиксированная длина пакета, заголовок не передаётся);

    • false (LORA_FIX_LENGTH_PAYLOAD_OFF) — режим explicit header (длина пакета передаётся в заголовке).

В данном случае передаётся макрос LORA_FIX_LENGTH_PAYLOAD_ON (true), включающий режим фиксированной длины. Следующий за ним ноль (payloadLen) задаёт размер ожидаемой полезной нагрузки в байтах для этого режима (если 0, радиочип принимает пакет согласно максимальному размеру буфера).

  • Контроль ошибок и FHSS. Значение true включает проверку CRC для принимаемых пакетов — это позволяет отбрасывать повреждённые данные. Следом идёт freqHopOn = 0, отключающий скачкообразную перестройку частоты (FHSS), и hopPeriod = 0 — период смены частоты в символах при FHSS. Поскольку сам режим выключен, это значение игнорируется.

  • IQ и режим приёма. Макрос LORA_IQ_INVERSION_ON (true) включает инверсию IQ‑составляющих сигнала. В LoRaWAN инверсия IQ применяется для разделения каналов «устройство‑шлюз» и «шлюз‑устройство», а в P2P‑сетях она должна быть одинаковой на передатчике и приёмнике. Последний параметр, rxContinuous = true, задаёт непрерывный приём: радиомодуль остаётся в состоянии RX постоянно, а не переходит в standby после первого принятого пакета.

В сумме это набор параметров физического уровня LoRa для данного примера: полоса 125 кГц, SF7, кодовая скорость 4/5, режим непрерывного приёма с инверсией IQ и фиксированной длиной пакета.

drawWaitingScreen

Перед тем, как завершить свою работу, функция setup вызывает функцию drawWaitingScreen, которая выводит на OLED‑монитор начальное сообщение о переходе узла в состояние ожидания получения данных (рис. 9).

Рис. 9. Сообщение об ожидании данных от передатчика
Рис. 9. Сообщение об ожидании данных от передатчика

drawPacketScreen

Когда узел приёмника получает данные от передатчика, из OnRxDone вызывается функция drawPacketScreen:

void drawPacketScreen()
{
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);

  // Строка 0: номер пакета
  display.drawString(0, 0, "LoRa RX #" + String(rxCount));

  // Строка 1: сам пакет (ограничение по ширине на случай длинного текста)
  display.drawStringMaxWidth(0, 12, 128, String(rxpacket));

  // Строка 2: RSSI и SNR в одной строке
  display.drawString(0, 32, "RSSI:" + String(rssi) + " SNR:" + String(lastSnr));

  // Строка 3: длина пакета
  display.drawString(0, 44, "Len: " + String(rxSize) + " B");

  display.display();
}

В первой строке (с номером 0) она выводит номер принятого пакета, во второй — принятую строку, в третьей — данные RSSI и SNR, и, наконец, в четвёртой — длину принятого пакета (рис. 10).

Рис. 10. На OLED-мониторе принятые от передатчика данные 
Рис. 10. На OLED‑мониторе принятые от передатчика данные 

loop

В программе определена функция loop, не блокирующая работу радио:

if(lora_idle) {
  lora_idle = false;
  Radio.Rx(0);
}
Radio.IrqProcess();

Вызывая Radio.Rx с параметром 0, она переводит чип в непрерывный режим приёма. Это значение задаёт бесконечный таймаут.

Radio.IrqProcess

Функция Radio.IrqProcess в каждом цикле loop опрашивает и обрабатывает прерывания радиочипа программно, вызывая нужную функцию обратного вызова из RadioEvents после того, как пакет будет принят.

OnRxDone

Функция обратного вызова OnRxDone вызывается драйвером радио при успешном приёме пакета. 

Она сохраняет RSSI, SNR, длину пакета, копирует полезную нагрузку в rxpacket и закрывает строку нулевым байтом, так как от передатчика ожидаются текстовые данные. После этого чип переводится в сон функцией Radio.Sleep, результат выводится в Serial и на OLED, а флаг lora_idle вновь устанавливается, чтобы цикл loop перезапустил приём.

Программа для передатчика

Программа передатчика использует принцип LBT (Listen Before Talk), прослушивая эфир перед тем, как приступить к передаче. Если канал занят, передатчик ждёт освобождения канала и только потом передаёт данные. В результате коллизий будет меньше.

Исходный код программы для передатчика находится в файле LoRa_Sender_LBT.ino

Как и в предыдущей программе приёмника, функция setup выполняет все необходимые действия по инициализации.

Вызов функции randomSeed нужен для того, чтобы случайные числа были по‑настоящему случайными:

randomSeed(esp_random());

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

Далее в программе инициализируется счётчик отправленных пакетов txNumber и подключаются функции обратного вызова OnTxDone и OnTxTimeout:

RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
Radio.Init( &RadioEvents );

Функция OnTxDone вызывается драйвером радиомодуля при успешном завершении передачи пакета, а функция OnTxTimeout — при таймауте — из‑за аппаратного сбоя, зависания радиочипа или другой похожей проблемы.

Очень важный шаг — установка частоты, на которой будет работать передатчик:

Radio.SetChannel( RF_FREQUENCY );

В нашей программе частота такая же, как у приёмника:

#define RF_FREQUENCY            868900000 // Hz

Если частоты не совпадут, приёмник не примет пакеты от передатчика.

Функция Radio.SetTxConfig задаёт параметры модуля для передачи:

Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                 LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                 LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                 true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );

По сравнению с похожей функцией приёмника здесь добавляются три параметра: TX_OUTPUT_POWER, fdev (третий параметр) и timeout (последний параметр).

Параметр TX_OUTPUT_POWER очень важен, так как он задаёт выходную мощность передатчика:

  • −9 дБм ≈ 0,126 мВт — минимальная мощность для тестов на малой дистанции;

  • 0 дБм = 1 мВт;

  • 5 дБм ≈ 3,16 мВт;

  • 10 дБм = 10 мВт;

  • 14 дБм ≈ 25 мВт;

  • 17 дБм ≈ 50 мВт;

  • 20 дБм = 100 мВт;

  • 22 дБм ≈ 158 мВт

Сейчас в программе LoRa_Sender_LBT.ino указана минимальная мощность:

#define TX_OUTPUT_POWER  -9

В реальных проектах ориентируйтесь на ограничения по мощности в вашей стране.

Параметр девиации частоты fdev при модуляции LoRa не применяется и задаётся как 0.

Что же касается параметра timeout, то он задаёт время, по истечении которого драйвер сгенерирует прерывание TxTimeout и вызовет функцию OnTxTimeout, если пакет не был успешно отправлен.

Перед началом передачи вызывается функция drawSendingScreen. Она выводит на OLED‑монитор номер текущей передачи (txCount), содержимое отправляемого пакета и статус «Sending...».

После успешной отправки вызывается функция drawStatusScreen, которая показывает на мониторе итог передачи после срабатывания OnTxDone или OnTxTimeout. При этом статус меняется на «TX done» или «TX timeout» в зависимости от значения lastTxOk.

Если же канал занят и задействована процедура LBT, в дело идёт функция drawChannelBusyScreen. Она показывает номер попытки прослушки (attempt/LBT_MAX_ATTEMPTS) и общий счётчик пропущенных передач (txSkippedCount).

После вызова Radio.SetTxConfig функция setup инициализирует OLED‑монитор и выводит на него строки о готовности к передаче:

на него строки о готовности к передаче:
display.drawString(0, 0, "LoRa Sender (LBT)");
display.drawString(0, 20, "Ready to send...");

Но как работает логика LBT?

Здесь ключевая функция аппаратного LBT — waitForFreeChannel:

bool waitForFreeChannel()
{
  bool channelFree = false;
  uint8_t attempt = 0;

  while (!channelFree && attempt < LBT_MAX_ATTEMPTS)
  {
    channelFree = Radio.IsChannelFree(MODEM_LORA, RF_FREQUENCY,
                                       LBT_RSSI_THRESHOLD,
                                       LBT_CARRIER_SENSE_TIME);

    if (!channelFree)
    {
      attempt++;
      Serial.printf("LBT: channel busy, attempt %d/%d\r\n", attempt, LBT_MAX_ATTEMPTS);
      drawChannelBusyScreen(attempt);
      delay(random(LBT_BACKOFF_MIN_MS, LBT_BACKOFF_MAX_MS));
    }
  }

  return channelFree;
}

Она вызывает в цикле функцию Radio.IsChannelFree, измеряющую RSSI на рабочей частоте и сравнивающую с порогом −80 дБм.

Если канал занят — увеличивается счётчик попыток, обновляется экран монитора через drawChannelBusyScreen, после чего делается случайная пауза перед повторной попыткой.

Если канал освободился за LBT_MAX_ATTEMPTS попыток, возвращается true, иначе — false..

Также обратите внимание, что внутри рабочего цикла передатчика loop вызывается waitForFreeChannel. Если канал так и не освободился, увеличивается счётчик txSkippedCount, после чего цикл пропускается без отправки данных.

Если же канал свободен, увеличивается txCount, обновляется содержимое OLED‑монитора передатчика через drawSendingScreen и запускается отправка данных функцией Radio.Send. При этом флаг lora_idle переключается в состояние в false до завершения передачи.

В конце каждой итерации цикла вызывается Radio.IrqProcess, что необходимо для обработки прерываний радиомодуля и вызова функций обратного вызова.

Для управления питанием OLED в программе определены функции VextON и VextOFF. Первая из них включает питание внешней периферии плате Heltec. Без этого OLED‑монитор отключен и не инициализируется. Вторая функция отключает питание монитора. Она используется для энергосбережения, если монитор не нужен.

Метеостанция на Heltec Automation HTIT‑WB32LAF и BME280

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

Приёмник LoRa_Weather_Receiver

Для приёма метеоданных я подготовил программу LoRa_Weather_Receiver.ino. Приёмник работает постоянно, ожидая данные от передатчика. Получив такие данные, он показывает их на своём OLED‑мониторе (рис. 11).

Рис. 11. На OLED-монитор выведены принятые через эфир данные о погоде
Рис. 11. На OLED‑монитор выведены принятые через эфир данные о погоде

Ранее мы передавали в эфир текстовую строку, но теперь нам нужно работать со структурой метеоданных WeatherPacket, с плотной упаковкой (выравнивание 1 байт):

#pragma pack(push, 1)
typedef struct {
  uint32_t packetId;      // порядковый номер пакета
  float    temperature;   // °C
  float    pressure;      // гПа (hPa)
  float    humidity;      // %RH
  float    illumination;  // лк (lux)
} WeatherPacket;
#pragma pack(pop) 

#define BUFFER_SIZE sizeof(WeatherPacket)
WeatherPacket rxPacket;

Датчик BME-280 не может измерять освещённость, но я добавил поле illumination для будущей модификации метеостанции.

Функция OnRxDone получает принятый пакет, проверяет его размер, выводит полученные данные на консоль для отладки, а также на экран монитора, вызывая для этого функцию drawPacketScreen:

void drawPacketScreen()
{
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);

  // Строка 0: номер пакета + служебная информация о приёме
  display.drawString(0, 0, "RX #" + String(rxPacket.packetId) +
        "  RSSI:" + String(rssi) + " SNR:" + String(lastSnr));

  // Метеоданные
  display.drawString(0, 14, "T: "   + String(rxPacket.temperature, 1)  + " C");
  display.drawString(0, 26, "P: "   + String(rxPacket.pressure, 1)     + " hPa");
  display.drawString(0, 38, "H: "   + String(rxPacket.humidity, 1)     + " %");
  // display.drawString(0, 50, "Lux: " + String(rxPacket.illumination, 0) + " lx (" + String(rxSize) + "B)");

  display.display();
}

Эта функция извлекает поля из принятой структуры с метеоданными и выводит их на OLED‑монитор. 

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

Передатчик LoRa_Weather_Sender_LBT

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

Сборка передатчика

Чтобы подключить к модулю Heltec Automation HTIT‑WB32LAF V3 датчик BME280, мне пришлось припаять к плате микроконтроллера гребёнки, которые идут с ним в комплекте. К сожалению, мне не удалось найти в продаже такой модуль с уже распаянными контактами.

Очень важно разобраться с распиновкой модуля Heltec Automation HTIT‑WB32LAF V3. На рис. 12 показаны необходимые для сборки метеостанции контакты.

Рис. 12. Контакты Heltec Automation HTIT-WB32LAF V3 для сборки метеостанции
Рис. 12. Контакты Heltec Automation HTIT‑WB32LAF V3 для сборки метеостанции

В процессе отладки я подавал питание на модуль HTIT‑WB32LAF V3 через USB.

Контакт 3V3 я использовал для BME280 на первом этапе, когда проверял подключение и работоспособность этой метеостанции. После отладки я переключил питание BME280 на вывод VE. В рабочем режиме на нём +3,3 В, а в режиме глубокого сна это напряжение отключается. Таким образом, «засыпая», мы снижаем потребление метеостанции.

Контакты SDA и SCL нужно подключить к соответствующим контактам BME280. Кроме этого, контакты VIN и GND модуля BME280 нужно подключить к контакту VE модуля HTIT‑WB32LAF V3 и к земле, соответственно (рис. 13).

Рис. 13. Метеостанция с передатчиком в сборе
Рис. 13. Метеостанция с передатчиком в сборе

Программа сканирования шины I²C

Перед тем как запускать программу передатчика, я провёл тестовое сканирование шины I²C на предмет обнаружения датчика BME280 программой I2CScanner.ino. Эта программа ищет устройства, подключённые к второй шине I²C (Wire1) на пинах GPIO41 (SDA) и GPIO42 (SCL).

Функция setup стартует Serial и после небольшого ожидания включает питание внешней периферии. Далее следует небольшая задержка, чтобы напряжение питания на внешней шине успело стабилизироваться после включения VE, прежде чем опрашивать датчик. 

После этого инициализируется вторая аппаратная шина I²C на пинах с номерами 41 и 42, к которым в метеостанции подключен датчик BME280. 

Цикл loop перебирает каждый три секунды все возможные 7-битные адреса I²C от 1 до 126. Для каждого адреса вызываются функции beginTransmission и endTransmission. Если устройство ответило, в отладочную консоль выводится его адрес в шестнадцатеричном формате:

I2C сканер запущен (шина Wire1, GPIO41/42)...
Сканирование...
Устройство найдено на адресе 0x76
Найдено устройств: 1
----------------------------

Если обнаружено устройство на адресе 0×76 или 0×77, значит датчик BME280 подключен верно и отзывается. 

Если устройство не обнаружено, проверьте соединения на макетной плате. Также при неудаче попробуйте заменить датчик BME280 — возможно, он неисправен.

Если возникает ошибка с кодом 4, появляется адрес с пометкой «Ошибка на адресе...». Если возникает эта ошибка, значит. проблема не в отсутствии устройства, а именно в целостности сигнала на шине (например, наводки или плохой контакт).

Программа передатчика метеоданных

Исходный код программы передатчика метеоданных вы найдёте в файле LoRa_Weather_Sender_LBT.ino.

В этой программе определена точно такая же структура WeatherPacket, что и в приёмнике. Для успешной передачи и приёма эти структуры должны полностью совпадать.

Для успешной компиляции программы передатчика вам будет нужно установить библиотеку Adafruit BME280 Library и все её зависимости. Это можно сделать в разделе Sketch Include Library Manage Libraries среды разработки Arduino IDE.

Все свои действия программа выполняет в функции setup. После завершения этой функции плата уходит в глубокий сон до перезагрузки. Цикл loop никогда не выполняется и определен так:

void loop() {
}

Рассмотрим функцию setup.

Получив управление, эта функция выполняет инициализацию:

Serial.begin(115200);
Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);
VextON();
delay(100);
display.init();
display.setFont(ArialMT_Plain_10);
initBME280();
randomSeed(esp_random());
RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
Radio.Init(&RadioEvents);
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                  LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                  LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                  true, 0, 0, LORA_IQ_INVERSION_ON, 3000);

Обратите внимание на небольшую задержку после вызова VextON. Она нужна, чтобы оборудование успело инициализироваться.

Хотя может показаться, что имя макроса SLOW_CLK_TPYE написано неверно, здесь нет ошибки.

На следующем этапе функция setup получает данные от метеостанции с помощью fillWeatherPacket, дожидается освобождения радиоканала, обновляет экран монитора и отправляет данные в эфир:

fillWeatherPacket(txPacket);
memcpy(txBuffer, &txPacket, BUFFER_SIZE);
if (!waitForFreeChannel()) {
  txSkippedCount++;
  Serial.println("LBT: Channel busy, skipping TX");
  goToSleep();
}
Serial.printf("\r\nTX #%lu: T=%.1f C, P=%.1f hPa, H=%.1f %%\r\n",
          (unsigned long)txPacket.packetId, txPacket.temperature,
          txPacket.pressure, txPacket.humidity);
txCount++;
drawSendingScreen(txPacket);
lora_idle = false;
Radio.Send(txBuffer, BUFFER_SIZE);

Далее функция дожидается завершения передачи, обрабатывая прерывание от чипа SX1262:

while (!lora_idle) {
  Radio.IrqProcess();
}

Перед переходом в режим глубокого сна функция setup приостанавливает свою работу на две секунды, чтобы можно было рассмотреть результат передачи на OLED‑мониторе, а затем переводит погодную станцию в режим глубокого сна функцией goToSleep:

delay(2000);
goToSleep();

Код функции goToSleep показан ниже:

void goToSleep() {
  Serial.println("Going to Deep Sleep for 60 seconds...");
  
  Radio.Sleep();
  display.sleep();
  VextOFF();
 
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * US_TO_S_FACTOR);
  esp_deep_sleep_start();
}

Прежде всего в спящий режим переводятся чип SX1262 и монитор. Далее вызывается функция VextOFF, которая отключает питание погодной станции и OLED‑монитора.

После отключения функция esp_sleep_enable_timer_wakeup настраивает таймер пробуждения реального времени RTC. Этот таймер продолжает работать даже в режиме глубокого сна.

Параметр TIME_TO_SLEEP задаёт время пробуждения в секундах, а параметр US_TO_S_FACTOR — множитель для перевода секунд в микросекунды:

#define US_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP  60 

Суффикс ULL означает unsigned long long. Он гарантирует, что константа имеет 64-битный беззнаковый тип. Это необходимо, чтобы не переполнилась 32-битная переменная.

Переход в режим глубокого сна включает функция esp_deep_sleep_start. Она отключает основное ядро, Wi‑Fi, большинство периферии, а затем переводит чип в режим минимального энергопотребления. 

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

Полезная документация и статьи

Возможно, вам будут полезны статьи и документация:

Итоги и планы на будущее

Надеюсь, что эта статья помогла вам разобраться с модулем Heltec Automation HTIT‑WB32LAF V3 и собрать метеостанцию, способную долго работать автономно. Такая метеостанция может передавать результаты измерений на большое расстояние.

Вы узнали, как переводить модуль и подключенное к нему оборудование в состояние глубокого сна. После пробуждения по таймеру модуль проведёт измерения и отправит данные на модуль приёмника, который тоже собран на Heltec Automation HTIT‑WB32LAF V3.

Одно из преимуществ этого передатчика — режим LBT: он «выходит в эфир» не сразу, а только дождавшись освобождения канала.

Хотя я пока и не писал об этом, передатчик использует так называемый широковещательный режим, когда любой приёмник в радиусе действия и с совпадающими параметрами физического уровня (частота, SF, BW, CR) примет и декодирует отправленный пакет.

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

Возможно, вам также будет интересно, что на базе модулей LoRa и микроконтроллеров ESP32 создают узлы таких сетей, как Meshtastic и MESHCORE

Мир LoRa огромен и интересен. Я и дальше собираюсь знакомить вас с ним, так что оставайтесь на связи!

Автор @AlexandreFrolov


НЛО прилетело и оставило здесь промокод для читателей нашего блога:
-15% на заказ нового VDS — HABRFIRSTVDS.

Положение об акции