How to adjust or cancel the LMIC libraries duty cycle check?

I have been working on a Seeduino XIAO SAMD21 real easy build LoRa board. It does also work as a TTN node using the MCCI LoRaWAN LMIC Library. I have a simple OTAA sketch working that sends an ASCII payload consisting of the send count. The XIAO is supported by the Arduino lowpower library and putting the device into a timed sleep is easy enough with sleep current as low as 4uA.

The build is easy and the number of parts needed fairly minimal, see below;

min_build_node

You can wire, with the board shown, the LoRa devices DIO0, DIO1, DIO2 to separate IO pins on the XIAO or you can save two IO pins on the XIAO using some diodes to share DIO0, DIO1 and DIO2 onto one IO pin. Not tried the FSK functionality that needs DIO2. Thus the XIAO board working as a TTN node can have 5 spare IO pins that can also be used as I2C and Serial UART ports.

Anyway onto the question;

In sleep mode the internal timers on the XIAO are halted, apart from the RTC one used for the wakeup. Thus when the board wakes up the timings used by the sketch (and library code) probably do not realise the board has been asleep, potentially for days or weeks.

As an example, if the spreading factor is set to SF12 and the TX_INTERVAL is set for 5 seconds and the board put into a many minutes sleep, at wake up the sketch spends around 150 seconds in the powered state, waiting to send the payload, presumably it thinks its only just powered up and is imposing what it thinks is the duty cycle limit. Whilst its waiting to transmit the board uses around 14mA.

No functions in the library or the examples seems to suggest a simple way of stopping the duty cycle check.

So any ideas, how do you make the sketch not apply the duty cycle limit, after all its been asleep for ages, so no real duty cycle limit to apply.

4 Likes

I seem to recall a library function that allows updating the stack with the amount of time the node has slept. Now if only I could remember if that is part of the LMIC stack…

Love the board!

I found this;

Which mentions this feature;

“A new function hal_sleep_lowpower(u1_t sleepval) is introduced to allow the main low-power loop to indicate to the LMIC stack how much sleep time (in seconds) it has to take into account when returning hal_ticks().”

And the OTAA example does appear to include support for the SAMD21G18A, the processor used in the XIAO.

Yes, that is what was rambling around in the back of my mind. Hope it solves your issue.

Thanks for the tip, that library looks like it was specifically designed to deal with deep sleep nodes. And since the XIAO\SAMD21 has an RTC I guess you ought to be able to work out how long its been asleep from a external pin interrupt too. We shall see.

mmm slow progress.

There is a problem with the example sketches in the LMIC_Low_Power library as far as my up to date Arduino IDE is concerned. Within the onEvent () function the large mass of code within the case EV_TXCOMPLETE: section needs to be in code braces. Apparently the complier in my IDE 1.8.13 objects to the variable declarations within the case EV_TXCOMPLETE: section.

With the code braces the code compiles, and when the node runs there is a least a join and one transmission of payload, but as yet no wakeup.

Ah well, fun times, I hope I dont need to take the gateway and node into a deep underground dark railway tunnel to get them to work …

I’ll join in with this in a week or so’s time once everyone in customer land has dived headlong in to office parties & the resulting solicitors meetings.

But up until now I’ve only been sending teeny tiny payloads so not been held up with the newer duty cycle code. But it would be useful to help advance the LMIC code base.

Well some progress.

I started at the beginning, as you do.

The readme for the LMIC_low_power library shows the debug printouts for when the host micro was an Atmega328P, so that where I started.

The code as published wont compile for the ATmega328P, a small change is needed to oslmic.h. And the example sketch needs a change too, you need to moce the variable definitions out of the case\select instances. It took a fair while to sort the problems.

Once the example actually compiles for the ATmega328P, it does work. The payload is the read of a voltage that is a digital temperature sensor, code complies thus;

Sketch uses 25878 bytes (80%) of program storage space. Maximum is 32256 bytes.
Global variables use 1008 bytes (49%) of dynamic memory, leaving 1040 bytes for local variables. Maximum is 2048 bytes.

So still a fair bit of space left.

If you force the code to use SF12, and you wake from deep sleep you would normally expect to wait the duty cycle limit, around 150secs, before the node transmits and that uses a heap of power. But the code does reset the limit, and to wake up and send the packet takes about 7 seconds. I had the sleep time set to 5 mins, and during the sleep the sensor current is about 10uA.

Next, with a much simplified sketch just for the ATmega328P, it should be possible to do the same for the SAMD21, and see how the debug outputs compare.

1 Like

Are you using the LMIC in the low power repro? It seems a little antique!

Yes.

Is there a more modern version that effectivly supports low power operation ?

Not that I’m aware of, but I’m happy to back port these changes in to the current MCCI LMIC version which is almost completely 1.0.3 compliant.

I suspect I’ve got away with the duty cycle on AVR because with only 8 seconds of sleep, the millis get to tick up over many lots of 8 seconds so it doesn’t trigger the limit. But as SAMD does properly sleep, it would do!

Sleep mode, on an AVR uses the watchdog, and 8 secs is the max time. Normally the time the processor is awake, every 8 secs and when the millis() counter runs is a very short period, only a few mS so it does not advance much.

The duty cycle only becomes an issue at higher spreading factors. At SF7 a modest 10byte payload has a duty cycle of once every 5.9secs, so in a wakeup from deep sleep you might not spot the problem. However at SF12 the duty cycle is 125secs, so very noticeable.

Well some progress, going back to the beginning and following the example for ATmega328P, I have the sleep mode sketches working for both ATmega328P and ATmega1284P.

