Skip to content

Commit 152a77b

Browse files
authored
Added external RTC as option (#152)
1 parent 3a362ee commit 152a77b

File tree

4 files changed

+306
-30
lines changed

4 files changed

+306
-30
lines changed

BresserWeatherSensorLW.ino

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
// NimBLE-Arduino 2.3.2 (optional)
3535
// ATC MiThermometer 0.5.0 (optional)
3636
// Theengs Decoder 1.9.9 (optional)
37+
// RTClib (Adafruit) 2.1.4 (optional)
3738
//
3839
// (installed from ZIP file:)
3940
// DistanceSensor_A02YYUW 1.0.2 (optional)
@@ -113,6 +114,7 @@
113114
// 20250317 Removed ARDUINO_M5STACK_Core2 (now all uppercase)
114115
// 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE, payloadSize to uplinkSize
115116
// 20250622 Updated to RadioLib v7.2.0, added custom delay (ESP32 light sleep)
117+
// 20250803 Added support for external RTC chips
116118
//
117119
// ToDo:
118120
// -
@@ -189,6 +191,12 @@ using namespace PowerFeather;
189191
#include <ArduinoJson.h>
190192
#include "BresserWeatherSensorLWCfg.h"
191193
#include "BresserWeatherSensorLWCmd.h"
194+
195+
#if defined(EXT_RTC)
196+
// Adafruit RTClib - https://github.com/adafruit/RTClib
197+
#include <RTClib.h>
198+
#endif
199+
192200
#include "src/LoadSecrets.h"
193201
#include "src/LoadNodeCfg.h"
194202
#include "src/AppLayer.h"
@@ -207,23 +215,20 @@ SPIClass *spi = nullptr;
207215
#endif
208216
#endif
209217

210-
211-
212218
#if defined(ARDUINO_LILYGO_T3S3_LR1121)
213219
static const uint32_t rfswitch_dio_pins[] = {
214220
RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6,
215-
RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC
216-
};
221+
RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
217222

218223
static const Module::RfSwitchMode_t rfswitch_table[] = {
219224
// mode DIO5 DIO6
220-
{ LR11x0::MODE_STBY, { LOW, LOW } },
221-
{ LR11x0::MODE_RX, { HIGH, LOW } },
222-
{ LR11x0::MODE_TX, { LOW, HIGH } },
223-
{ LR11x0::MODE_TX_HP, { LOW, HIGH } },
224-
{ LR11x0::MODE_TX_HF, { LOW, LOW } },
225-
{ LR11x0::MODE_GNSS, { LOW, LOW } },
226-
{ LR11x0::MODE_WIFI, { LOW, LOW } },
225+
{LR11x0::MODE_STBY, {LOW, LOW}},
226+
{LR11x0::MODE_RX, {HIGH, LOW}},
227+
{LR11x0::MODE_TX, {LOW, HIGH}},
228+
{LR11x0::MODE_TX_HP, {LOW, HIGH}},
229+
{LR11x0::MODE_TX_HF, {LOW, LOW}},
230+
{LR11x0::MODE_GNSS, {LOW, LOW}},
231+
{LR11x0::MODE_WIFI, {LOW, LOW}},
227232
END_OF_MODE_TABLE,
228233
};
229234
#endif // ARDUINO_LILYGO_T3S3_LR1121
@@ -272,6 +277,11 @@ bool lwStatusUplinkPending __attribute__((section(".uninitialized_data")));
272277
/// Real time clock
273278
ESP32Time rtc;
274279

280+
#if defined(EXT_RTC)
281+
// Create an instance of the external RTC class
282+
EXT_RTC ext_rtc;
283+
#endif
284+
275285
/// Application layer
276286
AppLayer appLayer(&rtc, &rtcLastClockSync);
277287

@@ -312,16 +322,16 @@ void uplinkDelay(uint32_t timeUntilUplink, uint32_t uplinkInterval)
312322
{
313323
// wait before sending uplink
314324
uint32_t minimumDelay = uplinkInterval * 1000UL;
315-
//uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per FUP & law!)
325+
// uint32_t interval = node.timeUntilUplink(); // calculate minimum duty cycle delay (per FUP & law!)
316326
uint32_t delayMs = max(timeUntilUplink, minimumDelay); // cannot send faster than duty cycle allows
317327

318328
log_d("Sending uplink in %u s", delayMs / 1000);
319-
#if defined(ESP32)
329+
#if defined(ESP32)
320330
esp_sleep_enable_timer_wakeup(delayMs * 1000);
321331
esp_light_sleep_start();
322-
#else
332+
#else
323333
delay(delayMs);
324-
#endif
334+
#endif
325335
}
326336

327337
/*!
@@ -437,6 +447,29 @@ void gotoSleep(uint32_t seconds)
437447
}
438448
#endif
439449

450+
#if defined(EXT_RTC)
451+
// Synchronize the internal RTC with the external RTC
452+
void syncRTCWithExtRTC(void)
453+
{
454+
DateTime now = ext_rtc.now();
455+
456+
// Convert DateTime to time_t
457+
struct tm timeinfo;
458+
timeinfo.tm_year = now.year() - 1900;
459+
timeinfo.tm_mon = now.month() - 1;
460+
timeinfo.tm_mday = now.day();
461+
timeinfo.tm_hour = now.hour();
462+
timeinfo.tm_min = now.minute();
463+
timeinfo.tm_sec = now.second();
464+
465+
time_t t = mktime(&timeinfo);
466+
467+
// Set the MCU's internal RTC (ESP32) or SW RTC (RP2040)
468+
struct timeval tv = {t, 0}; // `t` is seconds, 0 is microseconds
469+
settimeofday(&tv, nullptr);
470+
}
471+
#endif // EXT_RTC
472+
440473
/// Print date and time (i.e. local time)
441474
void printDateTime(void)
442475
{
@@ -640,6 +673,27 @@ void setup()
640673
}
641674
bootCount++;
642675

676+
#if defined(EXT_RTC)
677+
if ((rtcLastClockSync == 0) || ((rtc.getLocalEpoch() - rtcLastClockSync) > (CLOCK_SYNC_INTERVAL * 60)))
678+
{
679+
if (!ext_rtc.begin())
680+
{
681+
log_w("External RTC not available");
682+
}
683+
else if (ext_rtc.lostPower())
684+
{
685+
log_w("External RTC lost power");
686+
}
687+
else
688+
{
689+
syncRTCWithExtRTC();
690+
rtcLastClockSync = rtc.getLocalEpoch();
691+
rtcTimeSource = E_TIME_SOURCE::E_RTC;
692+
log_i("Set time and date from external RTC");
693+
}
694+
}
695+
#endif
696+
643697
// Set time zone
644698
setenv("TZ", timeZoneInfo.c_str(), 1);
645699
printDateTime();
@@ -686,37 +740,36 @@ void setup()
686740

687741
int16_t state = 0; // return value for calls to RadioLib
688742

689-
#if !defined(RADIO_CHIP)
690-
#if defined(ARDUINO_LILYGO_T3S3_SX1262) || defined(ARDUINO_LILYGO_T3S3_SX1276) || defined(ARDUINO_LILYGO_T3S3_LR1121)
691-
// Use local radio object with custom SPI configuration
692-
spi = new SPIClass(SPI);
693-
spi->begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
694-
radio = new Module(PIN_RECEIVER_CS, PIN_RECEIVER_IRQ, PIN_RECEIVER_RST, PIN_RECEIVER_GPIO, *spi);
695-
#endif
696-
#endif
743+
#if !defined(RADIO_CHIP)
744+
#if defined(ARDUINO_LILYGO_T3S3_SX1262) || defined(ARDUINO_LILYGO_T3S3_SX1276) || defined(ARDUINO_LILYGO_T3S3_LR1121)
745+
// Use local radio object with custom SPI configuration
746+
spi = new SPIClass(SPI);
747+
spi->begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
748+
radio = new Module(PIN_RECEIVER_CS, PIN_RECEIVER_IRQ, PIN_RECEIVER_RST, PIN_RECEIVER_GPIO, *spi);
749+
#endif
750+
#endif
697751

698752
radio.reset();
699753
LoRaWANNode node(&radio, &Region, subBand);
700754

701755
// setup the radio based on the pinmap (connections) in config.h
702756
log_v("Initialise radio");
703-
757+
704758
state = radio.begin();
705759
debug(state != RADIOLIB_ERR_NONE, "Initialise radio failed", state, true);
706760

707-
708-
// Using local radio object
709-
#if defined(ARDUINO_LILYGO_T3S3_LR1121)
761+
// Using local radio object
762+
#if defined(ARDUINO_LILYGO_T3S3_LR1121)
710763
radio.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
711764

712765
// LR1121 TCXO Voltage 2.85~3.15V
713766
radio.setTCXO(3.0);
714-
#endif
767+
#endif
715768

716-
#if defined(ESP32)
769+
#if defined(ESP32)
717770
// Optionally provide a custom sleep function - see config.h
718771
node.setSleepFunction(customDelay);
719-
#endif
772+
#endif
720773

721774
// activate node by restoring session or otherwise joining the network
722775
state = lwActivate(node);

BresserWeatherSensorLWCfg.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
// 20250317 Removed ARDUINO_heltec_wifi_lora_32_V3 and ARDUINO_M5STACK_Core2
7070
// (now all uppercase)
7171
// 20250318 Renamed PAYLOAD_SIZE to MAX_UPLINK_SIZE
72+
// 20250803 Added support for external RTC chips
7273
//
7374
// Note:
7475
// Depending on board package file date, some defines are written either
@@ -165,6 +166,13 @@ const uint8_t MAX_DOWNLINK_SIZE = 51;
165166
// Enable battery / supply voltage uplink
166167
#define ADC_EN
167168

169+
// Select one of the external RTC chips supported by Adafruit RTClib (optional)
170+
// https://github.com/adafruit/RTClib
171+
//#define EXT_RTC RTC_DS3231
172+
//#define EXT_RTC RTC_DS1307
173+
//#define EXT_RTC RTC_PCF8523
174+
//#define EXT_RTC RTC_PCF8563
175+
168176
// Enable OneWire temperature measurement
169177
#define ONEWIRE_EN
170178

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This was originally a remake of [BresserWeatherSensorTTN](https://github.com/mat
4949
* Implementation with Separation between LoRaWAN Network Layer and Application Layer for easy Repurposing
5050
* Loading of LoRaWAN Secrets from JSON File on LittleFS (optional)
5151
* Loading of Hardware/Deployment specific Configuration Parameters from JSON file on LittleFS (optional)
52+
* External RTC (with Backup Battery) Integration
5253

5354
## Contents
5455

@@ -60,6 +61,7 @@ This was originally a remake of [BresserWeatherSensorTTN](https://github.com/mat
6061
* [Predefined Board Configurations](#predefined-board-configurations)
6162
* [User-Defined Pinout and Radio Chip Configurations](#user-defined-pinout-and-radio-chip-configurations)
6263
* [User-Defined Battery Voltage Measurement](#user-defined-battery-voltage-measurement)
64+
* [Real-Time Clock (RTC)](#real-time-clock-rtc)
6365
* [LoRaWAN Network Service Configuration](#lorawan-network-service-configuration)
6466
* [Software Build Configuration](#software-build-configuration)
6567
* [Required Configuration](#required-configuration)
@@ -287,6 +289,33 @@ In `BresserWeatherSensorLWCfg.h`:
287289

288290
The function `getBatteryVoltage()` in [adc.cpp](https://github.com/matthias-bs/BresserWeatherSensorLW/src/adc.cpp) provides the battery voltage. Any board specific implementation should be placed there. `getBatteryVoltage()` returns `0` for any unknown board or a known board with out a default ADC input circuit to indicate that the battery voltage cannot be measured.
289291

292+
#### Real-Time Clock (RTC)
293+
294+
The MCU's built-in RTC provides time and date for scheduling wake-up from sleep mode and for algorithms like rain gauge or lightning counter post-processing.
295+
296+
The internal RTC retains operation while the MCU is in sleep mode. It can be set from different sources:
297+
298+
1. LoRaWAN Network Time
299+
300+
The `Device_Time_Req` MAC command allows to request the time from the LoRaWAN network service. This is not supported by all LNS (e.g. not available with Helium Network).
301+
302+
2. LoRaWAN Downlink Command
303+
304+
The command `CMD_SET_DATETIME` (see [Remote Configuration Commands / Status Requests via LoRaWAN](#remote-configuration-commands--status-requests-via-lorawan)) allows to set the RTC manually.
305+
306+
The time between queuing `CMD_SET_DATETIME` and the RTC actually being set is rather unpredictable due to the LNS's downlink scheduling. Furthermore, a loss of power (in case of a battery/solar powered node) will reset the MCU's integrated RTC.
307+
308+
3. External RTC
309+
310+
An external RTC chip with backup battery retains operation independently of the node's power supply. It is initially set when the LoRaWAN node is built (see [RTCSet.ino](extras/RTCSet/RTCSet.ino)).
311+
312+
A module with an RTC chip supported by the [Adafruit RTClib](https://github.com/adafruit/RTClib) is connected to the 3.3V power supply and to the MCU's I²C bus pins.
313+
314+
> [!IMPORTANT]
315+
> Check if the I²C interface requires additional pull-up resistors.
316+
317+
If enabled by setting `EXT_RTC` in [BresserWeatherSensorLWCfg.h](BresserWeatherSensorLWCfg.h), the external RTC takes precedence over the LoRaWAN Network Time.
318+
290319
### LoRaWAN Network Service Configuration
291320

292321
Create an account and set up a device configuration in your LoRaWAN network provider's web console, e.g. [The Things Network](https://www.thethingsnetwork.org/).
@@ -961,6 +990,7 @@ Based on
961990
* [Theengs Decoder](https://github.com/theengs/decoder) by [Theengs Project](https://github.com/theengs)
962991
* [DistanceSensor_A02YYUW](https://github.com/pportelaf/DistanceSensor_A02YYUW) by Pablo Portela
963992
* [Preferences](https://github.com/vshymanskyy/Preferences) by Volodymyr Shymanskyy
993+
* [RTClib](https://github.com/adafruit/RTClib) by Adafruit
964994

965995
## Legal
966996

0 commit comments

Comments
 (0)