Built-in project · Arduino Uno R3

LoRa weather station with acknowledgments on an Arduino Uno

A BME280 node sends temperature, humidity and pressure over LoRa with RadioHead, and a gateway acknowledges each reading and shows it on an I2C LCD. The whole build, an Arduino Uno and 4 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

Teaching? Assign it to your class in one click.

Advanced About 60 minutes to build and run. Runs in your browser. Free, and no account needed.

Build it as the Weather Station Kit: the parts in numbered bags and a booklet that checks every step. Retails for about $103, free on Mokxi.

The circuit runs here once the page has loaded.

What you'll learn

  • Sending sensor data over LoRa with RadioHead
  • Acknowledgments and retries for reliable messages
  • Packing values into a small fixed struct
  • Reading a BME280 over I2C
  • Why both radios must use the same frequency
  • Difficulty: Advanced
  • Time: About 60 minutes
  • Board: Arduino Uno R3
  • Parts: 8

How it works

This project is two Arduinos, each with an SX1278 LoRa radio. The node reads a BME280 for temperature, humidity and pressure every 5 seconds. It packs the three values and a counter into 8 bytes and sends them at 433 MHz.

The gateway listens. When a reading arrives, it sends an acknowledgment back, shows the values on a 16x2 LCD and prints them with the signal strength (RSSI) in the serial monitor.

If the node gets no acknowledgment, the RadioHead library resends up to 3 times. After that the node prints that no acknowledgment came. If the gateway hears nothing for 20 seconds, its LCD says node quiet.

Wiring table

Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.

Part and pinConnects to
LoRa module, SX1278 (Ra-02) (1) pin GNDArduino Uno R3 (1) pin GND
LoRa module, SX1278 (Ra-02) (1) pin SCKArduino Uno R3 (1) pin 13
LoRa module, SX1278 (Ra-02) (1) pin MISOArduino Uno R3 (1) pin 12
LoRa module, SX1278 (Ra-02) (1) pin MOSIArduino Uno R3 (1) pin 11
LoRa module, SX1278 (Ra-02) (1) pin NSSArduino Uno R3 (1) pin 10
LoRa module, SX1278 (Ra-02) (1) pin RSTArduino Uno R3 (1) pin 9
LoRa module, SX1278 (Ra-02) (1) pin DIO0Arduino Uno R3 (1) pin 2
LoRa module, SX1278 (Ra-02) (1) pin 3.3VArduino Uno R3 (1) pin 3V3
Pressure, humidity and temperature, BME280 pin SDAArduino Uno R3 (1) pin A4
Pressure, humidity and temperature, BME280 pin SCLArduino Uno R3 (1) pin A5
LoRa module, SX1278 (Ra-02) (2) pin GNDArduino Uno R3 (2) pin GND
LoRa module, SX1278 (Ra-02) (2) pin SCKArduino Uno R3 (2) pin 13
LoRa module, SX1278 (Ra-02) (2) pin MISOArduino Uno R3 (2) pin 12
LoRa module, SX1278 (Ra-02) (2) pin MOSIArduino Uno R3 (2) pin 11
LoRa module, SX1278 (Ra-02) (2) pin NSSArduino Uno R3 (2) pin 10
LoRa module, SX1278 (Ra-02) (2) pin RSTArduino Uno R3 (2) pin 9
LoRa module, SX1278 (Ra-02) (2) pin DIO0Arduino Uno R3 (2) pin 2
LoRa module, SX1278 (Ra-02) (2) pin 3.3VArduino Uno R3 (2) pin 3V3
Character LCD with I2C backpack pin SDAArduino Uno R3 (2) pin A4
Character LCD with I2C backpack pin SCLArduino Uno R3 (2) pin A5
Pressure, humidity and temperature, BME280 pin VIN5 V
Pressure, humidity and temperature, BME280 pin GNDGround
Character LCD with I2C backpack pin GNDGround
Character LCD with I2C backpack pin VCC5 V

The note on the canvas

Word for word, as it sits beside the circuit in the editor.

