Tapatalk

LORAwan with LowPower lib & LMIC

LORAwan with LowPower lib & LMIC

3

PostMay 10, 2018#1

Hello everybody,

i wrote a simple sketch to connect a whisper node Lora to TTN using LMIC and Rocket Scream Low-Power lib (1.6.0).
This works fine, as long as the whisper node is connected to USB power.
When I switch to battery supply  (3xAA alkaline or 1xLi-SOCl2), it stops working. Whisper Node resets and reboots in a endless loop.
Without Low-Power lib, it works fine from USB and battery.

Anybody a hint what's wrong?

2

PostMay 25, 2018#2

I'm interested in this configuration too. Can you publish your sketch, and I'll try to assist debugging.
Will a simple blink type example run in your power config with the RocketSream low power lib?
Thanks, W

3

PostMay 25, 2018#3

sure, here the sketch. It's more or less based on this: https://platformio.org/lib/show/852/IBM ... tn-abp.ino

It does not much more than some debug blinking and transmit a constant value every 60 seconds.

I think, a simple blink (without using LMIC) in lowpower works, but I have to verify this again. Lowpower+LMIC works too if powered from USB, however I have not measured if the device really goes into low power mode.

regards, j

Code: Select all

/*******************************************************************************
 * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
 *
 * 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 static payload, 
 * using frequency and encryption settings matching those of
 * the (early prototype version of) The Things Network.
 *
 * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in g1,
 *  0.1% in g2).
 *
 * ToDo:
 * - set NWKSKEY (value from staging.thethingsnetwork.com)
 * - set APPKSKEY (value from staging.thethingsnetwork.com)
 * - set DEVADDR (value from staging.thethingsnetwork.com)
 * - optionally comment #define DEBUG
 * - optionally comment #define SLEEP
 * - set TX_INTERVAL in seconds
 * - change mydata to another (small) static text
 *
 *******************************************************************************/
#define BLINK
// use low power sleep; comment next line to not use low power sleep
#define SLEEP

#ifdef SLEEP
  #include "LowPower.h"
  bool next = false;
#endif

#ifdef BLINK
#include <T2WhisperNode.h>
#endif

#include <lmic.h>
#include <hal/hal.h>
//#include <SPI.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] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xX };

// 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] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX };

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


// show debug statements; comment next line to disable debug statements
#define DEBUG
//#undef DEBUG


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

// static text, you can replace T
static uint8_t mydata[] = "DuAAFAXv";



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

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

#ifdef SLEEP
void do_sleep() {
  int var = 0;
  int NumberOf8Sec = 37;
  while(var < NumberOf8Sec){                //  here 37 x 8 seconds = about 5 minutes
    // do something n times
    delay(50);                              // had problems without. no idea why
    var++; 
    // put the processor to sleep for 8 seconds
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  }
}
#endif


void onEvent (ev_t ev) {
    #ifdef DEBUG
      Serial.println(F("Enter onEvent"));
    #endif
    
    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:
#ifdef DEBUG        
            Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
#endif            
            if(LMIC.dataLen) {
                // data received in rx slot after tx
                Serial.print(F("Data Received: "));
                Serial.write(LMIC.frame+LMIC.dataBeg, LMIC.dataLen);
                Serial.println();
            }
            // Schedule next transmission
#ifndef SLEEP
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
#else
            next = true;
#endif
            
            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;
    }
    #ifdef DEBUG
      Serial.println(F("Leave onEvent"));
    #endif
    
};


void blinkYellow(int d)
{
       digitalWrite(T2_WPN_LED_2, HIGH);
       delay(d);
       digitalWrite(T2_WPN_LED_2, LOW);
       delay(d);
};

void blinkBlue(int d)
{
       digitalWrite(T2_WPN_LED_1, HIGH);
       delay(d);
       digitalWrite(T2_WPN_LED_1, LOW);
       delay(d);
};



void do_send(osjob_t* j){

    #ifdef DEBUG
      Serial.println(F("Enter do_send"));
    #endif

    #ifdef BLINK
      blinkYellow(200); 
    #endif
    
    // 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 upstream data transmission at the next possible time.
        LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
        Serial.println(F("Packet queued"));
    }
    // Next TX is scheduled after TX_COMPLETE event.
    #ifdef DEBUG
      Serial.println(F("Leave do_send"));
    #endif
    
};

void setup() {

#ifdef BLINK
    blinkBlue(100);
 #endif


    #ifdef VCC_ENABLE
    // For Pinoccio Scout boards
    pinMode(VCC_ENABLE, OUTPUT);
    digitalWrite(VCC_ENABLE, HIGH);
    delay(1000);
    #endif

    // LMIC init
    os_init();
    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();

    // 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

    // 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.
    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.

    // Disable link check validation
    LMIC_setLinkCheckMode(0);

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

    // Start job
    do_send(&sendjob);
    
    #ifdef DEBUG
      Serial.println(F("Leave setup"));
    #endif
}

