Teensy 3.2 + Snooze + LoRaWAN LMIC stack problem

Status
Not open for further replies.

EmielcpNL

Active member
Hii guys,

I'm using an Teensy 3.2 with an SX1276 LoRa chip. For the LoRaWAN stack I'm using the LMIC v 1.5 library (Link).

Because it's battery powered I'm using duffs Snooze library in combination with an 32kHz crystal + 3V battery to put the Teensy in hibernate mode while not sending anything. Once every x minutes it wakes up, takes measurements and sends it's data to The Things Network.

The code works perfectly without putting it into hibernate or sleep mode, but when I insert the code for the hibernate it start acting weird. I get huge delays in sending the data. I think the millis() counter is out of sync with the LMIC stack, had the same problem using Arduino but fixed it with this piece of code:

Code:
//Give the AVR back the slept time back (simple version)
      cli();
      timer0_overflow_count += 8 * 64 * clockCyclesPerMicrosecond(); //give back 60 seconds of sleep
      sei();

os_getTime();   //VERY IMPORTANT after sleep to update os_time and not cause txbeg and os_time out of sync which causes send delays with the RFM95 on and eating power

Now I have no idea how to do this with the Teensy. Here's my simplified code of my Teensy + Snooze + LMIC without sensors (not relevant for my question):

Code:
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <Snooze.h>
#include <i2c_t3.h>

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { MyNWKSKEY };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { MyAPPSKEY };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = myDEVADDR ; // <-- Change this address for every node!

// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
const unsigned TX_INTERVAL = 60;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 10,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 4,
  .dio = {2, 3, LMIC_UNUSED_PIN},
};

int who;

SnoozeAlarm alarm;
SnoozeBlock config_teensy32(alarm);

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"));
      break;
    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) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
        WinterSlaap();
      }
      else {
        WinterSlaap();
      }
      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;
    default:
      Serial.println(F("Unknown event"));
      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 {
    byte payload [1];
    payload [0] = 1;
    LMIC_setTxData2(1, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Starting"));

  pinMode (21, OUTPUT);
  digitalWrite (21, HIGH);

  //alarm.setRtcTimer(0, 0, 20);// Uren, minuten, secondes

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();
  
  os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
  // On AVR, these values are stored in flash and only copied to RAM
  // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
  // If not running an AVR with PROGMEM, just use the arrays directly
  LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif

#if defined(CFG_eu868)
  // Set up the channels used by the Things Network, which corresponds
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used, which certainly
  // works, so it is good for debugging, but can overload those
  // frequencies, so be sure to configure the full frequency range of
  // your network here (unless your network autoconfigures them).
  // Setting up channels should happen after LMIC_setSession, as that
  // configures the minimal channel set.
  // NA-US channels 0-71 are configured automatically
  LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
  LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);      // g2-band
  // TTN defines an additional channel at 869.525Mhz using SF9 for class B
  // devices' ping slots. LMIC does not have an easy way to define set this
  // frequency and support for class B is spotty and untested, so this
  // frequency is not configured here.
#elif defined(CFG_us915)
  // NA-US channels 0-71 are configured automatically
  // but only one group of 8 should (a subband) should be active
  // TTN recommends the second sub band, 1 in a zero based count.
  // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
  LMIC_selectSubBand(1);
#endif

  // Disable link check validation
  LMIC_setLinkCheckMode(0);

  // TTN uses SF9 for its RX2 window.
  LMIC.dn2Dr = DR_SF9;

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF7, 14);

  // Start job
  do_send(&sendjob);
}

