diff --git a/aes/ideetron/AES-128_V10.cpp b/aes/ideetron/AES-128_V10.cpp
new file mode 100644
index 0000000..1d790f9
--- /dev/null
+++ b/aes/ideetron/AES-128_V10.cpp
@@ -0,0 +1,342 @@
+/******************************************************************************************
+#if defined(USE_IDEETRON_AES)
+* Copyright 2015, 2016 Ideetron B.V.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Lesser General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public License
+* along with this program. If not, see .
+******************************************************************************************/
+/******************************************************************************************
+*
+* File: AES-128_V10.cpp
+* Author: Gerben den Hartog
+* Compagny: Ideetron B.V.
+* Website: http://www.ideetron.nl/LoRa
+* E-mail: info@ideetron.nl
+******************************************************************************************/
+/****************************************************************************************
+*
+* Created on: 20-10-2015
+* Supported Hardware: ID150119-02 Nexus board with RFM95
+*
+* Firmware Version 1.0
+* First version
+****************************************************************************************/
+
+// This file was taken from
+// https://github.com/Ideetron/RFM95W_Nexus/tree/master/LoRaWAN_V31 for
+// use with LMIC. It was only cosmetically modified:
+// - AES_Encrypt was renamed to lmic_aes_encrypt.
+// - All other functions and variables were made static
+// - Tabs were converted to 2 spaces
+// - An #include and #if guard was added
+// - S_Table is now stored in PROGMEM
+
+#include "../../lmic/oslmic.h"
+
+#if defined(USE_IDEETRON_AES)
+
+/*
+********************************************************************************************
+* Global Variables
+********************************************************************************************
+*/
+
+static unsigned char State[4][4];
+
+static CONST_TABLE(unsigned char, S_Table)[16][16] = {
+ {0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76},
+ {0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0},
+ {0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15},
+ {0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75},
+ {0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84},
+ {0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF},
+ {0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8},
+ {0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2},
+ {0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73},
+ {0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB},
+ {0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79},
+ {0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08},
+ {0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A},
+ {0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E},
+ {0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF},
+ {0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16}
+};
+
+extern "C" void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key);
+static void AES_Add_Round_Key(unsigned char *Round_Key);
+static unsigned char AES_Sub_Byte(unsigned char Byte);
+static void AES_Shift_Rows();
+static void AES_Mix_Collums();
+static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key);
+static void Send_State();
+
+/*
+*****************************************************************************************
+* Description : Function for encrypting data using AES-128
+*
+* Arguments : *Data Data to encrypt is a 16 byte long arry
+* *Key Key to encrypt data with is a 16 byte long arry
+*****************************************************************************************
+*/
+void lmic_aes_encrypt(unsigned char *Data, unsigned char *Key)
+{
+ unsigned char i;
+ unsigned char Row,Collum;
+ unsigned char Round = 0x00;
+ unsigned char Round_Key[16];
+
+ //Copy input to State arry
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ State[Row][Collum] = Data[Row + (4*Collum)];
+ }
+ }
+
+ //Copy key to round key
+ for(i = 0; i < 16; i++)
+ {
+ Round_Key[i] = Key[i];
+ }
+
+ //Add round key
+ AES_Add_Round_Key(Round_Key);
+
+ //Preform 9 full rounds
+ for(Round = 1; Round < 10; Round++)
+ {
+ //Preform Byte substitution with S table
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]);
+ }
+ }
+
+ //Preform Row Shift
+ AES_Shift_Rows();
+
+ //Mix Collums
+ AES_Mix_Collums();
+
+ //Calculate new round key
+ AES_Calculate_Round_Key(Round,Round_Key);
+
+ //Add round key
+ AES_Add_Round_Key(Round_Key);
+ }
+
+ //Last round whitout mix collums
+ //Preform Byte substitution with S table
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ State[Row][Collum] = AES_Sub_Byte(State[Row][Collum]);
+ }
+ }
+
+ //Shift rows
+ AES_Shift_Rows();
+
+ //Calculate new round key
+ AES_Calculate_Round_Key(Round,Round_Key);
+
+ //Add round Key
+ AES_Add_Round_Key(Round_Key);
+
+ //Copy the State into the data array
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ Data[Row + (4*Collum)] = State[Row][Collum];
+ }
+ }
+
+}
+
+/*
+*****************************************************************************************
+* Description : Function that add's the round key for the current round
+*
+* Arguments : *Round_Key 16 byte long array holding the Round Key
+*****************************************************************************************
+*/
+static void AES_Add_Round_Key(unsigned char *Round_Key)
+{
+ unsigned char Row,Collum;
+
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ State[Row][Collum] = State[Row][Collum] ^ Round_Key[Row + (4*Collum)];
+ }
+ }
+}
+
+/*
+*****************************************************************************************
+* Description : Function that substitutes a byte with a byte from the S_Table
+*
+* Arguments : Byte The byte that will be substituted
+*
+* Return : The return is the found byte in the S_Table
+*****************************************************************************************
+*/
+static unsigned char AES_Sub_Byte(unsigned char Byte)
+{
+ unsigned char S_Row,S_Collum;
+ unsigned char S_Byte;
+
+ //Split byte up in Row and Collum
+ S_Row = ((Byte >> 4) & 0x0F);
+ S_Collum = (Byte & 0x0F);
+
+ //Find the correct byte in the S_Table
+ S_Byte = TABLE_GET_U1_TWODIM(S_Table, S_Row, S_Collum);
+
+ return S_Byte;
+}
+
+/*
+*****************************************************************************************
+* Description : Function that preforms the shift row operation described in the AES standard
+*****************************************************************************************
+*/
+static void AES_Shift_Rows()
+{
+ unsigned char Buffer;
+
+ //Row 0 doesn't change
+
+ //Shift Row 1 one left
+ //Store firt byte in buffer
+ Buffer = State[1][0];
+ //Shift all bytes
+ State[1][0] = State[1][1];
+ State[1][1] = State[1][2];
+ State[1][2] = State[1][3];
+ State[1][3] = Buffer;
+
+ //Shift row 2 two left
+ Buffer = State[2][0];
+ State[2][0] = State[2][2];
+ State[2][2] = Buffer;
+ Buffer = State[2][1];
+ State[2][1] = State[2][3];
+ State[2][3] = Buffer;
+
+ //Shift row 3 three left
+ Buffer = State[3][3];
+ State[3][3] = State[3][2];
+ State[3][2] = State[3][1];
+ State[3][1] = State[3][0];
+ State[3][0] = Buffer;
+}
+
+/*
+*****************************************************************************************
+* Description : Function that preforms the Mix Collums operation described in the AES standard
+*****************************************************************************************
+*/
+static void AES_Mix_Collums()
+{
+ unsigned char Row,Collum;
+ unsigned char a[4], b[4];
+ for(Collum = 0; Collum < 4; Collum++)
+ {
+ for(Row = 0; Row < 4; Row++)
+ {
+ a[Row] = State[Row][Collum];
+ b[Row] = (State[Row][Collum] << 1);
+
+ if((State[Row][Collum] & 0x80) == 0x80)
+ {
+ b[Row] = b[Row] ^ 0x1B;
+ }
+ }
+ State[0][Collum] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3];
+ State[1][Collum] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3];
+ State[2][Collum] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3];
+ State[3][Collum] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3];
+ }
+}
+
+/*
+*****************************************************************************************
+* Description : Function that calculaties the round key for the current round
+*
+* Arguments : Round Number of current Round
+* *Round_Key 16 byte long array holding the Round Key
+*****************************************************************************************
+*/
+static void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key)
+{
+ unsigned char i,j;
+ unsigned char b;
+ unsigned char Temp[4];
+ unsigned char Buffer;
+ unsigned char Rcon;
+
+ //Calculate first Temp
+ //Copy laste byte from previous key
+ for(i = 0; i < 4; i++)
+ {
+ Temp[i] = Round_Key[i+12];
+ }
+
+ //Rotate Temp
+ Buffer = Temp[0];
+ Temp[0] = Temp[1];
+ Temp[1] = Temp[2];
+ Temp[2] = Temp[3];
+ Temp[3] = Buffer;
+
+ //Substitute Temp
+ for(i = 0; i < 4; i++)
+ {
+ Temp[i] = AES_Sub_Byte(Temp[i]);
+ }
+
+ //Calculate Rcon
+ Rcon = 0x01;
+ while(Round != 1)
+ {
+ b = Rcon & 0x80;
+ Rcon = Rcon << 1;
+ if(b == 0x80)
+ {
+ Rcon = Rcon ^ 0x1b;
+ }
+ Round--;
+ }
+
+ //XOR Rcon
+ Temp[0] = Temp[0] ^ Rcon;
+
+ //Calculate new key
+ for(i = 0; i < 4; i++)
+ {
+ for(j = 0; j < 4; j++)
+ {
+ Round_Key[j + (4*i)] = Round_Key[j + (4*i)] ^ Temp[j];
+ Temp[j] = Round_Key[j + (4*i)];
+ }
+ }
+}
+
+#endif // defined(USE_IDEETRON_AES)
diff --git a/lmic/aes.c b/aes/lmic.c
similarity index 99%
rename from lmic/aes.c
rename to aes/lmic.c
index d2009e9..2a5bca3 100644
--- a/lmic/aes.c
+++ b/aes/lmic.c
@@ -25,7 +25,9 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include "oslmic.h"
+#include "../lmic/oslmic.h"
+
+#if defined(USE_ORIGINAL_AES)
#define AES_MICSUB 0x30 // internal use only
@@ -381,3 +383,4 @@ u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
return AESAUX[0];
}
+#endif
diff --git a/aes/other.c b/aes/other.c
new file mode 100644
index 0000000..52febdb
--- /dev/null
+++ b/aes/other.c
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Matthijs Kooijman
+ *
+ * LICENSE
+ *
+ * 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.
+ *
+ * NO WARRANTY OF ANY KIND IS PROVIDED.
+ *******************************************************************************/
+
+/*
+ * The original LMIC AES implementation integrates raw AES encryption
+ * with CMAC and AES-CTR in a single piece of code. Most other AES
+ * implementations (only) offer raw single block AES encryption, so this
+ * file contains an implementation of CMAC and AES-CTR, and offers the
+ * same API through the os_aes() function as the original AES
+ * implementation. This file assumes that there is an encryption
+ * function available with this signature:
+ *
+ * extern "C" void lmic_aes_encrypt(u1_t *data, u1_t *key);
+ *
+ * That takes a single 16-byte buffer and encrypts it wit the given
+ * 16-byte key.
+ */
+
+#include "../lmic/oslmic.h"
+
+#if !defined(USE_ORIGINAL_AES)
+
+// This should be defined elsewhere
+void lmic_aes_encrypt(u1_t *data, u1_t *key);
+
+// global area for passing parameters (aux, key) and for storing round keys
+u4_t AESAUX[16/sizeof(u4_t)];
+u4_t AESKEY[11*16/sizeof(u4_t)];
+
+// Shift the given buffer left one bit
+static void shift_left(xref2u1_t buf, u1_t len) {
+ while (len--) {
+ u1_t next = len ? buf[1] : 0;
+
+ u1_t val = (*buf << 1);
+ if (next & 0x80)
+ val |= 1;
+ *buf++ = val;
+ }
+}
+
+// Apply RFC4493 CMAC, using AESKEY as the key. If prepend_aux is true,
+// AESAUX is prepended to the message. AESAUX is used as working memory
+// in any case. The CMAC result is returned in AESAUX as well.
+static void os_aes_cmac(xref2u1_t buf, u2_t len, u1_t prepend_aux) {
+ if (prepend_aux)
+ lmic_aes_encrypt(AESaux, AESkey);
+ else
+ memset (AESaux, 0, 16);
+
+ while (len > 0) {
+ u1_t need_padding = 0;
+ for (u1_t i = 0; i < 16; ++i, ++buf, --len) {
+ if (len == 0) {
+ // The message is padded with 0x80 and then zeroes.
+ // Since zeroes are no-op for xor, we can just skip them
+ // and leave AESAUX unchanged for them.
+ AESaux[i] ^= 0x80;
+ need_padding = 1;
+ break;
+ }
+ AESaux[i] ^= *buf;
+ }
+
+ if (len == 0) {
+ // Final block, xor with K1 or K2. K1 and K2 are calculated
+ // by encrypting the all-zeroes block and then applying some
+ // shifts and xor on that.
+ u1_t final_key[16];
+ memset(final_key, 0, sizeof(final_key));
+ lmic_aes_encrypt(final_key, AESkey);
+
+ // Calculate K1
+ u1_t msb = final_key[0] & 0x80;
+ shift_left(final_key, sizeof(final_key));
+ if (msb)
+ final_key[sizeof(final_key)-1] ^= 0x87;
+
+ // If the final block was not complete, calculate K2 from K1
+ if (need_padding) {
+ msb = final_key[0] & 0x80;
+ shift_left(final_key, sizeof(final_key));
+ if (msb)
+ final_key[sizeof(final_key)-1] ^= 0x87;
+ }
+
+ // Xor with K1 or K2
+ for (u1_t i = 0; i < sizeof(final_key); ++i)
+ AESaux[i] ^= final_key[i];
+ }
+
+ lmic_aes_encrypt(AESaux, AESkey);
+ }
+}
+
+// Run AES-CTR using the key in AESKEY and using AESAUX as the
+// counter block. The last byte of the counter block will be incremented
+// for every block. The given buffer will be encrypted in place.
+static void os_aes_ctr (xref2u1_t buf, u2_t len) {
+ u1_t ctr[16];
+ while (len) {
+ // Encrypt the counter block with the selected key
+ memcpy(ctr, AESaux, sizeof(ctr));
+ lmic_aes_encrypt(ctr, AESkey);
+
+ // Xor the payload with the resulting ciphertext
+ for (u1_t i = 0; i < 16 && len > 0; i++, len--, buf++)
+ *buf ^= ctr[i];
+
+ // Increment the block index byte
+ AESaux[15]++;
+ }
+}
+
+u4_t os_aes (u1_t mode, xref2u1_t buf, u2_t len) {
+ switch (mode & ~AES_MICNOAUX) {
+ case AES_MIC:
+ os_aes_cmac(buf, len, /* prepend_aux */ !(mode & AES_MICNOAUX));
+ return os_rmsbf4(AESaux);
+
+ case AES_ENC:
+ // TODO: Check / handle when len is not a multiple of 16
+ for (u1_t i = 0; i < len; i += 16)
+ lmic_aes_encrypt(buf+i, AESkey);
+ break;
+
+ case AES_CTR:
+ os_aes_ctr(buf, len);
+ break;
+ }
+ return 0;
+}
+
+#endif // !defined(USE_ORIGINAL_AES)
diff --git a/lmic/lmic.c b/lmic/lmic.c
index 9867cce..752cf36 100644
--- a/lmic/lmic.c
+++ b/lmic/lmic.c
@@ -28,6 +28,10 @@
//! \file
#include "lmic.h"
+#if defined(DISABLE_BEACONS) && !defined(DISABLE_PING)
+#error Ping needs beacon tracking
+#endif
+
#if !defined(MINRX_SYMS)
#define MINRX_SYMS 5
#endif // !defined(MINRX_SYMS)
@@ -39,8 +43,6 @@
#define BCN_INTV_osticks sec2osticks(BCN_INTV_sec)
#define TXRX_GUARD_osticks ms2osticks(TXRX_GUARD_ms)
#define JOIN_GUARD_osticks ms2osticks(JOIN_GUARD_ms)
-#define DELAY_DNW1_osticks sec2osticks(DELAY_DNW1)
-#define DELAY_DNW2_osticks sec2osticks(DELAY_DNW2)
#define DELAY_JACC1_osticks sec2osticks(DELAY_JACC1)
#define DELAY_JACC2_osticks sec2osticks(DELAY_JACC2)
#define DELAY_EXTDNW2_osticks sec2osticks(DELAY_EXTDNW2)
@@ -73,20 +75,20 @@ static void startScan (void);
#if !defined(os_rlsbf2)
u2_t os_rlsbf2 (xref2cu1_t buf) {
- return (u2_t)(buf[0] | (buf[1]<<8));
+ return (u2_t)((u2_t)buf[0] | ((u2_t)buf[1]<<8));
}
#endif
#if !defined(os_rlsbf4)
u4_t os_rlsbf4 (xref2cu1_t buf) {
- return (u4_t)(buf[0] | (buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24));
+ return (u4_t)((u4_t)buf[0] | ((u4_t)buf[1]<<8) | ((u4_t)buf[2]<<16) | ((u4_t)buf[3]<<24));
}
#endif
#if !defined(os_rmsbf4)
u4_t os_rmsbf4 (xref2cu1_t buf) {
- return (u4_t)(buf[3] | (buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
+ return (u4_t)((u4_t)buf[3] | ((u4_t)buf[2]<<8) | ((u4_t)buf[1]<<16) | ((u4_t)buf[0]<<24));
}
#endif
@@ -402,6 +404,7 @@ static CONST_TABLE(ostime_t, DR2HSYM_osticks)[] = {
};
+#if !defined(DISABLE_BEACONS)
static ostime_t calcRxWindow (u1_t secs, dr_t dr) {
ostime_t rxoff, err;
if( secs==0 ) {
@@ -433,8 +436,10 @@ static void calcBcnRxWindowFromMillis (u1_t ms, bit_t ini) {
LMIC.bcnRxsyms = MINRX_SYMS + ms2osticksCeil(ms) / hsym;
LMIC.bcnRxtime = LMIC.bcninfo.txtime + BCN_INTV_osticks - (LMIC.bcnRxsyms-PAMBL_SYMS) * hsym;
}
+#endif // !DISABLE_BEACONS
+#if !defined(DISABLE_PING)
// Setup scheduled RX window (ping/multicast slot)
static void rxschedInit (xref2rxsched_t rxsched) {
os_clearMem(AESkey,16);
@@ -469,6 +474,7 @@ static bit_t rxschedNext (xref2rxsched_t rxsched, ostime_t cando) {
rxsched->rxsyms = LMIC.rxsyms;
goto again;
}
+#endif // !DISABLE_PING)
static ostime_t rndDelay (u1_t secSpan) {
@@ -521,6 +527,7 @@ static void setDrTxpow (u1_t reason, u1_t dr, s1_t pow) {
}
+#if !defined(DISABLE_PING)
void LMIC_stopPingable (void) {
LMIC.opmode &= ~(OP_PINGABLE|OP_PINGINI);
}
@@ -536,21 +543,19 @@ void LMIC_setPingable (u1_t intvExp) {
LMIC_enableTracking(0);
}
+#endif // !DISABLE_PING
#if defined(CFG_eu868)
// ================================================================================
//
// BEG: EU868 related stuff
//
-enum { NUM_DEFAULT_CHANNELS=6 };
-static CONST_TABLE(u4_t, iniChannelFreq)[12] = {
- // Join frequencies and duty cycle limit (0.1%)
- EU868_F1|BAND_MILLI, EU868_J4|BAND_MILLI,
- EU868_F2|BAND_MILLI, EU868_J5|BAND_MILLI,
- EU868_F3|BAND_MILLI, EU868_J6|BAND_MILLI,
+enum { NUM_DEFAULT_CHANNELS=3 };
+static CONST_TABLE(u4_t, iniChannelFreq)[6] = {
+ // Join frequencies and duty cycle limit (1%)
+ EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI,
// Default operational frequencies
EU868_F1|BAND_CENTI, EU868_F2|BAND_CENTI, EU868_F3|BAND_CENTI,
- EU868_F4|BAND_MILLI, EU868_F5|BAND_MILLI, EU868_F6|BAND_DECI
};
static void initDefaultChannels (bit_t join) {
@@ -558,16 +563,12 @@ static void initDefaultChannels (bit_t join) {
os_clearMem(&LMIC.channelDrMap, sizeof(LMIC.channelDrMap));
os_clearMem(&LMIC.bands, sizeof(LMIC.bands));
- LMIC.channelMap = 0x3F;
- u1_t su = join ? 0 : 6;
- for( u1_t fu=0; fu<6; fu++,su++ ) {
+ LMIC.channelMap = 0x07;
+ u1_t su = join ? 0 : 3;
+ for( u1_t fu=0; fu<3; fu++,su++ ) {
LMIC.channelFreq[fu] = TABLE_GET_U4(iniChannelFreq, su);
LMIC.channelDrMap[fu] = DR_RANGE_MAP(DR_SF12,DR_SF7);
}
- if( !join ) {
- LMIC.channelDrMap[5] = DR_RANGE_MAP(DR_SF12,DR_SF7);
- LMIC.channelDrMap[1] = DR_RANGE_MAP(DR_SF12,DR_FSK);
- }
LMIC.bands[BAND_MILLI].txcap = 1000; // 0.1%
LMIC.bands[BAND_MILLI].txpow = 14;
@@ -600,7 +601,7 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
if( band == -1 ) {
if( freq >= 869400000 && freq <= 869650000 )
freq |= BAND_DECI; // 10% 27dBm
- else if( (freq >= 868000000 && freq <= 868600000) ||
+ else if( (freq >= 865000000 && freq <= 868600000) ||
(freq >= 869700000 && freq <= 870000000) )
freq |= BAND_CENTI; // 1% 14dBm
else
@@ -683,19 +684,22 @@ static ostime_t nextTx (ostime_t now) {
}
+#if !defined(DISABLE_BEACONS)
static void setBcnRxParams (void) {
LMIC.dataLen = 0;
LMIC.freq = LMIC.channelFreq[LMIC.bcnChnl] & ~(u4_t)3;
LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
}
+#endif // !DISABLE_BEACONS
#define setRx1Params() /*LMIC.freq/rps remain unchanged*/
+#if !defined(DISABLE_JOIN)
static void initJoinLoop (void) {
#if CFG_TxContinuousMode
LMIC.txChnl = 0;
#else
- LMIC.txChnl = os_getRndU1() % 6;
+ LMIC.txChnl = os_getRndU1() % 3;
#endif
LMIC.adrTxPow = 14;
setDrJoin(DRCHG_SET, DR_SF7);
@@ -710,7 +714,7 @@ static ostime_t nextJoinState (void) {
// Try 869.x and then 864.x with same DR
// If both fail try next lower datarate
- if( ++LMIC.txChnl == 6 )
+ if( ++LMIC.txChnl == 3 )
LMIC.txChnl = 0;
if( (++LMIC.txCnt & 1) == 0 ) {
// Lower DR every 2nd try (having tried 868.x and 864.x with the same DR)
@@ -736,6 +740,7 @@ static ostime_t nextJoinState (void) {
// 1 - triggers EV_JOIN_FAILED event
return failed;
}
+#endif // !DISABLE_JOIN
//
// END: EU868 related stuff
@@ -773,7 +778,36 @@ bit_t LMIC_setupChannel (u1_t chidx, u4_t freq, u2_t drmap, s1_t band) {
void LMIC_disableChannel (u1_t channel) {
if( channel < 72+MAX_XCHANNELS )
- LMIC.channelMap[channel/4] &= ~(1<<(channel&0xF));
+ LMIC.channelMap[channel>>4] &= ~(1<<(channel&0xF));
+}
+
+void LMIC_enableChannel (u1_t channel) {
+ if( channel < 72+MAX_XCHANNELS )
+ LMIC.channelMap[channel>>4] |= (1<<(channel&0xF));
+}
+
+void LMIC_enableSubBand (u1_t band) {
+ ASSERT(band < 8);
+ u1_t start = band * 8;
+ u1_t end = start + 8;
+ for (int channel=start; channel < end; ++channel )
+ LMIC_enableChannel(channel);
+}
+void LMIC_disableSubBand (u1_t band) {
+ ASSERT(band < 8);
+ u1_t start = band * 8;
+ u1_t end = start + 8;
+ for (int channel=start; channel < end; ++channel )
+ LMIC_disableChannel(channel);
+}
+void LMIC_selectSubBand (u1_t band) {
+ ASSERT(band < 8);
+ for (int b=0; b<8; ++b) {
+ if (band==b)
+ LMIC_enableSubBand(b);
+ else
+ LMIC_disableSubBand(b);
+ }
}
static u1_t mapChannels (u1_t chpage, u2_t chmap) {
@@ -837,11 +871,13 @@ static void _nextTx (void) {
// No feasible channel found! Keep old one.
}
+#if !defined(DISABLE_BEACONS)
static void setBcnRxParams (void) {
LMIC.dataLen = 0;
LMIC.freq = US915_500kHz_DNFBASE + LMIC.bcnChnl * US915_500kHz_DNFSTEP;
LMIC.rps = setIh(setNocrc(dndr2rps((dr_t)DR_BCN),1),LEN_BCN);
}
+#endif // !DISABLE_BEACONS
#define setRx1Params() { \
LMIC.freq = US915_500kHz_DNFBASE + (LMIC.txChnl & 0x7) * US915_500kHz_DNFSTEP; \
@@ -852,6 +888,7 @@ static void setBcnRxParams (void) {
LMIC.rps = dndr2rps(LMIC.dndr); \
}
+#if !defined(DISABLE_JOIN)
static void initJoinLoop (void) {
LMIC.chRnd = 0;
LMIC.txChnl = 0;
@@ -890,6 +927,7 @@ static ostime_t nextJoinState (void) {
// 1 - triggers EV_JOIN_FAILED event
return failed;
}
+#endif // !DISABLE_JOIN
//
// END: US915 related stuff
@@ -917,7 +955,9 @@ static void reportEvent (ev_t ev) {
static void runReset (xref2osjob_t osjob) {
// Disable session
LMIC_reset();
+#if !defined(DISABLE_JOIN)
LMIC_startJoining();
+#endif // !DISABLE_JOIN
reportEvent(EV_RESET);
}
@@ -925,15 +965,30 @@ static void stateJustJoined (void) {
LMIC.seqnoDn = LMIC.seqnoUp = 0;
LMIC.rejoinCnt = 0;
LMIC.dnConf = LMIC.adrChanged = LMIC.ladrAns = LMIC.devsAns = 0;
- LMIC.moreData = LMIC.dn2Ans = LMIC.snchAns = LMIC.dutyCapAns = 0;
+#if !defined(DISABLE_MCMD_SNCH_REQ)
+ LMIC.snchAns = 0;
+#endif
+#if !defined(DISABLE_MCMD_DN2P_SET)
+ LMIC.dn2Ans = 0;
+#endif
+ LMIC.moreData = 0;
+#if !defined(DISABLE_MCMD_DCAP_REQ)
+ LMIC.dutyCapAns = 0;
+#endif
+#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING)
LMIC.pingSetAns = 0;
+#endif
LMIC.upRepeat = 0;
LMIC.adrAckReq = LINK_CHECK_INIT;
LMIC.dn2Dr = DR_DNW2;
LMIC.dn2Freq = FREQ_DNW2;
+#if !defined(DISABLE_BEACONS)
LMIC.bcnChnl = CHNL_BCN;
+#endif
+#if !defined(DISABLE_PING)
LMIC.ping.freq = FREQ_PING;
LMIC.ping.dr = DR_PING;
+#endif
}
@@ -941,6 +996,7 @@ static void stateJustJoined (void) {
// Decoding frames
+#if !defined(DISABLE_BEACONS)
// Decode beacon - do not overwrite bcninfo unless we have a match!
static int decodeBeacon (void) {
ASSERT(LMIC.dataLen == LEN_BCN); // implicit header RX guarantees this
@@ -976,6 +1032,7 @@ static int decodeBeacon (void) {
LMIC.bcninfo.flags |= BCN_FULL;
return 2;
}
+#endif // !DISABLE_BEACONS
static bit_t decodeFrame (void) {
@@ -983,6 +1040,7 @@ static bit_t decodeFrame (void) {
u1_t hdr = d[0];
u1_t ftype = hdr & HDR_FTYPE;
int dlen = LMIC.dataLen;
+ const char *window = (LMIC.txrxFlags & TXRX_DNW1) ? "RX1" : ((LMIC.txrxFlags & TXRX_DNW2) ? "RX2" : "Other");
if( dlen < OFF_DAT_OPTS+4 ||
(hdr & HDR_MAJOR) != HDR_MAJOR_V1 ||
(ftype != HDR_FTYPE_DADN && ftype != HDR_FTYPE_DCDN) ) {
@@ -992,6 +1050,9 @@ static bit_t decodeFrame (void) {
e_.info = dlen < 4 ? 0 : os_rlsbf4(&d[dlen-4]),
e_.info2 = hdr + (dlen<<8)));
norx:
+#if LMIC_DEBUG_LEVEL > 0
+ printf("%lu: Invalid downlink, window=%s\n", os_getTime(), window);
+#endif
LMIC.dataLen = 0;
return 0;
}
@@ -1086,6 +1147,10 @@ static bit_t decodeFrame (void) {
case MCMD_LCHK_ANS: {
//int gwmargin = opts[oidx+1];
//int ngws = opts[oidx+2];
+ LMIC.LinkCheckMargin = opts[oidx+1];
+ LMIC.LinkCheckGwCnt = opts[oidx+2];
+ LMIC.LinkCheckReq = 0;
+ LMIC.LinkCheckAns = 1;
oidx += 3;
continue;
}
@@ -1122,9 +1187,9 @@ static bit_t decodeFrame (void) {
continue;
}
case MCMD_DN2P_SET: {
+#if !defined(DISABLE_MCMD_DN2P_SET)
dr_t dr = (dr_t)(opts[oidx+1] & 0x0F);
u4_t freq = convFreq(&opts[oidx+2]);
- oidx += 5;
LMIC.dn2Ans = 0x80; // answer pending
if( validDR(dr) )
LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK;
@@ -1136,11 +1201,13 @@ static bit_t decodeFrame (void) {
DO_DEVDB(LMIC.dn2Dr,dn2Dr);
DO_DEVDB(LMIC.dn2Freq,dn2Freq);
}
+#endif // !DISABLE_MCMD_DN2P_SET
+ oidx += 5;
continue;
}
case MCMD_DCAP_REQ: {
+#if !defined(DISABLE_MCMD_DCAP_REQ)
u1_t cap = opts[oidx+1];
- oidx += 2;
// A value cap=0xFF means device is OFF unless enabled again manually.
if( cap==0xFF )
LMIC.opmode |= OP_SHUTDOWN; // stop any sending
@@ -1148,21 +1215,25 @@ static bit_t decodeFrame (void) {
LMIC.globalDutyAvail = os_getTime();
DO_DEVDB(cap,dutyCap);
LMIC.dutyCapAns = 1;
+#endif // !DISABLE_MCMD_DCAP_REQ
+ oidx += 2;
continue;
}
case MCMD_SNCH_REQ: {
+#if !defined(DISABLE_MCMD_SNCH_REQ)
u1_t chidx = opts[oidx+1]; // channel
u4_t freq = convFreq(&opts[oidx+2]); // freq
u1_t drs = opts[oidx+5]; // datarate span
LMIC.snchAns = 0x80;
if( freq != 0 && LMIC_setupChannel(chidx, freq, DR_RANGE_MAP(drs&0xF,drs>>4), -1) )
LMIC.snchAns |= MCMD_SNCH_ANS_DRACK|MCMD_SNCH_ANS_FQACK;
+#endif // !DISABLE_MCMD_SNCH_REQ
oidx += 6;
continue;
}
case MCMD_PING_SET: {
+#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING)
u4_t freq = convFreq(&opts[oidx+1]);
- oidx += 4;
u1_t flags = 0x80;
if( freq != 0 ) {
flags |= MCMD_PING_ANS_FQACK;
@@ -1172,9 +1243,12 @@ static bit_t decodeFrame (void) {
DO_DEVDB(LMIC.ping.dr, pingDr);
}
LMIC.pingSetAns = flags;
+#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING
+ oidx += 4;
continue;
}
case MCMD_BCNI_ANS: {
+#if !defined(DISABLE_MCMD_BCNI_ANS) && !defined(DISABLE_BEACONS)
// Ignore if tracking already enabled
if( (LMIC.opmode & OP_TRACK) == 0 ) {
LMIC.bcnChnl = opts[oidx+3];
@@ -1198,6 +1272,7 @@ static bit_t decodeFrame (void) {
- LMIC.bcnRxtime) << 8)),
e_.time = MAIN::CDEV->ostime2ustime(LMIC.bcninfo.txtime + BCN_INTV_osticks)));
}
+#endif // !DISABLE_MCMD_BCNI_ANS && !DISABLE_BEACONS
oidx += 4;
continue;
}
@@ -1262,6 +1337,9 @@ static bit_t decodeFrame (void) {
LMIC.dataBeg = poff;
LMIC.dataLen = pend-poff;
}
+#if LMIC_DEBUG_LEVEL > 0
+ printf("%lu: Received downlink, window=%s, port=%d, ack=%d\n", os_getTime(), window, port, ackup);
+#endif
return 1;
}
@@ -1279,9 +1357,36 @@ static void setupRx2 (void) {
}
-static void schedRx2 (ostime_t delay, osjobcb_t func) {
- // Add 1.5 symbols we need 5 out of 8. Try to sync 1.5 symbols into the preamble.
- LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dn2Dr);
+static void schedRx12 (ostime_t delay, osjobcb_t func, u1_t dr) {
+ ostime_t hsym = dr2hsym(dr);
+
+ LMIC.rxsyms = MINRX_SYMS;
+
+ // If a clock error is specified, compensate for it by extending the
+ // receive window
+ if (LMIC.clockError != 0) {
+ // Calculate how much the clock will drift maximally after delay has
+ // passed. This indicates the amount of time we can be early
+ // _or_ late.
+ ostime_t drift = (int64_t)delay * LMIC.clockError / MAX_CLOCK_ERROR;
+
+ // Increase the receive window by twice the maximum drift (to
+ // compensate for a slow or a fast clock).
+ // decrease the rxtime to compensate for. Note that hsym is a
+ // *half* symbol time, so the factor 2 is hidden. First check if
+ // this would overflow (which can happen if the drift is very
+ // high, or the symbol time is low at high datarates).
+ if ((255 - LMIC.rxsyms) * hsym < drift)
+ LMIC.rxsyms = 255;
+ else
+ LMIC.rxsyms += drift / hsym;
+
+ }
+
+ // Center the receive window on the center of the expected preamble
+ // (again note that hsym is half a sumbol time, so no /2 needed)
+ LMIC.rxtime = LMIC.txend + delay + PAMBL_SYMS * hsym - LMIC.rxsyms * hsym;
+
os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
}
@@ -1297,10 +1402,13 @@ static void setupRx1 (osjobcb_t func) {
// Called by HAL once TX complete and delivers exact end of TX time stamp in LMIC.rxtime
static void txDone (ostime_t delay, osjobcb_t func) {
+#if !defined(DISABLE_PING)
if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE|OP_PINGINI)) == (OP_TRACK|OP_PINGABLE) ) {
rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer!
LMIC.opmode |= OP_PINGINI;
}
+#endif // !DISABLE_PING
+
// Change RX frequency / rps (US only) before we increment txChnl
setRx1Params();
// LMIC.rxsyms carries the TX datarate (can be != LMIC.datarate [confirm retries etc.])
@@ -1310,20 +1418,20 @@ static void txDone (ostime_t delay, osjobcb_t func) {
if( /* TX datarate */LMIC.rxsyms == DR_FSK ) {
LMIC.rxtime = LMIC.txend + delay - PRERX_FSK*us2osticksRound(160);
LMIC.rxsyms = RXLEN_FSK;
+ os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
}
else
#endif
{
- LMIC.rxtime = LMIC.txend + delay + (PAMBL_SYMS-MINRX_SYMS)*dr2hsym(LMIC.dndr);
- LMIC.rxsyms = MINRX_SYMS;
+ schedRx12(delay, func, LMIC.dndr);
}
- os_setTimedCallback(&LMIC.osjob, LMIC.rxtime - RX_RAMPUP, func);
}
// ======================================== Join frames
+#if !defined(DISABLE_JOIN)
static void onJoinFailed (xref2osjob_t osjob) {
// Notify app - must call LMIC_reset() to stop joining
// otherwise join procedure continues.
@@ -1396,8 +1504,12 @@ static bit_t processJoinAccept (void) {
dlen = OFF_CFLIST;
for( u1_t chidx=3; chidx<8; chidx++, dlen+=3 ) {
u4_t freq = convFreq(&LMIC.frame[dlen]);
- if( freq )
+ if( freq ) {
LMIC_setupChannel(chidx, freq, 0, -1);
+#if LMIC_DEBUG_LEVEL > 1
+ printf("%lu: Setup channel, idx=%d, freq=%lu\n", os_getTime(), chidx, (unsigned long)freq);
+#endif
+ }
}
}
@@ -1424,7 +1536,11 @@ static bit_t processJoinAccept (void) {
LMIC.datarate = lowerDR(LMIC.datarate, LMIC.rejoinCnt);
}
LMIC.opmode &= ~(OP_JOINING|OP_TRACK|OP_REJOIN|OP_TXRXPEND|OP_PINGINI) | OP_NEXTCHNL;
+ LMIC.txCnt = 0;
stateJustJoined();
+ LMIC.dn2Dr = LMIC.frame[OFF_JA_DLSET] & 0x0F;
+ LMIC.rxDelay = LMIC.frame[OFF_JA_RXDLY];
+ if (LMIC.rxDelay == 0) LMIC.rxDelay = 1;
reportEvent(EV_JOINED);
return 1;
}
@@ -1445,7 +1561,7 @@ static void setupRx2Jacc (xref2osjob_t osjob) {
static void processRx1Jacc (xref2osjob_t osjob) {
if( LMIC.dataLen == 0 || !processJoinAccept() )
- schedRx2(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc));
+ schedRx12(DELAY_JACC2_osticks, FUNC_ADDR(setupRx2Jacc), LMIC.dn2Dr);
}
@@ -1458,6 +1574,8 @@ static void jreqDone (xref2osjob_t osjob) {
txDone(DELAY_JACC1_osticks, FUNC_ADDR(setupRx1Jacc));
}
+#endif // !DISABLE_JOIN
+
// ======================================== Data frames
// Fwd decl.
@@ -1489,7 +1607,7 @@ static void setupRx2DnData (xref2osjob_t osjob) {
static void processRx1DnData (xref2osjob_t osjob) {
if( LMIC.dataLen == 0 || !processDnData() )
- schedRx2(DELAY_DNW2_osticks, FUNC_ADDR(setupRx2DnData));
+ schedRx12(sec2osticks(LMIC.rxDelay +(int)DELAY_EXTDNW2), FUNC_ADDR(setupRx2DnData), LMIC.dn2Dr);
}
@@ -1499,7 +1617,7 @@ static void setupRx1DnData (xref2osjob_t osjob) {
static void updataDone (xref2osjob_t osjob) {
- txDone(DELAY_DNW1_osticks, FUNC_ADDR(setupRx1DnData));
+ txDone(sec2osticks(LMIC.rxDelay), FUNC_ADDR(setupRx1DnData));
}
// ========================================
@@ -1512,27 +1630,33 @@ static void buildDataFrame (void) {
// Piggyback MAC options
// Prioritize by importance
int end = OFF_DAT_OPTS;
+#if !defined(DISABLE_PING)
if( (LMIC.opmode & (OP_TRACK|OP_PINGABLE)) == (OP_TRACK|OP_PINGABLE) ) {
// Indicate pingability in every UP frame
LMIC.frame[end] = MCMD_PING_IND;
LMIC.frame[end+1] = LMIC.ping.dr | (LMIC.ping.intvExp<<4);
end += 2;
}
+#endif // !DISABLE_PING
+#if !defined(DISABLE_MCMD_DCAP_REQ)
if( LMIC.dutyCapAns ) {
LMIC.frame[end] = MCMD_DCAP_ANS;
end += 1;
LMIC.dutyCapAns = 0;
}
+#endif // !DISABLE_MCMD_DCAP_REQ
+#if !defined(DISABLE_MCMD_DN2P_SET)
if( LMIC.dn2Ans ) {
LMIC.frame[end+0] = MCMD_DN2P_ANS;
LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU;
end += 2;
LMIC.dn2Ans = 0;
}
+#endif // !DISABLE_MCMD_DN2P_SET
if( LMIC.devsAns ) { // answer to device status
LMIC.frame[end+0] = MCMD_DEVS_ANS;
- LMIC.frame[end+1] = LMIC.margin;
- LMIC.frame[end+2] = os_getBattLevel();
+ LMIC.frame[end+1] = os_getBattLevel();
+ LMIC.frame[end+2] = LMIC.margin;
end += 3;
LMIC.devsAns = 0;
}
@@ -1542,27 +1666,38 @@ static void buildDataFrame (void) {
end += 2;
LMIC.ladrAns = 0;
}
+ if( LMIC.LinkCheckReq ) { //Link Check Request
+ LMIC.frame[end+0] = MCMD_LCHK_REQ;
+ end += 1;
+ LMIC.LinkCheckAns = 0;
+ }
+#if !defined(DISABLE_BEACONS)
if( LMIC.bcninfoTries > 0 ) {
LMIC.frame[end] = MCMD_BCNI_REQ;
end += 1;
}
+#endif // !DISABLE_BEACONS
if( LMIC.adrChanged ) {
if( LMIC.adrAckReq < 0 )
LMIC.adrAckReq = 0;
LMIC.adrChanged = 0;
}
+#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING)
if( LMIC.pingSetAns != 0 ) {
LMIC.frame[end+0] = MCMD_PING_ANS;
LMIC.frame[end+1] = LMIC.pingSetAns & ~MCMD_PING_ANS_RFU;
end += 2;
LMIC.pingSetAns = 0;
}
+#endif // !DISABLE_MCMD_PING_SET && !DISABLE_PING
+#if !defined(DISABLE_MCMD_SNCH_REQ)
if( LMIC.snchAns ) {
LMIC.frame[end+0] = MCMD_SNCH_ANS;
LMIC.frame[end+1] = LMIC.snchAns & ~MCMD_SNCH_ANS_RFU;
end += 2;
LMIC.snchAns = 0;
}
+#endif // !DISABLE_MCMD_SNCH_REQ
ASSERT(end <= OFF_DAT_OPTS+16);
u1_t flen = end + (txdata ? 5+dlen : 4);
@@ -1622,6 +1757,7 @@ static void buildDataFrame (void) {
}
+#if !defined(DISABLE_BEACONS)
// Callback from HAL during scan mode or when job timer expires.
static void onBcnRx (xref2osjob_t job) {
// If we arrive via job timer make sure to put radio to rest.
@@ -1685,6 +1821,7 @@ void LMIC_disableTracking (void) {
LMIC.bcninfoTries = 0;
engineUpdate();
}
+#endif // !DISABLE_BEACONS
@@ -1718,6 +1855,7 @@ void LMIC_disableTracking (void) {
//
// ================================================================================
+#if !defined(DISABLE_JOIN)
static void buildJoinRequest (u1_t ftype) {
// Do not use pendTxData since we might have a pending
// user level frame in there. Use RX holding area instead.
@@ -1755,7 +1893,7 @@ bit_t LMIC_startJoining (void) {
// Cancel scanning
LMIC.opmode &= ~(OP_SCAN|OP_REJOIN|OP_LINKDEAD|OP_NEXTCHNL);
// Setup state
- LMIC.rejoinCnt = LMIC.txCnt = LMIC.pendTxConf = 0;
+ LMIC.rejoinCnt = LMIC.txCnt = 0;
initJoinLoop();
LMIC.opmode |= OP_JOINING;
// reportEvent will call engineUpdate which then starts sending JOIN REQUESTS
@@ -1764,6 +1902,7 @@ bit_t LMIC_startJoining (void) {
}
return 0; // already joined
}
+#endif // !DISABLE_JOIN
// ================================================================================
@@ -1772,6 +1911,7 @@ bit_t LMIC_startJoining (void) {
//
// ================================================================================
+#if !defined(DISABLE_PING)
static void processPingRx (xref2osjob_t osjob) {
if( LMIC.dataLen != 0 ) {
LMIC.txrxFlags = TXRX_PING;
@@ -1783,6 +1923,7 @@ static void processPingRx (xref2osjob_t osjob) {
// Pick next ping slot
engineUpdate();
}
+#endif // !DISABLE_PING
static bit_t processDnData (void) {
@@ -1828,6 +1969,7 @@ static bit_t processDnData (void) {
LMIC.opmode |= OP_REJOIN|OP_LINKDEAD;
reportEvent(EV_LINK_DEAD);
}
+#if !defined(DISABLE_BEACONS)
// If this falls to zero the NWK did not answer our MCMD_BCNI_REQ commands - try full scan
if( LMIC.bcninfoTries > 0 ) {
if( (LMIC.opmode & OP_TRACK) != 0 ) {
@@ -1838,6 +1980,7 @@ static bit_t processDnData (void) {
startScan(); // NWK did not answer - try scan
}
}
+#endif // !DISABLE_BEACONS
return 1;
}
if( !decodeFrame() ) {
@@ -1849,6 +1992,7 @@ static bit_t processDnData (void) {
}
+#if !defined(DISABLE_BEACONS)
static void processBeacon (xref2osjob_t osjob) {
ostime_t lasttx = LMIC.bcninfo.txtime; // save here - decodeBeacon might overwrite
u1_t flags = LMIC.bcninfo.flags;
@@ -1904,8 +2048,10 @@ static void processBeacon (xref2osjob_t osjob) {
#if CFG_us915
LMIC.bcnChnl = (LMIC.bcnChnl+1) & 7;
#endif
+#if !defined(DISABLE_PING)
if( (LMIC.opmode & OP_PINGINI) != 0 )
rxschedInit(&LMIC.ping); // note: reuses LMIC.frame buffer!
+#endif // !DISABLE_PING
reportEvent(ev);
}
@@ -1914,34 +2060,44 @@ static void startRxBcn (xref2osjob_t osjob) {
LMIC.osjob.func = FUNC_ADDR(processBeacon);
os_radio(RADIO_RX);
}
+#endif // !DISABLE_BEACONS
+#if !defined(DISABLE_PING)
static void startRxPing (xref2osjob_t osjob) {
LMIC.osjob.func = FUNC_ADDR(processPingRx);
os_radio(RADIO_RX);
}
+#endif // !DISABLE_PING
// Decide what to do next for the MAC layer of a device
static void engineUpdate (void) {
+#if LMIC_DEBUG_LEVEL > 0
+ printf("%lu: engineUpdate, opmode=0x%x\n", os_getTime(), LMIC.opmode);
+#endif
// Check for ongoing state: scan or TX/RX transaction
if( (LMIC.opmode & (OP_SCAN|OP_TXRXPEND|OP_SHUTDOWN)) != 0 )
return;
+#if !defined(DISABLE_JOIN)
if( LMIC.devaddr == 0 && (LMIC.opmode & OP_JOINING) == 0 ) {
LMIC_startJoining();
return;
}
+#endif // !DISABLE_JOIN
ostime_t now = os_getTime();
ostime_t rxtime = 0;
ostime_t txbeg = 0;
+#if !defined(DISABLE_BEACONS)
if( (LMIC.opmode & OP_TRACK) != 0 ) {
// We are tracking a beacon
ASSERT( now + RX_RAMPUP - LMIC.bcnRxtime <= 0 );
rxtime = LMIC.bcnRxtime - RX_RAMPUP;
}
+#endif // !DISABLE_BEACONS
if( (LMIC.opmode & (OP_JOINING|OP_REJOIN|OP_TXDATA|OP_POLL)) != 0 ) {
// Need to TX some data...
@@ -1957,6 +2113,7 @@ static void engineUpdate (void) {
// Delayed TX or waiting for duty cycle?
if( (LMIC.globalDutyRate != 0 || (LMIC.opmode & OP_RNDTX) != 0) && (txbeg - LMIC.globalDutyAvail) < 0 )
txbeg = LMIC.globalDutyAvail;
+#if !defined(DISABLE_BEACONS)
// If we're tracking a beacon...
// then make sure TX-RX transaction is complete before beacon
if( (LMIC.opmode & OP_TRACK) != 0 &&
@@ -1967,11 +2124,13 @@ static void engineUpdate (void) {
txbeg = 0;
goto checkrx;
}
+#endif // !DISABLE_BEACONS
// Earliest possible time vs overhead to setup radio
if( txbeg - (now + TX_RAMPUP) < 0 ) {
// We could send right now!
txbeg = now;
dr_t txdr = (dr_t)LMIC.datarate;
+#if !defined(DISABLE_JOIN)
if( jacc ) {
u1_t ftype;
if( (LMIC.opmode & OP_REJOIN) != 0 ) {
@@ -1982,7 +2141,9 @@ static void engineUpdate (void) {
}
buildJoinRequest(ftype);
LMIC.osjob.func = FUNC_ADDR(jreqDone);
- } else {
+ } else
+#endif // !DISABLE_JOIN
+ {
if( LMIC.seqnoDn >= 0xFFFFFF80 ) {
// Imminent roll over - proactively reset MAC
EV(specCond, INFO, (e_.reason = EV::specCond_t::DNSEQNO_ROLL_OVER,
@@ -2027,8 +2188,10 @@ static void engineUpdate (void) {
return;
}
+#if !defined(DISABLE_BEACONS)
// Are we pingable?
checkrx:
+#if !defined(DISABLE_PING)
if( (LMIC.opmode & OP_PINGINI) != 0 ) {
// One more RX slot in this beacon period?
if( rxschedNext(&LMIC.ping, now+RX_RAMPUP) ) {
@@ -2045,6 +2208,7 @@ static void engineUpdate (void) {
}
// no - just wait for the beacon
}
+#endif // !DISABLE_PING
if( txbeg != 0 && (txbeg - rxtime) < 0 )
goto txdelay;
@@ -2059,6 +2223,7 @@ static void engineUpdate (void) {
}
os_setTimedCallback(&LMIC.osjob, rxtime, FUNC_ADDR(startRxBcn));
return;
+#endif // !DISABLE_BEACONS
txdelay:
EV(devCond, INFO, (e_.reason = EV::devCond_t::TX_DELAY,
@@ -2102,9 +2267,12 @@ void LMIC_reset (void) {
LMIC.adrEnabled = FCT_ADREN;
LMIC.dn2Dr = DR_DNW2; // we need this for 2nd DN window of join accept
LMIC.dn2Freq = FREQ_DNW2; // ditto
+ LMIC.rxDelay = DELAY_DNW1;
+#if !defined(DISABLE_PING)
LMIC.ping.freq = FREQ_PING; // defaults for ping
LMIC.ping.dr = DR_PING; // ditto
LMIC.ping.intvExp = 0xFF;
+#endif // !DISABLE_PING
#if defined(CFG_us915)
initDefaultChannels();
#endif
@@ -2112,9 +2280,11 @@ void LMIC_reset (void) {
DO_DEVDB(LMIC.devNonce, devNonce);
DO_DEVDB(LMIC.dn2Dr, dn2Dr);
DO_DEVDB(LMIC.dn2Freq, dn2Freq);
+#if !defined(DISABLE_PING)
DO_DEVDB(LMIC.ping.freq, pingFreq);
DO_DEVDB(LMIC.ping.dr, pingDr);
DO_DEVDB(LMIC.ping.intvExp, pingIntvExp);
+#endif // !DISABLE_PING
}
@@ -2219,4 +2389,9 @@ void LMIC_setLinkCheckMode (bit_t enabled) {
LMIC.adrAckReq = enabled ? LINK_CHECK_INIT : LINK_CHECK_OFF;
}
-
+// Sets the max clock error to compensate for (defaults to 0, which
+// allows for +/- 640 at SF7BW250). MAX_CLOCK_ERROR represents +/-100%,
+// so e.g. for a +/-1% error you would pass MAX_CLOCK_ERROR * 1 / 100.
+void LMIC_setClockError(u2_t error) {
+ LMIC.clockError = error;
+}
diff --git a/lmic/lmic.h b/lmic/lmic.h
index 75757ad..87d7a44 100644
--- a/lmic/lmic.h
+++ b/lmic/lmic.h
@@ -89,6 +89,7 @@ enum { DRCHG_SET, DRCHG_NOJACC, DRCHG_NOACK, DRCHG_NOADRACK, DRCHG_NWKCMD };
enum { KEEP_TXPOW = -128 };
+#if !defined(DISABLE_PING)
//! \internal
struct rxsched_t {
u1_t dr;
@@ -100,8 +101,10 @@ struct rxsched_t {
u4_t freq;
};
TYPEDEF_xref2rxsched_t; //!< \internal
+#endif // !DISABLE_PING
+#if !defined(DISABLE_BEACONS)
//! Parsing and tracking states of beacons.
enum { BCN_NONE = 0x00, //!< No beacon received
BCN_PARTIAL = 0x01, //!< Only first (common) part could be decoded (info,lat,lon invalid/previous)
@@ -120,6 +123,7 @@ struct bcninfo_t {
s4_t lat; //!< Lat field of last beacon (valid only if BCN_FULL set)
s4_t lon; //!< Lon field of last beacon (valid only if BCN_FULL set)
};
+#endif // !DISABLE_BEACONS
// purpose of receive window - lmic_t.rxState
enum { RADIO_RST=0, RADIO_TX=1, RADIO_RX=2, RADIO_RXON=3 };
@@ -159,6 +163,10 @@ enum _ev_t { EV_SCAN_TIMEOUT=1, EV_BEACON_FOUND,
EV_TXSTART };
typedef enum _ev_t ev_t;
+enum {
+ // This value represents 100% error in LMIC.clockError
+ MAX_CLOCK_ERROR = 65536,
+};
struct lmic_t {
// Radio settings TX/RX (also accessed by HAL)
@@ -197,9 +205,14 @@ struct lmic_t {
u1_t datarate; // current data rate
u1_t errcr; // error coding rate (used for TX only)
u1_t rejoinCnt; // adjustment for rejoin datarate
+#if !defined(DISABLE_BEACONS)
s2_t drift; // last measured drift
s2_t lastDriftDiff;
s2_t maxDriftDiff;
+#endif
+
+ u2_t clockError; // Inaccuracy in the clock. CLOCK_ERROR_MAX
+ // represents +/-100% error
u1_t pendTxPort;
u1_t pendTxConf; // confirmed data
@@ -217,23 +230,41 @@ struct lmic_t {
s1_t adrAckReq; // counter until we reset data rate (0=off)
u1_t adrChanged;
+ u1_t rxDelay; // Rx delay after TX
+
u1_t margin;
bit_t ladrAns; // link adr adapt answer pending
bit_t devsAns; // device status answer pending
+ u1_t LinkCheckReq; // send LinkCheckReq command
+ u1_t LinkCheckAns; // LinkCheckAns received
+ u1_t LinkCheckMargin; // link margin in dB of the last successfully received LinkCheckReq
+ u1_t LinkCheckGwCnt; // number of gateways that successfully received the last LinkCheckReq
u1_t adrEnabled;
u1_t moreData; // NWK has more data pending
+#if !defined(DISABLE_MCMD_DCAP_REQ)
bit_t dutyCapAns; // have to ACK duty cycle settings
+#endif
+#if !defined(DISABLE_MCMD_SNCH_REQ)
u1_t snchAns; // answer set new channel
+#endif
// 2nd RX window (after up stream)
u1_t dn2Dr;
u4_t dn2Freq;
+#if !defined(DISABLE_MCMD_DN2P_SET)
u1_t dn2Ans; // 0=no answer pend, 0x80+ACKs
+#endif
// Class B state
+#if !defined(DISABLE_BEACONS)
u1_t missedBcns; // unable to track last N beacons
u1_t bcninfoTries; // how often to try (scan mode only)
+#endif
+#if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING)
u1_t pingSetAns; // answer set cmd and ACK bits
+#endif
+#if !defined(DISABLE_PING)
rxsched_t ping; // pingable setup
+#endif
// Public part of MAC state
u1_t txCnt;
@@ -242,10 +273,12 @@ struct lmic_t {
u1_t dataLen; // 0 no data or zero length data, >0 byte count of data
u1_t frame[MAX_LEN_FRAME];
+#if !defined(DISABLE_BEACONS)
u1_t bcnChnl;
u1_t bcnRxsyms; //
ostime_t bcnRxtime;
bcninfo_t bcninfo; // Last received beacon info
+#endif
u1_t noRXIQinversion;
};
@@ -261,10 +294,18 @@ bit_t LMIC_setupBand (u1_t bandidx, s1_t txpow, u2_t txcap);
#endif
bit_t LMIC_setupChannel (u1_t channel, u4_t freq, u2_t drmap, s1_t band);
void LMIC_disableChannel (u1_t channel);
+#if defined(CFG_us915)
+void LMIC_enableChannel (u1_t channel);
+void LMIC_enableSubBand (u1_t band);
+void LMIC_disableSubBand (u1_t band);
+void LMIC_selectSubBand (u1_t band);
+#endif
void LMIC_setDrTxpow (dr_t dr, s1_t txpow); // set default/start DR/txpow
void LMIC_setAdrMode (bit_t enabled); // set ADR mode (if mobile turn off)
+#if !defined(DISABLE_JOIN)
bit_t LMIC_startJoining (void);
+#endif
void LMIC_shutdown (void);
void LMIC_init (void);
@@ -274,15 +315,22 @@ void LMIC_setTxData (void);
int LMIC_setTxData2 (u1_t port, xref2u1_t data, u1_t dlen, u1_t confirmed);
void LMIC_sendAlive (void);
+#if !defined(DISABLE_BEACONS)
bit_t LMIC_enableTracking (u1_t tryBcnInfo);
void LMIC_disableTracking (void);
+#endif
+#if !defined(DISABLE_PING)
void LMIC_stopPingable (void);
void LMIC_setPingable (u1_t intvExp);
+#endif
+#if !defined(DISABLE_JOIN)
void LMIC_tryRejoin (void);
+#endif
void LMIC_setSession (u4_t netid, devaddr_t devaddr, xref2u1_t nwkKey, xref2u1_t artKey);
void LMIC_setLinkCheckMode (bit_t enabled);
+void LMIC_setClockError(u2_t error);
// Declare onEvent() function, to make sure any definition will have the
// C conventions, even when in a C++ file.
diff --git a/lmic/oslmic.h b/lmic/oslmic.h
index 1cd9e57..f4c5b80 100644
--- a/lmic/oslmic.h
+++ b/lmic/oslmic.h
@@ -34,7 +34,14 @@
// You should not, however, change the lmic.[hc]
#include
+#ifdef ARDUINO
+// When using the makefile, the target directory is put into the include
+// path, so we can include target-config.h directly. When using Arduino,
+// this is not the case, so we have to specify the path explicitly.
+#include "../hal/target-config.h"
+#else
#include
+#endif
#ifdef __cplusplus
extern "C"{
@@ -50,8 +57,6 @@ typedef uint16_t u2_t;
typedef int16_t s2_t;
typedef uint32_t u4_t;
typedef int32_t s4_t;
-typedef uint64_t u8_t;
-typedef int64_t s8_t;
typedef unsigned int uint;
typedef const char* str_t;
@@ -121,16 +126,16 @@ void os_runloop_once (void);
typedef s4_t ostime_t;
#if !HAS_ostick_conv
-#define us2osticks(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC) / 1000000))
-#define ms2osticks(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC) / 1000))
-#define sec2osticks(sec) ((ostime_t)( (s8_t)(sec) * OSTICKS_PER_SEC))
-#define osticks2ms(os) ((s4_t)(((os)*(s8_t)1000 ) / OSTICKS_PER_SEC))
-#define osticks2us(os) ((s4_t)(((os)*(s8_t)1000000 ) / OSTICKS_PER_SEC))
+#define us2osticks(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC) / 1000000))
+#define ms2osticks(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC) / 1000))
+#define sec2osticks(sec) ((ostime_t)( (int64_t)(sec) * OSTICKS_PER_SEC))
+#define osticks2ms(os) ((s4_t)(((os)*(int64_t)1000 ) / OSTICKS_PER_SEC))
+#define osticks2us(os) ((s4_t)(((os)*(int64_t)1000000 ) / OSTICKS_PER_SEC))
// Special versions
-#define us2osticksCeil(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000))
-#define us2osticksRound(us) ((ostime_t)( ((s8_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000))
-#define ms2osticksCeil(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 999) / 1000))
-#define ms2osticksRound(ms) ((ostime_t)( ((s8_t)(ms) * OSTICKS_PER_SEC + 500) / 1000))
+#define us2osticksCeil(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 999999) / 1000000))
+#define us2osticksRound(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC + 500000) / 1000000))
+#define ms2osticksCeil(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 999) / 1000))
+#define ms2osticksRound(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC + 500) / 1000))
#endif
diff --git a/lmic/radio.c b/lmic/radio.c
index 8ef7d37..7ac25e6 100644
--- a/lmic/radio.c
+++ b/lmic/radio.c
@@ -411,7 +411,7 @@ static void configLoraModem () {
static void configChannel () {
// set frequency: FQ = (FRF * 32 Mhz) / (2 ^ 19)
- u8_t frf = ((u8_t)LMIC.freq << 19) / 32000000;
+ uint64_t frf = ((uint64_t)LMIC.freq << 19) / 32000000;
writeReg(RegFrfMsb, (u1_t)(frf>>16));
writeReg(RegFrfMid, (u1_t)(frf>> 8));
writeReg(RegFrfLsb, (u1_t)(frf>> 0));
@@ -527,6 +527,18 @@ static void txlora () {
// now we actually start the transmission
opmode(OPMODE_TX);
+
+#if LMIC_DEBUG_LEVEL > 0
+ u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7
+ u1_t bw = getBw(LMIC.rps);
+ u1_t cr = getCr(LMIC.rps);
+ printf("%lu: TXMODE, freq=%lu, len=%d, SF=%d, BW=%d, CR=4/%d, IH=%d\n",
+ os_getTime(), LMIC.freq, LMIC.dataLen, sf,
+ bw == BW125 ? 125 : (bw == BW250 ? 250 : 500),
+ cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)),
+ getIh(LMIC.rps)
+ );
+#endif
}
// start transmitter (buf=LMIC.frame, len=LMIC.dataLen)
@@ -601,6 +613,24 @@ static void rxlora (u1_t rxmode) {
} else { // continous rx (scan or rssi)
opmode(OPMODE_RX);
}
+
+#if LMIC_DEBUG_LEVEL > 0
+ if (rxmode == RXMODE_RSSI) {
+ printf("RXMODE_RSSI\n");
+ } else {
+ u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7
+ u1_t bw = getBw(LMIC.rps);
+ u1_t cr = getCr(LMIC.rps);
+ printf("%lu: %s, freq=%lu, SF=%d, BW=%d, CR=4/%d, IH=%d\n",
+ os_getTime(),
+ rxmode == RXMODE_SINGLE ? "RXMODE_SINGLE" : (rxmode == RXMODE_SCAN ? "RXMODE_SCAN" : "UNKNOWN_RX"),
+ LMIC.freq, sf,
+ bw == BW125 ? 125 : (bw == BW250 ? 250 : 500),
+ cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)),
+ getIh(LMIC.rps)
+ );
+ }
+#endif
}
static void rxfsk (u1_t rxmode) {
diff --git a/target/arduino/examples/raw/raw.ino b/target/arduino/examples/raw/raw.ino
new file mode 100644
index 0000000..1e0382f
--- /dev/null
+++ b/target/arduino/examples/raw/raw.ino
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2015 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.
+ * NO WARRANTY OF ANY KIND IS PROVIDED.
+ *
+ * This example transmits data on hardcoded channel and receives data
+ * when not transmitting. Running this sketch on two nodes should allow
+ * them to communicate.
+ *******************************************************************************/
+
+#include
+#include
+#include
+
+#if !defined(DISABLE_INVERT_IQ_ON_RX)
+#error This example requires DISABLE_INVERT_IQ_ON_RX to be set. Update \
+ config.h in the lmic library to set it.
+#endif
+
+// How often to send a packet. Note that this sketch bypasses the normal
+// LMIC duty cycle limiting, so when you change anything in this sketch
+// (payload length, frequency, spreading factor), be sure to check if
+// this interval should not also be increased.
+// See this spreadsheet for an easy airtime and duty cycle calculator:
+// https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc
+#define TX_INTERVAL 2000
+
+// Pin mapping
+const lmic_pinmap lmic_pins = {
+ .nss = 6,
+ .rxtx = LMIC_UNUSED_PIN,
+ .rst = 5,
+ .dio = {2, 3, 4},
+};
+
+
+// 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 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) { }
+
+void onEvent (ev_t ev) {
+}
+
+osjob_t txjob;
+osjob_t timeoutjob;
+static void tx_func (osjob_t* job);
+
+// Transmit the given string and call the given function afterwards
+void tx(const char *str, osjobcb_t func) {
+ os_radio(RADIO_RST); // Stop RX first
+ delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet
+ LMIC.dataLen = 0;
+ while (*str)
+ LMIC.frame[LMIC.dataLen++] = *str++;
+ LMIC.osjob.func = func;
+ os_radio(RADIO_TX);
+ Serial.println("TX");
+}
+
+// Enable rx mode and call func when a packet is received
+void rx(osjobcb_t func) {
+ LMIC.osjob.func = func;
+ LMIC.rxtime = os_getTime(); // RX _now_
+ // Enable "continuous" RX (e.g. without a timeout, still stops after
+ // receiving a packet)
+ os_radio(RADIO_RXON);
+ Serial.println("RX");
+}
+
+static void rxtimeout_func(osjob_t *job) {
+ digitalWrite(LED_BUILTIN, LOW); // off
+}
+
+static void rx_func (osjob_t* job) {
+ // Blink once to confirm reception and then keep the led on
+ digitalWrite(LED_BUILTIN, LOW); // off
+ delay(10);
+ digitalWrite(LED_BUILTIN, HIGH); // on
+
+ // Timeout RX (i.e. update led status) after 3 periods without RX
+ os_setTimedCallback(&timeoutjob, os_getTime() + ms2osticks(3*TX_INTERVAL), rxtimeout_func);
+
+ // Reschedule TX so that it should not collide with the other side's
+ // next TX
+ os_setTimedCallback(&txjob, os_getTime() + ms2osticks(TX_INTERVAL/2), tx_func);
+
+ Serial.print("Got ");
+ Serial.print(LMIC.dataLen);
+ Serial.println(" bytes");
+ Serial.write(LMIC.frame, LMIC.dataLen);
+ Serial.println();
+
+ // Restart RX
+ rx(rx_func);
+}
+
+static void txdone_func (osjob_t* job) {
+ rx(rx_func);
+}
+
+// log text to USART and toggle LED
+static void tx_func (osjob_t* job) {
+ // say hello
+ tx("Hello, world!", txdone_func);
+ // reschedule job every TX_INTERVAL (plus a bit of random to prevent
+ // systematic collisions), unless packets are received, then rx_func
+ // will reschedule at half this time.
+ os_setTimedCallback(job, os_getTime() + ms2osticks(TX_INTERVAL + random(500)), tx_func);
+}
+
+// application entry point
+void setup() {
+ Serial.begin(115200);
+ Serial.println("Starting");
+ #ifdef VCC_ENABLE
+ // For Pinoccio Scout boards
+ pinMode(VCC_ENABLE, OUTPUT);
+ digitalWrite(VCC_ENABLE, HIGH);
+ delay(1000);
+ #endif
+
+ pinMode(LED_BUILTIN, OUTPUT);
+
+ // initialize runtime env
+ os_init();
+
+ // Set up these settings once, and use them for both TX and RX
+
+#if defined(CFG_eu868)
+ // Use a frequency in the g3 which allows 10% duty cycling.
+ LMIC.freq = 869525000;
+#elif defined(CFG_us915)
+ LMIC.freq = 902300000;
+#endif
+
+ // Maximum TX power
+ LMIC.txpow = 27;
+ // Use a medium spread factor. This can be increased up to SF12 for
+ // better range, but then the interval should be (significantly)
+ // lowered to comply with duty cycle limits as well.
+ LMIC.datarate = DR_SF9;
+ // This sets CR 4/5, BW125 (except for DR_SF7B, which uses BW250)
+ LMIC.rps = updr2rps(LMIC.datarate);
+
+ Serial.println("Started");
+ Serial.flush();
+
+ // setup initial job
+ os_setCallback(&txjob, tx_func);
+}
+
+void loop() {
+ // execute scheduled jobs and events
+ os_runloop_once();
+}
diff --git a/target/arduino/examples/ttn-abp/ttn-abp.ino b/target/arduino/examples/ttn-abp/ttn-abp.ino
new file mode 100644
index 0000000..a8b4a18
--- /dev/null
+++ b/target/arduino/examples/ttn-abp/ttn-abp.ino
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * 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.
+ * NO WARRANTY OF ANY KIND IS PROVIDED.
+ *
+ * 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 ABP (Activation-by-personalisation), where a DevAddr and
+ * Session keys are preconfigured (unlike OTAA, where a DevEUI and
+ * application key is configured, while the DevAddr and session keys are
+ * assigned/generated in the over-the-air-activation procedure).
+ *
+ * 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 a DevAddr, NwkSKey and
+ * AppSKey. Each device should have their own unique values for these
+ * fields.
+ *
+ * Do not forget to define the radio type correctly in config.h.
+ *
+ *******************************************************************************/
+
+#include
+#include
+#include
+
+// 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!
+
+// 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 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 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 = 60;
+
+// Pin mapping
+const lmic_pinmap lmic_pins = {
+ .nss = 6,
+ .rxtx = LMIC_UNUSED_PIN,
+ .rst = 5,
+ .dio = {2, 3, 4},
+};
+
+void onEvent (ev_t ev) {
+ Serial.print(os_getTime());
+ Serial.print(": ");
+ switch(ev) {
+ case EV_SCAN_TIMEOUT:
+ Serial.println(F("EV_SCAN_TIMEOUT"));
+ break;
+ case EV_BEACON_FOUND:
+ Serial.println(F("EV_BEACON_FOUND"));
+ break;
+ case EV_BEACON_MISSED:
+ Serial.println(F("EV_BEACON_MISSED"));
+ break;
+ case EV_BEACON_TRACKED:
+ Serial.println(F("EV_BEACON_TRACKED"));
+ break;
+ case EV_JOINING:
+ Serial.println(F("EV_JOINING"));
+ break;
+ case EV_JOINED:
+ Serial.println(F("EV_JOINED"));
+ break;
+ case EV_RFU1:
+ Serial.println(F("EV_RFU1"));
+ break;
+ case EV_JOIN_FAILED:
+ Serial.println(F("EV_JOIN_FAILED"));
+ break;
+ case EV_REJOIN_FAILED:
+ Serial.println(F("EV_REJOIN_FAILED"));
+ break;
+ 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(LMIC.dataLen);
+ Serial.println(F(" bytes of payload"));
+ }
+ // Schedule next transmission
+ os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
+ break;
+ case EV_LOST_TSYNC:
+ Serial.println(F("EV_LOST_TSYNC"));
+ break;
+ case EV_RESET:
+ Serial.println(F("EV_RESET"));
+ break;
+ case EV_RXCOMPLETE:
+ // data received in ping slot
+ Serial.println(F("EV_RXCOMPLETE"));
+ break;
+ case EV_LINK_DEAD:
+ Serial.println(F("EV_LINK_DEAD"));
+ break;
+ case EV_LINK_ALIVE:
+ Serial.println(F("EV_LINK_ALIVE"));
+ break;
+ default:
+ Serial.println(F("Unknown event"));
+ break;
+ }
+}
+
+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.
+}
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println(F("Starting"));
+
+ #ifdef VCC_ENABLE
+ // For Pinoccio Scout boards
+ pinMode(VCC_ENABLE, OUTPUT);
+ digitalWrite(VCC_ENABLE, HIGH);
+ delay(1000);
+ #endif
+
+ // LMIC init
+ os_init();
+ // Reset the MAC state. Session and pending data transfers will be discarded.
+ LMIC_reset();
+
+ // 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 (0x1, DEVADDR, nwkskey, appskey);
+ #else
+ // If not running an AVR with PROGMEM, just use the arrays directly
+ LMIC_setSession (0x1, DEVADDR, NWKSKEY, APPSKEY);
+ #endif
+
+ #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.
+ // NA-US channels 0-71 are configured automatically
+ 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)
+ // NA-US 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
+ LMIC_selectSubBand(1);
+ #endif
+
+ // Disable link check validation
+ LMIC_setLinkCheckMode(0);
+
+ // TTN uses SF9 for its RX2 window.
+ LMIC.dn2Dr = DR_SF9;
+
+ // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
+ LMIC_setDrTxpow(DR_SF7,14);
+
+ // Start job
+ do_send(&sendjob);
+}
+
+void loop() {
+ os_runloop_once();
+}
diff --git a/target/arduino/examples/ttn-otaa/ttn-otaa.ino b/target/arduino/examples/ttn-otaa/ttn-otaa.ino
new file mode 100644
index 0000000..d667aa2
--- /dev/null
+++ b/target/arduino/examples/ttn-otaa/ttn-otaa.ino
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * 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.
+ * NO WARRANTY OF ANY KIND IS PROVIDED.
+ *
+ * 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
+#include
+#include
+
+// 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]={ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+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] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };
+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 = 60;
+
+// Pin mapping
+const lmic_pinmap lmic_pins = {
+ .nss = 6,
+ .rxtx = LMIC_UNUSED_PIN,
+ .rst = 5,
+ .dio = {2, 3, 4},
+};
+
+void onEvent (ev_t ev) {
+ Serial.print(os_getTime());
+ Serial.print(": ");
+ switch(ev) {
+ case EV_SCAN_TIMEOUT:
+ Serial.println(F("EV_SCAN_TIMEOUT"));
+ break;
+ case EV_BEACON_FOUND:
+ Serial.println(F("EV_BEACON_FOUND"));
+ break;
+ case EV_BEACON_MISSED:
+ Serial.println(F("EV_BEACON_MISSED"));
+ break;
+ case EV_BEACON_TRACKED:
+ Serial.println(F("EV_BEACON_TRACKED"));
+ break;
+ case EV_JOINING:
+ Serial.println(F("EV_JOINING"));
+ break;
+ case EV_JOINED:
+ Serial.println(F("EV_JOINED"));
+
+ // Disable link check validation (automatically enabled
+ // during join, but not supported by TTN at this time).
+ LMIC_setLinkCheckMode(0);
+ break;
+ case EV_RFU1:
+ Serial.println(F("EV_RFU1"));
+ break;
+ case EV_JOIN_FAILED:
+ Serial.println(F("EV_JOIN_FAILED"));
+ break;
+ case EV_REJOIN_FAILED:
+ Serial.println(F("EV_REJOIN_FAILED"));
+ break;
+ break;
+ 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(LMIC.dataLen);
+ Serial.println(F(" bytes of payload"));
+ }
+ // Schedule next transmission
+ os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
+ break;
+ case EV_LOST_TSYNC:
+ Serial.println(F("EV_LOST_TSYNC"));
+ break;
+ case EV_RESET:
+ Serial.println(F("EV_RESET"));
+ break;
+ case EV_RXCOMPLETE:
+ // data received in ping slot
+ Serial.println(F("EV_RXCOMPLETE"));
+ break;
+ case EV_LINK_DEAD:
+ Serial.println(F("EV_LINK_DEAD"));
+ break;
+ case EV_LINK_ALIVE:
+ Serial.println(F("EV_LINK_ALIVE"));
+ break;
+ default:
+ Serial.println(F("Unknown event"));
+ break;
+ }
+}
+
+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.
+}
+
+void setup() {
+ Serial.begin(9600);
+ Serial.println(F("Starting"));
+
+ #ifdef VCC_ENABLE
+ // For Pinoccio Scout boards
+ pinMode(VCC_ENABLE, OUTPUT);
+ digitalWrite(VCC_ENABLE, HIGH);
+ delay(1000);
+ #endif
+
+ // LMIC init
+ os_init();
+ // Reset the MAC state. Session and pending data transfers will be discarded.
+ LMIC_reset();
+
+ // Start job (sending automatically starts OTAA too)
+ do_send(&sendjob);
+}
+
+void loop() {
+ os_runloop_once();
+}
diff --git a/target/arduino/export.sh b/target/arduino/export.sh
new file mode 100755
index 0000000..d05fcc3
--- /dev/null
+++ b/target/arduino/export.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+SRC=$(cd "$(dirname "$0")"; pwd)
+
+usage() {
+ echo "$0 [--link] TARGET_DIR"
+ echo "Create an Arduino-compatible library in the given directory, overwriting existing files. If --link is given, creates symbolic links for easy testing."
+}
+
+
+CPOPTS=
+case "$1" in
+ --help)
+ usage
+ exit 0
+ ;;
+ --link)
+ CPOPTS=--symbolic-link
+ shift;
+ ;;
+ --*)
+ echo "Unknown option: $1" 1>&2
+ exit 1
+ ;;
+esac
+
+TARGET=$1
+
+if [ -z "$TARGET" ]; then
+ usage
+ exit 1
+fi
+
+if ! [ -d "$(dirname "$TARGET")" ]; then
+ echo "Parent of $TARGET should exist" 1>&2
+ exit 1
+fi
+
+mkdir -p "$TARGET"/src
+cp $CPOPTS -f -v "$SRC"/library.properties "$TARGET"
+cp $CPOPTS -f -v "$SRC"/lmic.h "$TARGET"/src
+cp $CPOPTS -r -f -v "$SRC"/../../lmic "$TARGET"/src
+cp $CPOPTS -r -f -v "$SRC"/../../aes "$TARGET"/src
+cp $CPOPTS -r -f -v "$SRC"/hal "$TARGET"/src
+cp $CPOPTS -r -f -v "$SRC"/examples "$TARGET"
diff --git a/target/arduino/hal/hal.cpp b/target/arduino/hal/hal.cpp
new file mode 100644
index 0000000..53bf359
--- /dev/null
+++ b/target/arduino/hal/hal.cpp
@@ -0,0 +1,253 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Matthijs Kooijman
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * This the HAL to run LMIC on top of the Arduino environment.
+ *******************************************************************************/
+
+#include
+#include
+#include "../lmic.h"
+#include "hal.h"
+#include
+
+// -----------------------------------------------------------------------------
+// I/O
+
+static void hal_io_init () {
+ // NSS and DIO0 are required, DIO1 is required for LoRa, DIO2 for FSK
+ ASSERT(lmic_pins.nss != LMIC_UNUSED_PIN);
+ ASSERT(lmic_pins.dio[0] != LMIC_UNUSED_PIN);
+ ASSERT(lmic_pins.dio[1] != LMIC_UNUSED_PIN || lmic_pins.dio[2] != LMIC_UNUSED_PIN);
+
+ pinMode(lmic_pins.nss, OUTPUT);
+ if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
+ pinMode(lmic_pins.rxtx, OUTPUT);
+ if (lmic_pins.rst != LMIC_UNUSED_PIN)
+ pinMode(lmic_pins.rst, OUTPUT);
+
+ pinMode(lmic_pins.dio[0], INPUT);
+ if (lmic_pins.dio[1] != LMIC_UNUSED_PIN)
+ pinMode(lmic_pins.dio[1], INPUT);
+ if (lmic_pins.dio[2] != LMIC_UNUSED_PIN)
+ pinMode(lmic_pins.dio[2], INPUT);
+}
+
+// val == 1 => tx 1
+void hal_pin_rxtx (u1_t val) {
+ if (lmic_pins.rxtx != LMIC_UNUSED_PIN)
+ digitalWrite(lmic_pins.rxtx, val);
+}
+
+// set radio RST pin to given value (or keep floating!)
+void hal_pin_rst (u1_t val) {
+ if (lmic_pins.rst == LMIC_UNUSED_PIN)
+ return;
+
+ if(val == 0 || val == 1) { // drive pin
+ pinMode(lmic_pins.rst, OUTPUT);
+ digitalWrite(lmic_pins.rst, val);
+ } else { // keep pin floating
+ pinMode(lmic_pins.rst, INPUT);
+ }
+}
+
+static bool dio_states[NUM_DIO] = {0};
+
+static void hal_io_check() {
+ uint8_t i;
+ for (i = 0; i < NUM_DIO; ++i) {
+ if (lmic_pins.dio[i] == LMIC_UNUSED_PIN)
+ continue;
+
+ if (dio_states[i] != digitalRead(lmic_pins.dio[i])) {
+ dio_states[i] = !dio_states[i];
+ if (dio_states[i])
+ radio_irq_handler(i);
+ }
+ }
+}
+
+// -----------------------------------------------------------------------------
+// SPI
+
+static const SPISettings settings(10E6, MSBFIRST, SPI_MODE0);
+
+static void hal_spi_init () {
+ SPI.begin();
+}
+
+void hal_pin_nss (u1_t val) {
+ if (!val)
+ SPI.beginTransaction(settings);
+ else
+ SPI.endTransaction();
+
+ //Serial.println(val?">>":"<<");
+ digitalWrite(lmic_pins.nss, val);
+}
+
+// perform SPI transaction with radio
+u1_t hal_spi (u1_t out) {
+ u1_t res = SPI.transfer(out);
+/*
+ Serial.print(">");
+ Serial.print(out, HEX);
+ Serial.print("<");
+ Serial.println(res, HEX);
+ */
+ return res;
+}
+
+// -----------------------------------------------------------------------------
+// TIME
+
+static void hal_time_init () {
+ // Nothing to do
+}
+
+u4_t hal_ticks () {
+ // Because micros() is scaled down in this function, micros() will
+ // overflow before the tick timer should, causing the tick timer to
+ // miss a significant part of its values if not corrected. To fix
+ // this, the "overflow" serves as an overflow area for the micros()
+ // counter. It consists of three parts:
+ // - The US_PER_OSTICK upper bits are effectively an extension for
+ // the micros() counter and are added to the result of this
+ // function.
+ // - The next bit overlaps with the most significant bit of
+ // micros(). This is used to detect micros() overflows.
+ // - The remaining bits are always zero.
+ //
+ // By comparing the overlapping bit with the corresponding bit in
+ // the micros() return value, overflows can be detected and the
+ // upper bits are incremented. This is done using some clever
+ // bitwise operations, to remove the need for comparisons and a
+ // jumps, which should result in efficient code. By avoiding shifts
+ // other than by multiples of 8 as much as possible, this is also
+ // efficient on AVR (which only has 1-bit shifts).
+ static uint8_t overflow = 0;
+
+ // Scaled down timestamp. The top US_PER_OSTICK_EXPONENT bits are 0,
+ // the others will be the lower bits of our return value.
+ uint32_t scaled = micros() >> US_PER_OSTICK_EXPONENT;
+ // Most significant byte of scaled
+ uint8_t msb = scaled >> 24;
+ // Mask pointing to the overlapping bit in msb and overflow.
+ const uint8_t mask = (1 << (7 - US_PER_OSTICK_EXPONENT));
+ // Update overflow. If the overlapping bit is different
+ // between overflow and msb, it is added to the stored value,
+ // so the overlapping bit becomes equal again and, if it changed
+ // from 1 to 0, the upper bits are incremented.
+ overflow += (msb ^ overflow) & mask;
+
+ // Return the scaled value with the upper bits of stored added. The
+ // overlapping bit will be equal and the lower bits will be 0, so
+ // bitwise or is a no-op for them.
+ return scaled | ((uint32_t)overflow << 24);
+
+ // 0 leads to correct, but overly complex code (it could just return
+ // micros() unmodified), 8 leaves no room for the overlapping bit.
+ static_assert(US_PER_OSTICK_EXPONENT > 0 && US_PER_OSTICK_EXPONENT < 8, "Invalid US_PER_OSTICK_EXPONENT value");
+}
+
+// Returns the number of ticks until time. Negative values indicate that
+// time has already passed.
+static s4_t delta_time(u4_t time) {
+ return (s4_t)(time - hal_ticks());
+}
+
+void hal_waitUntil (u4_t time) {
+ s4_t delta = delta_time(time);
+ // From delayMicroseconds docs: Currently, the largest value that
+ // will produce an accurate delay is 16383.
+ while (delta > (16000 / US_PER_OSTICK)) {
+ delay(16);
+ delta -= (16000 / US_PER_OSTICK);
+ }
+ if (delta > 0)
+ delayMicroseconds(delta * US_PER_OSTICK);
+}
+
+// check and rewind for target time
+u1_t hal_checkTimer (u4_t time) {
+ // No need to schedule wakeup, since we're not sleeping
+ return delta_time(time) <= 0;
+}
+
+static uint8_t irqlevel = 0;
+
+void hal_disableIRQs () {
+ noInterrupts();
+ irqlevel++;
+}
+
+void hal_enableIRQs () {
+ if(--irqlevel == 0) {
+ interrupts();
+
+ // Instead of using proper interrupts (which are a bit tricky
+ // and/or not available on all pins on AVR), just poll the pin
+ // values. Since os_runloop disables and re-enables interrupts,
+ // putting this here makes sure we check at least once every
+ // loop.
+ //
+ // As an additional bonus, this prevents the can of worms that
+ // we would otherwise get for running SPI transfers inside ISRs
+ hal_io_check();
+ }
+}
+
+void hal_sleep () {
+ // Not implemented
+}
+
+// -----------------------------------------------------------------------------
+
+#if defined(LMIC_PRINTF_TO)
+static int uart_putchar (char c, FILE *)
+{
+ LMIC_PRINTF_TO.write(c) ;
+ return 0 ;
+}
+
+void hal_printf_init() {
+ // create a FILE structure to reference our UART output function
+ static FILE uartout;
+ memset(&uartout, 0, sizeof(uartout));
+
+ // fill in the UART file descriptor with pointer to writer.
+ fdev_setup_stream (&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
+
+ // The uart is the standard output device STDOUT.
+ stdout = &uartout ;
+}
+#endif // defined(LMIC_PRINTF_TO)
+
+void hal_init () {
+ // configure radio I/O and interrupt handler
+ hal_io_init();
+ // configure radio SPI
+ hal_spi_init();
+ // configure timer and interrupt handler
+ hal_time_init();
+#if defined(LMIC_PRINTF_TO)
+ // printf support
+ hal_printf_init();
+#endif
+}
+
+void hal_failed (const char *file, u2_t line) {
+#if defined(LMIC_FAILURE_TO)
+ LMIC_FAILURE_TO.println("FAILURE ");
+ LMIC_FAILURE_TO.print(file);
+ LMIC_FAILURE_TO.print(':');
+ LMIC_FAILURE_TO.println(line);
+ LMIC_FAILURE_TO.flush();
+#endif
+ hal_disableIRQs();
+ while(1);
+}
diff --git a/target/arduino/hal/hal.h b/target/arduino/hal/hal.h
new file mode 100644
index 0000000..e096569
--- /dev/null
+++ b/target/arduino/hal/hal.h
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Matthijs Kooijman
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * This the HAL to run LMIC on top of the Arduino environment.
+ *******************************************************************************/
+#ifndef _hal_hal_h_
+#define _hal_hal_h_
+
+static const int NUM_DIO = 3;
+
+struct lmic_pinmap {
+ u1_t nss;
+ u1_t rxtx;
+ u1_t rst;
+ u1_t dio[NUM_DIO];
+};
+
+// Use this for any unused pins.
+const u1_t LMIC_UNUSED_PIN = 0xff;
+
+// Declared here, to be defined an initialized by the application
+extern const lmic_pinmap lmic_pins;
+
+#endif // _hal_hal_h_
diff --git a/target/arduino/hal/target-config.h b/target/arduino/hal/target-config.h
new file mode 100644
index 0000000..026a18c
--- /dev/null
+++ b/target/arduino/hal/target-config.h
@@ -0,0 +1,80 @@
+#ifndef _lmic_arduino_hal_config_h_
+#define _lmic_arduino_hal_config_h_
+
+#define CFG_eu868 1
+//#define CFG_us915 1
+
+// This is the SX1272/SX1273 radio, which is also used on the HopeRF
+// RFM92 boards.
+//#define CFG_sx1272_radio 1
+// This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on
+// the HopeRF RFM95 boards.
+#define CFG_sx1276_radio 1
+
+// 16 μs per tick
+// LMIC requires ticks to be 15.5μs - 100 μs long
+#define US_PER_OSTICK_EXPONENT 4
+#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT)
+#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK)
+
+// Enable this to allow using printf() to print to the given serial port
+// (or any other Print object). This can be easy for debugging. The
+// current implementation only works on AVR, though.
+//#define LMIC_PRINTF_TO Serial
+
+// Any runtime assertion failures are printed to this serial port (or
+// any other Print object). If this is unset, any failures just silently
+// halt execution.
+#define LMIC_FAILURE_TO Serial
+
+// Set this to 1 to enable some basic debug output (using printf) about
+// RF settings used during transmission and reception. Set to 2 to
+// enable more verbose output. Make sure that printf is actually
+// configured (e.g. on AVR it is not by default), otherwise using it can
+// cause crashing.
+#define LMIC_DEBUG_LEVEL 0
+
+// Uncomment this to disable all code related to joining
+//#define DISABLE_JOIN
+// Uncomment this to disable all code related to ping
+#define DISABLE_PING
+// Uncomment this to disable all code related to beacon tracking.
+// Requires ping to be disabled too
+#define DISABLE_BEACONS
+
+// Uncomment these to disable the corresponding MAC commands.
+// Class A
+//#define DISABLE_MCMD_DCAP_REQ // duty cycle cap
+//#define DISABLE_MCMD_DN2P_SET // 2nd DN window param
+//#define DISABLE_MCMD_SNCH_REQ // set new channel
+// Class B
+//#define DISABLE_MCMD_PING_SET // set ping freq, automatically disabled by DISABLE_PING
+//#define DISABLE_MCMD_BCNI_ANS // next beacon start, automatical disabled by DISABLE_BEACON
+
+// In LoRaWAN, a gateway applies I/Q inversion on TX, and nodes do the
+// same on RX. This ensures that gateways can talk to nodes and vice
+// versa, but gateways will not hear other gateways and nodes will not
+// hear other nodes. By uncommenting this macro, this inversion is
+// disabled and this node can hear other nodes. If two nodes both have
+// this macro set, they can talk to each other (but they can no longer
+// hear gateways). This should probably only be used when debugging
+// and/or when talking to the radio directly (e.g. like in the "raw"
+// example).
+//#define DISABLE_INVERT_IQ_ON_RX
+
+// This allows choosing between multiple included AES implementations.
+// Make sure exactly one of these is uncommented.
+//
+// This selects the original AES implementation included LMIC. This
+// implementation is optimized for speed on 32-bit processors using
+// fairly big lookup tables, but it takes up big amounts of flash on the
+// AVR architecture.
+// #define USE_ORIGINAL_AES
+//
+// This selects the AES implementation written by Ideetroon for their
+// own LoRaWAN library. It also uses lookup tables, but smaller
+// byte-oriented ones, making it use a lot less flash space (but it is
+// also about twice as slow as the original).
+#define USE_IDEETRON_AES
+
+#endif // _lmic_arduino_hal_config_h_
diff --git a/target/arduino/library.properties b/target/arduino/library.properties
new file mode 100644
index 0000000..0dbf7d5
--- /dev/null
+++ b/target/arduino/library.properties
@@ -0,0 +1,9 @@
+name=IBM LMIC framework
+version=1.5.0+arduino-1
+author=IBM
+maintainer=Matthijs Kooijman
+sentence=Arduino port of the LMIC (LoraWAN-in-C, formerly LoraMAC-in-C) framework provided by IBM.
+paragraph=Supports SX1272/SX1276 and HopeRF RFM92/RFM95 tranceivers
+category=Communication
+url=http://www.research.ibm.com/labs/zurich/ics/lrsc/lmic.html
+architectures=*
diff --git a/target/arduino/lmic.h b/target/arduino/lmic.h
new file mode 100644
index 0000000..f2c7935
--- /dev/null
+++ b/target/arduino/lmic.h
@@ -0,0 +1,9 @@
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#include "lmic/lmic.h"
+
+#ifdef __cplusplus
+}
+#endif