Can LMIC 1.6 be set up to use a single channel and SF?

(Wolfgang Klenk) #1


Problem is, that the Single Channel Gateway is listening at SF7 on 868.100000 Mhz, but during the "join" procedure the LMIC 1.6 protocol stack randomly chooses a channel (-> RF frequency) and has an algorithm that tries out the different spreading factors.

So there is the situation that the receiver (Single Channel Gateway) listens on one particular channel (frequency) with one particular spreading factor (SF7), but the transmitter sends on randomly chosen channels and spreading factors.

Okay, maybe it "works as designed", but the question is:

Is there an (easy) way to tell the LMIC 1.6 protocol stack to stick to one channel and a predefined spreading factor?

Does anyone know how to setup a 1channel node with feather 32u4
(Wolfgang Klenk) #2

This is only valid for LMIC 1.6 sourcecode. I haven't looked at the 1.5 version.
In file lmic/lmic.c you find the following function:

static void initJoinLoop (void) {
#if CFG_TxContinuousMode
  LMIC.txChnl = 0;
    LMIC.txChnl = os_getRndU1() % 6; // Do NOT remove this line
    LMIC.txChnl = 0; // Add this line to avoid random choice of TX channel
    LMIC.adrTxPow = 14;
    setDrJoin(DRCHG_SET, DR_SF7);
    ASSERT((LMIC.opmode & OP_NEXTCHNL)==0);
    LMIC.txend = LMIC.bands[BAND_MILLI].avail + rndDelay(8);

Don't try to be too clever and remove the line containing _os_getRndU1()_. I did it and it did not work any longer. Probably there are other things going on behind the curtain than just creating a simple 1 byte random number.

If you do this modification you should be aware that you may violate the LoRaWAN specification and possibly regulations of your country. I recommend only to do it for testing, not for production.

DRAGINO problems and solutions topic
(Mogyoros) #3

Hi there!
I am using LMIC 1.5. Looking for the same answers as you... :slight_smile:
Found a way to lock join frequencies to a single channel by setting CH0,1,2 to the same frequency.

To fix the spreading factor: in lmic.c, in initJoinLoop modify

// setDrJoin(DRCHG_SET, DR_SF7);
setDrJoin(DRCHG_SET, LMIC.datarate);

In the main code use:
LMIC.datarate=DR_SFx - where x stands for the SF use want to use

To avoid SF hopping during join: in lmic.c, modify nextJoinState:
// setDrJoin(DRCHG_NOJACC, decDR((dr_t)LMIC.datarate));
setDrJoin(DRCHG_NOJACC, (dr_t)LMIC.datarate);

So it wont try to decrease the SF when previous join failed.

(Itacos) #4


I am using LMIC 1.5 but on arduino and I use this function LMIC_setupChannel to limit channel settings and comment all other channels :

     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

It seems to work for me with the single_chan_pkt_fwd , I just have a problem with packet loss (3 packet lost between 2 successful send ...)

I hope it can help you.


Over-the-air-activation OTAA with LMIC
(Mogyoros) #5

Hi @itacos !

You are seeing the packet loss, beacuse in that case the node is trying to use the other base channels (frequencies: 868.3 & 868.5 MHz). Which you can't disable using the lmic lib in this manner. I faced this issue aswell.
The only way is to modify the base channels/frequencies in the lib (lorabase.h, EU868_F2&F3).

For the rest of channels/frequencies (3-8) you probably can manipulate this using LMIC_setupChannel command.

We all now that this could violate the frequency usage (1%), and is not lorawan compliant, so shall careful doing so!

(Arjan) #6

For LMiC 1.5 LMIC_disableChannel(number) worked for me, for ABP, even for the default channels. Don’t know for 1.6 though.

(:warning: See also my extended answer further below.)

Somewhere after LMIC_setupChannel for ABP, and probably also after EV_JOINED for OTAA:

// Define the single channel and data rate (SF) to use
int channel = 0;
int dr = DR_SF7;

// Disable all channels, except for the one defined above. 
for(int i=0; i<9; i++) { // For EU; for US use i<71
  if(i != channel) {

// Set data rate (SF) and transmit power for uplink
LMIC_setDrTxpow(dr, 14);

(Mogyoros) #7

Hi Arjan!
I fully trust you, but I could not manage to do it that way. I spent days to read forums and the lib to figure it out, but I could not find the reason. (using a Nano+RFM95)
Hence I wrote a small addition to the lib, so I can set the base frequencies with a downstream package, and store them in EEPROM. When unit boots up, reads up these values for base channels stored in eeprom, and sets them to use during operation. With that I can keep the flexibility to modify anytime back to the original channel settings, and be later lorawan compliant, and use the node with lorawan gateways.

(Itacos) #8

Hi @arjanvanb and @mogyoros,

Thanks for advices, I have tested @arjanvanb solution and it works for me too in ABP mode with the LMiC 1.5 but don't know if it works for 1.6.

(Mogyoros) #9

Hi Arjan!
I gave it an another chance to make it work as you wrote, but no luck. During join it still tries all 3 base frequencies.
Maybe beacuse I am using OTAA? I will try to look into deeper tomorrow...
My code (examples/ttn-otaa):

void setup() {

// For Pinoccio Scout boards
digitalWrite(VCC_ENABLE, HIGH);

// LMIC init
// Reset the MAC state. Session and pending data transfers will be discarded.
int channel = 0;
int dr = DR_SF7;
for(int i=0; i<9; i++) { // For EU; for US use i<71
    if(i != channel) {
LMIC_setDrTxpow(dr, 14);
// Start job (sending automatically starts OTAA too)


Console output:

401: engineUpdate, opmode=0x808
Packet queued
613: engineUpdate, opmode=0xc
63718: engineUpdate, opmode=0xc
64407: TXMODE, freq=868500000, len=23, SF=7, BW=125, CR=4/5, IH=0
380736: RXMODE_SINGLE, freq=868500000, SF=7, BW=125, CR=4/5, IH=0
446212: RXMODE_SINGLE, freq=869525000, SF=12, BW=125, CR=4/5, IH=0
456355: engineUpdate, opmode=0xc
921139: engineUpdate, opmode=0xc
921832: TXMODE, freq=868100000, len=23, SF=7, BW=125, CR=4/5, IH=0
1238289: RXMODE_SINGLE, freq=868100000, SF=7, BW=125, CR=4/5, IH=0
1303765: RXMODE_SINGLE, freq=869525000, SF=12, BW=125, CR=4/5, IH=0
1313908: engineUpdate, opmode=0xc
1817924: engineUpdate, opmode=0xc
1818620: TXMODE, freq=868300000, len=23, SF=7, BW=125, CR=4/5, IH=0
2134950: RXMODE_SINGLE, freq=868300000, SF=7, BW=125, CR=4/5, IH=0
2200426: RXMODE_SINGLE, freq=869525000, SF=12, BW=125, CR=4/5, IH=0
2210570: engineUpdate, opmode=0xc
2495607: engineUpdate, opmode=0xc

And so on... As you can see It uses all 3 base channels during OTAA join.

(Mogyoros) #10

Confirmed. Works fine when using ABP. So the library definately handles this function differently when using OTAA.

(Arjan) #11

For 1.5, if one does not want to change the library, here is how one can make an ABP or OTAA sketch work with a single channel gateway and Matthijs Kooijman’s LMiC:

  • Somewhere above onEvent define:

    // Define the single channel and data rate (SF) to use
    int channel = 0;
    int dr = DR_SF7;
    // Disables all channels, except for the one defined above, and sets the
    // data rate (SF). This only affects uplinks; for downlinks the default
    // channels or the configuration from the OTAA Join Accept are used.
    // Not LoRaWAN compliant; FOR TESTING ONLY!
    void forceTxSingleChannelDr() {
        for(int i=0; i<9; i++) { // For EU; for US use i<71
            if(i != channel) {
        // Set data rate (SF) and transmit power for uplink
        LMIC_setDrTxpow(dr, 14);
  • For ABP, in init() replace LMIC_setDrTxpow(DR_SF7,14); with:

    // Only use one channel and SF
  • For OTAA, in init() after LMIC_reset(); add:

    // Make LMiC initialize the default channels, choose a channel, and
    // schedule the OTAA join
    // LMiC will already have decided to send on one of the 3 default
    // channels; ensure it uses the one we want
    LMIC.txChnl = channel;
    // ...and make sure we see the EV_JOINING event being logged
  • For OTAA, for EV_JOINED in onEvent use:

    case EV_JOINED:
        // Ignore the channels from the Join Accept
        // Disable link check validation (automatically enabled during join)

But note:

  • Only tested with LMiC 1.5, for EU868, channel 0.

  • For OTAA this assumes the first attempt succeeds; if the Join Request or Join Accept are somehow lost, LMiC will still try different channels and data rates due to the logic in nextJoinState

  • Only tested with an OTAA Join Accept received in RX1 (for EU868 using the same channel as the uplink/join request); when received in RX2 (for EU868 always using 869.525 on SF9), LMiC might also use other values to send? (But maybe the LMIC_setDrTxpow(dr, 14) suffices.)


For Matthijs’ LMiC 1.5, the OTAA flow is:

  • In examples/ttn-otaa/ttn-otaa.ino, calling LMIC_reset clears the keys after which LMIC_setTxData2 is called to schedule some data to be sent.

  • In src/lmic.c, engineUpdate controls the flow, like if something has been scheduled for transmission. When LMIC.devaddr == 0 this will first trigger LMIC_startJoining:

    • LMIC_startJoining calls initJoinLoop, which for EU868 also calls initDefaultChannels. (For US915, that’s already done in LMIC_reset.)

    • In initJoinLoop, for EU868 one out of 3 channels is selected (LMIC.txChnl = os_getRndU1() % 3) while for US915 it’s set to the first channel (LMIC.txChnl = 0).

    • The actual join is not started yet; LMIC_startJoining basically schedules joining to be the first thing to do when time permits. So when manually calling LMIC_startJoining, one can quickly use LMIC.txChnl = channel to change the channel that LMiC selected.

  • Whenever a join attempt fails, nextJoinState will select another channel and/or data rate (SF), without checking if the channel is activated; see One cannot change that behavior from one’s own sketch.

How to set arduino-lmic to run on only 868.1?
Single Channel Gateway part 2
Node feasibility
Big ESP32 + SX127x topic part 2
How to limit the number of tries for LMiC confirmed uplinks? (ACK)
Adafruit M0 LoRa & RPI Gateway
(Arjan) #12

For the US, which uses 64 channels while gateways might only use 8 (or one, for a single-channel gateway), the following from April 2016 might be relevant:


I’ve not validated, but someone might want to create a pull request if this bug is currently still in the code

(Aayk) #13

Thank you very much! Now my node is sending uplinks in a single channel in US _915

(Vjbmario) #14

Hello Guys,

First of all thank you so much for the information. I am really new in this world, and I am trying to build the complete system, LoraWan Gateway (Raspberry Pi 3B + Lora Shield v1.4) and a device (arduino nano connected to Lora RFM95).

I have already did the systems with help of this 2 tutorials:

And after I edit my code as arjanvanb explain to have transmission single channel… What is very strange for me is that I can always receive the transmission in LoraWan Gateway but after when I see in ttn site in data, sometimes the addr is wrong, as can be seen in the picture… Anyone understands


Thank you so much and I hope you could help me :slight_smile:

Best regards,
Valter Mário

(Arjan) #15

Why do you think the DevAddr is wrong? Which one do you expect?

Seems to me that your gateway is simply also receiving LoRa (or LoRaWAN) packets from other devices, which are not yours. That is to be expected, as the gateway radio receiver just listens and forwards to TTN, which will ignore the packets when from unknown devices. (A TTN DevAddr always starts with hexadecimal 26 or 27.)

(Jac Kersing) #16

Looks like you are also forwarding packets with CRC errors. If so, please disable that.
While testing make sure to keep at least 3 meter between the node and the gateway.

(Vjbmario) #17

Wow! Today when I woke up I took my system into working again but this time with Gateway 3 meters away and apparently it’s working perfect!

Since I had the opportunity to talk with you, I would like ask another thing to you: Apparently, my gateway only support uplink. Do you know some API or another way of doing it support Downlink with this hardware (Raspberry Pi 3 + Lora Shield)?

Thank you so much for your advice, it’s amazing have a community that answer so fast!

Thank you so much!

(Jac Kersing) #18

Have you checked DRAGINO problems and solutions topic for downlink enabled RPi setup?

(Bauvill) #19

Hi Wklenk,
Thanks for your posts and supports.
I have some few questions.
1-The single channel gateway you pointed in this link ( ) does not support downlinks so can the gateway send downlinks ??
2- I have a LoRaWan end device that runs the LMIC v.16 ( from your git repo), I can send data with it but I can’t receive any and I honestly have hard times understanding the code.
I would like to know if the is a version of the code that supports ABP since it is the only authentification supported by single channel gateway. I have to spend too much time trying to make it work, please help if you can Thanks.
Note: my end device and my gateway are based on RPI+LoRa GPS HAT.

(Wolfgang Klenk) #20

The code supports ABP, but there is no example how to do it.

You can check the example given here:

The important lines are these:

// LoRaWAN NwkSKey, network session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const PROGMEM u1_t NWKSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };

// LoRaWAN AppSKey, application session key
// This is the default Semtech key, which is used by the early prototype TTN
// network.
static const u1_t PROGMEM APPSKEY[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };

// LoRaWAN end-device address (DevAddr)
static const u4_t DEVADDR = 0x03FF0001 ; // <-- Change this address for every node!


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