void loop() {
  os_runloop_once();
  who = Snooze.hibernate( config_teensy32 );
  digitalWrite (21, HIGH);
  if (who == 35) {
    digitalWrite (21, HIGH);
    Serial.begin(115200);
    Serial.println ("Uit slaap!");
    delay(5000);
    
    //Give the AVR back the slept time back (simple version)
    //cli();
    //timer0_overflow_count += 8 * 64 * clockCyclesPerMicrosecond(); //give back seconds of sleep
    //sei();

    os_getTime();   //VERY IMPORTANT after sleep to update os_time and not cause txbeg and os_time out of sync which causes send delays with the RFM95 on and eating power
    //Do here whatever needs to be done after each of the sleepcycle (e.g. check for a condition to break for send or take measurements for mean values etc.)

    os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);  //do a send

    Serial.println(F("go to sleep ... "));
    Serial.println ("delay van 5sec");
    delay (5000);
  }
}

void WinterSlaap (){
  digitalWrite (21, LOW);
  Serial.flush();
  Snooze.hibernate( alarm );  
}

Anyone got experience with the Snooze + LMIC library? Thanks in advance
 
Last edited:
Never used the LMIC Library but Serial printing (USB) and sleeping are not going to work as you expect and could be some of your issues. For a more robust printing solution use the Hardware Serial ports for debug messages since they will come up faster and have less headaches. You can use a second Teensy connected by serial to this Teensy. Also if this library or you use any Timers then they might need reinitializing after waking from sleeping.
 
@duff did I use the Snooze library correctly?
Your example looks fine.

Changed the serial printing but didn't solve my problem. Still delays in sending my data
I looked into this library a little and it uses micros to keep track of it's "os" time. When you hibernate sleep the clock that runs micros is off, so it won't be updated during that sleep mode. This might be the issue you are having. Not sure how to fix it on that libraries end though and I don't have any of this equipment. Can you reinitialize the libraries "os" time some how?
 
Your example looks fine.

I looked into this library a little and it uses micros to keep track of it's "os" time. When you hibernate sleep the clock that runs micros is off, so it won't be updated during that sleep mode. This might be the issue you are having. Not sure how to fix it on that libraries end though and I don't have any of this equipment. Can you reinitialize the libraries "os" time some how?

Okay good to know! Well, I found this modified LMIC library by Pham (Link). It adds an function to count the time while sleeping: hal_sleep_lowpower(u1_t sleepval)

