Lmic plus rfm95w staying awake for long periods

Some more info on this. I’ve seen this now several times.I have nodes that wake both from timers and from a gpio interrupt. The timers always seem to come in, the gpio interrupt always wakes the node right away because the SPI clock is going through pin 13 on the teensy LC and so it makes the light go on. Once woken it should simply send a packet, that’s it. Confirm both by spectrum analyser and gateway logs show that the packet is not sent. Well… not sent for several minutes now it seems. Something is randomly holding up the sending of the packet to TTN. On the timer based sends I probably don’t notice if the send happens just delayed.

Maybe there’s an error in the code to handle duty cycling? In any case, for relatively time critical apps (Security sensors) this is a big deal.

There’s a nice challenge in here for someone to find this :slight_smile:
Kim

The LMIC code OS also takes care of duty cycle. When the MCU goes to sleep, this LMIC ticks that takes care of these duty cycle needs to be updated too (stops when you sleep). Myself is still looking into patching this to make it work properly with sleeping MCU.

Ok this is very interesting. That would make the next send be considered in essence “back to back” with the first. Still likely to be some “bugs” as a side effect of this use case as it doesn’t happen every time. Also I’m using SF7 so the duty cycle should only wait about 5s.

But thanks for confirming that there could be a problem in this area. Nice challenge for myself, though I don’t think I like to use the arduino ide for reading code so I’ll likely have to get familiar with arduino code in eclipse before I start :slight_smile:

Kim

When the mcu sleeps you will have to adjust timer0_overflow_count for original AVR microcontrollers, see the end of:

For other microcontrollers it will be different. Stm32 needs adjustment of systick_uptime_millis, see eg:

1 Like

Thanks very much tomtor for this helpful information. I’ll try it out and feedback the results when I have them.

Cheers,
Kim

Very interesting,
Would you mind detail this code, I imagine 8 is for 8s watchdog but why this 64 (prescaler?) ?

timer0_overflow_count+= 8 * 64 * clockCyclesPerMicrosecond();

Thanks

Hummm, that’s interesting, when I’m sleeping with my mini Lora Node I disable the timer0 and enable it back after sleep.

I’m doing this to achieve maximum low power and I never encounter this problem, is it repeatable or random ? I need to check.

Here a part of my sleeping code

/* ======================================================================
Function: goSleeping
Purpose : set the device to sleep mode
Input   : 
Output  : Global Status of what triggered wake up 
Comments: -
====================================================================== */
void goSleeping( uint8_t _wake_mode, uint8_t _wdt_period = APP_WATCHDOG_NONE)
{
  SERIAL_DEBUG.end();
  
  // light Off on board LED
  ulpn.setDevice(DEVICE_LED_OFF);

  // Turn off on board RGB LED
  ulpn.RGBShow(RGB_OFF);

  // turn off power from sensors 
  ulpn.setDevice(DEVICE_SENSORS_OFF); 

  // turn off power from RF Radio module
  ulpn.powerRadio(false);  

  // Disable all ATmel internal peripherals (for low power)
  ulpn.disableCPUDevices();
  
  // if we want to be wake up by the watchdog, set it up
  // immediatly after this call we're sleeping
  // ========================================
  ulpn.sleepDeviceWake( _wake_mode, _wdt_period); // zzz...
  // ========================================

  // We've been waked up (by a IRQ), disable ours until we done the job
  ulpn.setIRQ(IRQ_SWITCH_DISABLE | IRQ_EXTWAKE_DISABLE);

  // Enable back arduino core delay() function
  power_timer0_enable(); 

  // Enable sensors, RGB LED and onboard LED also
  ulpn.setDevice(DEVICE_SENSORS_ON );

  // Activate back Serial
  power_usart0_enable();
  SERIAL_DEBUG.begin(SERIAL_PORT_SPEED);

  ulpn.powerRadio(true);
}

Function disableCPUDevices for information