Try it
1. Press Run. Every 5 seconds the node sends its BME280 reading, and the gateway's LCD shows it.
2. Drag the BME280's sliders: the next reading on the LCD follows.
3. The node's serial monitor says "acknowledged" for every reading the gateway got.
4. Hold the gateway's reset button: the node retries 3 times, then says no acknowledgment came. Let go and it is acknowledged again.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Uno R3
// LoRa weather station, the sensor node: a BME280 read every 5 seconds and
// sent over an SX1278 (Ra-02) LoRa module to the gateway with RadioHead's
// RHReliableDatagram. The gateway acknowledges every reading; with no
// acknowledgment the library sends again, up to 3 retries, and the sketch
// says so if all of them go unanswered.
//
//   Ra-02     NSS -> 10   DIO0 -> 2   RST -> 9   SCK -> 13   MOSI -> 11
//             MISO -> 12   3.3V -> 3.3V   GND -> GND
//   BME280    SDA -> A4   SCL -> A5   VIN -> 5V   GND -> GND
//
// The SX1278 runs on 3.3 V and its pins are not 5 V tolerant: on the bench,
// put a level shifter on NSS, SCK, MOSI and RST, or use a 3.3 V board.
//
// Things to try. Drag the BME280's sliders. Hold the gateway's reset button
// and watch the retries run out. Change INTERVAL_MS for slower reports,
// which a battery node would use.

#include <SPI.h>
#include <RH_RF95.h>
#include <RHReliableDatagram.h>
#include <Wire.h>
#include <Adafruit_BME280.h>

const uint8_t NODE_ADDRESS = 1;
const uint8_t GATEWAY_ADDRESS = 2;
const float FREQUENCY = 433.0;  // MHz: the Ra-02 is a 433 MHz module
const unsigned long INTERVAL_MS = 5000;
const uint8_t RESET_PIN = 9;

RH_RF95 rf95(10, 2);  // NSS, DIO0
RHReliableDatagram manager(rf95, NODE_ADDRESS);
Adafruit_BME280 bme;

// What goes on the air: 8 bytes, the same layout on both boards.
struct __attribute__((packed)) Reading {
  int16_t centiC;        // temperature, hundredths of a degree C
  uint16_t deciHum;      // humidity, tenths of a percent
  uint16_t deciHpa;      // pressure, tenths of a hectopascal
  uint16_t count;
};

uint16_t count = 0;
unsigned long lastSent = 0;

void setup() {
  Serial.begin(9600);
  // Reset the radio, as the module's datasheet asks, before talking to it.
  pinMode(RESET_PIN, OUTPUT);
  digitalWrite(RESET_PIN, LOW);
  delay(10);
  digitalWrite(RESET_PIN, HIGH);
  delay(10);

  if (!bme.begin(0x76)) {
    Serial.println("no BME280 at 0x76: check SDA and SCL");
    while (1) {}
  }
  if (!manager.init()) {
    Serial.println("LoRa radio not found: check NSS, DIO0 and the 3.3 V supply");
    while (1) {}
  }
  rf95.setFrequency(FREQUENCY);
  rf95.setTxPower(13);
  manager.setRetries(3);
  manager.setTimeout(500);  // ms to wait for an acknowledgment
  Serial.println("LoRa weather node 1 ready");
}

void loop() {
  if (millis() - lastSent < INTERVAL_MS && lastSent != 0) return;
  lastSent = millis();

  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0;
  Reading r = {(int16_t)lround(t * 100), (uint16_t)lround(h * 10), (uint16_t)lround(p * 10), ++count};

  Serial.print("reading ");
  Serial.print(count);
  Serial.print(": ");
  Serial.print(t, 1);
  Serial.print(" C, ");
  Serial.print(h, 0);
  Serial.print(" %, ");
  Serial.print(p, 1);
  Serial.print(" hPa: ");
  if (manager.sendtoWait((uint8_t *)&r, sizeof(r), GATEWAY_ADDRESS)) {
    Serial.println("acknowledged");
  } else {
    Serial.println("no acknowledgment after 3 retries");
  }
}
sketch.ino · Arduino Uno R3
// LoRa weather station, the gateway: receives the sensor node's readings
// over an SX1278 (Ra-02) with RadioHead's RHReliableDatagram, acknowledges
// each one, and shows the latest on a 16x2 I2C LCD. Every reading also goes
// to the serial monitor with its RSSI, ready for a computer to log.
//
//   Ra-02     NSS -> 10   DIO0 -> 2   RST -> 9   SCK -> 13   MOSI -> 11
//             MISO -> 12   3.3V -> 3.3V   GND -> GND
//   LCD       SDA -> A4   SCL -> A5   VCC -> 5V   GND -> GND
//
// The SX1278 runs on 3.3 V and its pins are not 5 V tolerant: on the bench,
// put a level shifter on NSS, SCK, MOSI and RST, or use a 3.3 V board.
//
// Things to try. Change FREQUENCY here only and nothing arrives. Change
// STALE_MS to flag a quiet node sooner.

