Для реалізації цієї системи вам знадобляться такі компоненти:
GPS модуль: NEO-6M або NEO-8M (підключається через UART).
Датчик температури, вологості та тиску: BME280 (підключається через шину I2C разом із дисплеєм).
Плата LilyGO LoRa32: виступатиме в ролі передавача (TX).
Компонент Пін на датчику Пін на LilyGO LoRa32 Примітка
BME280 VCC 3V3 Живлення 3.3В
GND GND Земля
SDA GPIO 21 Спільна шина I2C з OLED екраном
SCL GPIO 22 Спільна шина I2C з OLED екраном
NEO-6M GPS VCC 3V3 або 5V Живлення модулю
GND GND Земля
TX GPIO 34 GPS TX іде на RX ESP32 (GPIO 34)
RX GPIO 12 GPS RX іде на TX ESP32 (GPIO 12)
Перед завантаженням встановіть через Менеджер Бібліотек Arduino:
RadioLib
Adafruit BME280 Library
TinyGPSPlus (від Mikal Hart)
Adafruit SSD1306 та Adafruit GFX
#include <RadioLib.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <TinyGPS++.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Піни LoRa
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_CS 18
#define LORA_RST 23
#define LORA_DIO0 26
// Піни GPS (Hardware Serial 2)
#define GPS_RX_PIN 34 // Підключати до TX GPS
#define GPS_TX_PIN 12 // Підключати до RX GPS
// Піни OLED (I2C)
#define OLED_RST 16
// Об'єкти
SX1276 radio = new Module(LORA_CS, LORA_DIO0, LORA_RST, RADIOLIB_NC);
Adafruit_BME280 bme;
TinyGPSPlus gps;
HardwareSerial gpsSerial(2);
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
// Скидання екрана
if (OLED_RST != -1) {
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
}
Wire.begin(21, 22); // I2C
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Ініціалізація BME280 (адреса 0x76 або 0x77)
if (!bme.begin(0x76, &Wire)) {
Serial.println(F("Помилка BME280!"));
}
// Ініціалізація SPI та LoRa
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
int state = radio.begin(868.0, 125.0, 9, 7, 0x12, 10, 8);
if (state != RADIOLIB_ERR_NONE) {
Serial.print(F("Помилка LoRa: "));
Serial.println(state);
}
}
void loop() {
// Зчитування даних з GPS модуля
while (gpsSerial.available() > 0) {
gps.encode(gpsSerial.read());
}
// Зчитування датчика BME280
float temp = bme.readTemperature();
float hum = bme.readHumidity();
float press = bme.readPressure() / 100.0F; // в гПа (hPa)
// Отримання координат
double lat = gps.location.isValid() ? gps.location.lat() : 0.0;
double lng = gps.location.isValid() ? gps.location.lng() : 0.0;
// Формування рядка даних (CSV-формат)
String payload = String(temp, 1) + "," +
String(hum, 1) + "," +
String(press, 1) + "," +
String(lat, 6) + "," +
String(lng, 6);
// Передача даних через LoRa
int state = radio.transmit(payload);
// Вивід на екран
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
if (state == RADIOLIB_ERR_NONE) {
display.println(F("TX: OK"));
} else {
display.println(F("TX: ERROR"));
}
display.print(F("T: ")); display.print(temp, 1); display.println(F(" C"));
display.print(F("H: ")); display.print(hum, 1); display.println(F(" %"));
display.print(F("P: ")); display.print(press, 1); display.println(F(" hPa"));
display.print(F("Lat: ")); display.println(lat, 4);
display.print(F("Lng: ")); display.println(lng, 4);
display.display();
delay(5000); // Відправка кожні 5 секунд
}
Ось готовий код для другої плати LilyGO LoRa32 (Приймач / RX).
Він приймає CSV-рядок вида Temp,Hum,Press,Lat,Lng, розбирає його на окремі числові змінні за допомогою парсингу і гарно виводить метеодані та координати GPS на OLED-дисплей.
#include <RadioLib.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Піни LoRa (SX1276)
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_CS 18
#define LORA_RST 23
#define LORA_DIO0 26
// Піни OLED (I2C)
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST 16 // Якщо екран не світиться, замініть на -1
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
SX1276 radio = new Module(LORA_CS, LORA_DIO0, LORA_RST, RADIOLIB_NC);
// Змінні для збереження розпарсених даних
float temp = 0.0;
float hum = 0.0;
float press = 0.0;
double lat = 0.0;
double lng = 0.0;
// Функція для парсингу CSV-рядка
bool parseCSV(String data) {
int valuesCount = 0;
String values[5];
// Розбиваємо рядок по комам
for (int i = 0; i < 5; i++) {
int index = data.indexOf(',');
if (index != -1) {
values[i] = data.substring(0, index);
data = data.substring(index + 1);
valuesCount++;
} else {
values[i] = data; // Останній елемент
valuesCount++;
break;
}
}
// Перевіряємо, чи дійсно отримали всі 5 параметрів
if (valuesCount == 5) {
temp = values[0].toFloat();
hum = values[1].toFloat();
press = values[2].toFloat();
lat = values[3].toDouble();
lng = values[4].toDouble();
return true;
}
return false;
}
void setup() {
Serial.begin(115200);
// Скидання OLED екрана
if (OLED_RST != -1) {
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
}
// Ініціалізація I2C та екрана
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("[OLED] Помилка ініціалізації!"));
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println(F("LilyGO LoRa32 RX"));
display.println(F("Очікування даних..."));
display.display();
// Ініціалізація SPI та LoRa (налаштування мають 1 в 1 збігатися з TX!)
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
int state = radio.begin(868.0, 125.0, 9, 7, 0x12, 10, 8);
if (state != RADIOLIB_ERR_NONE) {
Serial.print(F("Помилка LoRa: "));
Serial.println(state);
display.clearDisplay();
display.setCursor(0, 0);
display.println(F("LoRa Init Error!"));
display.display();
}
}
void loop() {
String incomingData;
// Блокуючий або чекаючий виклик прийому
int state = radio.receive(incomingData);
if (state == RADIOLIB_ERR_NONE) {
Serial.print(F("[RX Raw]: "));
Serial.println(incomingData);
// Парсимо CSV
if (parseCSV(incomingData)) {
int rssi = radio.getRSSI();
// Виводимо в Serial Monitor
Serial.println(F("--- Дані успішно розпарсено ---"));
Serial.print(F("Температура: ")); Serial.print(temp); Serial.println(F(" C"));
Serial.print(F("Вологість: ")); Serial.print(hum); Serial.println(F(" %"));
Serial.print(F("Тиск: ")); Serial.print(press); Serial.println(F(" hPa"));
Serial.print(F("Широта (Lat):")); Serial.println(lat, 6);
Serial.print(F("Довгота(Lng):")); Serial.println(lng, 6);
Serial.print(F("RSSI: ")); Serial.print(rssi); Serial.println(F(" dBm"));
// Виводимо на OLED екран
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print(F("T: ")); display.print(temp, 1); display.print(F("C "));
display.print(F("H: ")); display.print(hum, 1); display.println(F("%"));
display.print(F("P: ")); display.print(press, 1); display.println(F(" hPa"));
display.print(F("Lat: ")); display.println(lat, 4);
display.print(F("Lng: ")); display.println(lng, 4);
display.println(F("---------------------"));
display.print(F("RSSI: ")); display.print(rssi); display.println(F(" dBm"));
display.display();
} else {
Serial.println(F("Помилка формату CSV рядка!"));
}
}
else if (state != RADIOLIB_ERR_RX_TIMEOUT) {
Serial.print(F("Помилка прийому пакета, код: "));
Serial.println(state);
}
delay(10); // Коротка пауза для стабільності
}