void loop() {

#ifndef SLEEP

  os_runloop_once();
  
#else
  extern volatile unsigned long timer0_overflow_count;

  if (next == false) {

    os_runloop_once();

  } else {

    int sleepcycles = TX_INTERVAL / 8;  // calculate the number of sleepcycles (8s) given the TX_INTERVAL
    #ifdef DEBUG
      Serial.print(F("Enter sleeping for "));
      Serial.print(sleepcycles);
      Serial.println(F(" cycles of 8 seconds"));
      Serial.flush(); // give the serial print chance to complete
    #endif
    delay(200);
    for (int i=0; i<sleepcycles; i++) {
      // Enter power down state for 8 s with ADC and BOD module disabled
      delay(50);
      LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
      //LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_OFF, TIMER1_OFF, TIMER0_OFF, SPI_OFF, USART0_OFF, TWI_OFF);

      // LMIC uses micros() to keep track of the duty cycle, so
      // hack timer0_overflow for a rude adjustment:
      cli();
      timer0_overflow_count+= 8 * 64 * clockCyclesPerMicrosecond();
      sei();
    }
    #ifdef DEBUG
      Serial.println(F("Sleep complete"));
    #endif
    next = false;
    // Start job
    
    #ifdef BLINK
      blinkYellow(100);
      blinkYellow(100); 
    #endif
    do_send(&sendjob);
  }
  
#endif

}

2

PostJun 12, 2018#4

j77swiss wrote: sure, here the sketch. It's more or less based on this: https://platformio.org/lib/show/852/IBM ... tn-abp.ino
Thanks, sorry for my slow reply. KotahiNet posted some code that seems to be a near equivalent. I've not tested it yet, but hope to next week.
https://github.com/mikenz/LoRa-LMIC-1.5 ... er.ino#L35

2

PostJun 18, 2018#5

I can't bring your sketch to life @j77swiss. After compiling and uploading it to whispernode LoRa everything is fine for the first submit to TTN but then there is a random transmission, not every ~ 5 min as the sketch suggests. There's nothing on the serial output too even with defined DEBUG... can't find any Serial.begin(115200).

Is there any basic sketch for TTN and low power?

11

PostJun 19, 2018#6

Did I read something over on TTN about needing to connect JP15 in order for LMIC to work? 
https://www.thethingsnetwork.org/forum/ ... -dios/9148

2

PostJun 19, 2018#7

Thank you, Julian - after soldering JP15 transmitting temperature and humidity to TTN is fine.

The one thing left is transmitting the battery voltage...

3

PostJun 26, 2018#8

yes, I'm aware of the JP15 thing, and therefore the jumper is shorted.
However, it does not work when powered from battery, but runs fine if powered from USB.
@Julian C: do you have a sketch running with battery, low power + lmic connecting ton ttn?

2

PostApr 08, 2019#9

I, too, am having the exact same problem as j77swiss. I am running the LoRa Whisper Node with 2xAA batteries, arduino-lmic (https://github.com/mcci-catena/arduino-lmic), and LowPower (https://github.com/LowPowerLab/LowPower). I can confirm only the first transmission is making it to The Things Network (TTN) using ABP.

Powering via USB works for all transmissions, but I had two observations:

1. When the device emerges from sleep, it executes setup() again before entering loop(). I know this because "Starting" is printed.
2. The frame counter that appears on TTN stays at 0.
3. From this, I deduce it's not merely emerging from sleep and continuing in loop(), but appears to be resetting itself.

This is my code:

Code: Select all

/*******************************************************************************
 * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
 * Copyright (c) 2018 Terry Moore, MCCI
 * Copyright (c) 2019 Kyle T. Gabriel
 *
 * 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.
 *******************************************************************************/
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_HTU21DF.h>
#include <LowPower.h>
#include <SparkFunTSL2561.h>
#include <T2WhisperNode.h>
#include "Arduino.h"

// Enable (true) or disable (false) Serial
#define DEBUG true

// NPN transistor to power sensors
#define SENSOR_PWR_PIN 4

// Schedule measuring voltage
#define MEASURE_VOLTAGE_INTERVAL 21600  // 21600 seconds = 6 hours

// Schedule TX period (seconds)
#define TX_INTERVAL 600  // 600 seconds = 10 minutes

// LoRaWAN NwkSKey, network session key
static const PROGMEM u1_t NWKSKEY[16] = { REPLACE };
// LoRaWAN AppSKey, application session key
static const u1_t PROGMEM APPSKEY[16] = { REPLACE };
// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x00000000;

T2Flash myFlash;
Adafruit_HTU21DF htu = Adafruit_HTU21DF();
SFE_TSL2561 light;

#define Serial if (DEBUG) Serial 

// Counts how many transmissions to determine when to measure battery voltage
uint8_t TX_COUNT = 255;

// 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 arduino-lmic/project_config/lmic_project_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) { }

byte TX_COMPLETE = 0;
static uint8_t payload[8];
static osjob_t sendjob;

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

void onEvent (ev_t ev) {
//    Serial.print(os_getTime());
//    Serial.print(": ");
    switch(ev) {
        case EV_TXSTART:
            Serial.println(F("EV_TXSTART"));
            break;
        case EV_TXCOMPLETE:
            Serial.println(F("EV_TXCOMPLETE"));
            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 payload"));
            }
            TX_COMPLETE = 1;
            break;
        case EV_LOST_TSYNC:
        case EV_RESET:
        case EV_RXCOMPLETE:
        case EV_LINK_DEAD:
        case EV_LINK_ALIVE:
        case EV_SCAN_TIMEOUT:
        case EV_BEACON_FOUND:
        case EV_BEACON_MISSED:
        case EV_BEACON_TRACKED:
        case EV_JOINING:
        case EV_JOINED:
        case EV_JOIN_FAILED:
        case EV_REJOIN_FAILED:
        default:
            break;
    }
}