#include <SPI.h>
#include <RH_RF95.h>
#include <RHReliableDatagram.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const uint8_t GATEWAY_ADDRESS = 2;
const float FREQUENCY = 433.0;  // MHz, the same as the node
const unsigned long STALE_MS = 20000;
const uint8_t RESET_PIN = 9;

RH_RF95 rf95(10, 2);  // NSS, DIO0
RHReliableDatagram manager(rf95, GATEWAY_ADDRESS);
LiquidCrystal_I2C lcd(0x27, 16, 2);

struct __attribute__((packed)) Reading {
  int16_t centiC;
  uint16_t deciHum;
  uint16_t deciHpa;
  uint16_t count;
};

uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
unsigned long lastHeard = 0;
bool heard = false;
bool stale = false;

void setup() {
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  pinMode(RESET_PIN, OUTPUT);
  digitalWrite(RESET_PIN, LOW);
  delay(10);
  digitalWrite(RESET_PIN, HIGH);
  delay(10);
  if (!manager.init()) {
    Serial.println("LoRa radio not found: check NSS, DIO0 and the 3.3 V supply");
    lcd.print("No LoRa radio");
    while (1) {}
  }
  rf95.setFrequency(FREQUENCY);
  rf95.setTxPower(13);
  Serial.println("LoRa gateway listening");
  lcd.print("Waiting for node");
}

void loop() {
  uint8_t len = sizeof(buf);
  uint8_t from;
  // recvfromAck() sends the acknowledgment before it returns.
  if (manager.recvfromAck(buf, &len, &from)) {
    if (len != sizeof(Reading)) return;
    Reading r;
    memcpy(&r, buf, sizeof(r));
    heard = true;
    stale = false;
    lastHeard = millis();

    lcd.clear();
    lcd.print(r.centiC / 100.0, 1);
    lcd.print(" C  ");
    lcd.print(r.deciHum / 10.0, 0);
    lcd.print(" %");
    lcd.setCursor(0, 1);
    lcd.print(r.deciHpa / 10.0, 1);
    lcd.print(" hPa");

    Serial.print("node ");
    Serial.print(from);
    Serial.print(" #");
    Serial.print(r.count);
    Serial.print(": ");
    Serial.print(r.centiC / 100.0, 1);
    Serial.print(" C, ");
    Serial.print(r.deciHum / 10.0, 0);
    Serial.print(" %, ");
    Serial.print(r.deciHpa / 10.0, 1);
    Serial.print(" hPa, RSSI ");
    Serial.println(rf95.lastRssi());
  }

  if (heard && !stale && millis() - lastHeard > STALE_MS) {
    stale = true;
    lcd.setCursor(0, 1);
    lcd.print("node quiet 20 s ");
    Serial.println("no reading for 20 s");
  }
}

Code walkthrough

The key functions of the sketch, and what each one does.

setup sketch.ino

On both boards it pulses the radio's reset pin 9, then starts the radio with manager.init. The node also starts the BME280 at address 0x76. Both set 433 MHz and 13 dBm power. The node sets 3 retries and a 500 ms wait for each acknowledgment. If a part is missing, the sketch prints what to check and stops.

