commit 8c200e3309def253b1d3a33b02545ef2f2affcc5 Author: Stefan Brand Date: Thu Nov 26 16:56:36 2020 +0100 Initial Checkin diff --git a/LoRaWAN.cpp b/LoRaWAN.cpp new file mode 100755 index 0000000..5b36843 --- /dev/null +++ b/LoRaWAN.cpp @@ -0,0 +1,686 @@ +/* + LoRaWAN.cpp - Library for LoRaWAN protocol, uses RFM95W module + Created by Leo Korbee, March 31, 2018. + Released into the public domain. + @license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) + Thanks to all the folks who contributed on the base of this code. + (Gerben den Hartog, et al - Ideetron.nl) +*/ + +#include "Arduino.h" +#include "LoRaWAN.h" + + +// constructor +LoRaWAN::LoRaWAN(RFM95 &rfm95) +{ + _rfm95 = &rfm95; +} + + +void LoRaWAN::setKeys(unsigned char NwkSkey[], unsigned char AppSkey[], unsigned char DevAddr[]) +{ + _NwkSkey = NwkSkey; + _AppSkey = AppSkey; + _DevAddr = DevAddr; +} + +/* +***************************************************************************************** +* Description : Function contstructs a LoRaWAN package and sends it +* +* Arguments : *Data pointer to the array of data that will be transmitted +* Data_Length nuber of bytes to be transmitted +* Frame_Counter_Up Frame counter of upstream frames +* +***************************************************************************************** +*/ +void LoRaWAN::Send_Data(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter_Tx, lora_dr_t datarate,unsigned char Frame_Port) +{ + //Define variables + unsigned char i; + + //Direction of frame is up + unsigned char Direction = 0x00; + + unsigned char RFM_Data[64]; + unsigned char RFM_Package_Length; + + unsigned char MIC[4]; + + /* + @leo: + https://hackmd.io/s/S1kg6Ymo- + + 7…5 bits 4…2 bits 1…0 bits + MType RFU Major + + MType Description + 000 (0x00) Join Request + 001 (0x20) Join Accept + 010 (0x40) Unconfirmed Data Up + 011 (0x60) Unconfirmed Data Down + 100 (0x80) Confirmed Data Up + 101 (0xA0) Confirmed Data Down + 110 (0xC0) RFU + 111 (0xE0) Proprietary + */ + + // Unconfirmed data up + unsigned char Mac_Header = 0x40; + + // Confirmed data up + // unsigned char Mac_Header = 0x80; + + unsigned char Frame_Control = 0x00; + //unsigned char Frame_Port = 0x01; + + //Encrypt the data + Encrypt_Payload(Data, Data_Length, Frame_Counter_Tx, Direction); + + //Build the Radio Package + RFM_Data[0] = Mac_Header; + + RFM_Data[1] = _DevAddr[3]; + RFM_Data[2] = _DevAddr[2]; + RFM_Data[3] = _DevAddr[1]; + RFM_Data[4] = _DevAddr[0]; + + RFM_Data[5] = Frame_Control; + + RFM_Data[6] = (Frame_Counter_Tx & 0x00FF); + RFM_Data[7] = ((Frame_Counter_Tx >> 8) & 0x00FF); + + RFM_Data[8] = Frame_Port; + + //Set Current package length + RFM_Package_Length = 9; + + //Load Data + for(i = 0; i < Data_Length; i++) + { + RFM_Data[RFM_Package_Length + i] = Data[i]; + } + + //Add data Lenth to package length + RFM_Package_Length = RFM_Package_Length + Data_Length; + + //Calculate MIC + Calculate_MIC(RFM_Data, MIC, RFM_Package_Length, Frame_Counter_Tx, Direction); + + //Load MIC in package + for(i = 0; i < 4; i++) + { + RFM_Data[i + RFM_Package_Length] = MIC[i]; + } + + //Add MIC length to RFM package length + RFM_Package_Length = RFM_Package_Length + 4; + + //Set Lora Datarate + _rfm95->RFM_Set_Datarate(datarate); + //Send Package + _rfm95->RFM_Send_Package(RFM_Data, RFM_Package_Length); +} + +/* + Encryption stuff after this line +*/ +void LoRaWAN::Encrypt_Payload(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction) +{ + unsigned char i = 0x00; + unsigned char j; + unsigned char Number_of_Blocks = 0x00; + unsigned char Incomplete_Block_Size = 0x00; + + unsigned char Block_A[16]; + + //Calculate number of blocks + Number_of_Blocks = Data_Length / 16; + Incomplete_Block_Size = Data_Length % 16; + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + for(i = 1; i <= Number_of_Blocks; i++) + { + Block_A[0] = 0x01; + Block_A[1] = 0x00; + Block_A[2] = 0x00; + Block_A[3] = 0x00; + Block_A[4] = 0x00; + + Block_A[5] = Direction; + + Block_A[6] = _DevAddr[3]; + Block_A[7] = _DevAddr[2]; + Block_A[8] = _DevAddr[1]; + Block_A[9] = _DevAddr[0]; + + Block_A[10] = (Frame_Counter & 0x00FF); + Block_A[11] = ((Frame_Counter >> 8) & 0x00FF); + + Block_A[12] = 0x00; //Frame counter upper Bytes + Block_A[13] = 0x00; + + Block_A[14] = 0x00; + + Block_A[15] = i; + + //Calculate S + AES_Encrypt(Block_A, _AppSkey); //original + + + //Check for last block + if(i != Number_of_Blocks) + { + for(j = 0; j < 16; j++) + { + *Data = *Data ^ Block_A[j]; + Data++; + } + } + else + { + if(Incomplete_Block_Size == 0) + { + Incomplete_Block_Size = 16; + } + for(j = 0; j < Incomplete_Block_Size; j++) + { + *Data = *Data ^ Block_A[j]; + Data++; + } + } + } +} + +void LoRaWAN::Calculate_MIC(unsigned char *Data, unsigned char *Final_MIC, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction) +{ + unsigned char i; + unsigned char Block_B[16]; + + unsigned char Key_K1[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + unsigned char Key_K2[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + //unsigned char Data_Copy[16]; + + unsigned char Old_Data[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + unsigned char New_Data[16] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + + unsigned char Number_of_Blocks = 0x00; + unsigned char Incomplete_Block_Size = 0x00; + unsigned char Block_Counter = 0x01; + + //Create Block_B + Block_B[0] = 0x49; + Block_B[1] = 0x00; + Block_B[2] = 0x00; + Block_B[3] = 0x00; + Block_B[4] = 0x00; + + Block_B[5] = Direction; + + Block_B[6] = _DevAddr[3]; + Block_B[7] = _DevAddr[2]; + Block_B[8] = _DevAddr[1]; + Block_B[9] = _DevAddr[0]; + + Block_B[10] = (Frame_Counter & 0x00FF); + Block_B[11] = ((Frame_Counter >> 8) & 0x00FF); + + Block_B[12] = 0x00; //Frame counter upper bytes + Block_B[13] = 0x00; + + Block_B[14] = 0x00; + Block_B[15] = Data_Length; + + //Calculate number of Blocks and blocksize of last block + Number_of_Blocks = Data_Length / 16; + Incomplete_Block_Size = Data_Length % 16; + + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + Generate_Keys(Key_K1, Key_K2); + + //Preform Calculation on Block B0 + + //Preform AES encryption + AES_Encrypt(Block_B, _NwkSkey); + + //Copy Block_B to Old_Data + for(i = 0; i < 16; i++) + { + Old_Data[i] = Block_B[i]; + } + + //Preform full calculating until n-1 messsage blocks + while(Block_Counter < Number_of_Blocks) + { + //Copy data into array + for(i = 0; i < 16; i++) + { + New_Data[i] = *Data; + Data++; + } + + //Preform XOR with old data + XOR(New_Data,Old_Data); + + //Preform AES encryption + AES_Encrypt(New_Data, _NwkSkey); + + //Copy New_Data to Old_Data + for(i = 0; i < 16; i++) + { + Old_Data[i] = New_Data[i]; + } + + //Raise Block counter + Block_Counter++; + } + + //Perform calculation on last block + //Check if Datalength is a multiple of 16 + if(Incomplete_Block_Size == 0) + { + //Copy last data into array + for(i = 0; i < 16; i++) + { + New_Data[i] = *Data; + Data++; + } + + //Preform XOR with Key 1 + XOR(New_Data,Key_K1); + + //Preform XOR with old data + XOR(New_Data,Old_Data); + + //Preform last AES routine + // read NwkSkey from PROGMEM + AES_Encrypt(New_Data, _NwkSkey); + } + else + { + //Copy the remaining data and fill the rest + for(i = 0; i < 16; i++) + { + if(i < Incomplete_Block_Size) + { + New_Data[i] = *Data; + Data++; + } + if(i == Incomplete_Block_Size) + { + New_Data[i] = 0x80; + } + if(i > Incomplete_Block_Size) + { + New_Data[i] = 0x00; + } + } + + //Preform XOR with Key 2 + XOR(New_Data,Key_K2); + + //Preform XOR with Old data + XOR(New_Data,Old_Data); + + //Preform last AES routine + AES_Encrypt(New_Data, _NwkSkey); + } + + Final_MIC[0] = New_Data[0]; + Final_MIC[1] = New_Data[1]; + Final_MIC[2] = New_Data[2]; + Final_MIC[3] = New_Data[3]; +} + +void LoRaWAN::Generate_Keys(unsigned char *K1, unsigned char *K2) +{ + unsigned char i; + unsigned char MSB_Key; + + //Encrypt the zeros in K1 with the NwkSkey + AES_Encrypt(K1,_NwkSkey); + + //Create K1 + //Check if MSB is 1 + if((K1[0] & 0x80) == 0x80) + { + MSB_Key = 1; + } + else + { + MSB_Key = 0; + } + + //Shift K1 one bit left + Shift_Left(K1); + + //if MSB was 1 + if(MSB_Key == 1) + { + K1[15] = K1[15] ^ 0x87; + } + + //Copy K1 to K2 + for( i = 0; i < 16; i++) + { + K2[i] = K1[i]; + } + + //Check if MSB is 1 + if((K2[0] & 0x80) == 0x80) + { + MSB_Key = 1; + } + else + { + MSB_Key = 0; + } + + //Shift K2 one bit left + Shift_Left(K2); + + //Check if MSB was 1 + if(MSB_Key == 1) + { + K2[15] = K2[15] ^ 0x87; + } +} + + +void LoRaWAN::Shift_Left(unsigned char *Data) +{ + unsigned char i; + unsigned char Overflow = 0; + //unsigned char High_Byte, Low_Byte; + + for(i = 0; i < 16; i++) + { + //Check for overflow on next byte except for the last byte + if(i < 15) + { + //Check if upper bit is one + if((Data[i+1] & 0x80) == 0x80) + { + Overflow = 1; + } + else + { + Overflow = 0; + } + } + else + { + Overflow = 0; + } + + //Shift one left + Data[i] = (Data[i] << 1) + Overflow; + } +} + +void LoRaWAN::XOR(unsigned char *New_Data,unsigned char *Old_Data) +{ + unsigned char i; + + for(i = 0; i < 16; i++) + { + New_Data[i] = New_Data[i] ^ Old_Data[i]; + } +} + +/* +***************************************************************************************** +* Title : AES_Encrypt +* Description : +***************************************************************************************** +*/ +void LoRaWAN::AES_Encrypt(unsigned char *Data, unsigned char *Key) +{ + unsigned char Row, Column, Round = 0; + unsigned char Round_Key[16]; + unsigned char State[4][4]; + + // Copy input to State arry + for( Column = 0; Column < 4; Column++ ) + { + for( Row = 0; Row < 4; Row++ ) + { + State[Row][Column] = Data[Row + (Column << 2)]; + } + } + + // Copy key to round key + memcpy( &Round_Key[0], &Key[0], 16 ); + + // Add round key + AES_Add_Round_Key( Round_Key, State ); + + // Preform 9 full rounds with mixed collums + for( Round = 1 ; Round < 10 ; Round++ ) + { + // Perform Byte substitution with S table + for( Column = 0 ; Column < 4 ; Column++ ) + { + for( Row = 0 ; Row < 4 ; Row++ ) + { + State[Row][Column] = AES_Sub_Byte( State[Row][Column] ); + } + } + + // Perform Row Shift + AES_Shift_Rows(State); + + // Mix Collums + AES_Mix_Collums(State); + + // Calculate new round key + AES_Calculate_Round_Key(Round, Round_Key); + + // Add the round key to the Round_key + AES_Add_Round_Key(Round_Key, State); + } + + // Perform Byte substitution with S table whitout mix collums + for( Column = 0 ; Column < 4 ; Column++ ) + { + for( Row = 0; Row < 4; Row++ ) + { + State[Row][Column] = AES_Sub_Byte(State[Row][Column]); + } + } + + // Shift rows + AES_Shift_Rows(State); + + // Calculate new round key + AES_Calculate_Round_Key( Round, Round_Key ); + + // Add round key + AES_Add_Round_Key( Round_Key, State ); + + // Copy the State into the data array + for( Column = 0; Column < 4; Column++ ) + { + for( Row = 0; Row < 4; Row++ ) + { + Data[Row + (Column << 2)] = State[Row][Column]; + } + } +} // AES_Encrypt + + +/* +***************************************************************************************** +* Title : AES_Add_Round_Key +* Description : +***************************************************************************************** +*/ +void LoRaWAN::AES_Add_Round_Key(unsigned char *Round_Key, unsigned char (*State)[4]) +{ + unsigned char Row, Collum; + + for(Collum = 0; Collum < 4; Collum++) + { + for(Row = 0; Row < 4; Row++) + { + State[Row][Collum] ^= Round_Key[Row + (Collum << 2)]; + } + } +} // AES_Add_Round_Key + + +/* +***************************************************************************************** +* Title : AES_Sub_Byte +* Description : +***************************************************************************************** +*/ +unsigned char LoRaWAN::AES_Sub_Byte(unsigned char Byte) +{ +// unsigned char S_Row,S_Collum; +// unsigned char S_Byte; +// +// S_Row = ((Byte >> 4) & 0x0F); +// S_Collum = ((Byte >> 0) & 0x0F); +// S_Byte = S_Table [S_Row][S_Collum]; + + //return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; // original + return pgm_read_byte(&(S_Table [((Byte >> 4) & 0x0F)] [((Byte >> 0) & 0x0F)])); +} // AES_Sub_Byte + + +/* +***************************************************************************************** +* Title : AES_Shift_Rows +* Description : +***************************************************************************************** +*/ +void LoRaWAN::AES_Shift_Rows(unsigned char (*State)[4]) +{ + unsigned char Buffer; + + //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; + + 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; + + 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; +} // AES_Shift_Rows + + +/* +***************************************************************************************** +* Title : AES_Mix_Collums +* Description : +***************************************************************************************** +*/ +void LoRaWAN::AES_Mix_Collums(unsigned char (*State)[4]) +{ + 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] ^= 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]; + } +} // AES_Mix_Collums + + + +/* +***************************************************************************************** +* Title : AES_Calculate_Round_Key +* Description : +***************************************************************************************** +*/ +void LoRaWAN::AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key) +{ + unsigned char i, j, b, Rcon; + unsigned char Temp[4]; + + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + + if(b == 0x80) + { + Rcon ^= 0x1b; + } + Round--; + } + + // Calculate first Temp + // Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1. + Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] ); + Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] ); + Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] ); + Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] ); + + // XOR with Rcon + Temp[0] ^= Rcon; + + // Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (i << 2)] ^= Temp[j]; + Temp[j] = Round_Key[j + (i << 2)]; + } + } +} // AES_Calculate_Round_Key diff --git a/LoRaWAN.h b/LoRaWAN.h new file mode 100755 index 0000000..ee226c8 --- /dev/null +++ b/LoRaWAN.h @@ -0,0 +1,70 @@ +/* + LoRaWAN.h - Library header file for LoRaWAN protocol, uses RFM95W module + Created by Leo Korbee, March 31, 2018. + Released into the public domain. + @license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) + Thanks to all the folks who contributed before me on this code. + +*/ + +#include "RFM95.h" +#include "Arduino.h" + +#ifndef LoRaWAN_h +#define LoRaWAN_h + + +// for AES encryption +static const unsigned char PROGMEM 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} +}; + + +class LoRaWAN +{ + public: + LoRaWAN(RFM95 &rfm95); + void setKeys(unsigned char NwkSkey[], unsigned char AppSkey[], unsigned char DevAddr[]); + void Send_Data(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter_Tx, lora_dr_t datarate,unsigned char Frame_Port); + + private: + RFM95 *_rfm95; + // remember arrays are pointers! + unsigned char *_NwkSkey; + unsigned char *_AppSkey; + unsigned char *_DevAddr; + + void RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length); + // security stuff: + void Encrypt_Payload(unsigned char *Data, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction); + void Calculate_MIC(unsigned char *Data, unsigned char *Final_MIC, unsigned char Data_Length, unsigned int Frame_Counter, unsigned char Direction); + void Generate_Keys(unsigned char *K1, unsigned char *K2); + void Shift_Left(unsigned char *Data); + void XOR(unsigned char *New_Data,unsigned char *Old_Data); + void AES_Encrypt(unsigned char *Data, unsigned char *Key); + void AES_Add_Round_Key(unsigned char *Round_Key, unsigned char (*State)[4]); + unsigned char AES_Sub_Byte(unsigned char Byte); + void AES_Shift_Rows(unsigned char (*State)[4]); + void AES_Mix_Collums(unsigned char (*State)[4]); + void AES_Calculate_Round_Key(unsigned char Round, unsigned char *Round_Key); + + +}; + + +#endif diff --git a/README.md b/README.md new file mode 100644 index 0000000..93132be --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# ATTNode v3 Firmware (WiP) + +THIS IS STILL WORK IN PROGRESS AND VERY MUCH EXPERIMENTAL! + +This is the Work in Progress Repository for ATTNode v3 compatible firmware. + +As there is no PlatformIO Support for the ATTiny3216 yet, it is (for now) developed using Arduino IDE and the [MegaTinyCore](https://github.com/SpenceKonde/megaTinyCore) + +Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI) - for other pogramming variants see the MegaTinyCore documentation. diff --git a/RFM95.cpp b/RFM95.cpp new file mode 100755 index 0000000..83f7ee7 --- /dev/null +++ b/RFM95.cpp @@ -0,0 +1,342 @@ +/* + RFM95.cpp - Library for RFM95 LoRa module. + Created by Leo Korbee, March 31, 2018. + Released into the public domain. + @license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) + Thanks to all the folks who contributed on the base of this code. + (Gerben den Hartog, et al - Ideetron.nl) +*/ + +#include "Arduino.h" +#include "RFM95.h" +#include + +// constructor +RFM95::RFM95(int DIO0, int NSS) +{ + _DIO0 = DIO0; + _NSS = NSS; + // init tinySPI + SPI.setDataMode(SPI_MODE0); + SPI.begin(); +} + + +/* +***************************************************************************************** +* Description: Function used to initialize the RFM module on startup +***************************************************************************************** +*/ +void RFM95::init() +{ + // set pinmodes input/output + pinMode(_NSS, OUTPUT); + pinMode(_DIO0, INPUT); + + // Set default Datarate Config SF7BW125 + _sf = 0x74; + _bw = 0x72; + _mc = 0x04; + + // NSS for starting and stopping communication with the RFM95 module + digitalWrite(_NSS, HIGH); + + //Switch RFM to sleep + RFM_Write(0x01,0x00); + + //Set RFM in LoRa mode + RFM_Write(0x01,0x80); + + //Set RFM in Standby mode wait on mode ready + RFM_Write(0x01,0x81); + /* + while (digitalRead(DIO5) == LOW) + { + } + */ + delay(10); + + //Set carrair frequency + // 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + RFM_Write(0x06,0xD9); + RFM_Write(0x07,0x06); + RFM_Write(0x08,0x8B); + + // Minmal Power + //RFM_Write(0x09,0xF0); + + //PA pin (maximal power) + RFM_Write(0x09,0xFF); + + //BW = 125 kHz, Coding rate 4/5, Explicit header mode + RFM_Write(0x1D,0x72); + + //Spreading factor 7, PayloadCRC On + RFM_Write(0x1E,0xB4); + + //Rx Timeout set to 37 symbols + RFM_Write(0x1F,0x25); + + //Preamble length set to 8 symbols + //0x0008 + 4 = 12 + RFM_Write(0x20,0x00); + RFM_Write(0x21,0x08); + + //Low datarate optimization off AGC auto on + RFM_Write(0x26,0x0C); + + //Set LoRa sync word + RFM_Write(0x39,0x34); + + //Set IQ to normal values + RFM_Write(0x33,0x27); + RFM_Write(0x3B,0x1D); + + //Set FIFO pointers + //TX base adress + RFM_Write(0x0E,0x80); + //Rx base adress + RFM_Write(0x0F,0x00); + + //Switch RFM to sleep + RFM_Write(0x01,0x00); +} + + +/* +***************************************************************************************** +* Description : Funtion that writes a register from the RFM +* +* Arguments : RFM_Address Address of register to be written +* RFM_Data Data to be written +***************************************************************************************** +*/ + +void RFM95::RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data) +{ + //Set NSS pin Low to start communication + digitalWrite(_NSS,LOW); + + //Send Addres with MSB 1 to make it a write command + SPI.transfer(RFM_Address | 0x80); + //Send Data + SPI.transfer(RFM_Data); + + //Set NSS pin High to end communication + digitalWrite(_NSS,HIGH); +} + +/* +***************************************************************************************** +* Description : Funtion that reads a register from the RFM and returns the value +* +* Arguments : RFM_Address Address of register to be read +* +* Returns : Value of the register +***************************************************************************************** +*/ + +unsigned char RFM95::RFM_Read(unsigned char RFM_Address) +{ + unsigned char RFM_Data; + + //Set NSS pin low to start SPI communication + digitalWrite(_NSS,LOW); + + //Send Address + SPI.transfer(RFM_Address); + //Send 0x00 to be able to receive the answer from the RFM + RFM_Data = SPI.transfer(0x00); + + //Set NSS high to end communication + digitalWrite(_NSS,HIGH); + + //Return received data + return RFM_Data; +} + +/* +***************************************************************************************** +* Description : Set Datarate and Spreading Factor +* +* Arguments : datarate Lora Datarate Enum (see RFM95.h) +***************************************************************************************** +*/ + +void RFM95::RFM_Set_Datarate(lora_dr_t datarate) { + switch(datarate) { + case SF7BW125: + _sf = 0x74; + _bw = 0x72; + _mc = 0x04; + break; + case SF8BW125: + _sf = 0x84; + _bw = 0x72; + _mc = 0x04; + break; + case SF9BW125: + _sf = 0x94; + _bw = 0x72; + _mc = 0x04; + break; + case SF10BW125: + _sf = 0xA4; + _bw = 0x72; + _mc = 0x04; + break; + case SF11BW125: + _sf = 0xB4; + _bw = 0x72; + _mc = 0x0C; + break; + case SF12BW125: + _sf = 0xC4; + _bw = 0x72; + _mc = 0x0C; + break; + default: + _sf = 0x74; + _bw = 0x72; + _mc = 0x04; + break; + } +} + +/* +***************************************************************************************** +* Description : Function for sending a package with the RFM +* +* Arguments : *RFM_Tx_Package Pointer to arry with data to be send +* Package_Length Length of the package to send +***************************************************************************************** +*/ + +void RFM95::RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length) +{ + unsigned char i; + // unsigned char RFM_Tx_Location = 0x00; + + //Set RFM in Standby mode wait on mode ready + + RFM_Write(0x01,0x81); + /* + while (digitalRead(DIO5) == LOW) + { + } + */ + delay(10); + + //Switch DIO0 to TxDone + //RFM_Write(0x40,0x40); + //Set carrier frequency + + /* + fixed frequency + // 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + _rfm95.RFM_Write(0x06,0xD9); + _rfm95.RFM_Write(0x07,0x06); + _rfm95.RFM_Write(0x08,0x8B); + */ + + +//Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B +RFM_Write(0x06,0xD9); +RFM_Write(0x07,0x06); +RFM_Write(0x08,0x8B); + // EU863-870 specifications + /* + // TCNT0 is timer0 continous timer, kind of random selection of frequency + switch (TCNT0 % 8) + { + case 0x00: //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + RFM_Write(0x06,0xD9); + RFM_Write(0x07,0x06); + RFM_Write(0x08,0x8B); + break; + case 0x01: //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358 + RFM_Write(0x06,0xD9); + RFM_Write(0x07,0x13); + RFM_Write(0x08,0x58); + break; + case 0x02: //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024 + RFM_Write(0x06,0xD9); + RFM_Write(0x07,0x20); + RFM_Write(0x08,0x24); + break; + // added five more channels + case 0x03: // Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B + RFM_Write(0x06,0xD8); + RFM_Write(0x07,0xC6); + RFM_Write(0x08,0x8B); + break; + case 0x04: // Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358 + RFM_Write(0x06,0xD8); + RFM_Write(0x07,0xD3); + RFM_Write(0x08,0x58); + break; + case 0x05: // Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024 + RFM_Write(0x06,0xD8); + RFM_Write(0x07,0xE0); + RFM_Write(0x08,0x24); + break; + case 0x06: // Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1 + RFM_Write(0x06,0xD8); + RFM_Write(0x07,0xEC); + RFM_Write(0x08,0xF1); + break; + case 0x07: // Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE + RFM_Write(0x06,0xD8); + RFM_Write(0x07,0xF9); + RFM_Write(0x08,0xBE); + break; + // FSK 868.800 Mhz => not used in this config + // 869.525 - SF9BW125 (RX2 downlink only) for package received + + } +*/ + + //SF7 BW 125 kHz + RFM_Write(0x1E,_sf); //SF7 CRC On + RFM_Write(0x1D,_bw); //125 kHz 4/5 coding rate explicit header mode + RFM_Write(0x26,_mc); //Low datarate optimization off AGC auto on + + //Set IQ to normal values + RFM_Write(0x33,0x27); + RFM_Write(0x3B,0x1D); + + //Set payload length to the right length + RFM_Write(0x22,Package_Length); + + //Get location of Tx part of FiFo + //RFM_Tx_Location = RFM_Read(0x0E); + + //Set SPI pointer to start of Tx part in FiFo + //RFM_Write(0x0D,RFM_Tx_Location); + RFM_Write(0x0D,0x80); // hardcoded fifo location according RFM95 specs + + //Write Payload to FiFo + for (i = 0;i < Package_Length; i++) + { + RFM_Write(0x00,*RFM_Tx_Package); + RFM_Tx_Package++; + } + + //Switch RFM to Tx + RFM_Write(0x01,0x83); + + //Wait for TxDone + //while( digitalRead(_DIO0) == LOW ) + //{ + //} + + while((RFM_Read(0x12) & 0x08) != 0x08) + { + } + + //Clear interrupt + RFM_Write(0x12,0x08); + + //Switch RFM to sleep + RFM_Write(0x01,0x00); +} diff --git a/RFM95.h b/RFM95.h new file mode 100755 index 0000000..e15f063 --- /dev/null +++ b/RFM95.h @@ -0,0 +1,41 @@ +/* + RFM95.h - Library header file for RFM95 LoRa module. + Created by Leo Korbee, March 31, 2018. + Released into the public domain. + @license Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) + Thanks to all the folks who contributed beforme me on this code. + +*/ + +#ifndef RFM95_h +#define RMF95_h + +#include "Arduino.h" + +typedef enum lora_dr +{ + SF7BW125, + SF8BW125, + SF9BW125, + SF10BW125, + SF11BW125, + SF12BW125, +} lora_dr_t; + +class RFM95 +{ + public: + RFM95(int DIO0, int NSS); + void init(); + void RFM_Write(unsigned char RFM_Address, unsigned char RFM_Data); + unsigned char RFM_Read(unsigned char RFM_Address); + void RFM_Send_Package(unsigned char *RFM_Tx_Package, unsigned char Package_Length); + void RFM_Set_Datarate(lora_dr_t datarate); + private: + int _DIO0; + int _NSS; + unsigned char _sf, _bw, _mc; +}; + + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..6d2b927 --- /dev/null +++ b/config.h @@ -0,0 +1,12 @@ + + + // Onboard LED is on PA7 + #define LED_PIN PIN_PA7 + + // How many 32s Intervals to sleep - so 2 SLEEP_TIME 1 == 32s + #define SLEEP_TIME 10 + + // Information from The Things Network, device configuration ACTIVATION METHOD: ABP, msb left + unsigned char NwkSkey[16] = { 0x39, 0x15, 0x63, 0x65, 0x22, 0xD4, 0xD1, 0x04, 0x6E, 0x3C, 0x68, 0xB2, 0xE7, 0xE3, 0x3C, 0x1D }; + unsigned char AppSkey[16] = { 0xE6, 0xC2, 0x51, 0x4A, 0xF4, 0x3A, 0x1D, 0x7C, 0x31, 0x83, 0x2C, 0xE6, 0xA6, 0x5E, 0x41, 0x1D }; + unsigned char DevAddr[4] = { 0x26, 0x01, 0x38, 0xB3 }; diff --git a/v3_firmware.ino b/v3_firmware.ino new file mode 100644 index 0000000..648ac5e --- /dev/null +++ b/v3_firmware.ino @@ -0,0 +1,207 @@ + +#include +#include +#include +#include +#include +#include +#include "LoRaWAN.h" + +#define F_CPU 1000000UL + +#include "config.h" + + +// For Storing Frame Counter + +// Objects and Variables for LoRa +#define DIO0 PIN_PA4 +#define NSS PIN_PA5 +RFM95 rfm(DIO0, NSS); +LoRaWAN lora = LoRaWAN(rfm); +uint16_t Frame_Counter_Tx = 0x0000; + +// Global Variables for DeepSleep +volatile uint16_t counter = SLEEP_TIME; + +// List of unused Pins - will be disabled for Power Saving +const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB3, PIN_PB2, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0}; + +//ISR Routine for Sleep +ISR(RTC_PIT_vect) +{ + /* Clear interrupt flag by writing '1' (required) */ + RTC.PITINTFLAGS = RTC_PI_bm; + if (counter >= SLEEP_TIME) { + counter = 0; + } + else { + counter++; + } +} + +// Sleep Routine +void sleep_32s() { + Wire.end(); + SPI.end(); + cli(); + while (RTC.PITSTATUS > 0) {} + RTC.PITINTCTRL = RTC_PI_bm; + // 32 Sekunden + RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm; + while (RTC.PITSTATUS & RTC_CTRLBUSY_bm) {} + set_sleep_mode(SLEEP_MODE_PWR_DOWN); + sleep_enable(); + sei(); + sleep_cpu(); + sleep_disable(); + sei(); +} + +// Get Battery Voltage +uint16_t readSupplyVoltage() { //returns value in millivolts to avoid floating point + uint16_t temp = 0; + analogReference(VDD); + VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; + ADC0.CTRLD = ADC_INITDLY_DLY256_gc; + ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc; + uint16_t reading = analogRead(ADC_INTREF); + temp = reading / 64; + uint32_t intermediate = 1534500; + reading = 0; + reading = intermediate / temp; + return reading; +} + +// Get CPU Temp +uint16_t readTemp() { + //based on the datasheet, in section 30.3.2.5 Temperature Measurement + int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row + uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row + analogReference(INTERNAL1V1); + ADC0.SAMPCTRL = 0x1F; //Appears very necessary! + ADC0.CTRLD |= ADC_INITDLY_DLY32_gc; //Doesn't seem so necessary? + uint16_t adc_reading = analogRead(ADC_TEMPERATURE); // ADC conversion result with 1.1 V internal reference + analogReference(VDD); + ADC0.SAMPCTRL = 0x0; + ADC0.CTRLD &= ~(ADC_INITDLY_gm); + uint32_t temp = adc_reading - sigrow_offset; + temp *= sigrow_gain; + temp += 0x80; // Add 1/2 to get correct rounding on division below + temp >>= 8; // Divide result to get Kelvin + uint16_t temperature_in_K = temp; + uint16_t temp_in_C = temperature_in_K - 273; + return temp_in_C /10; // Return Celsius temperature +} + +// Crude Wear Leveling Algorithm to Spread the EEPROM Cell Wear Over +// the first 64 Byte. Using this Method the Theoretical EEPROM Livetime +// should be around 60 Years at a 10 Minute Sending Interval +// (100000 Erase Cycles per Cell * 32 Locations / 144 Measurements a day * 365) +// +// Returns the Next EEPROM Address for Saving the Frame Counter +uint8_t calcEepromAddr(uint16_t framecounter) { + uint8_t eeprom_addr = ((framecounter % 32) * sizeof(framecounter)); + if (eeprom_addr == 0) { + eeprom_addr = 62; + } else { + eeprom_addr = eeprom_addr - sizeof(framecounter); + } + return eeprom_addr; +} + + +// Blink x times +#ifdef LED_PIN +void blink(uint8_t num) { + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, 0); + for (uint8_t i = 0; i < num * 2; i++) { + digitalWrite(LED_PIN, !digitalRead(LED_PIN)); + delay(5); + } + digitalWrite(LED_PIN, 0); +} +#endif + +void setup() +{ + // Disable unused Pins (power saving) + for (int i=0; i<(sizeof(disabledPins)/sizeof(disabledPins[0]))-1; i++) + pinMode(disabledPins[i], INPUT_PULLUP); + + Wire.begin(); + delay(250); + + // Set RTC + while (RTC.STATUS > 0) {} + RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; + while (RTC.PITSTATUS > 0) {} + + // Setup LoraWAN + rfm.init(); + lora.setKeys(NwkSkey, AppSkey, DevAddr); + + // Get Framecounter from EEPROM + // Check if EEPROM is initialized + if (EEPROM.read(120) != 0x42) { + // Set first 64 byte to 0x00 for the wear leveling hack to work + for (int i = 0; i < 64; i++) + EEPROM.write(i, 0x00); + // Write the magic value so we know it's initialized + EEPROM.write(120, 0x42); + } else { + // Get the Last Saved (=Highest) Frame Counter + uint16_t Frame_Counter_Sv = 0x00000000; + uint8_t eeprom_addr = 0x0000; + EEPROM.get(eeprom_addr, Frame_Counter_Sv); + while (eeprom_addr < 32 * sizeof(Frame_Counter_Tx)) { + if (Frame_Counter_Sv > Frame_Counter_Tx) { + Frame_Counter_Tx = Frame_Counter_Sv; + } else { + break; + } + eeprom_addr += sizeof(Frame_Counter_Tx); + EEPROM.get(eeprom_addr, Frame_Counter_Sv); + } + } + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); + blink(1); +#endif + +} + +void loop() +{ + if (counter >= SLEEP_TIME ) { + Wire.begin(); + SPI.begin(); + + // Create Datastructure for Sending + struct lora_data { + uint8_t bat; + int32_t temp; + } __attribute__ ((packed)) data; + + //Add Battery Voltage + uint32_t batv = readSupplyVoltage(); + data.bat = (uint8_t)(batv/20); + if (batv % 20 > 9) + data.bat += 1; + + // Read CPU Temperature + data.temp = readTemp(); + + // Send LoRa Packet, Increment Frame Counter + lora.Send_Data((unsigned char *)&data, sizeof(data), Frame_Counter_Tx, SF7BW125, 0x01); + + // Save the next FrameCounter to EEPROM + Frame_Counter_Tx++; + EEPROM.put(calcEepromAddr(Frame_Counter_Tx), Frame_Counter_Tx); + + } + // Sleep until next Measurement + sleep_32s(); +}