/* ======================================================================
Function: disableCPUDevices
Purpose : disable Atmel integrated devices (for low power)
Input   : -
Output  : - 
Comments: - 
====================================================================== */
void ULPNode::disableCPUDevices(void)
{
  // Disable ADC 
  ADCSRA &= ~_BV(ADEN)  ; 

  // disable Analog comparator  
  ACSR |= _BV(ACD); 
  
  // Disable digital input buffers on all ADC0-ADC5 pins
  //DIDR0 = 0x3F;    

  // set I2C pin as input no pull up
  // this prevent current draw on I2C pins that
  // completly destroy our low power mode

  //Disable I2C interface so we can control the SDA and SCL pins directly
  TWCR &= ~(_BV(TWEN)); 

  // disable I2C module this allow us to control
  // SCA/SCL pins and reinitialize the I2C bus at wake up
  TWCR = 0;
  pinMode(SDA, INPUT);
  pinMode(SCL, INPUT);
  digitalWrite(SDA, LOW);
  digitalWrite(SCL, LOW);  

  /*
  power_adc_disable();
  power_usart0_disable();
  power_spi_disable();  
  power_twi_disable();
  power_timer0_disable(); 
  power_timer1_disable();
  power_timer2_disable();
  */

  power_all_disable();
}

/* ======================================================================
Function: sleepDeviceWake
Purpose : prepare sleep mode for total power down and sleep
Input   : what IRQ could wake us
          Watchdog duration if we want it to wake us
Output  : - 
Comments: - 
====================================================================== */
void ULPNode::sleepDeviceWake(uint8_t mode, uint8_t wdt_period)
{
  uint8_t irq_mode = 0;
  
  // save current interrupt state
  uint8_t oldSREG = SREG;

  // switch all interrupts off while messing with their 
  // settings or doing something that can trigger one
  // we will sleep, WE DO NOT WANT to be interrupted before sleeping
  cli();  

  // Does the external device if any need to wake us ?
  irq_mode |= (mode & SLEEP_WAKE_EXT) ? IRQ_EXTWAKE_ENABLE : IRQ_EXTWAKE_DISABLE ;

  // Does the switch push need to wake us ?
  irq_mode |= (mode & SLEEP_WAKE_SWITCH)  ? IRQ_SWITCH_ENABLE : IRQ_SWITCH_DISABLE ;

  // Setting IRQ
  setIRQ(irq_mode);
  
  // Do we want watchdog to wake us with interrupt ?
  if (mode & SLEEP_WAKE_WATCHDOG) {
    // WDTCSR period msb bit watchdog period is not contiguous 
    // adapt wdt_period to be compatible with WDTCSR
    wdt_period = ((wdt_period & 0x08)?_BV(WDP3):0x00) | (wdt_period & 0x07);
    
    // allow changes on watchdog config
    // set interrupt mode and period, and do a fresh start
    WDTCSR = _BV(WDCE) | _BV(WDE) ;
    WDTCSR = _BV(WDIE) | wdt_period;    
    wdt_reset(); 
  } else {
    // we don't want to be waked by the watchdog, so be 
    // sure to disable it by changing the config
    WDTCSR = _BV(WDCE) | _BV(WDE);
    WDTCSR = 0;    
  }

  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable(); 
  
  // Sequence timed instruction don't try to optimize
  if (mode & SLEEP_BOD_OFF) {
    // turn off BOD (sequenced order see datasheet)
    // Don't change anything before we're waked
    MCUCR = _BV(BODS) | _BV(BODSE);
    MCUCR = _BV(BODS); 
    sei();          
    sleep_cpu();   
  } else {
    sei();          
    sleep_cpu();   
  }
   
  // ...........
  // ZZZZZZZZZZZ
  // ...........
  
  // Waked !!!
  sleep_disable();
 
  // Restore original Interrupts
  SREG = oldSREG;
}
1 Like