void setup() {
  Serial.begin(9600);
  // Reset the radio, as the module's datasheet asks, before talking to it.
  pinMode(RESET_PIN, OUTPUT);
  digitalWrite(RESET_PIN, LOW);
  delay(10);
  digitalWrite(RESET_PIN, HIGH);
  delay(10);

  if (!bme.begin(0x76)) {
    Serial.println("no BME280 at 0x76: check SDA and SCL");
    while (1) {}
  }
  if (!manager.init()) {
    Serial.println("LoRa radio not found: check NSS, DIO0 and the 3.3 V supply");
    while (1) {}
  }
  rf95.setFrequency(FREQUENCY);
  rf95.setTxPower(13);
  manager.setRetries(3);
  manager.setTimeout(500);  // ms to wait for an acknowledgment
  Serial.println("LoRa weather node 1 ready");
}

loop sketch.ino

The node waits for INTERVAL_MS, reads the BME280, rounds the values into whole numbers and sends them with sendtoWait, which returns true only when the gateway acknowledges. The gateway calls recvfromAck, which sends the acknowledgment itself, then shows the reading and watches for STALE_MS.

void loop() {
  if (millis() - lastSent < INTERVAL_MS && lastSent != 0) return;
  lastSent = millis();

  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0;
  Reading r = {(int16_t)lround(t * 100), (uint16_t)lround(h * 10), (uint16_t)lround(p * 10), ++count};

  Serial.print("reading ");
  Serial.print(count);
  Serial.print(": ");
  Serial.print(t, 1);
  Serial.print(" C, ");
  Serial.print(h, 0);
  Serial.print(" %, ");
  Serial.print(p, 1);
  Serial.print(" hPa: ");
  if (manager.sendtoWait((uint8_t *)&r, sizeof(r), GATEWAY_ADDRESS)) {
    Serial.println("acknowledged");
  } else {
    Serial.println("no acknowledgment after 3 retries");
  }
}

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • Nothing arrives at the gateway. This project
    Both sketches must use the same FREQUENCY. Changing it on only one board breaks the link.
  • The monitor says LoRa radio not found. This project
    Check NSS on pin 10, DIO0 on pin 2, RST on pin 9, and the 3.3 V supply to the radio.
  • The monitor says no BME280 at 0x76. This project
    Check the SDA and SCL wires on A4 and A5 and the sensor power.
  • The module resets or the radio does not start. LoRa module
    It is a 3.3 V part. Power it at 3.3 V, with a decent supply that can give over 100 mA when transmitting, and add a 10 uF or more capacitor. Never connect 5 V to VCC or to the logic pins.
  • The two modules never hear each other. LoRa module
    Both must use the same frequency, such as 433 MHz, plus the same spreading factor, bandwidth and sync word. Attach the antenna before transmitting. Check SPI wiring: SCK, MISO, MOSI, NSS, RST and DIO0.
  • The sketch cannot find the sensor, or reads zeros. Pressure
    The I2C address is 0x76 when SDO is tied to GND and 0x77 when tied to VCC. Run an I2C scanner, and make sure the library address matches. Wire SDA and SCL correctly.
  • Humidity always reads zero or looks wrong. Pressure
    A BMP280 looks the same but has no humidity sensor. Check the chip ID register: 0x60 is a BME280 and 0x58 is a BMP280. Use the right library for yours.
  • The screen stays blank or shows only a row of blocks. Character LCD with I2C backpack
    The I2C address is usually 0x27 or 0x3F. Run an I2C scanner to find yours. Then turn the contrast trimmer on the backpack slowly until text appears.

Questions and answers

What does RHReliableDatagram add over plain LoRa?

An acknowledgment for every message. The gateway's recvfromAck() replies before it returns; if no reply comes within the timeout (500 ms here), sendtoWait() sends again, up to 3 retries, and then returns false.

What is sent over the air?

An 8-byte packed struct with the same layout on both boards: the temperature in hundredths of a degree, humidity and pressure in tenths, and a count.

Which frequency should I use?

The Ra-02 is a 433 MHz module, so both sketches call setFrequency(433.0). Use a frequency that is legal where you live, and the same one at both ends, or nothing arrives.

Can I wire the Ra-02 straight to a 5 V Uno?

It runs on 3.3 V and its pins are not 5 V tolerant. Put a level shifter on NSS, SCK, MOSI and RST, or use a 3.3 V board.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.