Moteino M0 - LMIC & LoRaWAN

Started by adam.g, March 04, 2019, 07:03:29 PM

adam.g

Hi Felix,

Is a Moteino M0 coupled with the RFM95 LoRa transceiver capable of connecting to a LoRaWAN gateway using the arduino-lmic library?

If so, what are the correct pin mappings? If not, is there any way that this can be accomplished?

Cheers,
Adam

Felix

Hi Adam,
Hmm... haven't tried it, but based on what I see in other threads like this, it should be very similar, with adjustments for M0 (untested):

const lmic_pinmap lmic_pins = {
  .nss = A2,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = LMIC_UNUSED_PIN,
  .dio = {9, DIO1, DIO2},
}


Try that, if it won't work then maybe it's worth a LoRaWAN/LMIC/TTN search in the forum and see what other resources people posted.

adam.g

#2
Hi Felix,

I appreciate the quick reply.

After several hours of research and testing, I thought I'd give an update on my progress. I ended up soldering jumpers between DIO1, DIO2 and RST on the RFM95 to Moteino M0 pins 7, 6 and 5, respectively.

Using the arduino-lmic ttn-otaa example, I added the following pin mappings.

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = A2,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 5,
    .dio = {9, 7, 6},
};

Unfortunately, this configuration has not been able to connect to my gateway and produces the following serial output:

Starting
Packet queued
65130: EV_JOINING
65226: EV_TXSTART
501942: EV_TXSTART
...
8886366: EV_JOIN_FAILED

This EV_TXSTART / EV_JOIN_FAILED loop goes on forever. I've confirmed that the lmic_project_config.h has the correct region and transceiver settings:

#define CFG_us915 1
#define CFG_sx1276_radio 1

I'm now at a loss as to what could be the problem. As far as I can't tell, there shouldn't be anything preventing the Moteino M0 and RFM95 from establishing a connection to The Things Network. I've tested a number of different configurations found on the LowPowerLab and The Things Network forums, as well as Github and elsewhere, without any luck. If anyone has had any success, I'd be keen to hear more about it!

Cheers,
Adam

Felix

I agree there should not be anything preventing it from working, short of something in the libraries.
The M0 is based on the SAMD21, I have not used but as long as that's supported by TTN/LoRaWAN libs, it should work.
I hope someone else can fill in anything we're missing.

mantonakakis

I'm not sure if this helps, but I have been using the SparkFun SAMD21 Pro RF LoRa board with good success with LMIC and TTN. OTAA, ABP, and downlink message reception have been working great with this board. Unfortunately the board draws about 30mA while in sleep mode, and about 60mA while awake and doing basically nothing (for example, during a delay() call).

I've got a couple Moteino M0s that should hopefully be in my hands tomorrow, and the LoRa radio module from LowPowerLab as well. I'll be testing power consumption with and without the radio module, and I'm hoping to get it working with LMIC and TTN like my SparkFun boards. Very excited to see what kind of battery life I can achieve!

If I'm able to get LMIC working, I will post back here with what I used for my setup. For now, maybe have a look at SparkFun's documentation for the LoRa Pro RF and see if it contains any helpful info? Their ABP sketch was pretty trivial to get working, the pin mapping in their example worked just fine for me.

adam.g

Quote from: mantonakakis on March 20, 2019, 06:29:10 PM
I'm not sure if this helps, but I have been using the SparkFun SAMD21 Pro RF LoRa board with good success with LMIC and TTN. OTAA, ABP, and downlink message reception have been working great with this board. Unfortunately the board draws about 30mA while in sleep mode, and about 60mA while awake and doing basically nothing (for example, during a delay() call).

I've got a couple Moteino M0s that should hopefully be in my hands tomorrow, and the LoRa radio module from LowPowerLab as well. I'll be testing power consumption with and without the radio module, and I'm hoping to get it working with LMIC and TTN like my SparkFun boards. Very excited to see what kind of battery life I can achieve!

If I'm able to get LMIC working, I will post back here with what I used for my setup. For now, maybe have a look at SparkFun's documentation for the LoRa Pro RF and see if it contains any helpful info? Their ABP sketch was pretty trivial to get working, the pin mapping in their example worked just fine for me.

Thanks for the message!

I recently purchased a new RAK2245 LoRa concentrator module and Raspberry Pi 3 B+ and set up a proper multi-channel gateway. Previously, I was attempting to use Adafruit's LoRa Raspberry Pi bonnet to set up a single-channel gateway, which was more trouble than it was worth.