double get_lux(unsigned char time) {
    boolean gain = 0;
    unsigned int ms;
    light.setTiming(gain, time, ms);
    Serial.print("TSLup: "); Serial.println(light.setPowerUp());
    delay(ms);  // Pause to measure light
    unsigned int data0, data1;
    uint8_t data_good = light.getData(data0, data1);
    Serial.print("TSLdn: "); Serial.println(light.setPowerDown());
    if (data_good) {
        double lux;
        boolean good;  // True if neither sensor is saturated
        good = light.getLux(gain, ms, data0, data1, lux);
        if (good) return lux;
        else return -10;
    }
}

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 {
        uint8_t payload_size = 6;

        digitalWrite(SENSOR_PWR_PIN, HIGH);  // Power sensors
        float humidity = htu.readHumidity();
        float temperature = htu.readTemperature();
        uint16_t lux = get_lux(2);
        if (lux < 0) lux = get_lux(1);  // If light sensor saturated, sample time
        if (lux < 0) lux = get_lux(0);
        digitalWrite(SENSOR_PWR_PIN, LOW);  // Depower sensors

        Serial.println(humidity);
        Serial.println(temperature);
        Serial.println(lux);
        
        // adjust for the f2sflt16 range (-1 to 1)
        humidity = humidity / 100;
        // float -> int
        uint16_t payloadHumid = LMIC_f2sflt16(humidity);
        // int -> bytes
        byte humidLow = lowByte(payloadHumid);
        byte humidHigh = highByte(payloadHumid);
        payload[0] = humidLow;
        payload[1] = humidHigh;

        temperature = temperature / 100; 
        uint16_t payloadTemp = LMIC_f2sflt16(temperature);
        byte tempLow = lowByte(payloadTemp);
        byte tempHigh = highByte(payloadTemp);
        payload[2] = tempLow;
        payload[3] = tempHigh;

        payload[4] = lowByte(int(lux));
        payload[5] = highByte(int(lux));

        if (TX_COUNT * TX_INTERVAL >= MEASURE_VOLTAGE_INTERVAL) {
            uint16_t volts_bat = T2Utils::readVoltage(T2_WPN_VBAT_VOLTAGE, T2_WPN_VBAT_CONTROL);
            Serial.println(volts_bat);
            payload[6] = lowByte(int(volts_bat));
            payload[7] = highByte(int(volts_bat));
            payload_size = 8;  // Add voltage to measurement payload
            TX_COUNT = 0;
        }
        ++TX_COUNT;

        for (int i=0; i<sizeof(payload); i++) {
            if (payload[i] < 16) {Serial.print("0");}
            Serial.print(payload[i], HEX);
            Serial.print(" ");
        }
        Serial.println();

        LMIC_setTxData2(1, payload, payload_size, 0);
        Serial.println(F("Packet queued"));
    }
}

void setup() {
    //while (!Serial); // wait for Serial to be initialized
    Serial.begin(115200);
    delay(5000);
    Serial.println(F("Starting"));

    pinMode(SENSOR_PWR_PIN, OUTPUT);

    htu.begin();
    light.begin();

    myFlash.init(T2_WPN_FLASH_SPI_CS);
    myFlash.powerDown();  // Power down Flash to save energy
  
    os_init();
    LMIC_reset();

    uint8_t appskey[sizeof(APPSKEY)];
    uint8_t nwkskey[sizeof(NWKSKEY)];
    memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
    memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
    LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);

    LMIC_selectSubBand(1);
    LMIC_setLinkCheckMode(0);
    LMIC.dn2Dr = DR_SF9;
    LMIC_setDrTxpow(DR_SF7,14);
}

void loop() {
  Serial.println("Send");
  do_send(&sendjob);

  byte TX_TIMEOUT = 0;
  while(TX_COMPLETE != 1) {
      os_runloop_once();
      if (++TX_TIMEOUT > 60000) {
          Serial.println("Event Timeout");
          break;
      }
  }
  TX_COMPLETE = 0;

  Serial.println("Sleep");
  Serial.flush();

  // Using Low-Power library to put the MCU to Sleep
  for (int i = 0; i < TX_INTERVAL; i+=8) {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);             
  }
}

PostApr 08, 2019#10

Upon further testing, I discovered if I remove my sensor code, the sketch runs as expected, and does not reset and run setup() after waking. This was likely due to the large size of the code. I'll report back if I uncover anything else of note.

Read more posts (5 remaining)