There was problems that needed to be fixed in the supplied sketches and oslmic.h before they would compile. Setup for a deep sleep of 5 minutes, and forced to SF12, the sketch is awake, transmits, and is powered for around 7 seconds, so is dealing with the duty cycle problem. With only 7 seconds of up time, say every hour, and a low deep sleep current, even a small battery could last a while.

The boards I was using to test are below, both are easy build boards, that can use only wired parts. Both can be fitted with an DS18B20 temperature sensor and have a MOSFET to turn of the battery measuring voltage divider. Deep sleep current of both boards is around 10uA. If you really do want to venture into modern surface mount technology you can on both boards add to the back an PCF8563 RTC, a FRAM and a power MOSFET for switching stuff on\off. The 1284P board can take an SD card socket too. LoRa modules as plug in Mikrobus type boards.

EasyNodes

For a simple payload that sends as ASCII the number of transmissions and the total powered up time, the memory used for the ATmega328P was;

23464 bytes (72%) of program storage space.
Global variables use 911 bytes (44%) of dynamic memory

An example sketch using a BME280 temperature, pressure and humidity sensor uses;

28992 bytes (89%) of program storage space.
Global variables use 1181 bytes (57%) of dynamic memory

So comfortably within the small amount of memory that an ATmega328P has.

But so far I cannot make the sketch work for the extremly easy build Seeeduino XIAO SAMD21 node, it deep sleeps OK, but can spend more then 100 seconds or more awake, eating into the battery, before it goes back to sleep again.

2 Likes

The LMIC_low_power library examples can use EEPROM on the AVR processors to store the packet sequence number (LMIC.seqnoUp) in order to keep correct value on reset.

Not tried it though …

But only good for ABP.

I don’t think anyone has really cracked the OTAA credentials save for LMIC yet.

I think I have worked out the problem with the XIAO SAMD21 not updating the duty cycle correctly.

I had always thought that 42 was the answer to the ultimate question of life, the universe, Arduino sketches and everything else.

Its not the answer is 44 !!!

2 Likes

Why 44 though ?

I had the sketch configured for a 300 second deep sleep which worked on the ATmega328P but not the SAMD21.

To make the LMIC duty cycle check use the sleep period a global variable, os_cumulated_sleep_time_in_seconds, was used in the libraries hal_ticks calculation. os_cumulated_sleep_time_in_seconds is updated in the sketch with each sleep period by the hal_sleep_lowpower(seconds) command.

The SAMD21 part of the original sketch was missing the command so I added it as hal_sleep_lowpower(300) to in effect update hal_ticks for a 300 second sleep. This did not work, the SAMD21 was staying awake for long periods. Interestingly though the cumulated sleep seconds was increasing by 44 at every sleep, not enough it should have been 300.

I rolled this over in my mind for a while, and then I realised;

300 - 44 = 256

The value was rolling over. The format of hal_sleep_lowpower was;

void hal_sleep_lowpower (u1_t sleepval)

Where u1_t is actually uint8_t. So no wonder there were problems, updating the library sleep period could only be done 255 seconds at a time. Not an issue for the ATmega328P, since the hal_sleep_lowpower(seconds) command was sent out at each 8 second watchdog sleep.

With that problem solved, the XIAO SAMD21 would be awake for around 8 seconds after each sleep, and the sleep current was a mere 4.4uA, not bad for such a simple node.

3 Likes

The LMIC library uses these typdefs;

// Target platform as C library
typedef uint8_t            bit_t;
typedef uint8_t            u1_t;
typedef int8_t             s1_t;
typedef uint16_t           u2_t;
typedef int16_t            s2_t;
typedef uint32_t           u4_t;
typedef int32_t            s4_t;
typedef unsigned int       uint;
typedef const char* str_t;

Does anyone know why it was done that way ?

If u1_t is uint8_t, why use u1_t at all ?

There may be something about it coming from IBM & the wonderful world of an integer not being two bytes on some processors etc

I’ve seen this in a couple of other unrelated projects - I think it is to save on keypresses or similar. But if I was so inclined, I’d make it u8 for an unsigned byte, that makes more sense to me.

In testing this I have been forcing the transmissions to be at SF12, as the problems with forcing the duty cycle are most likley at this data rate.

Of course this was only for ‘testing’ purposes, so I implemented some code that would, after a join and 5 payloads had been sent, adjust the hardware sleep time so as to keep within the fair use policy. This worked and after sending 5 payloads at 180 seconds intervals the program changed the sleep interval to 4114 seconds, so that just in case I ‘forgot’ to put the node out of test mode, no harm would be done.

It worked, but there was a problem, after the circa 68 minute sleep, the awake period was a few minutes, its normally 7 seconds. Studying the library code reveals this line used to calculate hal_ticks;

scaled = (micros()+os_cumulated_sleep_time_in_seconds*1000000) >> US_PER_OSTICK_EXPONENT;

Which as the library notes will rollover every 4295 seconds because of uint32_t being 0xFFFFFFFF = 4,294,967,295 (there is that 42 again).

I realised that there is no need to update the sleep seconds counter used by the library with the full 4114 seconds the hardward sleep needed, since the the duty cycle limit for the longest SF12 packet of 64 bytes has an air time of 2.4secs, so as long as the library sleep seconds counter is increased by 240 seconds or slightly more there should be no issues with duty cycle limits. The actual hardware sleep seconds would then be seperated from the sleep seconds the LMIC library thinks is happening.

What I am not sure of yet is what happens after circa 16 of the 4114 (4114/16) secs sleeps when hal_ticks will rollover.