OK, here we go:
It may be important to know that a DS3231 is connected with an alarm set up every minute on the minute.
Doing this, I can have the Arduino sleep forever, wake every minute and if it is the full hour, I do a send.
The immediate downlink consists of three bytes, being hour, minute and second to avoid the RTC from drifting away over time. But I see the problem on all my 328 nodes when I put them to sleep. The others have no downlinks.
The mentioned raincounter is not connected yet but would also fire the IRQ.
I am pretty sure that the problem has to do with putting the Arduino to sleep and hacking the timer counter. But from reading the internet everybody does it like that and there seems to be no other solution.
Testing of the whole stuff is a but cumbersome as it always takes about 3 hours for the problem to show up.
Except if I add - as mentioned before - huge amount of time to the counter. Then it happens after three minutes. Somehow the timing is messed up. But why and where?
Btw. I tried to enable the debug for the LMIC lib but the 328 has not enough memory for that.
/*******************************************************************************
* Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
* Copyright (c) 2018 Terry Moore, MCCI
*
* Permission is hereby granted, free of charge, to anyone
* obtaining a copy of this document and accompanying files,
* to do whatever they want with them without any restriction,
* including, but not limited to, copying, modification and redistribution.
* NO WARRANTY OF ANY KIND IS PROVIDED.
*
* This example sends a valid LoRaWAN packet with payload "Hello,
* world!", using frequency and encryption settings matching those of
* the The Things Network.
*
* This uses OTAA (Over-the-air activation), where where a DevEUI and
* application key is configured, which are used in an over-the-air
* activation procedure where a DevAddr and session keys are
* assigned/generated for use with all further communication.
*
* Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
* g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
* violated by this sketch when left running for longer)!
* To use this sketch, first register your application and device with
* the things network, to set or generate an AppEUI, DevEUI and AppKey.
* Multiple devices can use the same AppEUI, but each device has its own
* DevEUI and AppKey.
*
* Do not forget to define the radio type correctly in
* arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
*
*******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <LoraMessage.h>
#include "LowPower.h"
#include <Wire.h>
#include <RtcDS3231.h>
RtcDS3231<TwoWire> Rtc(Wire);
#include <TimeLib.h>
#include "Adafruit_SI1145.h"
Adafruit_SI1145 uv = Adafruit_SI1145();
// Use pin 2 as wake up pin
const int IRQwakeUpPin = 2;
//Pin for UV Meter
const int UVpin = A2;
// marked volatile so interrupt can safely modify them and
// other code can safely read and modify them
//volatile uint16_t interuptCount = 0;
volatile bool IRQinterruptFlag = false;
void IRQwakeUp() {
// since this interupted any other running code,
// don't do anything that takes long and especially avoid
// any communications calls within this routine
IRQinterruptFlag = true;
}
volatile boolean powerdown=false;
time_t t;
unsigned int Vbat;
unsigned int Vsolar;
unsigned int ValueCounter;
unsigned int UVindex;
unsigned int UVlevel;
unsigned int VISlevel;
unsigned int IRlevel;
unsigned long UVindexSum;
unsigned long UVlevelSum;
unsigned long VISlevelSum;
unsigned long IRlevelSum;
unsigned int raincount;
// This EUI must be in little-endian format, so least-significant-byte
// first. When copying an EUI from ttnctl output, this means to reverse
// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
// 0x70.
static const u1_t PROGMEM APPEUI[8] = { XXX };
void os_getArtEui (u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
// This should also be in little endian format, see above.
static const u1_t PROGMEM DEVEUI[8] = { XXX };
void os_getDevEui (u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
// This key should be in big endian format (or, since it is not really a
// number but a block of memory, endianness does not really apply). In
// practice, a key taken from ttnctl can be copied as-is.
// The key shown here is the semtech default key.
static const u1_t PROGMEM APPKEY[16] = { XXX };
void os_getDevKey (u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
static osjob_t sendjob;
// Pin mapping
const lmic_pinmap lmic_pins = {
.nss = 10,
.rxtx = LMIC_UNUSED_PIN,
.rst = 4,
.dio = {6, 7, LMIC_UNUSED_PIN},
};
void onEvent (ev_t ev) {
Serial.print(os_getTime());
Serial.print(": ");
switch(ev) {
case EV_SCAN_TIMEOUT:
Serial.println(F("EV_SCAN_TIMEOUT"));
break;
case EV_BEACON_FOUND:
Serial.println(F("EV_BEACON_FOUND"));
break;
case EV_BEACON_MISSED:
Serial.println(F("EV_BEACON_MISSED"));
break;
case EV_BEACON_TRACKED:
Serial.println(F("EV_BEACON_TRACKED"));
break;
case EV_JOINING:
Serial.println(F("EV_JOINING"));
break;
case EV_JOINED:
Serial.println(F("EV_JOINED"));
/*
{
u4_t netid = 0;
devaddr_t devaddr = 0;
u1_t nwkKey[16];
u1_t artKey[16];
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("netid: ");
Serial.println(netid, DEC);
Serial.print("devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("artKey: ");
for (int i=0; i<sizeof(artKey); ++i) {
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("nwkKey: ");
for (int i=0; i<sizeof(nwkKey); ++i) {
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
}
*/
// Disable link check validation (automatically enabled
// during join, but because slow data rates change max TX
// size, we don't use it in this example.
LMIC_setLinkCheckMode(0);
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_RFU1:
|| Serial.println(F("EV_RFU1"));
|| break;
*/
case EV_JOIN_FAILED:
Serial.println(F("EV_JOIN_FAILED"));
break;
case EV_REJOIN_FAILED:
Serial.println(F("EV_REJOIN_FAILED"));
break;
case EV_TXCOMPLETE:
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
if (LMIC.txrxFlags & TXRX_ACK)
Serial.println(F("Received ack"));
if (LMIC.dataLen==3) {
t = Rtc.GetDateTime();
Serial.print(F("alt: "));
Serial.print(hour(t));
Serial.print(F(":"));
Serial.print(minute(t));
Serial.print(F(":"));
Serial.println(second(t));
//Set time to received value
setTime((int)LMIC.frame[LMIC.dataBeg + 0],(int)LMIC.frame[LMIC.dataBeg + 1],(int)LMIC.frame[LMIC.dataBeg + 2],day(t),month(t),year(t));
Rtc.SetDateTime(now());
t = Rtc.GetDateTime();
Serial.print(F("neu: "));
Serial.print(hour(t));
Serial.print(F(":"));
Serial.print(minute(t));
Serial.print(F(":"));
Serial.println(second(t));
// data received in rx slot after tx
Serial.print(F("Received "));
Serial.print(LMIC.dataLen);
Serial.print(F(" bytes of payload"));
/*
for (int i = 0; i < LMIC.dataLen; i++) {
if (LMIC.frame[LMIC.dataBeg + i] < 0x10) {
Serial.print(F("0"));
}
Serial.print(LMIC.frame[LMIC.dataBeg + i], HEX);
}
*/
Serial.println();
}
Serial.println(F("sent"));
//send/receive cycle completed
powerdown=true;
break;
case EV_LOST_TSYNC:
Serial.println(F("EV_LOST_TSYNC"));
break;
case EV_RESET:
Serial.println(F("EV_RESET"));
break;
case EV_RXCOMPLETE:
// data received in ping slot
Serial.println(F("EV_RXCOMPLETE"));
break;
case EV_LINK_DEAD:
Serial.println(F("EV_LINK_DEAD"));
break;
case EV_LINK_ALIVE:
Serial.println(F("EV_LINK_ALIVE"));
break;
/*
|| This event is defined but not used in the code. No
|| point in wasting codespace on it.
||
|| case EV_SCAN_FOUND:
|| Serial.println(F("EV_SCAN_FOUND"));
|| break;
*/
case EV_TXSTART:
Serial.println(F("EV_TXSTART"));
break;
default:
Serial.print(F("Unknown event: "));
Serial.println((unsigned) ev);
break;
}
}
void do_send(osjob_t* j) {
// Check if there is not a current TX/RX job running
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println(F("OP_TXRXPEND, not sending"));
} else {
//Prepare data
LoraMessage message;
message.addUint16(raincount);
message.addUint16(UVindex);
message.addUint16(UVlevel);
message.addUint16(VISlevel);
message.addUint16(IRlevel);
message.addUint16(Vbat);
message.addUint16(Vsolar);
ValueCounter=0;
raincount=0;
UVindexSum=0;
UVlevelSum=0;
VISlevelSum=0;
IRlevelSum=0;
Serial.println(F(" sending..."));
// Prepare upstream data transmission at the next possible time.
LMIC_setTxData2(1, message.getBytes(), message.getLength(), 0);
Serial.print(os_getTime());
Serial.print(": ");
Serial.println(F("Packet queued"));
}
// Next TX is scheduled after TX_COMPLETE event.
}
void setup() {
Serial.begin(115200);
Serial.println(F("Starting"));
// Configure wake up pins as input.
// This will consumes few uA of current.
pinMode(IRQwakeUpPin, INPUT_PULLUP);
//Start RTC CLock
Rtc.Begin();
//Start UV/VIS/IR-Sensor
uv.begin();
Rtc.Enable32kHzPin(false);
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmTwo);
// Alarm 2 set to trigger at the top of the minute
DS3231AlarmTwo alarm2(
0,
0,
0,
DS3231AlarmTwoControl_OncePerMinute);
Rtc.SetAlarmTwo(alarm2);
// throw away any old alarm state before we ran
Rtc.LatchAlarmsTriggeredFlags();
// LMIC init
os_init();
// Reset the MAC state. Session and pending data transfers will be discarded.
LMIC_reset();
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
// Start job (sending automatically starts OTAA too)
measure(); //Do a first measurement at startup
powerdown=false;
os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
//Attach Interrupt for RTC
attachInterrupt(digitalPinToInterrupt(IRQwakeUpPin), IRQwakeUp, FALLING);
}
void loop() {
extern volatile unsigned long timer0_overflow_count;
os_runloop_once(); //check send status
if (powerdown) {
Serial.println(F(" ... go to sleep"));
Serial.flush();
// Enter power down state with ADC and BOD module disabled.
LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
Serial.begin(115200);
Serial.print(F("woken ... "));
if (IRQinterruptFlag) {
IRQinterruptFlag=false; //Remove flag
Serial.println(F("IRQ"));
DS3231AlarmFlag flag = Rtc.LatchAlarmsTriggeredFlags();
if (flag & DS3231AlarmFlag_Alarm2) { //RTC-Interrupt
Serial.print(F("-RTC-"));
cli();
timer0_overflow_count += 60 * 64 * clockCyclesPerMicrosecond(); //give back 60 seconds of sleep
sei();
measure();
t = Rtc.GetDateTime();
Serial.print(F(" "));
Serial.print(minute(t));
Serial.print(F(":"));
Serial.print(second(t));
if (minute(t)==0) { //send on full hour
powerdown=false;
os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
}
} else { //If not woken by RTC it must be Rain sensor
raincount+=1;
Serial.print(F("rain: "));
Serial.println(raincount);
}
}
}
} //loop
void measure() {
Vbat = analogRead(A0) / 1024.0 * 3300 * 2;
Vsolar = analogRead(A1) / 1024.0 * 3300 * 2;
//Measure UVlevel from analog ML8511
UVlevelSum+= analogRead(A2);
//Measure IR, VIS and UVindex from SI1145
VISlevelSum+=uv.readVisible();
IRlevelSum+=uv.readIR();
UVindexSum+=uv.readUV();
ValueCounter++;
UVindex=UVindexSum/ValueCounter;
UVlevel=UVlevelSum/ValueCounter;
VISlevel=VISlevelSum/ValueCounter;
IRlevel=IRlevelSum/ValueCounter;
}