Sparkfun The Thing Plus expLoRaBLE TTN OTAA, looking for sketch that consistently works

I have a Sparkfun SAMD21 Pro RF end devices working successfully on my The Things Industries TTN services using OTAA. However, I wanted to use The Thing Plus expLoRaBLE due to its lower power consumption and slightly cheaper board, its model number is WRL-17506 and is a NM180100 SiP which includes a Semtech SX1262 LoRa module paired with the Apollo3 MCU.

I followed the the Hookup Guide provided by Sparkfun for this device, downloaded and installed the required libraries, and used the sample sketch and modified with it my TTN device EUI and APPKEY.
Sketch compiled successfully.

When running the sketch, often takes over 4 minutes to finally join TTN, as reported by the TTN device console, after joining it seems to repeatedly resend the request to join packet, but no data packets seem to be sent, and the sketch never seems to confirm it had joined the network, though TTN did acknowledge and join request was accepted.

MY QUESTION: Does any one have a basic sketch for the Sparkfun The Thing Plus expLoRaBLE that works consistently that they would be willing to share? It would be most appreciated.

Serial Output Snippet (first minute), showing repeated xmit start, and rx timeouts.

Hi, can you provide a direct link to the board and the libraries you mention please.

If you want to include serial output, please include it in the body of the post formatted with the </> tool - this allows us to see it directly rather than downloading a file, do the virus checking dance & then opening in another program.

As no data packets, aka uplinks, can be sent until the device has joined, you may wish to review the flow chart in the Learn section (linked at top of page).

You may also want to think about what it means if TTN has issued a join accept but the device is not aware of that - you’ll find a bazillion topics on here with the fateful words “join accept” in them …

At this stage I’d not write off the sketch you are using as it appears to be consistent. A link to it would help us make further comment.

Descartes, thank you for your response. Below is the information you suggested.
In regards to the Sketch itself, it is based on the sketch provided within the SparkFun hookup guide,
however, that sketch is written for the SX127x chip, with comments for settings, and variable definitions to be changed for the SX126x chip, that I modified.

Click to see the snippet of the serial output log

SPI1 begin
00:00:00.034: engineUpdate[opmode=0x4]
00:00:05.983: engineUpdate[opmode=0x4]
374243: EV_TXSTART
00:00:05.989: engineUpdate[opmode=0x884]
00:00:05.993: TX[mod=LoRa,sf=10,bw=125,cr=4/5,nocrc=0,ih=0,fcnt=0,freq=902.3,pow=30,len=23]: 000000000000000000992700D87ED5B370A1A6241B6250
399063: EV_TXDONE
00:00:06.386: engineUpdate[opmode=0x884]
00:00:11.374: RX_MODE[mod=LoRa,sf=10,bw=500,cr=4/5,nocrc=1,ih=0,freq=923.3,rxtime=711549]
00:00:11.388: WARNING: rxtime is 237 ticks in the past! (ramp-up time 6 ms / 379 ticks)
00:00:11.409: RX: TIMEOUT
00:00:12.377: RX_MODE[mod=LoRa,sf=12,bw=500,cr=4/5,nocrc=1,ih=0,freq=923.3,rxtime=774241]
00:00:12.391: WARNING: rxtime is 237 ticks in the past! (ramp-up time 6 ms / 379 ticks)
00:00:12.449: RX: TIMEOUT
00:00:24.924: engineUpdate[opmode=0x4]
1558018: EV_TXSTART
00:00:24.929: engineUpdate[opmode=0x884]
00:00:24.933: TX[mod=LoRa,sf=10,bw=125,cr=4/5,nocrc=0,ih=0,fcnt=0,freq=911.7,pow=30,len=23]: 000000000000000000992700D87ED5B370178B517D5462
1582841: EV_TXDONE
00:00:25.327: engineUpdate[opmode=0x884]
00:00:30.315: RX_MODE[mod=LoRa,sf=10,bw=500,cr=4/5,nocrc=1,ih=0,freq=927.5,rxtime=1895327]
00:00:30.329: WARNING: rxtime is 243 ticks in the past! (ramp-up time 6 ms / 379 ticks)
00:00:30.349: RX: TIMEOUT
00:00:31.318: RX_MODE[mod=LoRa,sf=12,bw=500,cr=4/5,nocrc=1,ih=0,freq=923.3,rxtime=1958019]
00:00:31.332: WARNING: rxtime is 243 ticks in the past! (ramp-up time 6 ms / 379 ticks)
00:00:31.389: RX: TIMEOUT
Click to see The TTN device activity log:


