Use LMIC and OTAA, Implement BME280 support
This commit is contained in:
parent
8c200e3309
commit
ae5410871a
13 changed files with 414 additions and 1362 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
config.h
|
686
LoRaWAN.cpp
686
LoRaWAN.cpp
|
@ -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
|
|
70
LoRaWAN.h
70
LoRaWAN.h
|
@ -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
|
|
22
README.md
22
README.md
|
@ -1,9 +1,23 @@
|
||||||
# ATTNode v3 Firmware (WiP)
|
# 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)
|
||||||
|
|
342
RFM95.cpp
342
RFM95.cpp
|
@ -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
41
RFM95.h
|
@ -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
|
|
12
config.h
12
config.h
|
@ -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
150
firmware/BME280.cpp
Normal 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
42
firmware/BME280.h
Normal 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
22
firmware/config.h.example
Normal 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
181
firmware/firmware.ino
Normal 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
BIN
ide_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 155 KiB |
207
v3_firmware.ino
207
v3_firmware.ino
|
@ -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();
|
|
||||||
}
|
|
Loading…
Reference in a new issue