So I added this to my code after waking up, still the same problem... :(
 
Guy who made the custom lmic library said:

Well, I tested Snooze in standalone with a Teensy32 and it appears that the millis()/micros() counter is preserved. So normally there is no need to change anything to LMIC (not need to insert call to hal_sleep_lowpower()). However, I tested the LMIC example with Snooze on a Teensy and it is not working (the second TX is not scheduled correctly after the sleep). Currently I have no clue on why it is not working. My knowledge of LMIC may be quite limited.

regards,
 
Guy who made the custom lmic library said:

Well, I tested Snooze in standalone with a Teensy32 and it appears that the millis()/micros() counter is preserved. So normally there is no need to change anything to LMIC (not need to insert call to hal_sleep_lowpower()). However, I tested the LMIC example with Snooze on a Teensy and it is not working (the second TX is not scheduled correctly after the sleep). Currently I have no clue on why it is not working. My knowledge of LMIC may be quite limited.

regards,

millis/micros will be updated when woken if you use the timer driver not the rtc driver inwhich you do currently. Try the Timer driver instead and see if that helps.
 
millis/micros will be updated when woken if you use the timer driver not the rtc driver inwhich you do currently. Try the Timer driver instead and see if that helps.

Tried it with the code below, change every alarm to timer (20seconds). Changed who == 35 to 36. Exacly the same result! :( It's weird, because with the fix on the Arduino they do the same (update millis) so it should work right? :( Here's how they do it on AVR chips (arduino):

Code:
 //Give the AVR back the slept time back (simple version)
    //cli();
    //timer0_overflow_count += 8 * 64 * clockCyclesPerMicrosecond(); //give back seconds of sleep
    //sei();

Code I used for timer instead of RTC:

Code:
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <Snooze.h>
#include <i2c_t3.h>

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { 0x09, 0x47, 0x9E, 0x7C, 0xA2, 0xEF, 0x71, 0xFB, 0x11, 0x03, 0x85, 0x53, 0x52, 0xF0, 0xEB, 0x0A };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { 0xC0, 0x5B, 0x13, 0x22, 0x55, 0xC2, 0xC4, 0xF0, 0x30, 0x34, 0xE7, 0x72, 0x6A, 0x81, 0xF4, 0xC5 };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x2601190B ; // <-- Change this address for every node!

// These callbacks are only used in over-the-air activation, so they are
// left empty here (we cannot leave them out completely unless
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

static osjob_t sendjob;

// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
// const unsigned TX_INTERVAL = 60;

// Pin mapping
const lmic_pinmap lmic_pins = {
  .nss = 10,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = 4,
  .dio = {2, 3, LMIC_UNUSED_PIN},
};

#define resetPin 8

int who;

SnoozeTimer timer;
SnoozeBlock config_teensy32(timer);

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"));
      break;
    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) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
        WinterSlaap();
      }
      else {
        WinterSlaap();
      }
      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;
    default:
      Serial.println(F("Unknown event"));
      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 {
    byte payload [1];
    payload [0] = 2;
    LMIC_setTxData2(1, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void setup() {
  Serial.begin(115200);
  Serial.println(F("Starting"));

  pinMode (21, OUTPUT);
  digitalWrite (21, HIGH);

  timer.setTimer(20000);// milliseconde

  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();
  
  os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);
  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
#ifdef PROGMEM
  // On AVR, these values are stored in flash and only copied to RAM
  // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x1, DEVADDR, nwkskey, appskey);
#else
  // If not running an AVR with PROGMEM, just use the arrays directly
  LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
#endif

#if defined(CFG_eu868)
  // Set up the channels used by the Things Network, which corresponds
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used, which certainly
  // works, so it is good for debugging, but can overload those
  // frequencies, so be sure to configure the full frequency range of
  // your network here (unless your network autoconfigures them).
  // Setting up channels should happen after LMIC_setSession, as that
  // configures the minimal channel set.
  // NA-US channels 0-71 are configured automatically
  LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
  LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
  LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);      // g2-band
  // TTN defines an additional channel at 869.525Mhz using SF9 for class B
  // devices' ping slots. LMIC does not have an easy way to define set this
  // frequency and support for class B is spotty and untested, so this
  // frequency is not configured here.
#elif defined(CFG_us915)
  // NA-US channels 0-71 are configured automatically
  // but only one group of 8 should (a subband) should be active
  // TTN recommends the second sub band, 1 in a zero based count.
  // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
  LMIC_selectSubBand(1);
#endif

  // Disable link check validation
  LMIC_setLinkCheckMode(0);

  // TTN uses SF9 for its RX2 window.
  LMIC.dn2Dr = DR_SF9;

  // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
  LMIC_setDrTxpow(DR_SF7, 14);

  // Start job
  do_send(&sendjob);
}

void loop() {
  os_runloop_once();
  who = Snooze.hibernate( config_teensy32 );
  digitalWrite (21, HIGH);
  if (who == 36) {
    digitalWrite (21, HIGH);
    Serial.begin(115200);
    Serial.println ("Uit slaap!");
    delay(5000);

    os_getTime();   //VERY IMPORTANT after sleep to update os_time and not cause txbeg and os_time out of sync which causes send delays with the RFM95 on and eating power
    //Do here whatever needs to be done after each of the sleepcycle (e.g. check for a condition to break for send or take measurements for mean values etc.)

    os_setTimedCallback(&sendjob, os_getTime() + ms2osticks(10), do_send);  //do a send

    Serial.println(F("go to sleep ... "));
    Serial.println ("delay van 5sec");
    delay (5000);
  }
}

void WinterSlaap (){
  digitalWrite (21, LOW);
  Serial.flush();
  Snooze.hibernate( timer );  
}
 
Status
Not open for further replies.
Back
Top