diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0e56cf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +config.h diff --git a/LoRaWAN.cpp b/LoRaWAN.cpp deleted file mode 100755 index 5b36843..0000000 --- a/LoRaWAN.cpp +++ /dev/null @@ -1,686 +0,0 @@ -/* - 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 deleted file mode 100755 index ee226c8..0000000 --- a/LoRaWAN.h +++ /dev/null @@ -1,70 +0,0 @@ -/* - 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 index 93132be..0afd64d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,23 @@ # ATTNode v3 Firmware (WiP) -THIS IS STILL WORK IN PROGRESS AND VERY MUCH EXPERIMENTAL! +## Disclaimer -This is the Work in Progress Repository for ATTNode v3 compatible firmware. +THIS IS STILL WORK IN PROGRESS! -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) +## Configuration and Programming -Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI) - for other pogramming variants see the MegaTinyCore documentation. +This is the Work in Progress Repository for ATTNode v3 compatible firmware. At the moment it supports LoRa communication using OTAA and a BME280 sensor, as well as deep sleep between measurements. + +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). You also need to set the correct Settings for programming the ATTiny3216 in ArduionIDE. Here is a screenshot of the settings I use: + +![ArduinoIDE Settings](ide_settings.png) + +You also need to install the MCCI Arduino LMIC Library form the IDEs Library Manager or from https://github.com/mcci-catena/arduino-lmic + +Before Compiling and Flashing make sure to copy config.h.example to config.h and set your LoRa OTAA Keys there. You can also set the Sending Interval and used Sensors there. + +Programming is done using a [MicroUPDI Programmer](https://github.com/MCUdude/microUPDI) - for other pogramming variants see the MegaTinyCore documentation. + +## Acknowledgements + +Parts of this code where kindly provided by [@shempe](https://twitter.com/shempe) diff --git a/RFM95.cpp b/RFM95.cpp deleted file mode 100755 index 83f7ee7..0000000 --- a/RFM95.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/* - 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 deleted file mode 100755 index e15f063..0000000 --- a/RFM95.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - 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 deleted file mode 100644 index 6d2b927..0000000 --- a/config.h +++ /dev/null @@ -1,12 +0,0 @@ - - - // 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/firmware/BME280.cpp b/firmware/BME280.cpp new file mode 100644 index 0000000..67be7a4 --- /dev/null +++ b/firmware/BME280.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include "BME280.h" + +BME280::BME280() {} + +void BME280::getCalData() { + dig_T1 = read16_LE(0x88); + dig_T2 = readS16_LE(0x8A); + dig_T3 = readS16_LE(0x8C); + + dig_P1 = read16_LE(0x8E); + dig_P2 = readS16_LE(0x90); + dig_P3 = readS16_LE(0x92); + dig_P4 = readS16_LE(0x94); + dig_P5 = readS16_LE(0x96); + dig_P6 = readS16_LE(0x98); + dig_P7 = readS16_LE(0x9A); + dig_P8 = readS16_LE(0x9C); + dig_P9 = readS16_LE(0x9E); + + dig_H1 = read8(0xA1); + dig_H2 = readS16_LE(0xE1); + dig_H3 = read8(0xE3); + dig_H4 = (read8(0xE4) << 4) | (read8(0xE5) & 0xF); + dig_H5 = (read8(0xE6) << 4) | (read8(0xE5) >> 4); + dig_H6 = (int8_t)read8(0xE7); +} + +int32_t BME280::compensate_t(int32_t adc_T) +{ + int32_t var1, var2, T; + var1 = ((((adc_T>>3) - ((int32_t)dig_T1<<1))) * ((int32_t)dig_T2)) >> 11; + var2 = (((((adc_T>>4) - ((int32_t)dig_T1)) * ((adc_T>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14; + t_fine = var1 + var2; + T = (t_fine * 5 + 128) >> 8; + return T; +} + +int32_t BME280::compensate_p(int32_t adc_P) +{ + int32_t var1, var2; + uint32_t p; + + var1 = (((int32_t)t_fine)>>1) -(int32_t)64000; + var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((int32_t)dig_P6); + var2 = var2 + ((var1*((int32_t)dig_P5))<<1);var2 = (var2>>2)+(((int32_t)dig_P4)<<16); + var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((int32_t)dig_P2) * var1)>>1))>>18; + var1 =((((32768+var1))*((int32_t)dig_P1))>>15); + if (var1 == 0) { + return 0; + } + + p = (((uint32_t)(((int32_t)1048576)-adc_P)-(var2>>12)))*3125; + if (p < 0x80000000) { + p = (p << 1) / ((uint32_t)var1); + } else { + p = (p / (uint32_t)var1) * 2; + } + var1 = (((int32_t)dig_P9) * ((int32_t)(((p>>3) * (p>>3))>>13)))>>12; + var2 = (((int32_t)(p>>2)) * ((int32_t)dig_P8))>>13; + p = (uint32_t)((int32_t)p + ((var1 + var2 + dig_P7) >> 4)); + return p; +} + +int32_t BME280::compensate_h(int32_t adc_H) +{ + int32_t v_x1_u32r; + v_x1_u32r=(t_fine-((int32_t)76800)); + v_x1_u32r=(((((adc_H<<14)-(((int32_t)dig_H4)<<20)-(((int32_t)dig_H5)*v_x1_u32r))+ + ((int32_t)16384))>>15)*(((((((v_x1_u32r*((int32_t)dig_H6))>>10)* + (((v_x1_u32r*((int32_t)dig_H3))>>11)+((int32_t)32768)))>>10)+ + ((int32_t)2097152))*((int32_t)dig_H2)+8192)>>14)); + v_x1_u32r=(v_x1_u32r-(((((v_x1_u32r>>15)*(v_x1_u32r>>15))>>7)*((int32_t)dig_H1))>>4)); + v_x1_u32r=(v_x1_u32r < 0 ? 0 : v_x1_u32r); + v_x1_u32r=(v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r); + return (uint32_t)((v_x1_u32r>>12)/10); +} + +void BME280::getData(int32_t *t, int32_t *p, int32_t *h) { + + int32_t UP, UT, UH; + int32_t rawP, rawT; + + // Trigger Measurement + // Set Sensor Config + write8(0xF2, 0b00000001); // 1x Oversampling for Humidity + write8(0xF4, 0b00100101); // 1x Oversampling for Temperature, Pressure, Forced Mode + + delay(10); + + // Read Pressure + rawP = read16(0xF7); + rawP <<= 8; + rawP |= read8(0xF9); + UP = rawP >> 4; + + // Read Temperature + rawT = read16(0xFA); + rawT <<= 8; + rawT |= read8(0xFC); + UT = rawT >> 4; + + // Read Humidity + UH = read16(0xFD); + + // Compensate Values and Return + *t = compensate_t(UT); + *p = compensate_p(UP); + *h = compensate_h(UH); +} + +uint8_t BME280::read8(uint8_t addr) { + Wire.beginTransmission(BME280_I2CADDR); + Wire.write(addr); + Wire.endTransmission(); + Wire.requestFrom(BME280_I2CADDR, 1); + uint8_t ret = Wire.read(); + return ret; +} + +uint16_t BME280::read16(uint8_t addr) { + Wire.beginTransmission(BME280_I2CADDR); + Wire.write(addr); + Wire.endTransmission(); + Wire.requestFrom(BME280_I2CADDR, 2); + uint16_t ret = (Wire.read() << 8) | Wire.read(); + return ret; +} + +uint16_t BME280::read16_LE(uint8_t addr) { + uint16_t temp = read16(addr); + return (temp >> 8) | (temp << 8); +} + +int16_t BME280::readS16(uint8_t addr) { + return (int16_t)read16(addr); +} + +int16_t BME280::readS16_LE(uint8_t addr) { + return (int16_t)read16_LE(addr); +} + +void BME280::write8(uint8_t addr, uint8_t data) { + Wire.beginTransmission(BME280_I2CADDR); + Wire.write(addr); + Wire.write(data); + Wire.endTransmission(); +} diff --git a/firmware/BME280.h b/firmware/BME280.h new file mode 100644 index 0000000..69341db --- /dev/null +++ b/firmware/BME280.h @@ -0,0 +1,42 @@ +#ifndef BME280_H +#define BME280_H + +#include + +#define BME280_I2CADDR 0x76 + +class BME280 +{ +private: + // Variables for Calibration Values + uint8_t dig_H1, dig_H3; + int8_t dig_H6; + uint16_t dig_T1, dig_P1; + int16_t dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_H2, dig_H4, dig_H5; + + // Helper Variable + int32_t t_fine; + + // Functions for Calculation compensated Values + int32_t compensate_t(int32_t); + int32_t compensate_p(int32_t); + int32_t compensate_h(int32_t); + + // Helper Functions for Reading / Writing I2C Data + uint8_t read8(uint8_t addr); + uint16_t read16(uint8_t addr); + uint16_t read16_LE(uint8_t addr); + int16_t readS16(uint8_t addr); + int16_t readS16_LE(uint8_t addr); + void write8(uint8_t addr, uint8_t data); + +public: + BME280(void); + + // Get Calibration Data from Sensor + void getCalData(void); + // Read Pressure From Sensor + void getData(int32_t *t, int32_t *p, int32_t *h); + +}; +#endif diff --git a/firmware/config.h.example b/firmware/config.h.example new file mode 100644 index 0000000..fae2c17 --- /dev/null +++ b/firmware/config.h.example @@ -0,0 +1,22 @@ + +// ATTNode v3 Onboard LED is on PIN_PA7 +#define LED_PIN PIN_PA7 + +// Define which Sensor is installed +#define HAS_BME280 + +// How many Minutes to sleep between Measuring/Sending +// Actual Sleep Time is SLEEP_TIME*2*32 Seconds due to the 32s sleep intervals of the ATTiny3216 +#define SLEEP_TIME 10 + +// Specify TTN EU Bandplan and LoRa Chip for the LMIC +// See LMIC documentation / project_config.h for more options +#define CFG_eu868 1 +#define CFG_sx1276_radio 1 + +// Keys for OTAA Mode +// APPEUI and DEVEUI from TTN, LSB! +static const u1_t PROGMEM APPEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static const u1_t PROGMEM DEVEUI[8]={ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +// APPKey from TTN, MSB! +static const u1_t PROGMEM APPKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; diff --git a/firmware/firmware.ino b/firmware/firmware.ino new file mode 100644 index 0000000..a41dde4 --- /dev/null +++ b/firmware/firmware.ino @@ -0,0 +1,181 @@ + +#include +#include +#include +#include +#include + +// Use the local config.h for LMIC Configuration +#define ARDUINO_LMIC_PROJECT_CONFIG_H config.h +#include +#include +#include "config.h" + +#ifdef HAS_BME280 +#include "BME280.h" +BME280 sensor; +#endif + +// Define some LMIC Callbacks and Variables +void os_getArtEui (u1_t* buf) { + memcpy_P(buf, APPEUI, 8); +} +void os_getDevEui (u1_t* buf) { + memcpy_P(buf, DEVEUI, 8); +} +void os_getDevKey (u1_t* buf) { + memcpy_P(buf, APPKEY, 16); +} +static osjob_t sendjob; + +// Pin-Mapping for ATTNode v3 +const lmic_pinmap lmic_pins = { + .nss = PIN_PA5, + .rxtx = LMIC_UNUSED_PIN, + .rst = LMIC_UNUSED_PIN, + .dio = {PIN_PA4, PIN_PA6, LMIC_UNUSED_PIN}, +}; + +// 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; +} + +// Sleep Routine +void sleep_32s() { + 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(); +} + +// LMIC Callback Handling +void onEvent(ev_t ev) { + switch (ev) { + case EV_JOINED: + // Disable LinkCheck + LMIC_setLinkCheckMode(0); + break; + case EV_TXCOMPLETE: + // Schedule Next Transmit + do_send(&sendjob); + // Got to sleep for specified Time + for (int i = 0; i < int(SLEEP_TIME*2); i++) + sleep_32s(); + break; + } +} + +// 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; +} + +// 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 + +// Read Sensors and Send Data +// All Sensor Code and Data Preparation goes here +void do_send(osjob_t* j) { + // Prepare LoRa Data Packet + #ifdef HAS_BME280 + struct lora_data { + uint8_t bat; + int32_t temperature; + int32_t humidity; + int32_t pressure; + } __attribute__ ((packed)) data; + #endif + + if (LMIC.opmode & OP_TXRXPEND) { + delay(10); + } else { + // Add Battery Voltage (0.2V Accuracy stored in 1 byte) + uint32_t batv = readSupplyVoltage(); + data.bat = (uint8_t)(batv / 20); + if (batv % 20 > 9) + data.bat += 1; + + #ifdef HAS_BME280 + sensor.getData(&data.temperature, &data.pressure, &data.humidity); + #endif + + // Queue Paket for Sending + LMIC_setTxData2(1, (unsigned char *)&data, sizeof(data), 0); + } +} + +void setup() +{ + // Initialize SPI and I2C + Wire.begin(); + SPI.begin(); + + // Disable unused Pins (for power saving) + for (int i = 0; i < (sizeof(disabledPins) / sizeof(disabledPins[0])) - 1; i++) + pinMode(disabledPins[i], INPUT_PULLUP); + + // Set RTC + while (RTC.STATUS > 0) {} + RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; + while (RTC.PITSTATUS > 0) {} + + // Initialize Sensor(s) + #ifdef HAS_BME280 + sensor.getCalData(); + #endif + + // Setup LMIC + os_init(); + LMIC_reset(); // Reset LMIC state and cancel all queued transmissions + LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); // Compensate for Clock Skew + LMIC.dn2Dr = DR_SF9; // Downlink Band + LMIC_setDrTxpow(DR_SF7, 14); // Default to SF7 + + // Schedule First Send (Triggers OTAA Join as well) + do_send(&sendjob); + +#ifdef LED_PIN + pinMode(LED_PIN, OUTPUT); + blink(1); +#endif +} + +void loop() +{ + // Only Run the LMIC loop here. Actual Sending Code is in do_send() + os_runloop_once(); +} diff --git a/ide_settings.png b/ide_settings.png new file mode 100644 index 0000000..1813615 Binary files /dev/null and b/ide_settings.png differ diff --git a/v3_firmware.ino b/v3_firmware.ino deleted file mode 100644 index 648ac5e..0000000 --- a/v3_firmware.ino +++ /dev/null @@ -1,207 +0,0 @@ - -#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(); -}