Dragino with ultrasonic sensor and TTN

I am using an Arduino Uno with an HC-SR04 ultrasonic sensor and the Dragino LoRa shield with the LMIC library.
The goal is to measure the distance and send the value to the TTN via LoRa.

I have a running code to measure the distance with the ultrasonic sensor and I could register my device at TTN and send test text messages to the network.

In the next step I tried to combine both, i.e. to measure the distance and send the value via LoRa to TTN. Here I failed: the sensor measurement shows always 0 and I have no clue how to send the data to the network.

Do you know where this sensor fail comes from and if it shows the correct measurement, how to send it to the TTN?

Thanks for your help!


// References:
 // [feather] adafruit-feather-m0-radio-with-lora-module.pdf

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

//Sensor Stuff
#define trigPin 12 // über diesen Pin senden wir Ultraschall-Pulse
#define echoPin 13 // über diesen Pin empfangen wir das Echo
const int SENSORRANGE = 350; // Reichweite
const int schwellwert = 50; // Schwellwert wann Parkplatz als belegt gilt --> einstellen!
bool belegt; // wird dann ans Netzwerk gesendet

// 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.
# define FILLMEIN 0
# 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)

// LoRaWAN NwkSKey, network session key
// This should be in big-endian (aka msb).
static const PROGMEM u1_t NWKSKEY[16] = {....};

// LoRaWAN AppSKey, application session key
// This should also be in big-endian (aka msb).
static const u1_t PROGMEM APPSKEY[16] = {.... };

// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed, so this should be in big-endian (aka msb) too.
static const u4_t DEVADDR = .....; // <-- 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 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) { }

static osjob_t sendjob;

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

// Pin mapping Dragino Shield
const lmic_pinmap lmic_pins = {
    .nss = 10,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 9,
    .dio = {2, 6, 7},

void onEvent (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:
        || This event is defined but not used in the code. No
        || point in wasting codespace on it.
        || case EV_RFU1:
        ||     Serial.println(F("EV_RFU1"));
        ||     break;
        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.println(F("Received "));
              Serial.println(F(" bytes of payload"));
            // Schedule next transmission
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
        case EV_LOST_TSYNC:
        case EV_RESET:
        case EV_RXCOMPLETE:
            // data received in ping slot
        case EV_LINK_DEAD:
        case EV_LINK_ALIVE:
        || This event is defined but not used in the code. No
        || point in wasting codespace on it.
        || case EV_SCAN_FOUND:
        ||    Serial.println(F("EV_SCAN_FOUND"));
        ||    break;        
        case EV_TXSTART:
        case EV_TXCANCELED:
        case EV_RXSTART:
            // do not print anything -- it wrecks timing 
        case EV_JOIN_TXCOMPLETE:
            Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
            Serial.print(F("Unknown event: "));
            Serial.println((unsigned) ev);

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 {

       //Sensor Stuff
    long duration, distance;
    digitalWrite(trigPin, LOW);  // Signal auf LOW setzen
    delayMicroseconds(2);        // Kleine Wartezeit
    digitalWrite(trigPin, HIGH); // Signal auf HIGH setzen
    delayMicroseconds(10);       // Kleine Wartezeit
    digitalWrite(trigPin, LOW);    
    //pinMode(echoPin, INPUT);
    duration = pulseIn(echoPin, HIGH); // Zeitdauer bis Empfang des Echo
    belegt = 0; // init Wert: Parkplatz frei
    distance = duration/58;      // Distanz berechnen 
    //Serial.println("Gemessene Duration = ");
    Serial.print("Gemessene Distanz = ");
    if (distance >= SENSORRANGE || distance <= 0) {
        Serial.println("Fehler: Außerhalb der Reichweite oder kein Messwert");
    else {
       Serial.println(" cm");

    // Belegung
    if (distance < schwellwert) {
       belegt = true;
       Serial.println("Parkplatz belegt");
       uint8_t mydata[] = "Parkplatz belegt";
    else {
       Serial.println("Parkplatz frei");

        byte payload[2];
        payload[0] = highByte(belegt);
        payload[1] = lowByte(belegt);

        // Prepare upstream data transmission at the next possible time.
        LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
        Serial.println(F("Packet queued"));
    // Next TX is scheduled after TX_COMPLETE event.

void setup() {
//    pinMode(13, OUTPUT);
    while (!Serial); // wait for Serial to be initialized
    delay(100);     // per sample code on RF_95 test

    //Sensor Stuff
    pinMode(trigPin, OUTPUT);  // Zum Auslösen des Ultraschalls-Puls
    pinMode(echoPin, INPUT);   // Zum Einlesen der Dauer

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

    // LMIC init
    // Reset the MAC state. Session and pending data transfers will be discarded.

    // 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 (0x13, DEVADDR, nwkskey, appskey);
    // If not running an AVR with PROGMEM, just use the arrays directly
    LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY);

    #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. The LMIC doesn't let you change
    // the three basic settings, but we show them here.
    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) || defined(CFG_au915)
    // NA-US and AU 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
    #elif defined(CFG_as923)
    // Set up the channels used in your country. Only two are defined by default,
    // and they cannot be changed.  Use BAND_CENTI to indicate 1% duty cycle.
    // LMIC_setupChannel(0, 923200000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);
    // LMIC_setupChannel(1, 923400000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);

    // ... extra definitions for channels 2..n here
    #elif defined(CFG_kr920)
    // Set up the channels used in your country. Three are defined by default,
    // and they cannot be changed. Duty cycle doesn't matter, but is conventionally
    // BAND_MILLI.
    // LMIC_setupChannel(0, 922100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
    // LMIC_setupChannel(1, 922300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
    // LMIC_setupChannel(2, 922500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);

    // ... extra definitions for channels 3..n here.
    #elif defined(CFG_in866)
    // Set up the channels used in your country. Three are defined by default,
    // and they cannot be changed. Duty cycle doesn't matter, but is conventionally
    // BAND_MILLI.
    // LMIC_setupChannel(0, 865062500, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
    // LMIC_setupChannel(1, 865402500, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
    // LMIC_setupChannel(2, 865985000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);

    // ... extra definitions for channels 3..n here.
    # error Region not supported

    // Disable link check validation

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

    // Set data rate and transmit power for uplink

    // Start job

void loop() {
    unsigned long now;
    now = millis();
    if ((now & 512) != 0) {
      digitalWrite(13, HIGH);
    else {
      digitalWrite(13, LOW);



Those pins are required for SPI communication with the radio, you’ll have to move the sensor to other pins which are not being used. You seem to also be using pins 2, 6, 7, 9 and 10, and 0 and 1 are for serial.

So pick something from what is left - 3, 4, 5, or 8.

I think you can also run the sensor off the analog pins used digitally.

Note that LMiC itself will use up most of the memory resources of an Uno, most people who want to take an Arduino approach end up on a feather M0 or maybe MCCI’s STM32L0 BSP or similar.


Good to see you changed that text into a 2 bytes binary encoded payload. But there’s still something left in your code:

Above, sizeof(payload)-1 will make LMIC send 1 byte, not 2. Simply remove that -1.

The -1 part is probably a leftover of the test that used text? Even a fixed-length char array has a terminating null-character. That does not need to be included in the LoRaWAN packet, as the receiver already knows how long a LoRaWAN packet it. So, for text you’ll often see sizeof(...) - 1.

LoRaWAN examples using text are just evil.

1 Like

Thank you! That solved it! I was too fixed on the LoRa stuff that I haven’t checked he pins.

Thank you! You’re absolutely right. Now everything is working!