last updated: 2020-10-25
A first step was to dive a little deeper into sleep modes, so that a sensor could function on battery for al long time (min. 1 year). This information can be found in the tips and tricks section, and is necessary to understand this page.
As seen we will run the mega328 chip on internal RC on 8 MHz and then reduce the frequency in software down to 4 MHz and 1 MHz (the DS18B20 needs temporarly 4 MHz). To do so we have to burn an alternative bootloader (minicore).
Now we can add the LoRa chip (SX1276). For this we use a breakout board RFM95W from hoperf. The board needs the SPI interface (MOSI, MISO, SCK, SS), a Reset (input) and an Interrupt DIO0, to signalize if data is received.
We want to sent the temperature and the voltage over LoRa. To measure the voltage, it is necessary to connect AVCC
zo 3 V and and AREF
through a capacitor (100 nF) to ground.
The sketch awaits a return from a gateway, and uses an InvertIQ function to create a simple Gateway/Node logic.
To measure the energy consumption is not so easy. I use again a CurrentRanger from lowpowerlab.com to do so.
The first screen shows the mA range, the second screen the µA range and the third screen the auto mode (switches between two ranges).
If we measure the temperature every 2 minutes we need about:
0.01 mA·3600 s = 36 mAs = 0.01 mAh
30(4.5 mA·0.68 s) = 91.8 mAh = 0.0255 mAh
30(60 mA·0.05 s + 13 mA·0.083 s) = 122.8 mAh = 0.0341 mAh
If we don't wait for a returning message, we get about 0.07 mAh per hour or 610 mAh per year. As the voltage is dropping under 3 V an less energy is needed we can presume:
The battery live changes with the length the sketch has to wait for a response.
/* lora_sender_uno_duplex_DS1820_868_deep_sleep.ino by weigu.lu
*
* This code uses InvertIQ function to create a simple Gateway/Node logic.
* Gateway - Sends messages w. enableInvertIQ(), receives with disableInvertIQ()
* Node - Sends messages w. disableInvertIQ(), r
* InvertIQ function basically invert the LoRa I and Q signals.
*
* We use an mega328p (Arduino) on a breadboard with a minicore bootloader with
* 8MHz internal clock (https://github.com/MCUdude/MiniCore).
* Most of the time we sleep and go down to 1MHz by dividing the clock.
* An DS18B20 is connected with VCC to pin D3 and with the data input to pin D4.
* The DS18B20 library needs 8MHz! The temperature is send by serial to the monitor.
*
* RESET PC6 1 | | 28 PC5 A5
* D0 RX PD0 2 | | 27 PC4 A4
* D1 TX PD1 3 | | 26 PC3 A3
* D2 PD2 4 | | 25 PC2 A2
* D3 PWM PD3 5 | | 24 PC1 A1
* D4 PD4 6 | | 23 PC0 A0
* VCC 7 | | 22 GND
* GND 8 | | 21 AREF
* ctal PB6 9 | | 20 AVCC
* ctal PB7 10 | | 19 PB5 D13 SCK
* D5 PWM PD5 11 | | 18 PB4 D12 MISO
* D6 PWM PD6 12 | | 17 PB3 D11 MOSI PWM
* D7 PD7 13 | | 16 PB2 D10 PWM
* D8 PB0 14 | | 15 PB1 D9 PWM
*
* RFM95W: CS(NSS)=10, RST=9, IRQ(0)=2, freq = 868MHz
*
* If we get the temperature every 2 minutes we need about
* 0.009mA·3600s+7mA·0.05s·30+0.68mA·0.5s·30+65mA·0.05s·30+22mA+0.08s·30+10mA·0.02s·30
* = 32.4mAs + 10.5mAs + 10.2mAs + 97.5mAs + 52.8mAs + 6mAs = 209.4mAs = 0.0582mAh
*/
#include "LowPower.h"
#include <SPI.h>
#include <LoRa.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define DEBUG
const byte PIN_DS18B20_POW = 3;
const byte PIN_DS18B20_DATA = 4;
float temperature;
unsigned int voltage;
long send_interval = 15; // interval between sends, multiple of 8s (15 = 2min)
OneWire oneWire(PIN_DS18B20_DATA);
DallasTemperature DS18B20(&oneWire);
byte msg_out_id = 0; // counter of outgoing messages = message id
byte node_addr = 0x33; // address of this device
byte gateway_addr = 0xFE; // destination to send to 0xFE = gateway 0xFF = broadcast
byte packet_size = 0;
byte addr_in_rec, addr_in_sender, msg_in_id, msg_in_length;
String msg_out, msg_in, lora_rssi, lora_snr;
volatile bool flag_message_received = false; // flag set by callback
long millis_max_listen = 10000; // max listening time
long millis_now, millis_prev=0;
void setup() {
pinMode(PIN_DS18B20_POW,OUTPUT);
digitalWrite(PIN_DS18B20_POW,LOW);
#ifdef DEBUG
Serial.begin(38400);
Serial.println("LoRa duplex temperature sender (Arduino Uno)\n");
#endif // DEBUG
DS18B20.begin();
if (!LoRa.begin(868E6)) {
#ifdef DEBUG
Serial.println("Error starting LoRa!");
#endif // DEBUG
while (true); // endless loop
}
LoRa.onReceive(onReceive); // callback
void set_clock_to_1MHz();
delayMicroseconds(100);
millis_now = millis();
#ifdef DEBUG
Serial.println("Setup done");
Serial.end();
#endif // DEBUG
}
void loop() {
void set_clock_to_4MHz(); // DS18B20 lib needs min. 4MHz
delayMicroseconds(100);
temperature = get_temperature_DS18B20();
voltage = get_voltage();
void set_clock_to_1MHz(); // back to 1MHz
delayMicroseconds(100);
msg_out = String(temperature) + "C" + String(voltage) + "mV";
delay(random(100)); // randomize delay time to sent
lora_send_message(msg_out);
msg_out_id++; // increment message counter (ID)
#ifdef DEBUG
Serial.begin(38400);
Serial.print("message sent: ");
Serial.println(msg_out);
#endif // DEBUG
millis_prev = millis(); // wait for return message (til max_listen)
while ((millis_now - millis_prev) < millis_max_listen) {
if (flag_message_received) {
lora_read_message();
flag_message_received = false; // Set flag back to false
break;
}
millis_now = millis();
}
delayMicroseconds(10000); //! needed
#ifdef DEBUG
Serial.end();
#endif // DEBUG
// go to sleep
LoRa.sleep();
for (int i=0; i<send_interval; i++) {
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
}
float get_temperature_DS18B20() {
digitalWrite(PIN_DS18B20_POW,HIGH);
delayMicroseconds(100);
DS18B20.requestTemperatures();
LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF); // DS18B20 needs 750ms
delayMicroseconds(100);
return DS18B20.getTempCByIndex(0);
}
unsigned int get_voltage() { // in mV
ADCSRA = 0x85; // ADEN, div = 32 = 125kHz
ADCSRB &= 0xF8; // free running mode
ADMUX = 0x6E; // 01 Ref = Vcc 1 ADLAR (left 8 Bit)
delayMicroseconds(20000); // wait for Vref to settle
ADCSRA |= (1 << ADSC); // start 1 conversion
while (bit_is_set(ADCSRA,ADSC)); // measure and forget 1 conversion
delayMicroseconds(10000);
ADCSRA |= (1 << ADSC); // start 2 conversion
while (bit_is_set(ADCSRA,ADSC)); // measuring Vcc (in mV); 281600=1.1*256*1000
return 275000L / ADCH; // adjusted to real multimeters!
}
void onReceive(int packetSize) {
if (packetSize == 0) { // if there's no packet, return
return;
}
flag_message_received = true; //Set flag to perform read in main loop
}
void lora_send_message(String message_out) {
LoRa_txMode(); // set tx mode
LoRa.beginPacket(); // start packet
LoRa.write(gateway_addr); // add destination address
LoRa.write(node_addr); // add sender address
LoRa.write(msg_out_id); // add message ID (counter)
LoRa.write(message_out.length()); // add payload length
LoRa.print(message_out); // add payload
LoRa.endPacket(); // finish packet and send it
LoRa_rxMode(); // go back into receive mode
}
void lora_read_message() {
addr_in_rec = LoRa.read(); // recipient address
addr_in_sender = LoRa.read(); // sender address
msg_in_id = LoRa.read(); // incoming msg ID
msg_in_length = LoRa.read(); // incoming msg length
while (LoRa.available()) {
msg_in = LoRa.readString();
}
if (msg_in_length != msg_in.length()) { // check length for error
#ifdef DEBUG
Serial.println("error: message length does not match length");
#endif // DEBUG
return;
}
if (addr_in_rec != node_addr && addr_in_rec != 0xFF) {
#ifdef DEBUG
Serial.println("This message is not for me.");
#endif // DEBUG
return;
}
lora_rssi = LoRa.packetRssi();
lora_snr = LoRa.packetSnr();
#ifdef DEBUG
Serial.print("message received: " + msg_in);
Serial.print(" (Length: " + String(msg_in_length));
Serial.print(", ID: " + String(msg_in_id));
Serial.print(")\nfrom: 0x" + String(addr_in_sender, HEX));
Serial.print(" to: 0x" + String(addr_in_rec, HEX));
Serial.print(" (tRSSI: " + lora_rssi);
Serial.print(", Snr: " + lora_snr);
Serial.println(")\n");
#endif // DEBUG
String acknack = msg_in.substring(0,3);
msg_in.remove(0,3);
if ((acknack == "NAK") && (msg_in.toInt() == (msg_out_id-1))) {
lora_send_message(msg_out);
#ifdef DEBUG
Serial.print("message ");
Serial.print(msg_out_id-1);
Serial.println(" sent for a second time!");
#endif // DEBUG
}
else if ((acknack == "ACK") && (msg_in.toInt() == (msg_out_id-1))) {
#ifdef DEBUG
Serial.print("message ");
Serial.print(msg_out_id-1);
Serial.println(" OK!");
#endif // DEBUG
}
LoRa.sleep();
}
void LoRa_rxMode(){
LoRa.enableInvertIQ(); // active invert I and Q signals
LoRa.receive(); // set receive mode
}
void LoRa_txMode(){
LoRa.idle(); // set standby mode
LoRa.disableInvertIQ(); // normal mode
}