Currently, I have ABP working without any issues using the MCCI LoRaWAN LMIC library. However, the standard OTAA example still refuses to work with my TTN device and application EUIs and App Key. Can I ask what library you're using with the SparkFun Pro RF board? The pin mappings below allow for ABP to work properly, so it must be specifically an issue with OTAA.

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = A2,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 5,
    .dio = {9, 7, 6},
};


I'd be happy to hear thoughts or see the code you're using!

Cheers,
Adam


mantonakakis

#6
Hi Adam,

Glad you at least managed to get ABP working! That's a good sign, so I think OTAA shouldn't be impossible. I'm also using an RAK gateway (RAK831 w/RPi).
I can't share my full code, but I do have a simple "Hello World" sketch that works that I can share. It did take a little tweaking to get OTAA to actually activate the device. From memory, I think the two major factors that got it working included adding a delay to the first line of the setup() function (I used 5 seconds), and letting LMIC compensate for some clock error (also in setup). Here's the sketch, I just uploaded and confirmed that it successfully activated the device after the first activation message:

#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

//
// For normal use, we require that you edit the sketch to replace FILLMEIN
// with values assigned by the TTN console. However, for regression tests,
// we want to be able to compile these scripts. The regression tests define
// COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
// working but innocuous value.
//
#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif

// 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]= { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
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]= { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
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.
static const u1_t PROGMEM APPKEY[16] = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX};
void os_getDevKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);}

static uint8_t mydata[] = "Hello, world!";
static osjob_t sendjob;

const unsigned TX_INTERVAL = 10;

// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = 12,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 7,
    .dio = {6, 10, 11},
};

void onEvent (ev_t ev) {
    SerialUSB.print(os_getTime());
    SerialUSB.print(": ");
    switch(ev) {
        case EV_SCAN_TIMEOUT:
            SerialUSB.println(F("EV_SCAN_TIMEOUT"));
            break;
        case EV_BEACON_FOUND:
            SerialUSB.println(F("EV_BEACON_FOUND"));
            break;
        case EV_BEACON_MISSED:
            SerialUSB.println(F("EV_BEACON_MISSED"));
            break;
        case EV_BEACON_TRACKED:
            SerialUSB.println(F("EV_BEACON_TRACKED"));
            break;
        case EV_JOINING:
            SerialUSB.println(F("EV_JOINING"));
            break;
        case EV_JOINED:
            SerialUSB.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);
              SerialUSB.print("netid: ");
              SerialUSB.println(netid, DEC);
              SerialUSB.print("devaddr: ");
              SerialUSB.println(devaddr, HEX);
              SerialUSB.print("artKey: ");
              for (int i=0; i<sizeof(artKey); ++i) {
                SerialUSB.print(artKey[i], HEX);
              }
              SerialUSB.println("");
              SerialUSB.print("nwkKey: ");
              for (int i=0; i<sizeof(nwkKey); ++i) {
                SerialUSB.print(nwkKey[i], HEX);
              }
              SerialUSB.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:
        ||     SerialUSB.println(F("EV_RFU1"));
        ||     break;
        */
        case EV_JOIN_FAILED:
            SerialUSB.println(F("EV_JOIN_FAILED"));
            break;
        case EV_REJOIN_FAILED:
            SerialUSB.println(F("EV_REJOIN_FAILED"));
            break;
        case EV_TXCOMPLETE:
            SerialUSB.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
            if (LMIC.txrxFlags & TXRX_ACK) {
              SerialUSB.println(F("Received ack"));
            }
            if (LMIC.dataLen) {
              SerialUSB.print(F("Received "));
              SerialUSB.print(LMIC.dataLen);
              SerialUSB.println(F(" bytes of payload"));
              
              // Show the raw hex values:
              for (int i = 0; i < LMIC.dataLen; i++) {
                if (LMIC.frame[LMIC.dataBeg + i] < 0x10) {
                    SerialUSB.print(F("0"));
                }
                SerialUSB.print(LMIC.frame[LMIC.dataBeg + i], HEX);
                SerialUSB.print(" ");
              }
              SerialUSB.println();
            }
            
            // Schedule next transmission
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
            break;
        case EV_LOST_TSYNC:
            SerialUSB.println(F("EV_LOST_TSYNC"));
            break;
        case EV_RESET:
            SerialUSB.println(F("EV_RESET"));
            break;
        case EV_RXCOMPLETE:
            // data received in ping slot
            SerialUSB.println(F("EV_RXCOMPLETE"));
            break;
        case EV_LINK_DEAD:
            SerialUSB.println(F("EV_LINK_DEAD"));
            break;
        case EV_LINK_ALIVE:
            SerialUSB.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:
        ||    SerialUSB.println(F("EV_SCAN_FOUND"));
        ||    break;
        */
        case EV_TXSTART:
            SerialUSB.println(F("EV_TXSTART"));
            break;
        default:
            SerialUSB.print(F("Unknown event: "));
            SerialUSB.println((unsigned) ev);
            break;
    }
}