See

https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring.c

Not sure if my computation is correct, it is good if you double check.

@tomtor
Oh, I never thought it was incorrect, I’m just curious and would like to understand, at least trying to :wink:
Already checked timing in LMIC for arduino Zero, but never got it to understand the magic done ;-(

As already pointed by tomtor,

So the above formula may be inaccurate.

With a 1MHz CPU Clock, we get one Timer0 overflow every 64x256 µs.
So every second, we have 10^6/2^14 (roughly 61).
64 is a bit too fast

Do I forget something? Is it simply better to use a power 2 number?
Is the timing difference negligible to follow duty cycle policy?

Hi all
I know this thread is kind of stale but on the other hand, @Couin added something “recently”.
And, I do not see a resolution to the issue yet.

Thing is, I am experiencing the exact same thing with my Arduino Pro Minis and the RFM95 when putting the Arduino to sleep after transmissions.
After some time of running the nodes seem to randomly take minutes instead of seconds between queing a packet and the TX_complete.
Measuring the power needs, I found out that the RFM is on (4 mA) during that time causing the nodes to eat through the battery quite rapidly.
Of course I added the time0_overflow_count hack, adding back the amount of time I put the Arduino to sleep.
Thing is, this “cheating” seems to be related with the occurence of the effect in the first place.

To investigate, I added 600 seconds for every minute to the counter without putting the Arduino to sleep.
And this caused the problem to become enormous, causing huge delays after 2 or 3 sends already,

So, now my question:
Could it be that adding 8 seconds for every 8 seconds of sleep is too much?
And if so, why does it bother the LMIC?
As Couin pointed out, it might be more correct to only use 61 instead of 64 as multiplier.
Did anybody experience the behaviour and knows how to fix it?
Should I only use e.g. a multiplier of 60 to ensure I get more likely a slow running clock?
Sending every quarter of an hour to hour should not cause me problems with the duty cycles anyway.

As I supposed last time, it may be better to stick to 64.
My last post was more an explanation where this magic number comes from :slight_smile:

I remember reading some thoughtful discussions about how sleep should be handled.
It’s really easy to mess with LMIC scheduler.

I don’t remember this exact behaviour. I had issues I luckily managed to solve (can’t remember, it was nearly a year ago).
Could you share a (perhaps simplified) version of your code, the library and IDE used?

I did some more testing but do get nowhere.
I tried both the library from matthijskooijman and the supposedly newer (improved?) one here: mcci-catena/ arduino-lmic
but both behave the same.
The IDE used is Arduino 1.8.5.
Regarding the code, I am not sure if I can/should post a quite long code here. It is mainly the example otaa code with some modifications to put the Arduino to sleep.
What is very strange is, that the effect starts usually only after some hours.
Here is one example with a code that wakes up once per minute and tries to send once per hour:
Joining looks like that:

Starting
 sending...
4027: Packet queued
4080: EV_JOINING
11167: EV_TXSTART
332332: EV_JOINED
333199: EV_TXSTART
403949: EV_TXCOMPLETE (includes waiting for RX windows)
alt: 10:19:55
neu: 10:19:57
Received 3 bytes of payload
sent
 ... go to sleep
woken ... IRQ
-RTC- 20:0 ... go to sleep
woken ... IRQ
-RTC- 21:0 ... go to sleep

Then after one hour, a packet is sent:

-RTC- 58:0 ... go to sleep
woken ... IRQ
-RTC- 59:0 ... go to sleep
woken ... IRQ
-RTC- 0:0 sending...
151569065: EV_TXSTART
151569279: Packet queued
151639348: EV_TXCOMPLETE (includes waiting for RX windows)
alt: 11:0:1
neu: 11:0:11
Received 3 bytes of payload
sent
 ... go to sleep
woken ... IRQ
-RTC- 1:0 ... go to sleep
woken ... IRQ
-RTC- 2:0 ... go to sleep

This goes on for three hours and then this happens:

-RTC- 59:0 ... go to sleep
woken ... IRQ
-RTC- 0:0 sending...
278556535: Packet queued
329882220: EV_TXSTART
329952504: EV_TXCOMPLETE (includes waiting for RX windows)
alt: 14:13:41
neu: 14:14:1
Received 3 bytes of payload
sent
 ... go to sleep

The Packet queued message now comes way before the TX_START, because other than in the hours before, the LMIC seems to delay the sending for 13 minutes in this case.
It can be seen from the RTC printout that usually send is within the minute but here it is after 13 minutes.

Why is it doing this? And how can I avoid it.

The code where I schedule the sending is:

os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);