I followed the hookup guide for the device at [SparkFun expLoRaBLE Hookup Guide - SparkFun Learn]

Click to see the Sketch for the device
 * 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.
 * 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 config.h.

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

// 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]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void os_getJoinEui (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]={ 0x99, 0x27, 0x00, 0xD8, 0x7E, 0xD5, 0xB3, 0x70 };
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] = { 0xB5, 0x7B, 0x4F, 0xFC, 0x98, 0x60, 0x66, 0x2D, 0x6B, 0xF8, 0x40, 0xB3, 0x82, 0xC4, 0x19, 0xDF };
void os_getNwkKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);}

// The region to use, this just uses the first one (can be changed if
// multiple regions are enabled).
u1_t os_getRegion (void) { return LMIC_regionCode(0); }

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

// Timestamp of last packet sent
uint32_t last_packet = 0;

// When this is defined, a standard pinmap from standard-pinmaps.ino
// will be used.  If you need to use a custom pinmap, comment this line
// and enter the pin numbers in the lmic_pins variable below.

// All pin assignments use Arduino pin numbers (e.g. what you would pass
// to digitalWrite), or LMIC_UNUSED_PIN when a pin is not connected.
const lmic_pinmap lmic_pins = {
    // NSS input pin for SPI communication (required)
    .nss = D36,
    // If needed, these pins control the RX/TX antenna switch (active
    // high outputs). When you have both, the antenna switch can
    // powerdown when unused. If you just have a RXTX pin it should
    // usually be assigned to .tx, reverting to RX mode when idle).
    // The SX127x has an RXTX pin that can automatically control the
    // antenna switch (if internally connected on the transceiver
    // board). This pin is always active, so no configuration is needed
    // for that here.
    // On SX126x, the DIO2 can be used for the same thing, but this is
    // disabled by default. To enable this, set .tx to
    // LMIC_CONTROLLED_BY_DIO2 below (this seems to be common and
    // enabling it when not needed is probably harmless, unless DIO2 is
    // connected to GND or VCC directly inside the transceiver board).
    .rx = LMIC_UNUSED_PIN,
    // Radio reset output pin (active high for SX1276, active low for
    // others). When omitted, reset is skipped which might cause problems.
    .rst = D44,
    // DIO input pins.
    //   For SX127x, LoRa needs DIO0 and DIO1, FSK needs DIO0, DIO1 and DIO2
    //   For SX126x, Only DIO1 is needed (so leave DIO0 and DIO2 as LMIC_UNUSED_PIN)
    .dio = {/* DIO0 */ LMIC_UNUSED_PIN, /* DIO1 */ D40, /* DIO2 */ LMIC_UNUSED_PIN},
    // Busy input pin (SX126x only). When omitted, a delay is used which might
    // cause problems.
    .busy = D39,
    // TCXO oscillator enable output pin (active high).
    // For SX127x this should be an I/O pin that controls the TCXO, or
    // LMIC_UNUSED_PIN when a crystal is used instead of a TCXO.
    // For SX126x this should be LMIC_CONTROLLED_BY_DIO3 when a TCXO is
    // directly connected to the transceiver DIO3 to let the transceiver
    // start and stop the TCXO, or LMIC_UNUSED_PIN when a crystal is
    // used instead of a TCXO. Controlling the TCXO from the MCU is not
    // supported.
    .tcxo = LMIC_UNUSED_PIN,
#endif // !defined(USE_STANDARD_PINMAP)

void onLmicEvent (ev_t ev) {
    Serial.print(": ");
    switch(ev) {
        case EV_SCAN_TIMEOUT:
        case EV_BEACON_FOUND:
        case EV_BEACON_MISSED:
        case EV_BEACON_TRACKED:
        case EV_JOINING:
        case EV_JOINED:

            // Disable link check validation (automatically enabled
            // during join, but not supported by TTN at this time).
        case EV_RFU1:
        case EV_JOIN_FAILED:
        case EV_REJOIN_FAILED:
        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.print(F("Received "));
              Serial.println(F(" bytes of payload"));
        case EV_LOST_TSYNC:
        case EV_RESET:
        case EV_RXCOMPLETE:
            // data received in ping slot
        case EV_LINK_DEAD:
        case EV_LINK_ALIVE:
        case EV_SCAN_FOUND:
        case EV_TXSTART:
        case EV_TXDONE:
        case EV_DATARATE:
        case EV_START_SCAN:
        case EV_ADR_BACKOFF:

            Serial.print(F("Unknown event: "));

void setup() {

    // Wait up to 5 seconds for serial to be opened, to allow catching
    // startup messages on native USB boards (that do not reset when
    // serial is opened).
    unsigned long start = millis();
    while (millis() - start < 5000 && !Serial);


    // LMIC init

    // Enable this to increase the receive window size, to compensate
    // for an inaccurate clock.  // This compensate for +/- 10% clock
    // error, a lower value will likely be more appropriate.
    //LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100);

    // Start join

    // Make sure the first packet is scheduled ASAP after join completes
    last_packet = millis() - TX_INTERVAL;

    // Optionally wait for join to complete (uncomment this is you want
    // to run the loop while joining).
    while ((LMIC.opmode & (OP_JOINING)))

void loop() {
    // Let LMIC handle background tasks

    // If TX_INTERVAL passed, *and* our previous packet is not still
    // pending (which can happen due to duty cycle limitations), send
    // the next packet.
    if (millis() - last_packet > TX_INTERVAL && !(LMIC.opmode & (OP_JOINING|OP_TXRXPEND)))

void send_packet(){
    // Prepare upstream data transmission at the next possible time.
    uint8_t mydata[] = "Hello, world!";
    LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
    Serial.println(F("Packet queued"));

    last_packet = millis();

Any thoughts on this ^^^^

The Learn section references duty cycles and the Fair Use Policy - the sketch you are using massively breaches the FUP and potentially the legal limits. Please confirm you have read the FUP and understand the legal implications of duty cycle.

I have read the FUP and generally understand and agree that the test sketch being discussed here ( “HELLO WORLD”) would violate the FUP if allowed to run for long periods of time. In testing I generally turn of the device after a minute and testing a 3-4 times a day. Exceptions being today and about Feb 7th last week trying to debug the sample sketch. I have requests out on the SparkFun forum as well as TTN for anyone that has working sketch for this device.

Usage chart of my gateway from the Network Operations Center tab of my account

For my production devices (SAMD21 Pro RF) and sketches, when they run they are programmed to xmit a 14 byte payload once every 15 to 30 minutes. Unfortunately I cannot port this sketch to the SparkFun Explorable as the libraries the older working sketch use are NOT supported for the newer SparkFun The THings Plus ExpLoRaBLE (specially the SX1262 chip).

That’s not how we roll here and as Queen Gertrude says “The lady doth protest too much, methinks”. Push to send for testing provides control.

However if you have a NOC you have a TTI instance - can you not lodge a support request?

As to the actual problem, there’s still nothing apparently wrong with the sketch - it sends, LNS process & sends reply and the device does what?


Again, thanks for your time and effort…

You ask, :… and the device does what?" Based on the serial output it appears to loop
TX (the join request)
waits 5 seconds
RXMODE, with warning rxtime is xxx ticks in the past
waits 12 seconds
repeat the loop

This behavior continues, even after TTN:
Accepts join-request
successfully processes the join-request
forwards the join-accept message

My hypothesis is that the end device sketch fails to detect the join-accept message, or if detected, the sketch cannot properly process it (e.g. invalid RX pin mapping, issue in a lower level library, or bad sample sketch logic. I don’t know, thus my requests to SparkFun forum for a working sketch). Failing to have joined, the device loops to re-xmit the join-request

Maybe I should turn on my frequency analyzer and look for traffic from my gateway that would coincide with the forward join-accept message. .

You’re thinking too hard and the sketch doesn’t do detecting, it gets whatever the radio gives it - it’s the radio that needs to hear the Join Accept.

If you happened to have looked around the forum for Join Accept issues you’ll see this one comes up time & time again. Lots of DIYers not getting their joins complete. Rarely is it hardware or firmware related.

Where is your gateway in relation to the device, what model of gateway is it, does it work OK with other devices and what is the RSSI & SNR of the Join Request?

Yes you can correct, what I meant by my statement a week ago is that that my concern is the sketch does not have the correct pins defined, for the NM180100 process to obtain the signals (detect) from the SX126x chip, thus at the level of the sketch logic, it is possibly never sensing that a join-accept had been received by the SX1262x Sketch logic thus continued to loop xmitting the join request… My concern for incorrect pin-out definition is one of the reasons why I was asking forum members if they had sketch for this Sparkfun device that worked to establish OTAA with TTN.

This ^

To reinforce the question posed and stopping going in circles.

Are the gateway and node in the same room or on the same workshop table?