void do_send(osjob_t* j){
    if (LMIC.opmode & OP_TXRXPEND) {
        SerialUSB.println(F("OP_TXRXPEND, not sending"));
    } else {
        LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
        SerialUSB.println(F("Packet queued"));
    }
}

void setup() {
     delay(5000);
    SerialUSB.begin(115200);
    while(!SerialUSB);
    delay(100);
    SerialUSB.println(F("Starting"));

    os_init();
    LMIC_reset();

    LMIC_setLinkCheckMode(0);
    LMIC_setDrTxpow(DR_SF7,14);
    LMIC_selectSubBand(1);

    // Let LMIC compensate for +/- 1% clock error
    LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);

    do_send(&sendjob);
}

void loop() {
    os_runloop_once();
}


Edit: this code also accepts downlink messages and prints out the downlink bytes in as hex values. Edited the code to make sure this functionality was working properly.

mantonakakis

Got my Moteino M0 today, with the RFM95 module separate. Soldered it on the board, uploaded the above sketch, and it sort of worked - but seemed to get stuck after the first activation request.  I added jumpers between RST to 5, DIO1 to 7, and DIO2 to 6. Did a brief test, and it successfully activated and got a couple messages through (both up and down) before I shut it off.

Of course I had to change the pin map in the code above to match.

adam.g

Quote from: mantonakakis on March 21, 2019, 07:43:01 PM
Got my Moteino M0 today, with the RFM95 module separate. Soldered it on the board, uploaded the above sketch, and it sort of worked - but seemed to get stuck after the first activation request.  I added jumpers between RST to 5, DIO1 to 7, and DIO2 to 6. Did a brief test, and it successfully activated and got a couple messages through (both up and down) before I shut it off.

Of course I had to change the pin map in the code above to match.

Hi there,

Thanks for the code provided above. I was actually able to get my Moteino M0 and RFM95 working with OTAA with the default MCCI LMIC example the other day. What was the problem you ask? Well it turns out that the gateway and node antennas were too close! As soon as I moved my gateway to the other side of the room, I started to see some activity on the Gateway Traffic page of The Things Network. I was also able to add a simple decoder for the plain text message I was sending, and everything appears to work as expected. I'll see about adding the tweaks in your code to see if it improves how quickly the messages are transmitted.

Thanks for your help!
Adam

mantonakakis

Glad to hear it was something simple!
The two factors I mentioned seem to have mostly made the difference between occasional issues and very solid/repeatable/quick activation, so if you end up seeing any inconsistency, maybe give them a look.

heikkij

Old thread, but maybe better to reply than starting new ?

I used the same pin mapping as mentioned above, and got LMIC to send some messages.
However, with the code growing, and longer delay from init of the library to the first os_runloop_once(), the latter now crashes hard on the callback.

Can anyone verify that their M0 & LMIC (ABP) still works ?

void os_runloop_once() {
...
    if(j) { // run job callback                                                                                                                   
      j->func(j); << not sure what its calling, but crashes here
    }

alexsh1

Quote from: heikkij on November 19, 2020, 03:31:05 AM
Old thread, but maybe better to reply than starting new ?

I used the same pin mapping as mentioned above, and got LMIC to send some messages.
However, with the code growing, and longer delay from init of the library to the first os_runloop_once(), the latter now crashes hard on the callback.

Can anyone verify that their M0 & LMIC (ABP) still works ?

void os_runloop_once() {
...
    if(j) { // run job callback                                                                                                                   
      j->func(j); << not sure what its calling, but crashes here
    }


I have Moteino Mega Lora and another SAMD21 board (Rocket Scream Mini Ultra Pro V2) working with both ABP / OTAA.
LMIC has been updated (previous version has stopped being developed) as well as TTN moved to V2 and soon to be moved to V3.
You have to check all your libs if something does not work. Unfortunately, this is more a problem of RF95/LMIC.

alexsh1

Hi Felix,

I understand on Moteino M0 RST, DIO1 and DIO2 have to be manually soldered to certain SAMD21 pins, i.e. unlike Moteino Mega there is no easy (soldering three pads) LMIC option?

Thanks
Alex

Felix

Your understanding is correct. You'd need to use some wires if you'd like to do that on the M0.

heikkij

Btw, I never got LMIC to work, but I had better luck with this:
https://github.com/adafruit/TinyLoRa

Since reset is not hooked up to RFM95, you should solder a wire to some digital pin on the M0