MIC Mismatch when trying to connect to TTN

I am trying to use the ttn_otaa example of the MCCI_LoRaWAN_LMIC_library but I get an error.
AppEUI or joinEUI is all 0 so doubt there is an issue. If I chnage my DevEUI in any way I dont get any messages at all, because TTN cant match the data to my device so that is probaply correct aswell. so only AppKey is wrong, probaply.
Is there a way to see what TTN was expecting and what it got MIC wise ? I know it would be a secruity horror but for debugging it it would be nice.
According to this wirteup arduino-lmic/LoRaWAN-at-a-glance.pdf at master · mcci-catena/arduino-lmic · GitHub, the MIC is 4 bytes and calculated from the AppKey as AES Key and MHDR binry ored with the Payload.
in MHDR what is major and how di I check if its correct? I asume RFU is just 0 as its reserved?. And what is DevNonce? I asume AppEUI and DevEUI are correct so only those (Major, DevNonce, AppKey) could be wrong.

 * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
 * Copyright (c) 2018 Terry Moore, MCCI
 * 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
 * arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.

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

extern "C" {
  #include "user_interface.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.
# 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)

// 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_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]={ 0xB2, 0x59, 0x05, 0xD0, 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.
static u1_t APPKEY[16] = { 0x2A,0x21,0x3C,0xE1,0x0A,0xEB,0x7D,0xDB,0x40,0x3B,0x59,0x51,0xBD,0x95,0xA5,0xFD };
void os_getDevKey (u1_t* buf) {  memcpy_P(buf, APPKEY, 16);} 

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

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

#define PIN_ss 4
#define PIN_rst 2
#define PIN_dio0 5
#define PIN_dio1 16

// Pin mapping
// Adapted for Feather M0 per p.10 of [feather]
const lmic_pinmap lmic_pins = {
    .nss = PIN_ss,                       // chip select on feather (rf95module) CS
    .rxtx = LMIC_UNUSED_PIN,
    .rst = PIN_rst,                       // reset pin
    .dio = {PIN_dio0, PIN_dio1, LMIC_UNUSED_PIN}, // assumes external jumpers [feather_lora_jumper]
                                    // DIO1 is on JP1-1: is io1 - we connect to GPO6
                                    // DIO1 is on JP5-3: is D2 - we connect to GPO5

void printHex2(unsigned v) {
    v &= 0xff;
    if (v < 16)
    Serial.print(v, HEX);

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:
              u4_t netid = 0;
              devaddr_t devaddr = 0;
              u1_t nwkKey[16];
              u1_t artKey[16];
              LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
              Serial.print("netid: ");
              Serial.println(netid, DEC);
              Serial.print("devaddr: ");
              Serial.println(devaddr, HEX);
              Serial.print("AppSKey: ");
              for (size_t i=0; i<sizeof(artKey); ++i) {
                if (i != 0)
              Serial.print("NwkSKey: ");
              for (size_t i=0; i<sizeof(nwkKey); ++i) {
                      if (i != 0)
            // 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.
        || 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.print(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"));
              u1_t c2 = APPKEY[0];
              for(int i=1;i<16;i++)
                APPKEY[i-1] = APPKEY[i];
              APPKEY[15] = c2;

              Serial.print("Appkey is ");
              for(int i=0;i<16;i++)
                Serial.print(APPKEY[i], HEX);

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

int l = -1;
int l2 = -1;

void setup() {
    l = -1;
    l2 = -1;

    Serial.print("Appkey is ");
    for(int i=0;i<16;i++)
      Serial.print(APPKEY[i], HEX);

    #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.
    LMIC_setClockError(MAX_CLOCK_ERROR * 2 / 100);
    /*#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(APPKEY)];
    uint8_t nwkskey[sizeof(NWKSKEY)];
    memcpy_P(appskey, APPKEY, sizeof(APPKEY));
    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);

    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

     LMIC.dn2Dr = SF9; 

    // Start job (sending automatically starts OTAA too)
    pinMode(lmic_pins.dio[0], INPUT);

void loop() {
    int n = digitalRead(lmic_pins.dio[0]);
    int n2 = digitalRead(lmic_pins.dio[1]);
      Serial.print("dio0 ");

      l = n;
      Serial.print("dio1 ");

      l2 = n2;


// project-specific definitions
#define CFG_eu868 1
//#define CFG_us915 1
//#define CFG_au915 1
//#define CFG_as923 1
// #define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP      /* for as923-JP; also define CFG_as923 */
//#define CFG_kr920 1
//#define CFG_in866 1
#define CFG_sx1276_radio 1
#define USE_ORIGINAL_AES //larger but faster for ARM

Settings from TTN Console:

General information
End device ID eui-70b3d57ed00559b2

Frequency plan Europe 863-870 MHz (SF9 for RX2 - recommended)
LoRaWAN version LoRaWAN Specification 1.0.3
Regional Parameters version RP001 Regional Parameters 1.0.3 revision A
Created at Sep 22, 2022 14:44:22
Activation information
AppEUI 0000000000000000
DevEUI 70B3D57ED00559B2
AppKey ••••••••••••••••••••••••••••••••
NwkKey ••••••••••••••••••••••••••••••••

I guess the most likely cause is app/join eui or dev eui entered incorrectly, either a typing/copying error, or entering it lsb first when it was expected to be msb first (or vice versa). In my experience, depending on the platform, you should reverse the bytes of both the app/join eui and the dev eui, but use the app key as is. Once you get this right, the library should take care of the rest of the protocol and you generally don’t need to know what MHDR etc means.
I don’t know the exact behaviour with dev eui or app eui being 0.

The devnonce is an arbitrary but unique number used during the join procedure. It has to be unique for each attempt for security reasons, so someone can’t do a replay attack. From LoraWAN version 1.0.4 and up, it has to monotonically increase for each join attempt, not just be random + unique. Joins should happen only rarely, your application should try to keep the credentials / lorawan state cached somewhere.

Anyway I see that you have set the lorawan version to 1.0.3 in the backend, so that should be much of a problem yet.

when I change DevEUI I dont see messages at all, I did tryx to reverse it without success. also I did try to reverse my AppKey wthout success. If the DevNonce is a random number how do TTN and my device agree on ti since its used to make the MIC? I can imagien if that one is reset it can be an issue.

It is part of the transmitted join request.

and according to the docs this very number is also used to generate th MIC, can the be an issue, or it that unlikley? will ttn just accept the transmitted number and and use ti to calc the MIC aswell ?

TTN will show an error if it does not accept the nonce. However it won’t even look at the nonce if the MIC isn’t correct. If you see join requests in the application and get errors that the MIC isn’t right you need to check your AppKey. Make sure it is entered in the code with the bytes in the same order the console shows them.
(So 01 02 AA will be 0x01, 0x02, 0xAA in code)

TN shows: 2A213CE10AEB7DDB403B5951BD95A5FD
and in code I wrote:

static u1_t APPKEY[16] = { 0x2A,0x21,0x3C,0xE1,0x0A,0xEB,0x7D,0xDB,0x40,0x3B,0x59,0x51,0xBD,0x95,0xA5,0xFD };

so that matches

How close are the GW & device and what is your RSSI & SNR value?

Nope, me neither and this won’t be the first or second time this month I’ve said that I’m not sure about all zeros for the App/JoinEUI on LMIC.

Worth a try.

Too loud may trash the CRC but then it won’t get passed along to the LNS

Overall, for latest LMIC ttn-otaa, with a Dev & AppEUI as LSB, it works for me.

It’s hard to tell which version this library is because there is a the whole channel plan setup that isn’t in the latest version. Plus the various bits of code like the setting up of the DIO0 mode and looking at the DIO pins in the main loop.

For no apparent reason, the AppKey is barrel rolled shifted to the left if the Join fails - so it will definitely continue to fail for the next 15 attempts, each one, guess what, yielding a MIC mismatch.

OR, and this may win the “Niche Issue of the Week Award”, the use of Original AES may not work as expected on ESP32 which may then have other effects on the header.

There is also an unhealthy amount of detail being dug in to to debug this which may be complicating the issue.

But as the uplink interval is currently set to 5 seconds, further assistance will await the OP having a clear understanding of the Fair Use Policy, that is if they can reach their keyboard from the detention cells whilst awaiting trial for breaching duty cycle. :man_police_officer: :police_car:

Maybe but that then depends on the GW and how configured - dont think we have been told which/how… I know some ship with a default of allowing forwarding on CRC fail! :scream_cat: You need to know to uncheck a tick box in some UI’s :frowning:

If a failing CRC uplink reaches the LNS, it ain’t gonna get as far as a MIC check …

Ah yeh, probably true…just looking for some small, marginal level of corruption that just fails MIC without a crash and burn…

bertween 5 and 50m. never hear of RSSI and SNR what are those and where do I find them ?

interresting thing I noticed even when setting the interval to 5 seconds it doesnt use the value (so the code uses a different value and the #define is ignored). it take longer then 1 minute to retry. I did try that in order for better testing but with no success (as it still takes over 1 moinuite to retry). Lora Sais 10% active time or 1% active tiem depending on channel and TTN sais maximum uptime of 30 seconds. And as this is all a dev board I plug it in open the console and see if it works and unplug it again so no fair use policy was broken.
the AES key shift was added in order to test if another combination may work for what ever reason. Right now my hardware is a Adafruit Huzzah Feather (ESP8266) and a RFM96

As for AppEUI being all 0 TTN and LMIC said its ok if tis all 0, but even if I want to change ti I cant as the TTN webside wont allow me to do this, for whatever reason. (its greyed out)

RFM96 is 433/470Mhz version - your s/w looks to set for EU868?

[quote=“m_l, post:1, topic:59375”]

// project-specific definitions
#define CFG_eu868 1
//#define CFG_us915 1
//#define CFG_au915 1
//#define CFG_as923 1
// #define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP; also define CFG_as923 */
//#define CFG_kr920 1
//#define CFG_in866 1


The RFM95(W) is the option for 868Mhz

yeah its an RFM95W my bad, and as the gateway also is at 868MHz I think that part works.


It couldn’t really, it has to prepare the uplink, send it and then wait 5 seconds before the Rx1 window opens, then another second for the Rx2 window. Plus there is some level of housekeeping.

A push button to test, then review the logs, adjust, repeat. No one ever got code to fix itself by just running it as much as possible. But some people do end up poorer for it when the authorities turn up to arrest them.

As for plug-in, test, un-plug, you don’t need to set it to 5 seconds, LMIC will try a join immediately. But the impact of forgetting to un-plug a device set to that is too much for us all to cope with, so we ask that people just don’t put in values like that.

Well that’s all right then, no point testing it. Or create a new device.

Si signal strength? Should be good enough as I am directly nex to to the gateway. if I remeber correctly in the message was either -100dB or -17dB. (the message where also the MIC error was) about SNR I cant say anything as I dont know how I would check that. does TTN somehow logs that ?

there is no gateway near my hosue I always have to travel 2km so there is no issue in that regard. Also as O stated the join attempts need much longer then 5 seconds for a retry, it still is about 1 minute if not longer. the whole thing needs atleast 20 seconds to even try its first attempt.

Overall, for latest LMIC ttn-otaa, with a Dev & AppEUI as LSB, it works for me.

what do you configure in TTN and the LMIC ino ?

It’s hard to tell which version this library is because there is a the whole channel plan setup that isn’t in the latest version. Plus the various bits of code like the setting up of the DIO0 mode and looking at the DIO pins in the main loop.

Huazzah Feather doenst have a lora modul so I naturall needed to tell the library what pins to use. the DIO lookup is for dwebugging purpose to see whats going on and if theyy are connected correctly.

MCCI LoraWAN LMIC Library by IBM is version 4.1.1

As I said, housekeeping is required and there is some element of duty cycle limitations in LMIC. As for the first attempt, you’ll see that the second to last line of setup is to do a send, so it is triggered as soon as possible. Quite what the effect of hijacking the pinMode of DIO0 will do I’m not sure.

The pins, the Dev & AppEUI’s and AppKey plus set the interval to 300 seconds.

Yes, you need to tell it the correct pins, no you don’t need to debug, it will tell you if you got them wrong. Do you have enough experience with LMIC to be able to determine that the DIO pins are changing to debug with that?

Why a Huzzah Feather - the Feather M0 with RFM95 is almost purpose made for LoRaWAN. Or use an ESP32.

How did you end up with the channel plan info and other legacy items in the TTN OTAA script?

I really think you need to start with a clean setup and not try to make additions to the code - you’ve got the fundamental electronics working as otherwise they’d not be a MIC mismatch - you just need to get the EUI’s & key correct.

A huge difference! (look up dB scaling!) - -100dbm fine for LoRa dev work, -17dbm and the devices ARE EFFECTIVELY SHOUTING AT EACH OTHER drowning out correct signal, saturating/distorting front ends and causing channel bleed over etc… all conditions which get in the way of effective developemnt and debug and which destroy ability to deploy reliably… :slight_smile: :man_shrugging:

You need to look at these carefully and specifically and use/follow the advise oft repeated on this very Forum.