Use LMIC and OTAA, Implement BME280 support

This commit is contained in:
seiichiro 2020-12-01 21:04:27 +01:00
parent 8c200e3309
commit ae5410871a
13 changed files with 414 additions and 1362 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
config.h

View file

@ -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-
75 bits 42 bits 10 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

View file

@ -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

View file

@ -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
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)

342
RFM95.cpp
View file

@ -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 <SPI.h>
// 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);
}

41
RFM95.h
View file

@ -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

View file

@ -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 };

150
firmware/BME280.cpp Normal file
View file

@ -0,0 +1,150 @@
#include <Arduino.h>
#include <stdint.h>
#include <Wire.h>
#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();
}

42
firmware/BME280.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef BME280_H
#define BME280_H
#include <stdint.h>
#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

22
firmware/config.h.example Normal file
View file

@ -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 };

181
firmware/firmware.ino Normal file
View file

@ -0,0 +1,181 @@
#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <SPI.h>
#include <Wire.h>
// Use the local config.h for LMIC Configuration
#define ARDUINO_LMIC_PROJECT_CONFIG_H config.h
#include <lmic.h>
#include <hal/hal.h>
#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();
}

BIN
ide_settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

View file

@ -1,207 +0,0 @@
#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROM.h>
#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();
}