and in the do_send I do this

Serial.println(F(" sending..."));
LMIC_setTxData2(1, message.getBytes(), message.getLength(), 0);
Serial.print(os_getTime());
Serial.print(": ");
Serial.println(F("Packet queued"));

message consists of 16 bytes put together by LoraMessage.h
After the send it runs the os_runloop_once(); in the loop() until TX_Complete and goes to sleep again.

What are you receiving in the downlink? Are those confirmed uplinks, application downlinks, or is TTN sending some MAC command that might confuse LMiC? (Click an item in TTN Console to see what’s sent.)

1 Like

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;
}

I think you’re right :sunglasses:

you just goto deepsleep and wake up… without re initialising the I2C bus … so The DS3231 can react a bit strange sometimes imho

’ to avoid the RTC from drifting away over time ’
not neccessary - it’s a very accurate RTC… only check once a month / year

Sure, you are totally correct there :face_with_monocle:
The downlink evrey hour is neither important to me nor do I believe it is part of the problem.
I just added it out of curiosity (before noticing the other problem) so I kept it for now.
If everything else works, I could change code to only request a downlink every week/month or so for correction.

The DS3231 itself on the other hand is important for me because with the Arduino in deepsleep I have absolutely no other means of time tracking. But it is behaving neatly, so far.

Any idea why my LMIC timing is getting messed up, though? This really drives me crazy and it is hard to believe that I am the only one having seen this issue. Well, I am not, see start of post. But it seemed to have gone away for the others or they did not post their solution.

Ah, good point. Now I know what you mean. Well, I will have to deal with it and reinitialize the I2C to avoid more new problems.

Ah, that code explains much of the logging you posted earlier!

Sounds silly, but what if you’d print a bit more to see if changing timer0_overflow_count makes the expected difference? Untested:

Serial.print(micros());
Serial.print(F(" / "));
Serial.println(os_getTime() / OSTICKS_PER_SEC);

Serial.println(F("-RTC-"));
cli();
timer0_overflow_count += 60 * 64 * clockCyclesPerMicrosecond(); //give back 60 seconds of sleep
sei();

Serial.print(micros());
Serial.print(F(" / "));
Serial.println(os_getTime() / OSTICKS_PER_SEC);

Also note the following from @matthijskooijman, which also changes timer0_millis:

A while ago, I implemented a workaround by updating the Arduino timer value. This uses internal Arduino variables, so is likely to break at some point if Arduino changes its implementation. It is also specific to the AVR Arduino core, other architectures likely need something else. Still, here it is (from here):

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
      extern volatile unsigned long timer0_millis;
      extern volatile unsigned long timer0_overflow_count;
      timer0_millis += slept;
      // timer0 uses a /64 prescaler and overflows every 256 timer ticks
      timer0_overflow_count += microsecondsToClockCycles((uint32_t)slept * 1000) / (64 * 256);
}

I’ve no idea if that’s needed… The code that is referenced by Matthijs, mentions:

// Update the millis() and micros() counters, so duty cycle
// calculations remain correct. This is a hack, fiddling with
// Arduino's internal variables, which is needed until
// https://github.com/arduino/Arduino/issues/5087 is fixed.
1 Like