diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/datasheets/EPDC_UC8156.pdf b/datasheets/EPDC_UC8156.pdf new file mode 100644 index 0000000..7fec806 Binary files /dev/null and b/datasheets/EPDC_UC8156.pdf differ diff --git a/datasheets/LoRaModule_RFM95W.pdf b/datasheets/LoRaModule_RFM95W.pdf new file mode 100644 index 0000000..1a17f88 Binary files /dev/null and b/datasheets/LoRaModule_RFM95W.pdf differ diff --git a/datasheets/MCU_ATmega328P.pdf b/datasheets/MCU_ATmega328P.pdf new file mode 100644 index 0000000..e98e8dc Binary files /dev/null and b/datasheets/MCU_ATmega328P.pdf differ diff --git a/datasheets/PMIC_AEM10941.pdf b/datasheets/PMIC_AEM10941.pdf new file mode 100644 index 0000000..9bfef9c Binary files /dev/null and b/datasheets/PMIC_AEM10941.pdf differ diff --git a/datasheets/PVcell_SLMD121H04L.pdf b/datasheets/PVcell_SLMD121H04L.pdf new file mode 100644 index 0000000..b63c8f4 Binary files /dev/null and b/datasheets/PVcell_SLMD121H04L.pdf differ diff --git a/datasheets/RTC_MCP7940M.pdf b/datasheets/RTC_MCP7940M.pdf new file mode 100644 index 0000000..09b2861 Binary files /dev/null and b/datasheets/RTC_MCP7940M.pdf differ diff --git a/datasheets/SPI FLASH_W25x40cl.pdf b/datasheets/SPI FLASH_W25x40cl.pdf new file mode 100644 index 0000000..4a405a5 Binary files /dev/null and b/datasheets/SPI FLASH_W25x40cl.pdf differ diff --git a/datasheets/Unique ID_DS2401.pdf b/datasheets/Unique ID_DS2401.pdf new file mode 100644 index 0000000..b4fcab3 Binary files /dev/null and b/datasheets/Unique ID_DS2401.pdf differ diff --git a/examples/MinimalTemplate/AES-128.cpp b/examples/MinimalTemplate/AES-128.cpp new file mode 100644 index 0000000..786ca2b --- /dev/null +++ b/examples/MinimalTemplate/AES-128.cpp @@ -0,0 +1,284 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#include +#include +#include "AES-128.h" + + +uint8_t 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} +}; + + + + +/* +***************************************************************************************** +* Title : AES_Encrypt +* Description : +***************************************************************************************** +*/ +void AES_Encrypt(uint8_t *Data, uint8_t *Key) +{ + uint8_t Row, Column, Round = 0; + uint8_t Round_Key[16]; + uint8_t 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 AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]) +{ + uint8_t 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 : +***************************************************************************************** +*/ +uint8_t AES_Sub_Byte(uint8_t Byte) +{ + return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; +} // AES_Sub_Byte + + +/* +***************************************************************************************** +* Title : AES_Shift_Rows +* Description : +***************************************************************************************** +*/ +void AES_Shift_Rows(uint8_t (*State)[4]) +{ + uint8_t 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 AES_Mix_Collums(uint8_t (*State)[4]) +{ + uint8_t Row,Collum; + uint8_t 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 AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key) +{ + uint8_t i, j, b, Rcon; + uint8_t Temp[4]; + + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + + if(b == 0x80) + { + Rcon ^= 0x1b; + } + Round--; + } + + // Calculate first Temp + // Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1. + Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] ); + Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] ); + Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] ); + Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] ); + + // XOR with Rcon + Temp[0] ^= Rcon; + + // Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (i << 2)] ^= Temp[j]; + Temp[j] = Round_Key[j + (i << 2)]; + } + } +} // AES_Calculate_Round_Key + + + + + diff --git a/examples/MinimalTemplate/AES-128.h b/examples/MinimalTemplate/AES-128.h new file mode 100644 index 0000000..848907d --- /dev/null +++ b/examples/MinimalTemplate/AES-128.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#ifndef AES128_H +#define AES128_H + + #include + + /************************************************************************************** + * FUNCTION + **************************************************************************************/ + + void AES_Encrypt(uint8_t *Data, uint8_t *Key); + void AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]); + uint8_t AES_Sub_Byte(uint8_t Byte); + void AES_Shift_Rows(uint8_t (*State)[4]); + void AES_Mix_Collums(uint8_t (*State)[4]); + void AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key); + void Send_State(); + +#endif diff --git a/examples/MinimalTemplate/Cayenne_LPP.cpp b/examples/MinimalTemplate/Cayenne_LPP.cpp new file mode 100644 index 0000000..de15ff2 --- /dev/null +++ b/examples/MinimalTemplate/Cayenne_LPP.cpp @@ -0,0 +1,289 @@ +/* + * Cayenne_LPP.c + * + * Created: 4-10-2017 11:45:10 + * Author: adri + */ +#include +#include "Cayenne_LPP.h" + + +CayenneLPP::CayenneLPP(sLoRa_Message *buffer_Ptr) +{ + if(buffer_Ptr != 0) + { + buffer = buffer_Ptr; + } +} + +CayenneLPP::~CayenneLPP() +{ + buffer = 0; +} + + +void CayenneLPP::clearBuffer (void) +{ + if(buffer == 0) + { + return; + } + + // Clear the buffer and the number of bytes in it. + memset(&(buffer->Data[0]), 0, LORA_FIFO_SIZE); + buffer->Count = 0; +} + + +void CayenneLPP::addGPS(uint8_t channel, double latitude, double longitude, double altitude) +{ + if((buffer == 0) || ((buffer->Count + LPP_GPS_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + int32_t lat = (int32_t)(latitude * 10000.0); + int32_t lon = (int32_t)(longitude * 10000.0); + int32_t alt = (int32_t)(altitude * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GPS; + buffer->Data[(buffer->Count) + 2] = lat >> 16; + buffer->Data[(buffer->Count) + 3] = lat >> 8; + buffer->Data[(buffer->Count) + 4] = lat; + buffer->Data[(buffer->Count) + 5] = lon >> 16; + buffer->Data[(buffer->Count) + 6] = lon >> 8; + buffer->Data[(buffer->Count) + 7] = lon; + buffer->Data[(buffer->Count) + 8] = alt >> 16; + buffer->Data[(buffer->Count) + 9] = alt >> 8; + buffer->Data[(buffer->Count) + 10] = alt; + buffer->Count += LPP_GPS_SIZE; // 11 + return; +} + + +void CayenneLPP::addAnalogOutput(uint8_t channel, double value) // sLoRa_Message *buffer, +{ + int16_t val; + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (int16_t) (value * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_OUTPUT; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val; + buffer->Count += LPP_ANALOG_OUTPUT_SIZE; // 4 + return; +} + + +void CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_OUTPUT; + buffer->Data[(buffer->Count) + 2] = value; + + // Add the added length to the buffer counter. + buffer->Count += LPP_DIGITAL_OUTPUT_SIZE; +} + + + +void CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_INPUT; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_DIGITAL_INPUT_SIZE; +} + + + +void CayenneLPP::addAnalogInput(uint8_t channel, float value) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ANALOG_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) (value * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT_SIZE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_ANALOG_INPUT_SIZE; +} + + + +void CayenneLPP::addLuminosity(uint8_t channel, float lux) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_LUMINOSITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) lux; + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT_SIZE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_LUMINOSITY_SIZE; +} + +void CayenneLPP::addPresence(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_PRESENCE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_PRESENCE; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_PRESENCE_SIZE; +} + +void CayenneLPP::addTemperature(uint8_t channel, float celsius) +{ + uint16_t temp; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_TEMPERATURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + temp = (uint16_t) (celsius * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_TEMPERATURE; + buffer->Data[(buffer->Count) + 2] = temp >> 8; + buffer->Data[(buffer->Count) + 3] = temp >> 0; + buffer->Count += LPP_TEMPERATURE_SIZE; +} + +void CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) //sLoRa_Message *buffer, +{ + uint16_t humidity; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_RELATIVE_HUMIDITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + humidity = (uint16_t) (rh * 2.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_RELATIVE_HUMIDITY; + buffer->Data[(buffer->Count) + 2] = humidity; + buffer->Count += LPP_RELATIVE_HUMIDITY_SIZE; +} + +void CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) +{ + uint16_t x_axis, y_axis, z_axis; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ACCELEROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to unsigned integers. + x_axis = (uint16_t) (x * 1000.0); + y_axis = (uint16_t) (y * 1000.0); + z_axis = (uint16_t) (z * 1000.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ACCELEROMETER; + buffer->Data[(buffer->Count) + 2] = x_axis >> 8; + buffer->Data[(buffer->Count) + 3] = x_axis >> 0; + buffer->Data[(buffer->Count) + 4] = y_axis >> 8; + buffer->Data[(buffer->Count) + 5] = y_axis >> 0; + buffer->Data[(buffer->Count) + 6] = z_axis >> 8; + buffer->Data[(buffer->Count) + 7] = z_axis >> 0; + buffer->Count += LPP_ACCELEROMETER_SIZE; +} + +void CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) +{ + int16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_BAROMETRIC_PRESSURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t)(hpa * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_BAROMETRIC_PRESSURE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_BAROMETRIC_PRESSURE_SIZE; +} + +void CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) +{ + + int16_t vx, vy, vz; + + // Check for invalid input pointer or if adding this data will cause the message to be larger then the maximum payload size. + if((buffer == 0) || ((buffer->Count + LPP_GYROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to signed integers + vx = (int16_t)(x * 100.0); + vy = (int16_t)(y * 100.0); + vz = (int16_t)(z * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GYROMETER; + buffer->Data[(buffer->Count) + 2] = vx >> 8; + buffer->Data[(buffer->Count) + 3] = vx >> 0; + buffer->Data[(buffer->Count) + 4] = vy >> 8; + buffer->Data[(buffer->Count) + 5] = vy >> 0; + buffer->Data[(buffer->Count) + 6] = vz >> 8; + buffer->Data[(buffer->Count) + 7] = vz >> 0; + buffer->Count += LPP_GYROMETER_SIZE; +} diff --git a/examples/MinimalTemplate/Cayenne_LPP.h b/examples/MinimalTemplate/Cayenne_LPP.h new file mode 100644 index 0000000..59b67f2 --- /dev/null +++ b/examples/MinimalTemplate/Cayenne_LPP.h @@ -0,0 +1,78 @@ +/* + * Cayenne_LPP.h + * + * Created: 4-10-2017 11:45:55 + * Author: adri + */ + + +#ifndef CAYENNE_LPP_H_ +#define CAYENNE_LPP_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + + #include "lorawan_def.h" + + /****************************************************************************************** + DEFINITIONS + ******************************************************************************************/ + + #define LPP_DIGITAL_INPUT 0 // 1 byte + #define LPP_DIGITAL_OUTPUT 1 // 1 byte + #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE 102 // 1 byte, 1 + #define LPP_TEMPERATURE 103 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS 136 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + + // Data ID + Data Type + Data Size + #define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte + #define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte + #define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE_SIZE 3 // 1 byte, 1 + #define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + class CayenneLPP + { + public: + CayenneLPP (sLoRa_Message *buffer_Ptr); + ~CayenneLPP (); + + void clearBuffer (void); + void addGPS (uint8_t channel, double latitude, double longitude, double altitude); + void addAnalogOutput (uint8_t channel, double value); + void addDigitalOutput (uint8_t channel, uint8_t value); + void addDigitalInput (uint8_t channel, uint8_t value); + void addAnalogInput (uint8_t channel, float value); + void addLuminosity (uint8_t channel, float lux); + void addPresence (uint8_t channel, uint8_t value); + void addTemperature (uint8_t channel, float celsius); + void addRelativeHumidity (uint8_t channel, float rh); + void addAccelerometer (uint8_t channel, float x, float y, float z); + void addBarometricPressure (uint8_t channel, float hpa); + void addGyrometer (uint8_t channel, float x, float y, float z); + + private: + sLoRa_Message *buffer; + }; + +#endif /* CAYENNE_LPP_H_ */ diff --git a/examples/MinimalTemplate/DS2401.cpp b/examples/MinimalTemplate/DS2401.cpp new file mode 100644 index 0000000..1431eec --- /dev/null +++ b/examples/MinimalTemplate/DS2401.cpp @@ -0,0 +1,185 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include "Arduino.h" +#include "DS2401.h" +#include "lorapaper.h" + +/* +***************************************************************************************** +* Description: Function is used to read the all the bytes provide by the DS2401 +* +* Arguments: *DS_Bytes pointer to an array of 8 uint8_ts +***************************************************************************************** +*/ +bool DS_Read(uint8_t *DS_Bytes) +{ + uint8_t i; + + //Preform reset pulse + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(700); + pinMode(DS2401,INPUT); + delayMicroseconds(700); + + //Send command 0x33 LSB + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + + //Read DS bytes + for (i = 0 ; i < RAM_SIZE ; i++) + { + DS_Bytes[i] = DS_ReadByte(); + } + + //Shutdown DS2401 + digitalWrite(DS2401, HIGH); + + // Check whether the CRC matches with the last byte from the DS2401. + return DS_CheckCRC(DS_Bytes); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 1 to the DS2401 +***************************************************************************************** +*/ +void DS_WR1(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(80); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 0 to the DS2401 +***************************************************************************************** +*/ +void DS_WR0(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(80); + pinMode(DS2401,INPUT); + delayMicroseconds(2); +} + +/* +***************************************************************************************** +* Description: Function is used to read the one byte provided by the DS2401 +* +* Return: Returns the byte received from the DS2401 +***************************************************************************************** +*/ +uint8_t DS_ReadByte(void) +{ + uint8_t DS_Byte = 0; + uint8_t i, t = 1; + + for (i = 0 ; i < 8 ; i++) + { + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(10); + + if(digitalRead(DS2401) == 1) + { + DS_Byte |= t; + } + + t = t << 1; + delayMicroseconds(80); + } + return DS_Byte; +} + +/* +***************************************************************************************** +* Description : This function does a CRC check on the received data from the DS2401 +* Arguments : *DS_bytes pointer to the arry that holds the DS bytes +* Return : Returns 0x01 when CRC check is OK +***************************************************************************************** +*/ +bool DS_CheckCRC(uint8_t *DS_bytes) +{ + uint8_t DS_current_byte = 0x00; + uint8_t DS_crc = 0x00; + uint8_t DS_Polynom = 0x0C; + uint8_t DS_crc_carry = 0x00; + uint8_t i = 0x00; + uint8_t j = 0x00; + + /*Loop for all 6 DS bytes*/ + for (i = 0 ; i < 7 ; i++) + { + DS_current_byte = DS_bytes[i]; //Get first byte + + /*Calculate CRC for all bits*/ + for (j = 0 ; j < 8 ; j++) + { + /*XOR bit 0 with bit 0 of current CRC*/ + if ((DS_crc & 0x01) != (DS_current_byte & 0x01)) + { + DS_crc_carry = 0x80; + } + else + { + DS_crc_carry = 0x00; + } + + /*shift CRC*/ + DS_crc = DS_crc >> 1; + + /*Check for carry*/ + if (DS_crc_carry == 0x80) + { + DS_crc = DS_crc | DS_crc_carry; + DS_crc = DS_crc ^ DS_Polynom; + } + + DS_current_byte = DS_current_byte >> 1; + } + } + + // Check whether the calculated CRC and the last received byte match. When they do retrun true to indicate that the received bytes are valid otherwise return false + if (DS_crc == DS_bytes[7]) + { + return true; + } + return false; +} diff --git a/examples/MinimalTemplate/DS2401.h b/examples/MinimalTemplate/DS2401.h new file mode 100644 index 0000000..c03503c --- /dev/null +++ b/examples/MinimalTemplate/DS2401.h @@ -0,0 +1,47 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef DS2401_H +#define DS2401_H + + #include + #include + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RAM_SIZE 8 + + /********************************************************************************************* + FUNCTION PORTOTYPES + *********************************************************************************************/ + bool DS_Read (uint8_t *DS_Bytes); + bool DS_CheckCRC (uint8_t *DS_bytes); + void DS_WR1 (void); + void DS_WR0 (void); + uint8_t DS_ReadByte (void); + +#endif diff --git a/examples/MinimalTemplate/Encrypt.cpp b/examples/MinimalTemplate/Encrypt.cpp new file mode 100644 index 0000000..afd095b --- /dev/null +++ b/examples/MinimalTemplate/Encrypt.cpp @@ -0,0 +1,404 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "Encrypt.h" +#include "AES-128.h" +#include +#include "timers.h" +#include "lorawan_def.h" + +/* +***************************************************************************************** +* INCLUDE GLOBAL VARIABLES +***************************************************************************************** +*/ + +/* +***************************************************************************************** +* Description : Function used to encrypt and decrypt the data in a LoRaWAN data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data to de/encrypt +* *Session_Data pointer to sLoRa_Session structure +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Encrypt_Payload(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i = 0x00; + uint8_t j; + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + uint8_t Block_A[16]; + + if((data==0) || (Key==0) || (lenght == 0) || (Message == 0)) + { + return; + } + + //Calculate number of blocks + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + for(i = 0x00; 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; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_A[5] = UPSTREAM_DIR; + } + else + { + Block_A[5] = DOWNSTREAM_DIR; + } + + Block_A[6] = Message->DevAddr[3]; + Block_A[7] = Message->DevAddr[2]; + Block_A[8] = Message->DevAddr[1]; + Block_A[9] = Message->DevAddr[0]; + + Block_A[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_A[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_A[12] = 0x00; //Frame counter upper Bytes + Block_A[13] = 0x00; + Block_A[14] = 0x00; + Block_A[15] = i + 1; + + //Calculate S + AES_Encrypt(Block_A, Key); + + //Check for last block + if(i != (Number_of_Blocks - 1)) + { + for(j = 0; j < 16; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + else + { + if(Incomplete_Block_Size == 0) + { + Incomplete_Block_Size = 16; + } + for(j = 0; j < Incomplete_Block_Size; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + } +} + +/* +***************************************************************************************** +* Description : Function used to build a the data that is used for calculating the MIC of a data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data +* *Session_Data pointer to sLoRa_Session sturct +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Construct_Data_MIC(uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message) +{ + uint8_t i; + uint8_t MIC_Data[80], MIC_lenght; + uint8_t Block_B[16]; + + //Construct Block B + Block_B[0] = 0x49; + Block_B[1] = 0x00; + Block_B[2] = 0x00; + Block_B[3] = 0x00; + Block_B[4] = 0x00; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_B[5] = UPSTREAM_DIR; + } + else + { + Block_B[5] = DOWNSTREAM_DIR; + } + + Block_B[6] = Message->DevAddr[3]; + Block_B[7] = Message->DevAddr[2]; + Block_B[8] = Message->DevAddr[1]; + Block_B[9] = Message->DevAddr[0]; + + Block_B[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_B[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_B[12] = 0x00; //Frame counter upper bytes + Block_B[13] = 0x00; + Block_B[14] = 0x00; + Block_B[15] = lenght; + + //Copy Block B into MIC data + for(i = 0x00; i < 16; i++) + { + MIC_Data[i] = Block_B[i]; + } + + //Add data to it + for(i = 0x00; i < lenght; i++) + { + MIC_Data[i + 16] = data[i]; + } + + //Calculate the correct buffer length + MIC_lenght = 16 + lenght; + + //Calculate the MIC + Calculate_MIC(&MIC_Data[0], MIC_lenght, Session_Data->NwkSKey, Message); +} + +/* +***************************************************************************************** +* Description : Function used to calculate the MIC of data +* +* Arguments : *Buffer pointer to the buffer cointaining the data the MIC should be calculated from +* *Key pointer to key used for the MIC calculation +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Calculate_MIC(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i, j; + uint8_t Key_K1[16] = {0}; + uint8_t Key_K2[16] = {0}; + uint8_t Old_Data[16] = {0}; + uint8_t New_Data[16] = {0}; + + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + //Calculate number of Blocks and blocksize of last block + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + + //if there is an incomplete block at the end add 1 to the number of blocks + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + Generate_Keys(Key, Key_K1, Key_K2); + + //Preform full calculating until n-1 messsage blocks + for(j = 0x0; j < (Number_of_Blocks - 1); j++) + { + //Copy data into array + for(i = 0; i < 16; i++) + { + New_Data[i] = data[(j*16)+i]; + } + + //Preform XOR with old data + XOR(New_Data,Old_Data); + + //Preform AES encryption + AES_Encrypt(New_Data,Key); + + //Copy New_Data to Old_Data + for(i = 0; i < 16; i++) + { + Old_Data[i] = New_Data[i]; + } + } + + //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[((Number_of_Blocks -1)*16)+i]; + } + + //Preform XOR with Key 1 + XOR(New_Data, Key_K1); + + //Preform XOR with old data + XOR(New_Data, Old_Data); + + //Preform last AES routine + AES_Encrypt(New_Data, Key); + } + else + { + //Copy the remaining data and fill the rest + for(i = 0; i < 16; i++) + { + if(i < Incomplete_Block_Size) + { + New_Data[i] = data[((Number_of_Blocks -1)*16)+i]; + } + 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, Key); + } + + Message->MIC[0] = New_Data[0]; + Message->MIC[1] = New_Data[1]; + Message->MIC[2] = New_Data[2]; + Message->MIC[3] = New_Data[3]; +} + +/* +***************************************************************************************** +* Description : Function used to generate keys for the MIC calculation +* +* Arguments : *Key pointer to key used for the MIC calculation +* *K1 pointer to Key1 +* *K2 pointer ot Key2 +***************************************************************************************** +*/ +void Generate_Keys(uint8_t *Key, uint8_t *K1, uint8_t *K2) +{ + uint8_t i; + uint8_t MSB_Key; + + //Encrypt the zeros in K1 with the NwkSkey + AES_Encrypt(K1,Key); + + //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 Shift_Left(uint8_t *Data) +{ + uint8_t i; + uint8_t Overflow = 0; + //uint8_t 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 XOR(uint8_t *New_Data,uint8_t *Old_Data) +{ + uint8_t i; + + for(i = 0; i < 16; i++) + { + New_Data[i] = New_Data[i] ^ Old_Data[i]; + } +} diff --git a/examples/MinimalTemplate/Encrypt.h b/examples/MinimalTemplate/Encrypt.h new file mode 100644 index 0000000..23d29be --- /dev/null +++ b/examples/MinimalTemplate/Encrypt.h @@ -0,0 +1,50 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef ENCRYPT_H +#define ENCRYPT_H + + /* + ******************************************************************************************** + * INCLUDES + ******************************************************************************************** + */ + #include + #include "lorawan_def.h" + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + void Construct_Data_MIC (uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message); + void Calculate_MIC (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Encrypt_Payload (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Generate_Keys (uint8_t *Key, uint8_t *K1, uint8_t *K2); + void Shift_Left (uint8_t *Data); + void XOR (uint8_t *New_Data,uint8_t *Old_Data); + +#endif diff --git a/examples/MinimalTemplate/I2C.cpp b/examples/MinimalTemplate/I2C.cpp new file mode 100644 index 0000000..2cd9b3b --- /dev/null +++ b/examples/MinimalTemplate/I2C.cpp @@ -0,0 +1,66 @@ +/* + * I2C.c + * + * Created: 22-9-2017 08:48:32 + * Author: adri + */ +#include +#include "I2C.h" + + +void I2C_init (void) +{ + Wire.begin(); +} + + +void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data); + Wire.endTransmission(); +} + + +void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data, lenght); + Wire.endTransmission(); +} + + + +uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register) +{ + uint8_t retVal; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom(slaveAddress, (uint8_t)1); + retVal = Wire.read(); + Wire.endTransmission(); + return retVal; +} + + +void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + uint8_t retVal, i; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)slaveAddress, lenght); + + // Read until all bytes have been retrieved. + for( i = 0 ; i < lenght ; i++) + { + data[i] = Wire.read(); + } + + Wire.endTransmission(); +} diff --git a/examples/MinimalTemplate/I2C.h b/examples/MinimalTemplate/I2C.h new file mode 100644 index 0000000..f329aeb --- /dev/null +++ b/examples/MinimalTemplate/I2C.h @@ -0,0 +1,27 @@ +/* + * I2C.h + * + * Created: 22-9-2017 08:48:47 + * Author: adri + */ + + +#ifndef I2C_H_ +#define I2C_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + #include + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void I2C_init (void); + void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data); + void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register); + void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + +#endif /* I2C_H_ */ diff --git a/examples/MinimalTemplate/LoRaMAC.cpp b/examples/MinimalTemplate/LoRaMAC.cpp new file mode 100644 index 0000000..620b716 --- /dev/null +++ b/examples/MinimalTemplate/LoRaMAC.cpp @@ -0,0 +1,794 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include +#include +#include +#include "spi_functions.h" +#include "AES-128.h" +#include "RFM95.h" +#include "Encrypt.h" +#include "LoRaMAC.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** + FUNCTIONS +******************************************************************************************/ +LORAMAC::LORAMAC(sLoRaWAN *LoRaPTR) +{ + // Check if the pointer is not invalid. + if(LoRaPTR != NULL) + { + // Copy the LoRa structure to the pointer. + lora = LoRaPTR; + } + else + { + //Re-Initialize the RFM95 module to set it to a lowe power state. + RFM_Init(lora); + + lora = 0; + } +} + +LORAMAC::~LORAMAC() +{ + lora = NULL; +} + + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::init(void) +{ + // Check if the pointer is not invalid. + if(lora == NULL) + { + return; + } + + // Initialize the random number generation. + Init_DevNonce_Generation(); + + //Initialize the RFM95 module + RFM_Init(lora); + +} + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::OTAA_connect(void) +{ + uint8_t attempt = 0; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check if the activation method is set to ABP. If so OTAA join procedure is not required. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + return; + } + + // Set the flag for OTAA done to false. + lora->OTAA.OTAAdone = false; + + // Continue to retry the OTAA connection + while(lora->OTAA.OTAAdone == false) + { + // Wait ten seconds before attempting to connect with LoRaWAN + /* + delay(10000); + Serial.print("Sending Join Request: "); + Serial.println(attempt++, DEC); + */ + // Transmit the join request to the back-end. + LoRa_Send_JoinReq(); + + // Enable the ms tick and clear the delay variable to time the JOIN_DELAY + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for join delay 1 + while(lora->timeslot < JOIN_DELAY_1) + {} + + // Receive the Join Accept from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&lora->OTAA.TxChDr, RECEIVE_RX_TIMEOUT_MS); + + // Check if the JOIN_ACCEPT message was received in time slot 1 + if(lora->OTAA.OTAAdone == true) + { + disable_ms_tick(); + return; // No need to wait for Time slot 2. + } + + // Wait for join delay 2 + while(lora->timeslot < JOIN_DELAY_2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->OTAA.RxChDr.channel = CH10_869_525; + lora->OTAA.RxChDr.datarate = SF12_BW125kHz; + LORA_Receive_Data(&lora->OTAA.RxChDr, RECEIVE_RX_TIMEOUT_MS); + + disable_ms_tick(); + + // Check if the JOIN_ACCEPT message was received in time slot 2 + if(lora->OTAA.OTAAdone == true) + { + return; // No need to wait for Time slot 2. + } + else + { + // Increment the OTAA channel for the next join attempt + switch(lora->OTAA.TxChDr.channel) + { + case CH00_868_100: + lora->OTAA.TxChDr.channel = CH01_868_300; + break; + case CH01_868_300: + lora->OTAA.TxChDr.channel = CH02_868_500; + break; + case CH02_868_500: + default: + lora->OTAA.TxChDr.channel = CH00_868_100; + break; + } + } + } +} + + +/****************************************************************************************** +* Description : Function for transmitting data Confirmed Up and then receive the back-ends +* reply. +* Arguments : lora pointer to the complete LoRaWAN structure. +******************************************************************************************/ +void LORAMAC::LORA_send_and_receive (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Set the TX message to confirmed up, so the back-end must reply. Copy the TX settings as the LORA_Send_Data will increment the Tx channel when channel hopping is enabled. + lora->TX.Confirmation = CONFIRMED; + lora->Session.RxChDr.channel = lora->Session.TxChDr.channel; + lora->Session.RxChDr.datarate = lora->Session.TxChDr.datarate; + + // Transmit data in the TX buffer. + LORA_Send_Data(); + + // Enable the ms tick and clear the delay variable to time the RECEIVE_DELAY1 + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for time slot 1 + while(lora->timeslot < RECEIVE_DELAY1) + {} + + // Receive the back-end reply from the gateway with the settings from the Transmission. + if(LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_RX_TIMEOUT_MS) == RX_MESSAGE) + { + // On success, disable the ms tick timer and return with the data. + disable_ms_tick(); + return; // No need to wait for Receive slot 2. + } + + // Wait for time slot 2 before + while(lora->timeslot < RECEIVE_DELAY2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->Session.RxChDr.channel = CH10_869_525; + lora->Session.RxChDr.datarate = SF09_BW125kHz; + + // Disable the timer. + disable_ms_tick(); + + // Receive the reply from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_DELAY2); +} + + +/* +***************************************************************************************** +* Description : Function that is used to build a LoRaWAN data message and then transmit it. +* +* Arguments : *Data_Tx pointer to tranmit buffer +* *Session_Data pointer to sLoRa_Session structure +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LORA_Send_Data(void) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check whether the data payload is within the array size. + if(lora->TX.Count > LORA_FIFO_SIZE) + { + lora->TX.Count = LORA_FIFO_SIZE; + } + + lora->TX.Frame_Port = 1; //Frame port always 1 for now + lora->TX.Frame_Control = 0; + + //Load device address from session data into the message + lora->TX.DevAddr[0] = lora->Session.DevAddr[0]; + lora->TX.DevAddr[1] = lora->Session.DevAddr[1]; + lora->TX.DevAddr[2] = lora->Session.DevAddr[2]; + lora->TX.DevAddr[3] = lora->Session.DevAddr[3]; + + //Load the frame counter from the session data into the message + lora->TX.Frame_Counter = lora->Session.frame_counter_up; + + //Set confirmed or unconfirmed bit + if(lora->TX.Confirmation == CONFIRMED) + { + lora->TX.MAC_Header = CONFIRMED_DATA_UP; // 0x80 + } + //Confirmed + else + { + lora->TX.MAC_Header = UNCONFIRMED_DATA_UP; // 0x40 + } + + /* Build the LoRaWAN Package */ + + // Load mac header + RFM_Data[0] = (uint8_t)lora->TX.MAC_Header; + + //Load the device address in reverse order + RFM_Data[1] = lora->TX.DevAddr[3]; + RFM_Data[2] = lora->TX.DevAddr[2]; + RFM_Data[3] = lora->TX.DevAddr[1]; + RFM_Data[4] = lora->TX.DevAddr[0]; + + // Load the frame control + RFM_Data[5] = lora->TX.Frame_Control; + + //Load frame counter + RFM_Data[6] = (uint8_t)(lora->Session.frame_counter_up >> 0); + RFM_Data[7] = (uint8_t)(lora->Session.frame_counter_up >> 8); + + //Set data counter to 8 + RFM_Counter = 8; + + //If there is data load the Frame_Port field, encrypt the data and load the data + if(lora->TX.Count != 0) + { + //Load Frame port field + RFM_Data[8] = lora->TX.Frame_Port; + + //Raise package counter for the Frame Port field + RFM_Counter++; + + // Copy the data into the RFM buffer. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.Data[0]), lora->TX.Count); + + // Encrypt the copied data in the RFM_Data array, so the original contents of the TX.Data array are not overwritten by the encryption. + Encrypt_Payload(&(RFM_Data[RFM_Counter]), lora->TX.Count, &(lora->Session.AppSKey[0]), &(lora->TX)); + + //Add data Length to package counter + RFM_Counter += lora->TX.Count; + } + + //Calculate MIC + Construct_Data_MIC(&(RFM_Data[0]), RFM_Counter, &(lora->Session), &(lora->TX)); + + // Load the calculated MIC in the RFM transmit array. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.MIC[0]), 4); + + //Add MIC length to RFM package length + RFM_Counter += 4; + + // Send Package to the RFM and transmit the data. + RFM_Send_Package(RFM_Data, RFM_Counter, &(lora->Session.TxChDr), lora->Session.Transmit_Power, &(lora->CH_list)); + + // Raise Frame counter + if(lora->Session.frame_counter_up != UINT16_MAX) + { + //Raise frame counter + lora->Session.frame_counter_up++; + } + else + { + // End of session is reached. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // Clear the frame counter when using ABP + lora->Session.frame_counter_up = 0; + } + else + { + // When using OTAA begin a new session to reset the frame counter. + + } + } + + //Change channel for next message if hopping is activated + LORA_increment_tx_channel(); +} + + +/* +***************************************************************************************** +* Description : Function that is used to receive a LoRaWAN message and retrieve the data from the RFM +* Also checks on CRC, MIC and Device Address +* This function is used for Class A and C motes. +* +* Arguments : *Data_Rx pointer to receive buffer +* *Session_Data pointer to sLoRa_Session struct +* *OTAA_Data pointer to sLoRa_OTAA struct +* *Message_Rx pointer to sLoRa_Message struct used for the received message information +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +RFM_RETVAL LORAMAC::LORA_Receive_Data(eDR_CH *RxSettings, uint16_t Timeout_ms) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i, N; + uint8_t Frame_Options_Length; + uint8_t Data_Location; + uint32_t frequency; + + // Check for invalid lora pointer + if(lora == NULL) + { + return LORA_POINTER_INVALID; + } + + + // If it is a type A device switch RFM to single receive + if(lora->Mote_Class == CLASS_A) + { + lora->RX.retVal = RFM_Single_Receive(RxSettings, Timeout_ms, &(lora->CH_list)); + } + else + { + //Switch RFM to standby + RFM_Switch_Mode(0x01); + lora->RX.retVal = NEW_MESSAGE; + } + + // If there is a message received get the data from the RFM + if(lora->RX.retVal == NEW_MESSAGE) + { + lora->RX.retVal = RFM_Get_Package(&RFM_Data[0], &RFM_Counter); + + //If mote class C switch RFM back to continuous receive + if(lora->Mote_Class == CLASS_C) + { + //Switch RFM to Continuous Receive + RFM_Continuous_Receive(RxSettings, &(lora->CH_list)); + } + } + + /* Clear the DIO# pins of the RFM, then switch the RFM to sleep */ + SPI_Write(RFM_NSS, 0x12, 0xE0); + SPI_Write(RFM_NSS, 0x01, 0x80); + + // If CRC is not OK, return with CRC_NOK to indicate that the received messages is incomplete. + if(lora->RX.retVal != CRC_OK) + { + return CRC_NOK; + } + + // Get the MAC_Header + lora->RX.MAC_Header = (eMESSAGE_TYPES) RFM_Data[0]; + + //Join Accept message + switch(lora->RX.MAC_Header) // 0x20 + { + ///------------------------------------------------------------------------------------------------------------------------------------------------------ + case JOIN_ACCEPT: + + // Decrypt the data + for(i = 0 ; i < ((RFM_Counter - 1) >> 4) ; i++) + { + AES_Encrypt(&(RFM_Data[(i << 4)+1]), lora->OTAA.AppKey); + } + + // Calculate MIC + RFM_Counter -= 4; + + // Get MIC + Calculate_MIC(RFM_Data, RFM_Counter, lora->OTAA.AppKey, &(lora->RX)); + + // Check if the calculated and received MIC match or not. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC), 4) == 0) + { + lora->RX.retVal = MIC_OK; + } + else + { + // When the MIC is NOK, return the MIC_NOK_OTAA + return MIC_NOK_OTAA; + } + + // Get AppNonce + lora->OTAA.AppNonce[0] = RFM_Data[1]; + lora->OTAA.AppNonce[1] = RFM_Data[2]; + lora->OTAA.AppNonce[2] = RFM_Data[3]; + + // Get Net ID + lora->OTAA.NetID[0] = RFM_Data[4]; + lora->OTAA.NetID[1] = RFM_Data[5]; + lora->OTAA.NetID[2] = RFM_Data[6]; + + // Get session Device address + lora->Session.DevAddr[3] = RFM_Data[7]; + lora->Session.DevAddr[2] = RFM_Data[8]; + lora->Session.DevAddr[1] = RFM_Data[9]; + lora->Session.DevAddr[0] = RFM_Data[10]; + + // Get the DLsettings + lora->CH_list.rx1_dr_offset = (RFM_Data[11] & 0x70) >> 4; + lora->CH_list.rx2_dr = RFM_Data[11] & 0x0F; + + // Get the RXDelay + lora->CH_list.rx_delay = RFM_Data[12]; + + // Calculate Network Session Key + lora->Session.NwkSKey[0] = 0x01; + + // Load AppNonce + lora->Session.NwkSKey[1] = lora->OTAA.AppNonce[0]; + lora->Session.NwkSKey[2] = lora->OTAA.AppNonce[1]; + lora->Session.NwkSKey[3] = lora->OTAA.AppNonce[2]; + + // Load NetID + lora->Session.NwkSKey[4] = lora->OTAA.NetID[0]; + lora->Session.NwkSKey[5] = lora->OTAA.NetID[1]; + lora->Session.NwkSKey[6] = lora->OTAA.NetID[2]; + + // Load Dev Nonce + lora->Session.NwkSKey[7] = lora->OTAA.DevNonce[0]; + lora->Session.NwkSKey[8] = lora->OTAA.DevNonce[1]; + + // Pad with zeros + lora->Session.NwkSKey[9] = 0x00; + lora->Session.NwkSKey[10] = 0x00; + lora->Session.NwkSKey[11] = 0x00; + lora->Session.NwkSKey[12] = 0x00; + lora->Session.NwkSKey[13] = 0x00; + lora->Session.NwkSKey[14] = 0x00; + lora->Session.NwkSKey[15] = 0x00; + + // Copy to AppSkey + memcpy(lora->Session.AppSKey, lora->Session.NwkSKey, 16); + + //Change first byte of AppSKey + lora->Session.AppSKey[0] = 0x02; + + // Calculate the keys + AES_Encrypt(lora->Session.NwkSKey, lora->OTAA.AppKey); + AES_Encrypt(lora->Session.AppSKey, lora->OTAA.AppKey); + + // Reset Frame counters + lora->Session.frame_counter_up = 0; + lora->Session.frame_counter_down = 0; + + // Check for the optional CF list in the JOin Accept message + if(RFM_Counter > 13) + { + //printStringAndHex("Join Accept Message: ", RFM_Data, RFM_Counter); + // Calculate the number of frequencies + N = (RFM_Counter - 14) / 3; + + // Maximum of 5 frequencies + if(N > CFLIST_FREQUENCIES_MAX) + { + N = CFLIST_FREQUENCIES_MAX; + } + + //Serial.println("CFlist"); + + // Retrieve all frequencies and print them to the serial port. + for( i = 0 ; i < N ; i++) + { + // Construct the frequency value and multiply the value with 100 to get the frequency in Hertz. + frequency = (((uint32_t)(RFM_Data[13 + (i*3)]) << 0) | ((uint32_t)(RFM_Data[14 + (i*3)]) << 8) | ((uint32_t)(RFM_Data[15 + (i*3)]) << 16)) * 100; + + + // Check if the frequency is between the 867.0MHz and 868.0 MHz and the boundaries of the channel list haven't been reached yet. + if((frequency > 867000000) && (frequency < 868000000) && (lora->CH_list.index <= CFLIST_FREQUENCIES_MAX)) + { + // Add the retrieved frequency to the list of channel settings and increment the index; + lora->CH_list.channel[(lora->CH_list.index)] = calculate_frequency_settings(frequency); + lora->CH_list.index += 1; + } + } + } + + // Print the received variables + /* + printStringAndHex("Device Address: ", lora->Session.DevAddr, 4); + printStringAndHex("Network Session Key: ", lora->Session.NwkSKey, 16); + printStringAndHex("Application Session Key: ", lora->Session.AppSKey, 16); +*/ + //Clear Data counter + lora->RX.Count = 0; + lora->OTAA.OTAAdone = true; + lora->RX.retVal = OTAA_COMPLETE; + break; + + ///------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + case UNCONFIRMED_DATA_UP: + case UNCONFIRMED_DATA_DOWN: + case CONFIRMED_DATA_UP: + case CONFIRMED_DATA_DOWN: + + //Get device address from received data + lora->RX.DevAddr[0] = RFM_Data[4]; + lora->RX.DevAddr[1] = RFM_Data[3]; + lora->RX.DevAddr[2] = RFM_Data[2]; + lora->RX.DevAddr[3] = RFM_Data[1]; + + //Get frame control field + lora->RX.Frame_Control = RFM_Data[5]; + + //Get the frame counter by combining two bytes + lora->RX.Frame_Counter = ((uint16_t)RFM_Data[7] << 8) | (uint16_t)RFM_Data[6]; + + //Lower Package length with 4 to remove MIC length + RFM_Counter -= 4; + + //Calculate MIC + Construct_Data_MIC(&RFM_Data[0], RFM_Counter, &(lora->Session), &(lora->RX)); + + // Compare the calculated MIC and the Received MIC. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC[0]), 4) != 0) + { + lora->RX.retVal = MIC_NOK_MESSAGE; + return MIC_NOK_MESSAGE; + } + + // Compare the Received message's device address and the session device address to check whether this message was intended for this mote. + if(memcmp(lora->RX.DevAddr, lora->Session.DevAddr, 4) != 0) + { + //Return when ADDRESS_NOK + lora->RX.retVal = ADDRESS_NOK; + return ADDRESS_NOK; + } + + //Get length of frame options field + Frame_Options_Length = (lora->RX.Frame_Control & 0x0F); + + //Add length of frame options field to data location + Data_Location = 8 + Frame_Options_Length; + + //Check if there's is data in the package of not. OTherwise there might be + if(RFM_Counter == Data_Location) + { + // The Received number of bytes and the data start location match, so there's no data in the received message. + RFM_Counter = 0; + } + else + { + //Get port field when there is data + lora->RX.Frame_Port = RFM_Data[8]; + + //Calculate the amount of data in the package + lora->RX.Count = (RFM_Counter - Data_Location - 1); + + //Correct the data location by 1 for the Fport field + Data_Location++; + + // Copy and decrypt the data + memcpy(lora->RX.Data, &(RFM_Data[Data_Location]), lora->RX.Count); + + if(lora->RX.Frame_Port == 0) + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.NwkSKey, &(lora->RX)); + } + else + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.AppSKey, &(lora->RX)); + } + } + lora->RX.retVal = RX_MESSAGE; + break; + ///------------------------------------------------------------------------------------------------------------------------------------------------------- + default: + lora->RX.retVal = MAC_HEADER_NOK; + break; + } + // Return the message status + return lora->RX.retVal; +} + +/****************************************************************************************** +* Description : Function that is used to generate device nonce used in the join request function +* This is based on a pseudo random function in the Arduino library +* +* Arguments : *Devnonce pointer to the devnonce arry of withc is uint8_t[2] +******************************************************************************************/ +void LORAMAC::Generate_DevNonce(uint8_t *DevNonce) +{ + uint16_t RandNumber; + + // Check for invalid lora pointer + if(DevNonce == NULL) + { + return; + } + // Generate a random number between 0x0000 to 0xFFFF + RandNumber = random(0xFFFF); + //Serial.println(RandNumber, DEC); + + // Return the Dev Nonce value. + DevNonce[0] = (uint8_t)(RandNumber >> 0); + DevNonce[1] = (uint8_t)(RandNumber >> 8); +} + +/* +***************************************************************************************** +* Description : Function that is used to send a join request to a network. +* +* Arguments : *OTAA_Data pointer to sLoRa_OTAA struct +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LoRa_Send_JoinReq(void) +{ + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + //Initialize RFM data buffer + uint8_t data[23], lenght; + + // Set the OTAA done to false, so the higher layer code can detect if OTAA has been completed successfully + lora->OTAA.OTAAdone = false; + lora->TX.MAC_Header = JOIN_REQUEST; //Join request 0x00 + + //Construct OTAA Request message + //Load Header in package + data[0] = lora->TX.MAC_Header; + + //Load AppEUI in package + for(i = 0 ; i < 8 ; i++) + { + data[i+1] = lora->OTAA.AppEUI[7-i]; + } + + //Load DevEUI in package + for(i = 0; i < 8; i++) + { + data[i+9] = lora->OTAA.DevEUI[7-i]; + } + + //Generate DevNonce + Generate_DevNonce(&(lora->OTAA.DevNonce[0])); + + //Load DevNonce in package + data[17] = lora->OTAA.DevNonce[0]; + data[18] = lora->OTAA.DevNonce[1]; + + //Set length of package + lenght = 19; + + //Get MIC + Calculate_MIC(data, lenght, lora->OTAA.AppKey, &(lora->TX)); + + //Load MIC in package + memcpy(&(data[19]), &(lora->TX.MIC[0]), 4); + + //Set lenght of package to the right length + lenght = 23; + + //Send Package + RFM_Send_Package(data, lenght, &(lora->OTAA.TxChDr), lora->OTAA.Transmit_Power, &(lora->CH_list)); +} + + + +/******************************************************************************************************************************************************** +* Description : Initialize the random number generation seed, so all generated random numbers are new when generated. +********************************************************************************************************************************************************/ +#define EEADDR 0x3FF +void LORAMAC::Init_DevNonce_Generation(void) +{ + uint8_t RandSeed; + unsigned int addr = 0; + + RandSeed = eeprom_read_byte((uint8_t*)addr); + //Serial.print("Random Seed: "); + //Serial.println(RandSeed, HEX); + + eeprom_write_byte((uint8_t*)addr, (RandSeed+1)); + + // Initialize the Random seed with the retrieved seed value + randomSeed(RandSeed); +} + + +/******************************************************************************************************************************************************** +* Description : Increment the transmit channel according to the connection methode and the available CF list from the Over The Air Activation. +********************************************************************************************************************************************************/ +void LORAMAC::LORA_increment_tx_channel (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Check whether channel hopping is enabled or not. + if(lora->CH_list.channel_hopping_on == false) + { + return; + } + + // Increment the Transmit channel + lora->Session.TxChDr.channel = (eLoRaWAN_CHANNELS)(lora->Session.TxChDr.channel + 1); + + // Depending on the activation method, increment the transmission channel to a maximum channel + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // With ABP only three channels are available + if(lora->Session.TxChDr.channel > CH02_868_500) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } + else + { + // Check if the incremented channel is above the number of received channels from OTAA + if(lora->Session.TxChDr.channel > ((eLoRaWAN_CHANNELS) CH02_868_500 + (lora->CH_list.index))) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } +} diff --git a/examples/MinimalTemplate/LoRaMAC.h b/examples/MinimalTemplate/LoRaMAC.h new file mode 100644 index 0000000..541f01a --- /dev/null +++ b/examples/MinimalTemplate/LoRaMAC.h @@ -0,0 +1,62 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef LORAMAC_H +#define LORAMAC_H + + /********************************************************************************************* + * INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + /********************************************************************************************* + * TYPE DEFINITION + *********************************************************************************************/ + + /****************************************************************************************** + * FUNCTION PROTOTYPES + ******************************************************************************************/ + class LORAMAC + { + public: + LORAMAC(sLoRaWAN *LoRaPTR); + ~LORAMAC(); + + void init (void); + void OTAA_connect (void); + void LORA_Send_Data (void); + void LORA_send_and_receive (void); + void LORA_increment_tx_channel (void); + RFM_RETVAL LORA_Receive_Data (eDR_CH *RxSettings, uint16_t Timeout_ms); + void LoRa_Send_JoinReq (void); + void Init_DevNonce_Generation (void); + void Generate_DevNonce (uint8_t *DevNonce); + + private: + sLoRaWAN *lora; // Pointer to the LoRaWAN structure with all the settings and information + }; +#endif diff --git a/examples/MinimalTemplate/LowPower.cpp b/examples/MinimalTemplate/LowPower.cpp new file mode 100644 index 0000000..6aff586 --- /dev/null +++ b/examples/MinimalTemplate/LowPower.cpp @@ -0,0 +1,1195 @@ +/******************************************************************************* +* LowPower Library +* Version: 1.80 +* Date: 04-10-2018 +* Author: Lim Phang Moh +* Company: Rocket Scream Electronics +* Website: www.rocketscream.com +* +* This is a lightweight low power library for Arduino. +* +* This library is licensed under Creative Commons Attribution-ShareAlike 3.0 +* Unported License. +* +* Revision Description +* ======== =========== +* 1.80 Added support for ATmega88 and ATmega168P. PowerExtStandby() +* modified because not supported on Atmega88 / Atmega168 +* Contributed by mrguen. +* 1.70 Added support for ATmega644P and ATmega1284P. +* Contributed by alexreinert. +* 1.60 Added support for ATmega256RFR2. Contributed by Rodmg. +* 1.50 Fixed compiler optimization (Arduino IDE 1.6.x branch) on BOD enable +* function that causes the function to be over optimized. +* 1.40 Added support for ATSAMD21G18A. +* Library format compliant with Arduino IDE 1.5.x. +* 1.30 Added support for ATMega168, ATMega2560, ATMega1280 & ATMega32U4. +* Tested to work with Arduino IDE 1.0.1 - 1.0.4. +* 1.20 Remove typo error in idle method for checking whether Timer 0 was +* turned off. +* Remove dependecy on WProgram.h which is not required. +* Tested to work with Arduino IDE 1.0. +* 1.10 Added #ifndef for sleep_bod_disable() for compatibility with future +* Arduino IDE release. +* 1.00 Initial public release. +*******************************************************************************/ +#if defined (__AVR__) + #include + #include + #include + #include +#elif defined (__arm__) + +#else + #error "Processor architecture is not supported." +#endif + +#include "LowPower.h" + +#if defined (__AVR__) +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#ifndef sleep_bod_disable +#define sleep_bod_disable() \ +do { \ + unsigned char tempreg; \ + __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ + "ori %[tempreg], %[bods_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" "\n\t" \ + "andi %[tempreg], %[not_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" \ + : [tempreg] "=&d" (tempreg) \ + : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ + [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ + [not_bodse] "i" (~_BV(BODSE))); \ +} while (0) +#endif +#endif + +#define lowPowerBodOn(mode) \ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); + +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#define lowPowerBodOff(mode)\ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sleep_bod_disable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); +#endif + +// Some macros is still missing from AVR GCC distribution for ATmega32U4 +#if defined __AVR_ATmega32U4__ + // Timer 4 PRR bit is currently not defined in iom32u4.h + #ifndef PRTIM4 + #define PRTIM4 4 + #endif + + // Timer 4 power reduction macro is not defined currently in power.h + #ifndef power_timer4_disable + #define power_timer4_disable() (PRR1 |= (uint8_t)(1 << PRTIM4)) + #endif + + #ifndef power_timer4_enable + #define power_timer4_enable() (PRR1 &= (uint8_t)~(1 << PRTIM4)) + #endif +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega328P/168 into idle state. Please make sure you +* understand the implication and result of disabling module. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 8. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega32U4 into idle state. Please make sure you +* understand the implication and result of disabling module. +* Take note that Timer 2 is not available and USART0 is replaced +* with USART1 on ATmega32U4. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 4. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 5. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 6. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 7. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 8. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +* 10.usb USB module disable control: +* (a) USB_OFF - Turn off USB module +* (b) USB_ON - Leave USB module in its default state +*******************************************************************************/ +#if defined __AVR_ATmega32U4__ +void LowPowerClass::idle(period_t period, adc_t adc, + timer4_t timer4, timer3_t timer3, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb) +{ + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (twi == TWI_OFF) power_twi_disable(); + if (usb == USB_OFF) power_usb_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (twi == TWI_OFF) power_twi_enable(); + if (usb == USB_OFF) power_usb_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega644P & ATmega1284P into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra USART 1 compared to an ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 8. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega644P__) || defined (__AVR_ATmega1284P__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega2560 & ATmega1280 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 3, 2, 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart3 USART3 module disable control: +* (a) USART3_OFF - Turn off USART3 module +* (b) USART3_ON - Leave USART3 module in its default state +* +* 11.usart2 USART2 module disable control: +* (a) USART2_OFF - Turn off USART2 module +* (b) USART2_ON - Leave USART2 module in its default state +* +* 12.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 13.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 14.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart3 == USART3_OFF) power_usart3_disable(); + if (usart2 == USART2_OFF) power_usart2_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart3 == USART3_OFF) power_usart3_enable(); + if (usart2 == USART2_OFF) power_usart2_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega256RFR2 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 11.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 12.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega256RFR2__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + + +/******************************************************************************* +* Name: adcNoiseReduction +* Description: Putting microcontroller into ADC noise reduction state. This is +* a very useful state when using the ADC to achieve best and low +* noise signal. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::adcNoiseReduction(period_t period, adc_t adc, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_ADC); + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + } + #endif +} + +/******************************************************************************* +* Name: powerDown +* Description: Putting microcontroller into power down state. This is +* the lowest current consumption state. Use this together with +* external pin interrupt to wake up through external event +* triggering (example: RTC clockout pin, SD card detect pin). +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerDown(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_DOWN); + #else + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerSave +* Description: Putting microcontroller into power save state. This is +* the lowest current consumption state after power down. +* Use this state together with an external 32.768 kHz crystal (but +* 8/16 MHz crystal/resonator need to be removed) to provide an +* asynchronous clock source to Timer 2. Please take note that +* Timer 2 is also used by the Arduino core for PWM operation. +* Please refer to wiring.c for explanation. Removal of the external +* 8/16 MHz crystal/resonator requires the microcontroller to run +* on its internal RC oscillator which is not so accurate for time +* critical operation. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerSave(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_SAVE); + #else + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: powerStandby +* Description: Putting microcontroller into power standby state. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerStandby(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_STANDBY); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerExtStandby +* Description: Putting microcontroller into power extended standby state. This +* is different from the power standby state as it has the +* capability to run Timer 2 asynchronously. +* Not implemented on Atmega88 and Atmega168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerExtStandby(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + + #if defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) // SLEEP_MODE_EXT_STANDBY not implemented on Atmega88 / Atmega168 + #else + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_EXT_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + } + #endif + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: ISR (WDT_vect) +* Description: Watchdog Timer interrupt service routine. This routine is +* required to allow automatic WDIF and WDIE bit clearance in +* hardware. +* +*******************************************************************************/ +ISR (WDT_vect) +{ + // WDIE & WDIF is cleared in hardware upon entering this ISR + wdt_disable(); +} + +#elif defined (__arm__) +#if defined (__SAMD21G18A__) +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into idle mode. This is the lowest current +* consumption mode. Requires separate handling of clock and +* peripheral management (disabling and shutting down) to achieve +* the desired current consumption. +* +* Argument Description +* ========= =========== +* 1. idleMode Idle mode level (0, 1, 2) where IDLE_2 level provide lowest +* current consumption in this mode. +* +*******************************************************************************/ +void LowPowerClass::idle(idle_t idleMode) +{ + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + PM->SLEEP.reg = idleMode; + __DSB(); + __WFI(); +} + +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into standby mode. This is the lowest current +* consumption mode. Use this together with the built-in RTC (use +* RTCZero library) or external pin interrupt to wake up through +* external event triggering. +* +* Argument Description +* ========= =========== +* 1. NIL +* +*******************************************************************************/ +void LowPowerClass::standby() +{ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + __DSB(); + __WFI(); +} + +#else + #error "Please ensure chosen MCU is ATSAMD21G18A." +#endif +#else + #error "Processor architecture is not supported." +#endif + +LowPowerClass LowPower; diff --git a/examples/MinimalTemplate/LowPower.h b/examples/MinimalTemplate/LowPower.h new file mode 100644 index 0000000..5a8864b --- /dev/null +++ b/examples/MinimalTemplate/LowPower.h @@ -0,0 +1,173 @@ +#ifndef LowPower_h +#define LowPower_h + +#include "Arduino.h" + +enum period_t +{ + SLEEP_15MS, + SLEEP_30MS, + SLEEP_60MS, + SLEEP_120MS, + SLEEP_250MS, + SLEEP_500MS, + SLEEP_1S, + SLEEP_2S, + SLEEP_4S, + SLEEP_8S, + SLEEP_FOREVER +}; + +enum bod_t +{ + BOD_OFF, + BOD_ON +}; + +enum adc_t +{ + ADC_OFF, + ADC_ON +}; + +enum timer5_t +{ + TIMER5_OFF, + TIMER5_ON +}; + +enum timer4_t +{ + TIMER4_OFF, + TIMER4_ON +}; + +enum timer3_t +{ + TIMER3_OFF, + TIMER3_ON +}; + +enum timer2_t +{ + TIMER2_OFF, + TIMER2_ON +}; + +enum timer1_t +{ + TIMER1_OFF, + TIMER1_ON +}; + +enum timer0_t +{ + TIMER0_OFF, + TIMER0_ON +}; + +enum spi_t +{ + SPI_OFF, + SPI_ON +}; + +enum usart0_t +{ + USART0_OFF, + USART0_ON +}; + +enum usart1_t +{ + USART1_OFF, + USART1_ON +}; + +enum usart2_t +{ + USART2_OFF, + USART2_ON +}; + +enum usart3_t +{ + USART3_OFF, + USART3_ON +}; + +enum twi_t +{ + TWI_OFF, + TWI_ON +}; + +enum usb_t +{ + USB_OFF, + USB_ON +}; + +enum idle_t +{ + IDLE_0, + IDLE_1, + IDLE_2 +}; + +class LowPowerClass +{ + public: + #if defined (__AVR__) + + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega644P__ || defined (__AVR_ATmega1284P__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega2560__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega256RFR2__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega32U4__ + void idle(period_t period, adc_t adc, timer4_t timer4, + timer3_t timer3, timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb); + #else + #error "Please ensure chosen MCU is either 88, 168, 168P, 328P, 32U4, 2560 or 256RFR2." + #endif + void adcNoiseReduction(period_t period, adc_t adc, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerDown(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerSave(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerStandby(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerExtStandby(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + + #elif defined (__arm__) + + #if defined (__SAMD21G18A__) + void idle(idle_t idleMode); + void standby(); + #else + #error "Please ensure chosen MCU is ATSAMD21G18A." + #endif + + #else + + #error "Processor architecture is not supported." + + #endif +}; + +extern LowPowerClass LowPower; +#endif diff --git a/examples/MinimalTemplate/MinimalTemplate.ino b/examples/MinimalTemplate/MinimalTemplate.ino new file mode 100644 index 0000000..c31b7ea --- /dev/null +++ b/examples/MinimalTemplate/MinimalTemplate.ino @@ -0,0 +1,88 @@ +/**************************************************************************************** +* This MinimalTemplate demonstrates a simple counter, being updated everytime if there is +* sufficient energy harvested. The supercap voltage v_scap is measured minutely while +* the ATmega328p processor is in deep sleep all remaining time. Triggering is done via +* external RTC to minimize current consumption during deep sleep phase. IF the voltage is +* charged above a certain limit (ie 4.2V), an image update is triggered. +**************************************************************************************** +* File: MinimalTemplate.ino +* Author: Robert Poser +* Created on: 19-12-2019 +* Supported Hardware: LoraPaper (with RFM95, PV cell, supercap & 1.1" EPD) +* +* Libraries used in this sketch are based on the LoRaWAN stack from IDEETRON/NEXUS, for +* more infos please check this great source: https://github.com/Ideetron/Nexus-Low-Power +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +****************************************************************************************/ +#include "lorapaper.h" +#include "mcp7940.h" +#include "DS2401.h" +#include "spi_flash.h" +#include "RFM95.h" +#include "PL_microEPD44.h" + +/******** GLOBAL VARIABLES ****************************************************************/ +sTimeDate TimeDate; // RTC time and date variables +PL_microEPD epd(EPD_CS, EPD_RST, EPD_BUSY); // Initialize the EPD. + +volatile bool RTC_ALARM = false; // Interrupt variable +uint16_t v_scap, i; // V_Supercap, counter of failed downloads & max_syncs + +/********* INTERRUPTS *********************************************************************/ +ISR(INT1_vect) { // Interrupt vector for the alarm of the MCP7940 Real Time + RTC_ALARM = true; // Clock. Do not use I2C functions or long delayshere +} + +/********* MAIN ***************************************************************************/ +void setup(void) { + analogReference(EXTERNAL); // use AREF for reference voltage + + pinMode(SW_TFT, OUTPUT); // Switch for V_Scap + pinMode(DS2401, OUTPUT); // Authenticating IC DS2401p+ + pinMode(RFM_NSS, OUTPUT); // RFM95W NSS = CS + digitalWrite(DS2401, HIGH); // to save power... + digitalWrite(SW_TFT, HIGH); // to save power... + + SPI.begin(); // Initialize the SPI port + SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); + flash_power_down(); // To save power... + SPI.endTransaction(); // to save power... + SPI.end(); // to save power... + + v_scap = analogRead(A7); // 1st Dummy-read which always delivers strange values...(to skip) + I2C_init(); // Initialize I2C pins + mcp7940_init(&TimeDate, 1); // Generate minutely interrupt +} + +void loop(){ + if(RTC_ALARM == true){ // Catch the minute alarm from the RTC. + RTC_ALARM = false; // Clear the boolean. + + mcp7940_reset_minute_alarm(1); + mcp7940_read_time_and_date(&TimeDate); + + digitalWrite(SW_TFT, LOW); // Turn ON voltage divider + delay(1); // To stabilize analogRead + v_scap = analogRead(A7); // Measure V_scap + digitalWrite(SW_TFT, HIGH); // Turn OFF voltage divider + + //if (v_scap >= 320) { // Proceed only if (Vscap > 4,2V)--> DEFAULT! + if (v_scap >= 0) { // Always proceed --> Use this for debugging! + SPI.begin(); + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); + epd.begin(false); // Turn ON EPD without refresh to save power + SPI_Write(RFM_NSS, 0x01, 0x00); // Switch RFM to sleep + epd.printText("Hello World!", 1, 2, 1); // Following lines look a bit complex; due to + epd.printText(String(++i), 20, 20, 3); + epd.drawBitmapLM(90, 15, wIcon_sunny, 24, 24); // Just to demonstrate how to write little + epd.update(); // Send the framebuffer and do the update + epd.end(); // To save power... + digitalWrite(RFM_NSS, LOW); // To save power... + SPI.endTransaction(); // To save power... + SPI.end(); // To save power... + } + } else + LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); // To save power... +} diff --git a/examples/MinimalTemplate/PL_microEPD44.cpp b/examples/MinimalTemplate/PL_microEPD44.cpp new file mode 100644 index 0000000..6343c9a --- /dev/null +++ b/examples/MinimalTemplate/PL_microEPD44.cpp @@ -0,0 +1,365 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#include "LowPower.h" +#include "PL_microEPD44.h" +#include +#include "spi_functions.h" + +PL_microEPD::PL_microEPD(int8_t _cs, int8_t _rst, int8_t _busy) : Adafruit_GFX(EPD_WIDTH, +EPD_HEIGHT) { + cs = _cs; + rst = _rst; + busy = _busy; +} + +// PUBLIC + +// ****************************************************************************************** +// BEGIN - Resetting UC8156 driver IC and configuring all sorts of behind-the-scenes-settings +// By default (WHITEERASE=TRUE) a clear screen update is triggered once to erase the screen. +// ****************************************************************************************** +void PL_microEPD::begin(bool whiteErase) { + pinMode(cs, OUTPUT); + pinMode(busy, INPUT); + pinMode(rst, OUTPUT); + + delay(10); + + digitalWrite(rst, HIGH); + delay(5); + pinMode(9, OUTPUT); // VDD for EPD + digitalWrite(9, HIGH); // Enable VDD for UC8156 + delay(5); + digitalWrite(rst, LOW); // Trigger global reset + delay(5); + digitalWrite(rst, HIGH); + delay(5); + + delay(10); + + waitForBusyInactive(); + + _width=24; _height=24; nextline= _width/4; _buffersize=_width*_height/4; + _EPDwidth=148; _EPDheight=72; + writeRegister(EPD_PANELSETTING, 0x12, -1, -1, -1); + writeRegister(EPD_WRITEPXRECTSET, 0, 31, 0, 23); + writeRegister(EPD_VCOMCONFIG, 0x00, 0x00, 0x24, 0x07); + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); + writeRegister(EPD_LOADMONOWF, 0x60, -1, -1, -1); + writeRegister(EPD_INTTEMPERATURE, 0x0A, -1, -1, -1); + writeRegister(EPD_BOOSTSETTING, 0x22, 0x37, -1, -1); // DC 30%/30% (Normal/Softstart), 125KHz, 16384 CC + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + setTextColor(EPD_BLACK); //Set text color to black as default + + if (whiteErase) + WhiteErase(); //Start with a white refresh if TRUE + clear(); +} + +// ************************************************************************************ +// CLEAR - Erases the image buffer and triggers an image update and sets the cursor +// back to the origin coordinates (0,0). +// ************************************************************************************ +void PL_microEPD::clearBuffer() { + memset(buffer, 0xFF, sizeof(buffer)); + setCursor(0,0); +} + +void PL_microEPD::clear(int c) { + writeRegister(EPD_WRITEPXRECTSET, 0, 71, 0, 147); + digitalWrite(cs, LOW); + SPI.transfer(0x10); + if (c==EPD_BLACK) + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0x00); + else + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + clearBuffer(); +} + +void PL_microEPD::setCursorSegment(int x, int y) { + if (x < (_EPDwidth - _width) && y < (_EPDheight - _height)) { + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + } else { + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + writeRegister(EPD_PIXELACESSPOS, 0, 0, -1, -1); + } +} + +// ************************************************************************************ +// DRAWPIXEL - Draws pixel in the memory buffer at position X, Y with the value of the +// parameter color (2 bit value). +// ************************************************************************************ +void PL_microEPD::drawPixel(int16_t x, int16_t y, uint16_t color) { + + if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height) || (color>4 )) return; + x+=2; + uint8_t pixels = buffer[x/4 + (y) * nextline]; + switch (x%4) { //2-bit grayscale dot + case 0: buffer[x/4 + (y) * nextline] = (pixels & 0x3F) | ((uint8_t)color << 6); break; + case 1: buffer[x/4 + (y) * nextline] = (pixels & 0xCF) | ((uint8_t)color << 4); break; + case 2: buffer[x/4 + (y) * nextline] = (pixels & 0xF3) | ((uint8_t)color << 2); break; + case 3: buffer[x/4 + (y) * nextline] = (pixels & 0xFC) | (uint8_t)color; break; + } +} + +// ************************************************************************************ +// UPDATE - Triggers an image update based on the content written in the image buffer. +// There are three different updateModes supported: EPD_UPD_FULL(0) is set by default, +// achieves four greyelevels, takes about 800ms and refreshes all pixels. This is the +// update mode having the best image quality. EPD_UPD_PART(1) is a variant of the +// previous one but only changing pixels are refreshed. This results in less flickering +// for the price of a slightly higher pixel to pixel crosstalk. EPD_UPD_MONO(2) is +// again a variant of the previous update mode but only about 250ms long. this allows +// slightly faster and more responsive updates for the price of only two greylevels +// being supported (EPD_BLACK and EPD_WHITE). Depending on your application it is +// recommended to insert a full update EPD_UPD_FULL(0) after a couple of mono updates +// to increase the image quality. +// THIS KIND OF DISPLAY IS NOT SUITED FOR LONG RUNNING ANIMATIONS OR APPLICATIONS WITH +// CONTINUOUSLY HIGH UPDATE RATES. AS A RULE OF THUMB PLEASE TRIGGER UPDATES IN AVERAGE +// NOT FASTER THAN MINUTELY (OR RUN BACK2BACK UPDATES NOT LONGER AS ONE HOUR PER DAY.) +// ************************************************************************************ +void PL_microEPD::update(int updateMode) { + if (updateMode==3) + powerOn(0); + //powerOn(1); + else + powerOn(); + switch (updateMode) { + case 0: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x00, -1, -1, -1); + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); + break; + case 2: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated + break; + case 3: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegister(EPD_DRIVERVOLTAGE, 0x00, 0x88, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + //writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x07, -1, -1, -1); //Only changing pixels updated (delta mode) + LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); + LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF); + break; + } + powerOff(); +} + + +// ************************************************************************************ +// SETVBORDERCOLOR - Sets the color of the VBorder around the active area. By default +// this is set to White (matching to the Paperino FrontCover) and should not be changed +// ************************************************************************************ +void PL_microEPD::setVBorderColor(int color) { + if (color==3) writeRegister(EPD_BORDERSETTING, 0xF7, -1, -1, -1); // + if (color==0) writeRegister(EPD_BORDERSETTING, 0x07, -1, -1, -1); // + update(EPD_UPD_PART); + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); // +} + +// PRIVATE + +// ************************************************************************************ +// POWERON - Activates the defined high voltages needed to update the screen. The +// command should always be called before triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOn(bool lowpower) { + waitForBusyInactive(); + writeRegister(EPD_SETRESOLUTION, 0, 239, 0, 147); + writeRegister(EPD_TCOMTIMING, 0x67, 0x55, -1, -1); // GAP=115µs; S2G=G2S=85µs @1MHz + writeRegister(EPD_POWERSEQUENCE, 0x00, 0x00, 0x00, -1); + if (lowpower) + writeRegister(EPD_POWERCONTROL, 0x81, -1, -1, -1); // TCON 500KHz; Internal clock auto-enable + else + writeRegister(EPD_POWERCONTROL, 0xC1, -1, -1, -1); // TCON 1MHz; Internal clock always enabled + while (readRegister(0x15) == 0) {} // Wait until Internal Pump is ready +} + + +// ************************************************************************************ +// POWEROFF - Deactivates the high voltages needed to update the screen. The +// command should always be called after triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOff() { + writeRegister(EPD_POWERCONTROL, 0xD0, -1, -1, -1); + waitForBusyInactive(); + writeRegister(EPD_POWERCONTROL, 0xC0, -1, -1, -1); + waitForBusyInactive(); +} + + +// ************************************************************************************ +// Fills a segment of 24 x 24 px size with text, supporting textSize(<=4) +// ************************************************************************************ +void PL_microEPD::printText(String text, int x, int y, int size){ + int i=0, xo=0, yo=0; + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + int ys = y, xs = x; + setTextSize(size); + clearBuffer(); + + while (i _width) + xs -= size*6; + x -=size*6; + i += 1; + } +} + +void PL_microEPD::fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + clearBuffer(); + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + fillRect(0, _height-h, w, _height, color); + writeRegister(EPD_WRITEPXRECTSET, y - _height, y -1, x - _width, x -1); + writeRegister(EPD_PIXELACESSPOS, y - _height, x - _width, -1, -1); + writeBuffer(); +} + +void PL_microEPD::drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h){ + clearBuffer(); + x = _EPDwidth-_width-x; + y = _EPDheight-_height-y; + drawBitmap(0, 0, bitmap, w, h, 0); // WHITE=3 or BLACK=1 + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + writeBuffer(); +} + +// ************************************************************************************ +// WRITEBUFFER - Sends the content of the memory buffer to the UC8156 driver IC. +// ************************************************************************************ +void PL_microEPD::writeBuffer(boolean previousRAM){ + if (previousRAM) + writeRegister(EPD_DATENTRYMODE, 0x37, -1, -1, -1); + else + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + //SPI_Read_Array(cs, 0x10, buffer, _buffersize); //(write)this clears the buffer..(?) + + digitalWrite(cs, LOW); + SPI.transfer(0x10); + for (int i=0; i < _buffersize; i++) + SPI.transfer(buffer[i]); + digitalWrite(cs, HIGH); + + waitForBusyInactive(); +} + + +// ************************************************************************************ +// WRITE REGISTER - Sets register ADDRESS to value VAL1 (optional: VAL2, VAL3, VAL4) +// ************************************************************************************ +void PL_microEPD::writeRegister(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + waitForBusyInactive(); +} + +void PL_microEPD::writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + //waitForBusyInactive(EPD_TMG_SR2); +} +// ************************************************************************************ +// READREGISTER - Returning the value of the register at the specified address +// ************************************************************************************ +byte PL_microEPD::readRegister(char address){ + byte data; + digitalWrite(cs, LOW); + SPI.transfer(address | EPD_REGREAD); + data = SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + waitForBusyInactive(); + return data; // can be improved +} + + +// ************************************************************************************ +// WHITE ERASE - Triggers two white updates to erase the screen and set back previous +// ghosting. Recommended after each power cycling. +// ************************************************************************************ +void PL_microEPD::WhiteErase() { + clear(EPD_WHITE); + update(); +} + + +// ************************************************************************************ +// WAITFORBUSYINACTIVE - Sensing to ‘Busy’ pin to detect the UC8156 driver status. +// Function returns only after driver IC is free again for listening to new commands. +// ************************************************************************************ +void PL_microEPD::waitForBusyInactive(){ + while (digitalRead(busy) == LOW) {} +} + +// ************************************************************************************ +// DEEPSLEEP - Putting the UC8156 in deep sleep mode with less than 1µA current @3.3V. +// Reset pin toggling needed to wakeup the driver IC again. +// ************************************************************************************ +void PL_microEPD::deepSleep(void) { + writeRegister(0x21, 0xff, 0xff, 0xff, 0xff); +} + +void PL_microEPD::end(void) { + digitalWrite(rst, HIGH); // Turn VDD for EPD off and put remaining + delay(5); + digitalWrite(9, LOW); // GPIOs LOW to minimize power consumption + delay(5); + digitalWrite(rst, LOW); // to around 1µA + pinMode(busy, OUTPUT); + digitalWrite(busy, LOW); + digitalWrite(cs, LOW); +} diff --git a/examples/MinimalTemplate/PL_microEPD44.h b/examples/MinimalTemplate/PL_microEPD44.h new file mode 100644 index 0000000..76cec5f --- /dev/null +++ b/examples/MinimalTemplate/PL_microEPD44.h @@ -0,0 +1,89 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#ifndef PL_microEPD_h +#define PL_microEPD_h + +#include "Adafruit_GFX.h" + +#define EPD_WIDTH (146) +#define EPD_HEIGHT (240) + +#define EPD_BLACK 0x00 +#define EPD_DGRAY 0x01 +#define EPD_LGRAY 0x02 +#define EPD_WHITE 0x03 + +#define EPD_UPD_FULL 0x00 // Triggers a Full update, 4 GL, 800ms +#define EPD_UPD_PART 0x01 // Triggers a Partial update, 4 GL, 800ms +#define EPD_UPD_MONO 0x02 // Triggers a Partial Mono update, 2 GL, 250ms +#define EPD_UPD_LOWP 0x03 // Triggers a Partial Mono update, 2 GL, 250ms, low power + +#define EPD_REVISION 0x00 // Revision, Read only +#define EPD_PANELSETTING 0x01 +#define EPD_DRIVERVOLTAGE 0x02 +#define EPD_POWERCONTROL 0x03 +#define EPD_BOOSTSETTING 0x04 +#define EPD_INTERVALSETTING 0x05 +#define EPD_TCOMTIMING 0x06 +#define EPD_INTTEMPERATURE 0x07 +#define EPD_SETRESOLUTION 0x0C +#define EPD_WRITEPXRECTSET 0x0D +#define EPD_PIXELACESSPOS 0x0E +#define EPD_DATENTRYMODE 0x0F +#define EPD_BYPASSRAM 0x12 +#define EPD_DISPLAYENGINE 0x14 +#define EPD_VCOMCONFIG 0x18 +#define EPD_BORDERSETTING 0x1D +#define EPD_POWERSEQUENCE 0x1F +#define EPD_SOFTWARERESET 0x20 +#define EPD_PROGRAMMTP 0x40 +#define EPD_MTPADDRESSSETTING 0x41 +#define EPD_LOADMONOWF 0x44 +#define EPD_REGREAD 0x80 // Instruction R/W bit set HIGH for data READ + +class PL_microEPD : public Adafruit_GFX { + +public: + PL_microEPD(int8_t _cs, int8_t _rst=-1, int8_t _busy=-1); + + void begin(bool erase=true); + void clearBuffer(); + void clear(int c=EPD_WHITE); + void drawPixel(int16_t x, int16_t y, uint16_t color); + void update(int updateMode=EPD_UPD_FULL); + void setVBorderColor(int color); + void powerOn(bool lowpower=0); + void powerOff(void); + void writeRegister(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + void writeBuffer(boolean previousRAM = false); + void printText(String text, int x, int y, int s); + void setCursorSegment(int x, int y); + void drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h); + void fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + byte buffer[144]; //buffer for 24x24 character (size 2) + void waitForBusyInactive(); + void deepSleep(void); + void end(void); + +private: + int _EPDsize, _EPDwidth, _EPDheight, _buffersize; + int cs, rst, busy; + int nextline; + void writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + byte readRegister(char address); + void scrambleBuffer(void); + //void writeBuffer(boolean previousRAM = false); + void WhiteErase(void); + +}; + +#endif diff --git a/examples/MinimalTemplate/RFM95.cpp b/examples/MinimalTemplate/RFM95.cpp new file mode 100644 index 0000000..fb117d6 --- /dev/null +++ b/examples/MinimalTemplate/RFM95.cpp @@ -0,0 +1,583 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "spi_functions.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** +* Description: Function used to initialize the RFM module on startup +******************************************************************************************/ +void RFM_Init(sLoRaWAN *lora) +{ + //Switch RFM to sleep + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x00); + + //Wait until RFM is in sleep mode + delay(10); + + //Set RFM in LoRa mode + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x80); + + //Switch RFM to standby + RFM_Switch_Mode(0x01); + + //Set channel to channel 0 868.100 MHz + RFM_Change_Channel(CH00_868_100, &(lora->CH_list)); + + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected. + */ + RFM_Set_Output_Power(0x0F); + + //Switch LNA boost on + SPI_Write(RFM_NSS, 0x0C, 0x23); + + //Set RFM To datarate 0 SF12 BW 125 kHz + RFM_Change_Datarate((eLoRaWAN_DATARATES)0x00, RECEIVE_RX_TIMEOUT_MS); + + //Rx Timeout set to 37 symbols + SPI_Write(RFM_NSS, 0x1F, 0x25); + + //Preamble length set to 8 symbols + //0x0008 + 4 = 12 + SPI_Write(RFM_NSS, 0x20, 0x00); + SPI_Write(RFM_NSS, 0x21, 0x08); + + //Set LoRa sync word + SPI_Write(RFM_NSS, 0x39, 0x34); + + //Set FIFO pointers + //Tx base address + SPI_Write(RFM_NSS, 0x0E, 0x80); + + //Rx base address + SPI_Write(RFM_NSS, 0x0F, 0x00); + + // Switch RFM to sleep + SPI_Write(RFM_NSS, 0x01, 0x80); +} + +/****************************************************************************************** +* Description : Function for sending a package with the RFM +* +* Arguments : *RFM_Tx_Package pointer to buffer with data and counter of data +* *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Send_Package(uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list) +{ + uint8_t i; + uint8_t RFM_Tx_Location = 0; + + //Set RFM in Standby mode + RFM_Switch_Mode(0x01); + + //Switch Datarate + RFM_Change_Datarate(TxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Switch Channel + RFM_Change_Channel(TxSettings->channel, list); + + //Switch RFM_DIO0 to TxDone + SPI_Write(RFM_NSS, 0x40, 0x40); + + //Set IQ to normal values + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + + // Set the Output Power + RFM_Set_Output_Power(TxPower); + + //Set payload length to the right length + SPI_Write(RFM_NSS, 0x22, lenght); + + //Get location of Tx part of FiFo + RFM_Tx_Location = SPI_Read(RFM_NSS, 0x0E); + + //Set SPI pointer to start of Tx part in FiFo + SPI_Write(RFM_NSS, 0x0D, RFM_Tx_Location); + + // Write the Payload to FiFo + SPI_Write_Array(RFM_NSS, 0x00, data, lenght); + + ////Set NSS pin Low to start communication + //digitalWrite(RFM_NSS, LOW); +// + ////Send Address with MSB 1 to make it a Write command + //SPI.transfer(0x00 | 0x80); + // + ////Send the data array + //SPI.transfer(data, lenght); +// + ////Set NSS pin High to end communication + //digitalWrite(RFM_NSS, HIGH); + + + //Switch RFM to Tx + SPI_Write(RFM_NSS, 0x01, 0x83); + + //Wait for TxDone + while(digitalRead(RFM_DIO0) == LOW) + {} + + //Clear interrupt + SPI_Write(RFM_NSS, 0x12, 0x08); +} + +/****************************************************************************************** +* Description : Function to switch RFM to single receive mode, used for Class A motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Single_Receive(eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list) +{ + RFM_RETVAL Message_Status = INIT; + + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, timeout_ms); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch RFM to Single reception + RFM_Switch_Mode(0x06); + + //Wait until RxDone or Timeout + //Wait until timeout or RxDone interrupt + while((digitalRead(RFM_DIO0) == LOW) && (digitalRead(RFM_DIO1) == LOW)) + { + } + + //Check for Timeout + if(digitalRead(RFM_DIO1) == HIGH) + { + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + Message_Status = RX_TIMEOUT; + //Serial.println("RX Timeout"); + } + + //Check for RxDone + if(digitalRead(RFM_DIO0) == HIGH) + { + Message_Status = NEW_MESSAGE; + } + + return Message_Status; +} + + +/****************************************************************************************** +* Description : Function to switch RFM to continuous receive mode, used for Class C motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Continuous_Receive(eDR_CH * RxSettings, eCHANNEL_LIST *list) +{ + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch to continuous receive + RFM_Switch_Mode(0x05); +} + +/****************************************************************************************** +* Description : Function to retrieve a message received by the RFM +* +* Arguments : *RFM_Rx_Package pointer to sBuffer struct containing the data received +* and number of bytes received +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Get_Package(uint8_t *data, uint8_t *counter) +{ + uint8_t i; + uint8_t RFM_Interrupts = 0; + uint8_t RFM_Package_Location = 0; + RFM_RETVAL Message_Status; + + // Get the RFM's interrupt status register value + RFM_Interrupts = SPI_Read(RFM_NSS, 0x12); + + // UART_Send_Data(RFM_Interrupts,0x01); + + // Check if the CRC of the received package is correct. + if((RFM_Interrupts & 0x20) != 0x20) + { + Message_Status = CRC_OK; + } + else + { + Message_Status = CRC_NOK; + } + + RFM_Package_Location = SPI_Read(RFM_NSS, 0x10); /*Read start position of received package*/ + (*counter) = SPI_Read(RFM_NSS, 0x13); /*Read length of received package*/ + + SPI_Write(RFM_NSS, 0x0D, RFM_Package_Location); /*Set SPI pointer to start of package*/ + + // Read the FIFO from the RFM in a single read operation. + SPI_Read_Array(RFM_NSS, 0x00, data, (*counter)); + + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + return Message_Status; +} + + + + +/****************************************************************************************** +* Description : Function to change the datarate of the RFM module. Setting the following +* register: Spreading factor, Bandwidth and low datarate optimization. +* +* Arguments : Datarate the datarate to set +******************************************************************************************/ +void RFM_Change_Datarate(eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms) +{ + uint16_t TimeOutSymbols; + + /* Determine the new Timeout Symbol Time depending on the Datarate. */ + switch(Datarate) + { + //------------------------------------------------------------------------------------------------------------- + case SF12_BW125kHz: //SF12 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 5); // Divide the Timeout Time by 32 (Symbol time = 32.77ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF11_BW125kHz: //SF11 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 4); // Divide the Timeout Time by 16 (Symbol time = 16.38ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF10_BW125kHz: //SF10 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 3); // Divide the Timeout Time by 8 (Symbol time = 8.19ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF09_BW125kHz: //SF9 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 2); // Divide the timeout time by 4 (Symbol time = 4.10ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF08_BW125kHz: //SF8 BW 125 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 2.05ms) + break; + + //------------------------------------------------------------------------------------------------------------- + default: + case SF07_BW125kHz: //SF7 BW 125 kHz + TimeOutSymbols = (timeout_ms); // Copy the timeout value directly (Symbol time = 1.02ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF07_BW250kHz: //SF7 BW 250 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 0.5ms) + break; + } + + /* Limit the Timeout to 1023 symbols, since the Time-out symbols register has a maximum size of 10 bits. 2^10 = 1024. */ + if(TimeOutSymbols > 1023) + { + TimeOutSymbols = 1023; + } + + /* Set the Datarate, CRC settings, the number of symbol for the required timeout and the Low Datarate optimizer On */ + switch(Datarate) + { + case SF12_BW125kHz: //SF12 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xC4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF12, CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF11_BW125kHz: //SF11 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xB4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF11 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF10_BW125kHz: //SF10 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xA4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF10 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF09_BW125kHz: //SF9 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x94 | ((uint8_t) (TimeOutSymbols >> 8))); //SF9 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF08_BW125kHz: //SF8 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x84 | ((uint8_t) (TimeOutSymbols >> 8))); //SF8 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW125kHz: //SF7 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW250kHz: //SF7 BW 250kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x82); //250 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + } +} + +/****************************************************************************************** +* Description : Function to change the channel of the RFM module. Setting the following +* register: Channel +* Arguments : Channel the channel to set +******************************************************************************************/ +void RFM_Change_Channel(eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list) +{ + uint32_t hex; + // Check if the given pointer is not invalid. + if(list == NULL) + { + return; + } + + switch(Channel) + { + default: + case CH00_868_100: //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x06); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH01_868_300: //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x13); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH02_868_500: //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x20); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH03_867_100: //Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xC6); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH04_867_300: //Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xD3); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH05_867_500: //Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xE0); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH06_867_700: //Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xEC); + SPI_Write(RFM_NSS, 0x08,0xF1); + break; + case CH07_867_900: //Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xF9); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CH10_869_525: //Receive channel 869.525 MHz / 61.035 Hz = 14246334 = 0xD961BE + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x61); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CFLIST_INDEX_1: + if(list->index >= 1) + { + hex = list->channel[0]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_2: + if(list->index >= 2) + { + hex = list->channel[1]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_3: + if(list->index >= 3) + { + hex = list->channel[2]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_4: + if(list->index >= 4) + { + hex = list->channel[3]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_5: + if(list->index >= 5) + { + hex = list->channel[4]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + } +} + + +/****************************************************************************************** +* Description : Function to calculate the RFM register value for the given frequency. +* Arguments : Transmit frequency in Hertz. +******************************************************************************************/ +uint32_t calculate_frequency_settings (uint32_t frequency_Hz) +{ + double calc; + calc = (double)frequency_Hz / 61.035; + Serial.print("Freq: "); + Serial.print((uint32_t) frequency_Hz); + Serial.print('\t'); + Serial.println((uint32_t) calc); + return calc; +} + +/****************************************************************************************** +* Description : Function to change the operation mode of the RFM. Switching mode and wait +* for mode ready flag +* DO NOT USE FOR SLEEP +* +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_Switch_Mode(uint8_t Mode) +{ + Mode |= 0x80; //Set high bit for LoRa mode + + //Switch mode on RFM module + SPI_Write(RFM_NSS, 0x01,Mode); + + //Wait on mode ready + while(digitalRead(RFM_DIO5) == LOW) + { + } +} + + +/****************************************************************************************** +* Description : +* Function to change the IQ bits in the RFM95, which will allow the RFM module to send messages with the same LoRa modulation +* as a gateway transmitter or receive message from motes just like a gateway would be able to do. +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_SetIQ(IQ_FUNCTION function) +{ + switch(function) + { + //########################################################################### + case GATEWAY_TRANSMITTER: + // IQ set to gateway transmission + SPI_Write(RFM_NSS, 0x33, 0x26); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + case MOTE_TRANSCEIVER: + // IQ set to default Mote transceiver + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + default: + case GATEWAY_RECEIVER: + //Invert IQ for receiving messages from gateway + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + break; + } +} + + +/************************************************************************************************************************************************************** +* Description : +* Function to set the transmit power of the RFM95. +* Arguments : power 0x00 - 0x0F to set the output power. Pout = 17-(15-power) +**************************************************************************************************************************************************************/ +void RFM_Set_Output_Power(uint8_t power) +{ + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected on the RFM board. Limit the output power to the maximum and set + * the output power with the last 4 bits of the register. + */ + SPI_Write(RFM_NSS, 0x09, 0xF0 | (power & 0x0F)); +} diff --git a/examples/MinimalTemplate/RFM95.h b/examples/MinimalTemplate/RFM95.h new file mode 100644 index 0000000..e9c9164 --- /dev/null +++ b/examples/MinimalTemplate/RFM95.h @@ -0,0 +1,78 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef RFM95_H +#define RFM95_H + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RECEIVE_RX_TIMEOUT_MS 500 + + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + MOTE_TRANSCEIVER, // Default settings for the RFM to function as a mote which can send messages to and receive messages from a gateway. + GATEWAY_TRANSMITTER, // IQ settings to let the RFM95 behave as an Gateway sending messages to a mote. + GATEWAY_RECEIVER // IQ settings to let the RFM95 behave as an Gateway receiving messages from a mote. + }IQ_FUNCTION; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + void RFM_Init (sLoRaWAN *lora); + void RFM_Send_Package (uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Single_Receive (eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list); + void RFM_Continuous_Receive (eDR_CH * RxSettings, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Get_Package (uint8_t *data, uint8_t *counter); + void RFM_Change_Datarate (eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms); + void RFM_Change_Channel (eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list); + uint32_t calculate_frequency_settings (uint32_t frequency_Hz); + void RFM_Switch_Mode (uint8_t Mode); + void RFM_SetIQ (IQ_FUNCTION function); + void RFM_Set_Output_Power (uint8_t power); + + /* SPI functions */ + uint8_t RFM_Read (uint8_t RFM_Address); + void RFM_Read_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + void RFM_Write (uint8_t RFM_Address, uint8_t RFM_Data); + void RFM_Write_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + +#endif diff --git a/examples/MinimalTemplate/encoder.h b/examples/MinimalTemplate/encoder.h new file mode 100644 index 0000000..2104bec --- /dev/null +++ b/examples/MinimalTemplate/encoder.h @@ -0,0 +1,32 @@ +/* +// This is the code you will need to add to the 'encoder' section in the TTN backend to let the +// weather forecast demo run properly. + +function Encoder(object, port) { + // Encode downlink messages sent as + // object to an array or buffer of bytes. + var bytes = [9]; + + bytes[0] = parseInt(object.temperature); + bytes[2] = parseInt(object.humidity); + bytes[4] = parseInt(object.date); + bytes[6] = parseInt(object.hour); + bytes[8] = parseInt(object.mins); + bytes[10] = parseInt(object.icon); + + bytes[12] = parseInt(object.rainPrecipProb0); + bytes[14] = parseInt(object.rainPrecipProb1); + bytes[16] = parseInt(object.rainPrecipProb2); + bytes[18] = parseInt(object.rainPrecipProb3); + bytes[20] = parseInt(object.rainPrecipProb4); + bytes[22] = parseInt(object.rainPrecipProb5); + bytes[24] = parseInt(object.rainPrecipProb6); + bytes[26] = parseInt(object.rainPrecipProb7); + bytes[28] = parseInt(object.rainPrecipProb8); + bytes[30] = parseInt(object.rainPrecipProb9); + bytes[32] = parseInt(object.rainPrecipProb10); + bytes[34] = parseInt(object.rainPrecipProb11); + + return bytes; +} +*/ diff --git a/examples/MinimalTemplate/lorapaper.h b/examples/MinimalTemplate/lorapaper.h new file mode 100644 index 0000000..12c7ff5 --- /dev/null +++ b/examples/MinimalTemplate/lorapaper.h @@ -0,0 +1,38 @@ +#ifndef LORAPAPER_DEMOBOARD_H_ +#define LORAPAPER_DEMOBOARD_H_ + +/****************************************************************************************** + DEFINES +/******************************************************************************************/ + +#include +#include "I2C.h" +#include "spi_functions.h" +#include "progmem.h" +#include "LowPower.h" + + +#define EPD_RST A2 +#define EPD_BUSY A1 +#define EPD_CS A3 +#define HV_BST 9 +#define SW_TFT A0 +#define DS2401 2 // One wire pin for the DS2401 +#define RTC_MFP 3 +#define RFM_DIO0 4 +#define RFM_DIO1 5 +#define RFM_DIO5 6 +#define RFM_DIO2 7 +#define RFM_NSS 10 +#define SPI_FLASH_CS 8 + + +/****************************************************************************************** + STRUCTURES +******************************************************************************************/ +typedef struct{ + uint8_t Counter = 0; + uint16_t LoRaWAN_message_interval = 1; // Variable to set the number of Timer2 timer ticks between LoRaWAN messages. +}sAPP; + +#endif diff --git a/examples/MinimalTemplate/lorawan_def.h b/examples/MinimalTemplate/lorawan_def.h new file mode 100644 index 0000000..031f283 --- /dev/null +++ b/examples/MinimalTemplate/lorawan_def.h @@ -0,0 +1,385 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Struct.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + + +#ifndef STRUCT_H +#define STRUCT_H + + #include + #include + + #define LORA_FIFO_SIZE 64 + #define CFLIST_FREQUENCIES_MAX 5 + #define DEFAULT_FREQUENCIES 3 + #define LORAWAN_MAX_PAYLOAD 52 + + /********************************************************************************************* + ENUMERATION DEFENITIONS + *********************************************************************************************/ + // LoRaWAN Mote class specification + typedef enum + { + CLASS_A, + //CLASS_B, + CLASS_C + }eLoRaWAN_MOTE_CLASS; + + // Enumeration to set the used back-end and use the TX and RX settings for that particular back-end. + typedef enum + { + MANUAL, // Manual mode for setting the TX and RX channels and data rate for OTAA and data messages + SEMTECH, // Semtech's the patent holder for the LoRaWAN chips. + The_Things_Network, // The Things Industries back-end, which is only functional for OTAA motes. ABP is barely supported and the service may have some downtime from time-to-time for maintenance. + KPN // The Dutch full-coverage LoRaWAN network provider in the Netherlands, which has LoRaWAN localization capabilities, but it's services come at a cost. + }eBACKENDS; + + /* MAC HEADER types */ + typedef enum + { + JOIN_REQUEST = 0x00, // 0000 0000 + JOIN_ACCEPT = 0x20, // 0010 0000 + UNCONFIRMED_DATA_UP = 0x40, // 0100 0000 + UNCONFIRMED_DATA_DOWN = 0x60, // 0110 0000 + CONFIRMED_DATA_UP = 0x80, // 1000 0000 + CONFIRMED_DATA_DOWN = 0xA0, // 1010 0000 + CONFIRMED_BITMASK = 0x80, + UNCONFIRMED_BITMASK = 0x40, + INIT_VAL = 0x00 + }eMESSAGE_TYPES; + + // Receive delay enumeration for OTAA Join Delay and message delays. + typedef enum + { + JOIN_DELAY_1 = 4900, + JOIN_DELAY_2 = 5900, + RECEIVE_DELAY1 = 900, + RECEIVE_DELAY2 = 1900 + }eRECEIVE_DELAY; + + + // Direction enumeration for the MIC calculation. See page 20, chapter 4.3.3 of the Lora Specification 1.02 + typedef enum + { + UPSTREAM_DIR = 0, // Uplink (Away from mote) + DOWNSTREAM_DIR = 1 // Downlink (towards the mote) + }eDIRECTION; + + // LoRaWAN Datarates + typedef enum + { + SF12_BW125kHz = 0x01, + SF11_BW125kHz = 0x02, + SF10_BW125kHz = 0x03, + SF09_BW125kHz = 0x04, + SF08_BW125kHz = 0x05, + SF07_BW125kHz = 0x06, + SF07_BW250kHz = 0x07, + SF09_BW500kHz = 0x08, + SF_BITMASK = 0x03 + }eLoRaWAN_DATARATES; + + // Channel enumeration + typedef enum + { + CH00_868_100, + CH01_868_300, + CH02_868_500, + CFLIST_INDEX_1, + CFLIST_INDEX_2, + CFLIST_INDEX_3, + CFLIST_INDEX_4, + CFLIST_INDEX_5, + CH03_867_100, + CH04_867_300, + CH05_867_500, + CH06_867_700, + CH07_867_900, + CH10_869_525 + }eLoRaWAN_CHANNELS; + + // Confirmed and unconfirmed messages type enumeration. + typedef enum + { + UNCONFIRMED, + CONFIRMED + }eCONFIRMATION; + + // Activation methods + typedef enum + { + ACTIVATION_BY_PERSONALISATION, + OVER_THE_AIR_ACTIVATION + }eMOTE_NETWORK_JOIN; + + //return values for the RFM95 + typedef enum + { + INIT, //0 Initialization error return code + CRC_OK, //1 CRC of the received message is incorrect, indicating an incomplete or corrupted message + CRC_NOK, //2 CRC of the received message is correct + MIC_OK, //3 Message Integrity Checksum is correct. + MIC_NOK_OTAA, //4 MIC of the OTAA message is incorrect. + MIC_NOK_MESSAGE, //5 MIC of the received message is incorrect. + ADDRESS_OK, //6 Device address of the received message matches that of this LoRaWAN mote. + ADDRESS_NOK, //7 The device address of the received message doesn't match to the dev address of this mote. + MAC_HEADER_NOK, //8 Message header has an incorrect value, not matching any LoRaWAN headers + RX_TIMEOUT, //9 The RFM hasn't receive any message and got a time-out instead. + OTAA_POINTER, //10 Over The Air Activation structure has an incorrect pointer + RX_MESSAGE, //11 Message received. + OTAA_COMPLETE, //12 OTAA has been successfully completed. + NEW_MESSAGE, //13 When a new message has been received. + DEFAULT_MAC, //14 When a message is received with an invalid MAC header, this return value will be send back. + LORA_POINTER_INVALID//15 Invalid Lora pointer + }RFM_RETVAL; + + + + /********************************************************************************************* + STRUCT DEFENITIONS + *********************************************************************************************/ + + // Structure for both datarate and channel as they are alway used together. + typedef struct + { + eLoRaWAN_DATARATES datarate; + eLoRaWAN_CHANNELS channel; + }eDR_CH; + + // Structure for all the RFM channel settings retrieved from the OTAA connection and the default settings + typedef struct + { + uint32_t channel[CFLIST_FREQUENCIES_MAX]; + uint8_t index; + uint8_t rx1_dr_offset; + uint8_t rx2_dr; + uint8_t rx_delay; + bool channel_hopping_on; + }eCHANNEL_LIST; + + + /* + Structure used to store session data of a LoRaWAN session if ABP is used, the following parameters must be supplied by the user + if OTAA is used, the OTAA JOIN procedure will produce the parameters for the session. + */ + typedef struct + { + uint8_t NwkSKey[16]; // The Network Session Key is used to encrypt the payload + uint8_t AppSKey[16]; // The Application Session Key is used to encrypt the payload + uint8_t DevAddr[4]; // The Device Address is used to identify the messages send and send to this Mote + uint16_t frame_counter_down; // Frame counter for messages received from the back-end + uint16_t frame_counter_up; // Frame counter for messages send to the back-end. + eRECEIVE_DELAY Receive_delay; // Wait time between transmitting an confirmed LoRaWAN message and listening to the Back-end's downlink. + uint8_t Transmit_Power; // Transmission power settings 0x00 to 0x0F. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_Session; + + + /* + Structure for connecting with Over The Air Activation to the back-end to retrieve session settings. + */ + typedef struct + { + uint8_t DevEUI[8]; // Device EUI. Unique number to identify the Mote with on the back-end. + uint8_t AppEUI[16]; // Application Key used to encrypt and calculate the session parameters with. + uint8_t AppKey[16]; // Application EUI, used to specify which Application the MOte belongs to. + uint8_t DevNonce[2]; // DevNonce is a random value that can only be used once for a join_request. The back-end will store all previously used values. + uint8_t AppNonce[3]; + uint8_t NetID[3]; + bool OTAAdone; // Boolean for indicating if the OTAA procedure was succesfull; + eRECEIVE_DELAY JoinDelay; // Join Delay for waiting an given amount of time between the JOIN and Accept messages + uint8_t Transmit_Power; // Transmission power settings for the OTAA connection. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_OTAA; + + + // Structure to store information of a LoRaWAN message to transmit or received + typedef struct + { + eMESSAGE_TYPES MAC_Header; // Message type header. + uint8_t DevAddr[4]; // Device address number used to identify the Mote with when communicating + uint8_t Frame_Control; // + uint16_t Frame_Counter; // Frame Counter of the message used for determining whether the message is received more than once. + uint8_t Frame_Port; // Frame port + uint8_t Frame_Options[15]; // Array for the frame options data, if available + uint8_t MIC[4]; // Array for the calculated result for the MIC + eCONFIRMATION Confirmation; // Either it an UNCONFIRMED or CONFIRMED message up or downstream. + RFM_RETVAL retVal; // Returns status value for receiving. Indicates whether a timeout, MIC NOK or other error status occured. + uint8_t Count; // Index count for the data array + uint8_t Data[LORA_FIFO_SIZE]; // Transmit and receive data array. + }sLoRa_Message; + + + //Structure used for storing settings of the mote + typedef struct + { + eLoRaWAN_MOTE_CLASS Mote_Class = CLASS_A; // Mote Class, only CLASS A or CLASS C are supported. + eMOTE_NETWORK_JOIN activation_method = ACTIVATION_BY_PERSONALISATION; + //eMOTE_NETWORK_JOIN activation_method = OVER_THE_AIR_ACTIVATION; // Variable used to specify whether ABP or OTAA is used for activation of the mote on the back-end. + eBACKENDS back_end = SEMTECH; // Variable to identify which back-end is used and to provide automatic OTAA and data messages channel, datarate and timeslot configuration. + bool Channel_Hopping_enabled = false; // Enables channel hopping when set to true, the channel won't be changed when set to false + volatile uint16_t timeslot; // Timing variable for timeslot 1 and 2. + + // List of additional channels in hexadecimal format pre-calculated for the RFM frequency register + eCHANNEL_LIST CH_list = + { + .channel = {0}, + .index = 0, + .rx1_dr_offset = 0, + .rx2_dr = 0, + .rx_delay = 0, + .channel_hopping_on = true + }; + + /* Session parameters for the current session */ + sLoRa_Session Session = + { + //.NwkSKey = {0xA0, 0x5B, 0x04, 0x27, 0xBC, 0xE8, 0x68, 0x61, 0xFC, 0xE3, 0xC9, 0x82, 0x43, 0x22, 0x50, 0x95}, + //.NwkSKey = {0x46, 0xB9, 0x00, 0x68, 0x46, 0xA6, 0x38, 0x38, 0xC0, 0x1E, 0xA1, 0x89, 0x98, 0xA8, 0x64, 0x49}, + //.NwkSKey = {0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}, + //.NwkSKey = {0x9D, 0x89, 0x88, 0x07, 0x44, 0x80, 0xAD, 0x9B, 0xF1, 0x3E, 0x7D, 0x7D, 0xF8, 0x6C, 0x97, 0xA3}, + //.NwkSKey = {0xB6, 0xB7, 0x01, 0x1D, 0xC7, 0x5F, 0x6A, 0x8B, 0x27, 0x36, 0xD9, 0xEE, 0x18, 0x29, 0x10, 0x03}, + //.NwkSKey = {0xC6, 0x0E, 0x1B, 0x62, 0x21, 0x72, 0xDF, 0x58, 0x2C, 0x94, 0x60, 0xE0, 0xA8, 0x3F, 0xC9, 0xFA}, + //.NwkSKey = {0x5F, 0xFE, 0xD7, 0xCD, 0x39, 0xCB, 0xA3, 0x05, 0x81, 0x6D, 0x66, 0x51, 0x98, 0x72, 0x41, 0xB1}, + //.NwkSKey = {0xB7, 0x95, 0x61, 0xDB, 0x1C, 0xF9, 0x3C, 0x8D, 0xBF, 0xF2, 0x7D, 0x8E, 0x61, 0xD8, 0x70, 0x6F}, + //.NwkSKey = {0x70, 0x12, 0x46, 0x98, 0xC5, 0xE0, 0x11, 0x7C, 0xD4, 0x5C, 0x32, 0xCE, 0xA2, 0x11, 0x07, 0x15}, + //.NwkSKey = {0x76, 0x9A, 0x6E, 0x60, 0xA5, 0xC0, 0xE3, 0xC9, 0x8F, 0x78, 0xB0, 0x89, 0xCF, 0xC1, 0x3D, 0xE4}, + //.NwkSKey = {0x7D, 0xA6, 0x9F, 0xD0, 0x9C, 0xCC, 0x72, 0x82, 0x06, 0x62, 0x90, 0x59, 0x15, 0x84, 0x29, 0x4C}, + //.NwkSKey = {0xC4, 0x35, 0x99, 0xB7, 0xD9, 0x6E, 0x85, 0x62, 0x4A, 0x1A, 0x75, 0x2F, 0x14, 0xF9, 0x49, 0x6B}, + //.NwkSKey = {0x7C, 0x47, 0xCA, 0xAD, 0xC5, 0x26, 0x41, 0x19, 0xEB, 0x16, 0x07, 0xED, 0xE6, 0xB6, 0x6D, 0xC8}, + //.NwkSKey = {0x38, 0x53, 0xD1, 0xF5, 0x58, 0x74, 0xD0, 0xC1, 0x93, 0x76, 0x81, 0x8B, 0x64, 0xC9, 0xF9, 0x1C}, + //.NwkSKey = {0xA6, 0x5F, 0xEF, 0xC0, 0x47, 0xB1, 0xC2, 0xD0, 0x28, 0x40, 0x0D, 0xDE, 0xCE, 0xA4, 0xDC, 0x2F}, + //.NwkSKey = {0x83, 0x62, 0xC7, 0xE5, 0xA3, 0xC9, 0x4A, 0x33, 0x0A, 0x43, 0xC3, 0xF9, 0x4E, 0x15, 0x9E, 0x5D}, + //.NwkSKey = {0xC1, 0x01, 0xF2, 0x71, 0x2C, 0x60, 0xB8, 0x0C, 0x46, 0xA9, 0x8C, 0xA4, 0x87, 0x45, 0x40, 0x7D}, + //.NwkSKey = {0x78, 0x87, 0x29, 0x2F, 0x32, 0x9F, 0x4F, 0xBE, 0x45, 0x1D, 0x34, 0xB3, 0x5D, 0xE1, 0xFA, 0x71}, + //.NwkSKey = {0x45, 0x1A, 0xEE, 0x9C, 0x30, 0xC5, 0xAA, 0x8C, 0x2D, 0xB4, 0xA4, 0xEB, 0x37, 0xE2, 0x0B, 0x9B}, + //.NwkSKey = {0x40, 0x6A, 0xB7, 0x95, 0x52, 0x26, 0xB2, 0x10, 0x31, 0x15, 0x11, 0x03, 0xAC, 0x08, 0xB3, 0x36}, + .NwkSKey = {0x9E, 0x13, 0x34, 0xCB, 0x39, 0x39, 0xA4, 0x8B, 0x02, 0x91, 0xA4, 0xA4, 0xB8, 0x36, 0x31, 0x26}, + .AppSKey = {0xBC, 0xDC, 0x20, 0x30, 0x55, 0x09, 0xEA, 0xC9, 0x3B, 0x65, 0x19, 0xD4, 0xA5, 0xC0, 0xD8, 0x27}, + //.AppSKey = {0x89, 0xE8, 0x41, 0xDA, 0x62, 0xE7, 0x50, 0xCA, 0xBE, 0xEF, 0x2A, 0xAA, 0x19, 0x31, 0xF9, 0x25}, + //.AppSKey = {0xDD, 0x09, 0x11, 0x07, 0x73, 0x04, 0xE5, 0x9D, 0xE6, 0x3A, 0x24, 0xE7, 0x9E, 0x25, 0xF4, 0x2F}, + //.AppSKey = {0x6C, 0x61, 0x57, 0xE8, 0xF4, 0x0A, 0x7B, 0xE0, 0x8F, 0x71, 0x7B, 0x07, 0x01, 0x30, 0x58, 0xC5}, + //.AppSKey = {0x0B, 0xF2, 0x13, 0x20, 0xCC, 0xB0, 0x99, 0xF3, 0xBE, 0x0B, 0x96, 0xCE, 0x59, 0x66, 0x98, 0x94}, + //.AppSKey = {0x2F, 0x79, 0x4C, 0x52, 0xC1, 0x93, 0x10, 0x2D, 0xAC, 0x4F, 0x03, 0x4D, 0x87, 0x0D, 0x0A, 0xA8}, + //.AppSKey = {0xFB, 0xC3, 0x0C, 0x40, 0xDF, 0x26, 0xE7, 0x94, 0x0A, 0x8A, 0x04, 0x95, 0x75, 0x4D, 0xCB, 0x02}, + //.AppSKey = {0xAD, 0x90, 0x26, 0xF7, 0xFF, 0x85, 0xA3, 0x32, 0x76, 0x61, 0x10, 0x5B, 0xE8, 0x16, 0x75, 0xCC}, + //.AppSKey = {0xD5, 0x9B, 0x18, 0xB0, 0xB6, 0x7F, 0x60, 0x7D, 0xE9, 0x23, 0x4C, 0x70, 0x6F, 0xB5, 0xFF, 0xC1}, + //.AppSKey = {0xCC, 0x49, 0x22, 0xDE, 0xEE, 0xDA, 0xC9, 0x57, 0x2B, 0x5E, 0xD6, 0x23, 0x69, 0xF5, 0xBF, 0xC6}, + //.AppSKey = {0x20, 0xC5, 0x79, 0x9F, 0x6E, 0xE8, 0x51, 0x51, 0x5C, 0x5D, 0x27, 0xA4, 0x8B, 0x3D, 0x99, 0x67}, + //.AppSKey = {0x0F, 0x0A, 0xD0, 0x0A, 0x8A, 0x57, 0x1B, 0x28, 0xF9, 0xED, 0xB4, 0xBB, 0x40, 0xD1, 0x11, 0xC8}, + //.AppSKey = {0x68, 0xFE, 0xB9, 0x8D, 0x78, 0x96, 0x51, 0x8C, 0x45, 0xD5, 0x67, 0xB9, 0x4B, 0x00, 0x27, 0x4C}, + //.AppSKey = {0x24, 0x47, 0x0E, 0xF6, 0xC5, 0xCD, 0x34, 0x9F, 0xAD, 0xED, 0x30, 0xEB, 0xC6, 0x02, 0xBC, 0x2F}, + //.AppSKey = {0xE6, 0x3E, 0x71, 0x0D, 0x49, 0x2E, 0xBB, 0x60, 0x6F, 0x04, 0x8E, 0xB3, 0x71, 0xFA, 0x3A, 0x89}, + //.AppSKey = {0x8C, 0xA4, 0x33, 0x5C, 0x0C, 0x56, 0x6B, 0x56, 0x47, 0x7B, 0x05, 0xD7, 0xCC, 0x11, 0xC1, 0x98}, + //.AppSKey = {0x48, 0x24, 0x9C, 0x8A, 0x25, 0xFF, 0xD2, 0xA4, 0xAB, 0xF6, 0xC0, 0x36, 0x73, 0x3B, 0x33, 0xB9}, + //.AppSKey = {0x3E, 0x36, 0x9A, 0x80, 0x93, 0x9A, 0xAD, 0x8B, 0x61, 0xDA, 0x3D, 0xE1, 0xE8, 0x1F, 0x0A, 0x0A}, + //.AppSKey = {0xA7, 0x55, 0xF5, 0xF7, 0x25, 0x3B, 0x98, 0x7E, 0x3A, 0x68, 0xA1, 0x68, 0x95, 0x97, 0xD4, 0x65}, + //.AppSKey = {0xE6, 0x5E, 0xCF, 0x63, 0x06, 0x79, 0x3E, 0xA2, 0x09, 0x35, 0xA7, 0x28, 0x88, 0x69, 0x82, 0xDB}, + //.AppSKey = {0x15, 0x0F, 0x46, 0x45, 0x0F, 0xB5, 0x76, 0xF8, 0xBA, 0xA2, 0x1D, 0xFB, 0x3D, 0xA5, 0x84, 0x81}, + //.DevAddr = {0x3A, 0x12, 0x34, 0x56}, + //.DevAddr = {0x26, 0x01, 0x14, 0xF4}, + //.DevAddr = {0x26, 0x01, 0x15, 0x16}, + //.DevAddr = {0x26, 0x01, 0x13, 0x4F}, + //.DevAddr = {0x26, 0x01, 0x1E, 0x04}, + //.DevAddr = {0x26, 0x01, 0x17, 0x9F}, + //.DevAddr = {0x26, 0x01, 0x15, 0xE3}, + //.DevAddr = {0x26, 0x01, 0x1B, 0x47}, + //.DevAddr = {0x26, 0x01, 0x1E, 0xDA}, + //.DevAddr = {0x26, 0x01, 0x13, 0x27}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x3A}, + //.DevAddr = {0x26, 0x01, 0x18, 0xE4}, + //.DevAddr = {0x26, 0x01, 0x10, 0x0F}, + //.DevAddr = {0x26, 0x01, 0x15, 0x01}, + //.DevAddr = {0x26, 0x01, 0x12, 0x0B}, + //.DevAddr = {0x26, 0x01, 0x11, 0x67}, + //.DevAddr = {0x26, 0x01, 0x1E, 0x8C}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x25}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x6F}, + .DevAddr = {0x26, 0x01, 0x10, 0x52}, + //.DevAddr = {0x26, 0x01, 0x18, 0xAC}, + //.DevAddr = {0x26, 0x01, 0x1D, 0x67}, + //.DevAddr = {0x26, 0x01, 0x1A, 0xCA}, + .frame_counter_down = 0, + .frame_counter_up = 0, + .Receive_delay = RECEIVE_DELAY2, + .Transmit_Power = 0x0F, + .TxChDr = {SF09_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* OTAA configuration and encryption parameters */ + sLoRa_OTAA OTAA = + { + .DevEUI = {0}, // If the DS2401 is used, set the DEV EUI to zero. + .AppEUI = {0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x01, 0x80, 0x13}, // TTN Ideetron Application EUI, change this to your own APP EUI code + //.AppEUI = {0x01, 0xF7, 0x03, 0x0E, 0x19, 0x00, 0x00, 0x55}, // TTN Ideetron Application EUI, change this to your own APP EUI code + //.AppKey = {0x0B, 0x32, 0x3C, 0x2F, 0xDD, 0xF3, 0x47, 0xB3, 0xDA, 0x20, 0x4F, 0x20, 0xE0, 0x13, 0xAA, 0x45}, + .AppKey = {0xB3, 0x23, 0xC2, 0xFD, 0xDF, 0x34, 0x7B, 0x3D, 0xA2, 0x04, 0xF2, 0x0E, 0x01, 0x3A, 0xA4, 0x59}, + .DevNonce = {0}, + .AppNonce = {0}, + .NetID = {0}, + .OTAAdone = false, + .JoinDelay = JOIN_DELAY_2, + .Transmit_Power = 0x0F, + .TxChDr = {SF10_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* Message structure for both receive and transmitting */ + sLoRa_Message TX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + sLoRa_Message RX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + }sLoRaWAN; + + + +#endif diff --git a/examples/MinimalTemplate/mcp7940.cpp b/examples/MinimalTemplate/mcp7940.cpp new file mode 100644 index 0000000..d352542 --- /dev/null +++ b/examples/MinimalTemplate/mcp7940.cpp @@ -0,0 +1,322 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: mcp7940.h +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 22-09-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include "HardwareSerial.h" +#include "I2C.h" +#include "mcp7940.h" + +// private functions +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII); +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal); +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal); + + + +/* + @brief + Initialize the mcp7940 Real Time Clock (RTC) with the compilation strings to set the current date and time. Also configures the RTC to generate an + interrupt on the next minute roll-over with the alarm 0 function of the RTC. The RTC will pull the MFP pin low, which is held high with an internal + pull-up on pin D3. + @parameters + *TimeDate Pointer to a structure where the current time will be written to once converted from the compiler __BUILD__ and __TIME__ strings. + alarm_in_x_minutes The number of minutes from the current time on which the RTC will generate an interrupt. +*/ +void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes) +{ + uint8_t date [] = __DATE__; // Sep 27 2017 + uint8_t time [] = __TIME__; // 13:50:25 + + // Enable the internal pull-up on pin D3 by setting D3 bit in PORTD and DDRD as the MFP of the RTC is open drain and using an internal pull-up is more efficient. + PORTD |= 0x08; + DDRD &= ~0x08; + + //Serial.println((char*)date); + //Serial.println((char*)time); + + // Convert the compilation time and date to decimal numbers in order to set the RTC time. + TimeDate->hours = convert_ASCII_to_decimal(&(time[0])); + TimeDate->minutes = convert_ASCII_to_decimal(&(time[3])); + TimeDate->seconds = convert_ASCII_to_decimal(&(time[6])); + TimeDate->day = convert_ASCII_to_decimal(&(date[4])); + TimeDate->year = convert_ASCII_to_decimal(&(date[9])); + TimeDate->weekDay = 1; // There's no weekday in the time and date compiler string, so always set it to 1 upon start. + + // Compare the string of three letters to determine the current month as a decimal value + if(memcmp(date, "Jan", 3) == 0) + { + TimeDate->month = 1; + }else if (memcmp(date, "Feb", 3) == 0) + { + TimeDate->month = 2; + }else if (memcmp(date, "Mar", 3) == 0) + { + TimeDate->month = 3; + }else if (memcmp(date, "Apr", 3) == 0) + { + TimeDate->month = 4; + }else if (memcmp(date, "May", 3) == 0) + { + TimeDate->month = 5; + }else if (memcmp(date, "Jun", 3) == 0) + { + TimeDate->month = 6; + }else if (memcmp(date, "Jul", 3) == 0) + { + TimeDate->month = 7; + }else if (memcmp(date, "Aug", 3) == 0) + { + TimeDate->month = 8; + }else if (memcmp(date, "Sep", 3) == 0) + { + TimeDate->month = 9; + }else if (memcmp(date, "Oct", 3) == 0) + { + TimeDate->month = 10; + }else if (memcmp(date, "Nov", 3) == 0) + { + TimeDate->month = 11; + }else if (memcmp(date, "Dec", 3) == 0) + { + TimeDate->month = 12; + } + //Serial.print("Converted: "); + //mcp7940_print(TimeDate); + + // Write the converted time and date settings to the RTC and print it to the serial port. + mcp7940_set_time_and_date(TimeDate); + mcp7940_print(TimeDate); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); // Disable the square wave output on the RTC and Alarm 0 + + // Reload alarm 0 to generate an interrupt over x minutes + if(alarm_in_x_minutes > 0) + { + mcp7940_reset_minute_alarm(alarm_in_x_minutes); + } + + // Enable Alarm 0, set the MFP pin to idle HIGH, active LOW. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x10); + + // read back the set time and date in order to check if the time and date was set properly. + mcp7940_read_time_and_date(TimeDate); + + // Enable the Alarm to generate an external interrupt on a falling edge of INT1 + EICRA = 0x08; // The falling edge of INT1 generates an interrupt request. + EIMSK = 0x02; // Enable the external interrupt +} + + +/* + @brief + Function to disable the RTC and all alarms. +*/ +void mcp7940_disable (void) +{ + // Disable all alarms and square wave outputs. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); + + // Disable the RTC by clearing bit 7 of the RTC_SEC register, which will stop the 32kHz crystal. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); +} + + + + + +/* + @brief + After the RTC's alarm has triggered, the alarm must be re-configured to generate an alarm again after alarm_in_x_minutes minutes. + @parameters + alarm_in_x_minutes The number of minutes from now when the RTC must generate an interrupt again. +*/ +void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes) +{ + uint8_t value, current_time_minutes, new_time_minutes; + + if(alarm_in_x_minutes > 59) + { + alarm_in_x_minutes = 59; + } + + // Read the current minutes register to calculate the match value for the minute register. + current_time_minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + + // Calculate the new time settings. + new_time_minutes = current_time_minutes + alarm_in_x_minutes; + + // Check whether the additional number of minutes and the current time settings will be larger than 59. if larger, subtract 59 from the sum to calculate the new time + if(new_time_minutes > 59) + { + // Subtract 59 from the sum of the current time in minutes and the added number of minutes. + new_time_minutes -= 59; + } + + // Rewrite the Alarm 0 minutes register. + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_MIN, convert_decimal_to_binary(MIN_MASK, new_time_minutes)); + + /* + Set MFP pin High on Alarm interrupt on minutes match and clear the ALM0IF (Alarm interrupt Flag) now that the Alarm 0 minutes register is different + from the current time or the interrupt would re-trigger. Read the register first to keep the current weekday value and add the three bits 2:0 to + the value written to the register. + */ + value = I2C_read_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY); + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY, 0x10 | (value & 0x07)); +} + + + +/* + @brief + Function to read the time and date from the RTC, convert it to decimal values and store the values in the reference structure. + @parameters + TimeDate pointer to the structure where all the retrieved time and date must be stored. +*/ +void mcp7940_read_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + return; + + // Read the Time and date from the RTC and convert it to a time and date in decimal values. + TimeDate->seconds = convert_binary_to_decimal(SEC_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_SEC)); + TimeDate->minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + TimeDate->hours = convert_binary_to_decimal(HOUR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR)); + TimeDate->weekDay = WEEKDAY_MASK & I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_WEEKDAY); + + TimeDate->day = convert_binary_to_decimal(DATE_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_DATE)); + TimeDate->month = convert_binary_to_decimal(MONTH_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH)); + TimeDate->year = convert_binary_to_decimal(YEAR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR)); +} + + +/* + @brief + Function to write the referenced time and date in the TimeDate structure to the RTC + @parameters + TimeDate pointer to the structure where the new time and date settings are stored. +*/ +void mcp7940_set_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + { + return; + } + + // Disable the oscillator to stop the RTC while the time and date are configured to prevent roll-overs from occurring while writing new values. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MIN, convert_decimal_to_binary(MIN_MASK, TimeDate->minutes)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR, 0x40 | convert_decimal_to_binary(HOUR_MASK, TimeDate->hours)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, WEEKDAY_MASK, convert_decimal_to_binary(WEEKDAY_MASK, TimeDate->weekDay)); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_DATE, convert_decimal_to_binary(DATE_MASK, TimeDate->day)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH, convert_decimal_to_binary(MONTH_MASK, TimeDate->month)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR, convert_decimal_to_binary(YEAR_MASK, TimeDate->year)); + + // Write the seconds as last since, we'll reactivate the oscillator with this write operation as well. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x80 | convert_decimal_to_binary(SEC_MASK, TimeDate->seconds)); +} + + + +/* + @brief + Convert the RTC's tens and ones binary format to a decimal number. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New binary value which needs to be converted to decimal +*/ +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal) +{ + return (((regVal & mask) >> 4) * 10) + (regVal & 0x0F); +} + + +/* + @brief + Convert a decimal value to binary tens and ones values. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New decimal value which needs to be converted to binary +*/ +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal) +{ + uint8_t tens, ones; + tens = decimal / 10; + ones = decimal % 10; + return ((mask &(tens << 4)) | (ones & 0x0F)); +} + +/* + @brief + Converts two ASCII characters to a single decimal value to convert the compilers __BUILD__ and __TIME__ string to decimal values. + @parameters + ASCII pointer to the first ASCII character. +*/ +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII) +{ + uint8_t retVal = 0; + + if((ASCII[0] >= '0') && (ASCII[0] <= '9')) + { + retVal = (ASCII[0] - '0') * 10; + } + + if((ASCII[1] >= '0') && (ASCII[1] <= '9')) + { + retVal += (ASCII[1] - '0'); + } + + return retVal; +} + +/* + @brief + Prints the referenced structure in a human readable format to the serial port "hh:mm:ss day:n dd/mm/yy" +*/ +void mcp7940_print(sTimeDate *TimeDate) +{ + Serial.print(TimeDate->hours, DEC); + Serial.print(':'); + Serial.print(TimeDate->minutes, DEC); + Serial.print(':'); + Serial.print(TimeDate->seconds, DEC); + Serial.print(" day:"); + Serial.print(TimeDate->weekDay, DEC); + + Serial.print('\t'); + + Serial.print(TimeDate->day, DEC); + Serial.print('/'); + Serial.print(TimeDate->month, DEC); + Serial.print('/'); + Serial.println(TimeDate->year, DEC); +} diff --git a/examples/MinimalTemplate/mcp7940.h b/examples/MinimalTemplate/mcp7940.h new file mode 100644 index 0000000..70f622e --- /dev/null +++ b/examples/MinimalTemplate/mcp7940.h @@ -0,0 +1,85 @@ +/* + * MCP7940.h + * + * Created: 22-9-2017 08:21:21 + * Author: adri + */ + + +#ifndef MCP7940_H_ +#define MCP7940_H_ + + //#define MCP7940_SLAVE_ADDRESS 0xDE + #define MCP7940_SLAVE_ADDRESS 0x6F + /****************************************************************************************** + ENUMERATION + ******************************************************************************************/ + typedef enum + { + SEC_MASK = 0x70, + MIN_MASK = 0x70, + HOUR_MASK = 0x10, + WEEKDAY_MASK= 0x07, + DATE_MASK = 0x30, + MONTH_MASK = 0x10, + YEAR_MASK = 0xF0 + }eMASKS; + + typedef enum + { + // Timekeeping + RTC_SEC = 0x00, + RTC_MIN = 0x01, + RTC_HOUR = 0x02, + RTC_WEEKDAY = 0x03, + RTC_DATE = 0x04, + RTC_MONTH = 0x05, + RTC_YEAR = 0x06, + CONTROL = 0x07, + OSCTRIM = 0x08, + + // Alarms 0 + ALARM0_SEC = 0x0A, + ALARM0_MIN = 0x0B, + ALARM0_HOUR = 0x0C, + ALARM0_WEEKDAY = 0x0D, + ALARM0_DATE = 0x0E, + ALARM0_MONTH = 0x0F, + + + // Alarms 1 + ALARM1_SEC = 0x11, + ALARM1_MIN = 0x12, + ALARM1_HOUR = 0x13, + ALARM1_WEEKDAY = 0x14, + ALARM1_DATE = 0x15, + ALARM1_MONTH = 0x16 + }eMCP7940_REGISTERS; + + /****************************************************************************************** + Structures + ******************************************************************************************/ + typedef struct + { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day; + uint8_t month; + uint8_t year; + uint8_t weekDay; + }sTimeDate; + + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes); + void mcp7940_disable (void); + void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes); + void mcp7940_read_time_and_date (sTimeDate *TimeDate); + void mcp7940_set_time_and_date (sTimeDate *TimeDate); + void mcp7940_print (sTimeDate *TimeDate); + +#endif /* MCP7940_H_ */ diff --git a/examples/MinimalTemplate/progmem.h b/examples/MinimalTemplate/progmem.h new file mode 100644 index 0000000..c6b972a --- /dev/null +++ b/examples/MinimalTemplate/progmem.h @@ -0,0 +1,60 @@ +const unsigned char wIcon_sunny [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x01, 0x80, 0x30, + 0x00, 0x80, 0x20, + 0x00, 0x1f, 0x00, + 0x01, 0xb1, 0x80, + 0x0e, 0x70, 0x80, + 0x18, 0x18, 0xc0, + 0x10, 0x0c, 0x4c, + 0x30, 0x0f, 0x80, + 0x20, 0x00, 0xc0, + 0x20, 0x00, 0x40, + 0x20, 0x00, 0x40, + 0x30, 0x00, 0x40, + 0x10, 0x00, 0x40, + 0x0c, 0x00, 0xc0, + 0x07, 0xff, 0x80, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + + +const unsigned char wIcon_cloudy [] PROGMEM= { // Icon example2, you will need to use + 0x00, 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x01, 0x80, 0x80, 0xc0, + 0x01, 0xc0, 0x01, 0xc0, + 0x00, 0xe0, 0x01, 0xc0, + 0x00, 0x41, 0xc1, 0x80, + 0x00, 0x07, 0xf0, 0x00, + 0x00, 0x0f, 0xfc, 0x00, + 0x07, 0xfc, 0x1c, 0x00, + 0x1f, 0xfc, 0x0e, 0x00, + 0x3c, 0x1e, 0x06, 0x00, + 0x38, 0x07, 0x06, 0x3c, + 0x70, 0x03, 0xe6, 0x3c, + 0x60, 0x01, 0xfe, 0x00, + 0xe0, 0x01, 0xfe, 0x00, + 0xc0, 0x00, 0x0e, 0x00, + 0xc0, 0x00, 0x07, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xe0, 0x00, 0x03, 0x00, + 0x60, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x03, 0x00, + 0x38, 0x00, 0x07, 0x00, + 0x3c, 0x00, 0x0e, 0x00, + 0x1f, 0xff, 0xfc, 0x00, + 0x07, 0xff, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; diff --git a/examples/MinimalTemplate/spi_flash.cpp b/examples/MinimalTemplate/spi_flash.cpp new file mode 100644 index 0000000..d0d718f --- /dev/null +++ b/examples/MinimalTemplate/spi_flash.cpp @@ -0,0 +1,180 @@ +#include "Arduino.h" +#include +#include "spi_functions.h" +#include "spi_flash.h" +#include "lorapaper.h" + + +void flash_ID (sFLASH_ID * ID) +{ + if(ID == NULL) + { + return; + } + + flash_release_power_down(); + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_MANUFACTURERE_ID); // 0x90 + + //Send 0x000000 for address + SPI.transfer(0x00); + SPI.transfer(0x00); + SPI.transfer(0x00); + + //Get Manufacture ID + ID->manufacturerID = SPI.transfer(0x00); + + //Get device ID + ID->deviceID = SPI.transfer(0x00); + + //Set CS pin HIGH + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +void flash_power_down (void) +{ + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_POWER_DOWN); // 0xB9 + + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_release_power_down (void) +{ + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction and four dummy bytes + SPI.transfer(FLASH_RELEASE_POWER_DOWN); // 0xAB + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // ID7:ID0 will repeat this until SPI transfer has finished. + + digitalWrite(SPI_FLASH_CS, HIGH); + + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_write(uint32_t address, uint8_t *data, uint16_t n) +{ + uint8_t i; + + flash_release_power_down(); + + // Enable writing to the Flash Chip + flash_write_enable(); + + //delay(1); + + // Set CS low + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_PAGE_PROGRAM); // 0x02 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + //SPI.transfer(i); // writes the value of i to the + SPI.transfer(data[i]); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); + + //delay(2000); + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_read(uint32_t address, uint8_t *data, uint16_t n) +{ + uint16_t i; + + flash_release_power_down(); + + // Set CS low again + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_READ_DATA); // 0x03 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + data[i] = SPI.transfer(0x00); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_enable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_ENABLE); // 0x06 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_disable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_DISABLE); // 0x04 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +uint8_t flash_status (void) +{ + uint8_t status = 0; + + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_READ_STATUS); // 0x05 + + // Read the status byte + status = SPI.transfer(0x00); + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); + + return status; +} diff --git a/examples/MinimalTemplate/spi_flash.h b/examples/MinimalTemplate/spi_flash.h new file mode 100644 index 0000000..04d3ed8 --- /dev/null +++ b/examples/MinimalTemplate/spi_flash.h @@ -0,0 +1,76 @@ +// spi_flash.h + +#ifndef _SPI_FLASH_h +#define _SPI_FLASH_h + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define SPI_FLASH_PAGE_SIZE 256 + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + FLASH_WRITE_ENABLE = 0x06, + FLASH_WRITE_EN_VOLATILE = 0x50, + FLASH_WRITE_DISABLE = 0x04, + FLASH_READ_STATUS = 0x05, + FLASH_WRITE_STATUS = 0x01, + FLASH_READ_DATA = 0x03, + FLASH_FAST_READ = 0x0B, + FLASH_FAST_READ_DUAL = 0x3B, + FLASH_READ_DUAL_IO = 0xBB, + FLASH_PAGE_PROGRAM = 0x02, + FLASH_SECTOR_ERASE = 0x20, + FLASH_BLOCK_ERASE_32K = 0x52, + FLASH_BLOCK_ERASE_64K = 0xD8, + FLASH_CHIP_ERASE = 0xC7, + FLASH_CHIP_ERASE2 = 0x60, + FLASH_POWER_DOWN = 0xB9, + FLASH_RELEASE_POWER_DOWN= 0xAB, + FLASH_MANUFACTURERE_ID = 0x90, + FLASH_ID_DUAL_IO = 0x92, + FLASH_JEDEC_ID = 0x9F, + FLASH_READ_UNIQUE_ID = 0x4B + }eFLASH_COMMANDS; + + typedef enum + { + REGISTER_PROTECT = 0x80, + TOP_BOT_PROTECT = 0x20, + BLOCK_PROTECT = 0x1C, + WRITE_ENABLE_LATCH = 0x02, + R_W_IN_PROGRESS = 0x01 + }eSTATUS_BITS; + + /****************************************************************************************** + STRUCTURE + ******************************************************************************************/ + typedef struct + { + uint8_t deviceID; + uint8_t manufacturerID; + }sFLASH_ID; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void flash_ID (sFLASH_ID * ID); + void flash_power_down (void); + void flash_release_power_down (void); + void flash_write (uint32_t address, uint8_t *data, uint16_t n); + void flash_read (uint32_t address, uint8_t *data, uint16_t n); + void flash_write_enable (void); + void flash_write_disable (void); + uint8_t flash_status (void); + +#endif diff --git a/examples/MinimalTemplate/spi_functions.cpp b/examples/MinimalTemplate/spi_functions.cpp new file mode 100644 index 0000000..73b435f --- /dev/null +++ b/examples/MinimalTemplate/spi_functions.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "spi_functions.h" + +/****************************************************************************************** +* Description : Function that reads a register and returns the value +* Arguments : Address of register to be read +* Returns : Value of the register +******************************************************************************************/ + +uint8_t SPI_Read(uint8_t CS_pin, uint8_t register_Address) { + uint8_t RFM_Data; + + digitalWrite(CS_pin, LOW); // Set NSS pin low to start SPI communication + SPI.transfer(register_Address); // Send Address + RFM_Data = SPI.transfer(0x00); // Send 0x00 to be able to receive the answer from the RFM + digitalWrite(CS_pin, HIGH); // Set NSS high to end communication + return RFM_Data; // Return received data +} + +/****************************************************************************************** +* Description: Function that writes a register with the given value +* Arguments: Address of the register to be written +* Data Data to be written to the register +******************************************************************************************/ +void SPI_Write(uint8_t CS_pin, uint8_t register_Address, uint8_t Data) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a writ command + SPI.transfer(Data); //Send Data + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + + +/********************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* Lenght: The number of bytes needed to transmit +**********************************************************************************************/ +void SPI_Write_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a Write command + SPI.transfer(Data, lenght); //Send the data array + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + +/***************************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the RFM register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* lenght: The number of bytes needed to transmit +*****************************************************************************************************/ +void SPI_Read_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set Chip select pin low to start SPI communication + SPI.transfer(register_Address); //Send the register Address and then read the contents of the receive buffer in the RFM + SPI.transfer(Data, lenght); //Set NSS high to end communication + digitalWrite(CS_pin, HIGH); +} diff --git a/examples/MinimalTemplate/spi_functions.h b/examples/MinimalTemplate/spi_functions.h new file mode 100644 index 0000000..f9e7a88 --- /dev/null +++ b/examples/MinimalTemplate/spi_functions.h @@ -0,0 +1,17 @@ +#ifndef _SPI_Functions_h +#define _SPI_Functions_h + + #if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" + #else + #include "WProgram.h" + #endif + + #include + + void SPI_Write (uint8_t CS_pin, uint8_t register_Address, uint8_t Data); + uint8_t SPI_Read (uint8_t CS_pin, uint8_t register_Address); + void SPI_Write_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + void SPI_Read_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + +#endif diff --git a/examples/MinimalTemplate/timers.cpp b/examples/MinimalTemplate/timers.cpp new file mode 100644 index 0000000..ab4b098 --- /dev/null +++ b/examples/MinimalTemplate/timers.cpp @@ -0,0 +1,68 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: timers.cpp +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "avr/sleep.h" +#include "Arduino.h" +#include "timers.h" + + +void disable_ms_tick (void) +{ + TCCR1B = 0x00; // Disable Timer 1 +} + + +/* + @brief Configures the timer to create an interrupt every millisecond for timing purposes. +*/ +void enable_ms_tick (void) +{ + // Disable the Timer before configuring + disable_ms_tick(); + + // Use T1 since it's a 16 bit timer, but Timer 1 uses the same prescaler, so be aware. + TCCR1A = 0x00; + + // Clear the timer counter. + TCNT1 = 0; + + // (16MHz / 1) / 1000 = 16000 Ticks per millisecond. + OCR1A = 16000; + + // Enable Output compare A Match interrupts + TIMSK1 = 0x02; + + // Enable Timer1 by setting a non-zero prescaler. + TCCR1B = 0x09; // 0x00x0 1001: use CTC mode and set the prescaler to 1/1 +} diff --git a/examples/MinimalTemplate/timers.h b/examples/MinimalTemplate/timers.h new file mode 100644 index 0000000..157a7ae --- /dev/null +++ b/examples/MinimalTemplate/timers.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Waitloop.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef TIMERS_H +#define TIMERS_H + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + + #include + + void disable_ms_tick (void); + void enable_ms_tick (void); + +#endif diff --git a/examples/SaveImgToFlash/AES-128.cpp b/examples/SaveImgToFlash/AES-128.cpp new file mode 100644 index 0000000..786ca2b --- /dev/null +++ b/examples/SaveImgToFlash/AES-128.cpp @@ -0,0 +1,284 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#include +#include +#include "AES-128.h" + + +uint8_t 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} +}; + + + + +/* +***************************************************************************************** +* Title : AES_Encrypt +* Description : +***************************************************************************************** +*/ +void AES_Encrypt(uint8_t *Data, uint8_t *Key) +{ + uint8_t Row, Column, Round = 0; + uint8_t Round_Key[16]; + uint8_t 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 AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]) +{ + uint8_t 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 : +***************************************************************************************** +*/ +uint8_t AES_Sub_Byte(uint8_t Byte) +{ + return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; +} // AES_Sub_Byte + + +/* +***************************************************************************************** +* Title : AES_Shift_Rows +* Description : +***************************************************************************************** +*/ +void AES_Shift_Rows(uint8_t (*State)[4]) +{ + uint8_t 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 AES_Mix_Collums(uint8_t (*State)[4]) +{ + uint8_t Row,Collum; + uint8_t 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 AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key) +{ + uint8_t i, j, b, Rcon; + uint8_t Temp[4]; + + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + + if(b == 0x80) + { + Rcon ^= 0x1b; + } + Round--; + } + + // Calculate first Temp + // Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1. + Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] ); + Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] ); + Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] ); + Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] ); + + // XOR with Rcon + Temp[0] ^= Rcon; + + // Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (i << 2)] ^= Temp[j]; + Temp[j] = Round_Key[j + (i << 2)]; + } + } +} // AES_Calculate_Round_Key + + + + + diff --git a/examples/SaveImgToFlash/AES-128.h b/examples/SaveImgToFlash/AES-128.h new file mode 100644 index 0000000..848907d --- /dev/null +++ b/examples/SaveImgToFlash/AES-128.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#ifndef AES128_H +#define AES128_H + + #include + + /************************************************************************************** + * FUNCTION + **************************************************************************************/ + + void AES_Encrypt(uint8_t *Data, uint8_t *Key); + void AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]); + uint8_t AES_Sub_Byte(uint8_t Byte); + void AES_Shift_Rows(uint8_t (*State)[4]); + void AES_Mix_Collums(uint8_t (*State)[4]); + void AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key); + void Send_State(); + +#endif diff --git a/examples/SaveImgToFlash/Cayenne_LPP.cpp b/examples/SaveImgToFlash/Cayenne_LPP.cpp new file mode 100644 index 0000000..de15ff2 --- /dev/null +++ b/examples/SaveImgToFlash/Cayenne_LPP.cpp @@ -0,0 +1,289 @@ +/* + * Cayenne_LPP.c + * + * Created: 4-10-2017 11:45:10 + * Author: adri + */ +#include +#include "Cayenne_LPP.h" + + +CayenneLPP::CayenneLPP(sLoRa_Message *buffer_Ptr) +{ + if(buffer_Ptr != 0) + { + buffer = buffer_Ptr; + } +} + +CayenneLPP::~CayenneLPP() +{ + buffer = 0; +} + + +void CayenneLPP::clearBuffer (void) +{ + if(buffer == 0) + { + return; + } + + // Clear the buffer and the number of bytes in it. + memset(&(buffer->Data[0]), 0, LORA_FIFO_SIZE); + buffer->Count = 0; +} + + +void CayenneLPP::addGPS(uint8_t channel, double latitude, double longitude, double altitude) +{ + if((buffer == 0) || ((buffer->Count + LPP_GPS_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + int32_t lat = (int32_t)(latitude * 10000.0); + int32_t lon = (int32_t)(longitude * 10000.0); + int32_t alt = (int32_t)(altitude * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GPS; + buffer->Data[(buffer->Count) + 2] = lat >> 16; + buffer->Data[(buffer->Count) + 3] = lat >> 8; + buffer->Data[(buffer->Count) + 4] = lat; + buffer->Data[(buffer->Count) + 5] = lon >> 16; + buffer->Data[(buffer->Count) + 6] = lon >> 8; + buffer->Data[(buffer->Count) + 7] = lon; + buffer->Data[(buffer->Count) + 8] = alt >> 16; + buffer->Data[(buffer->Count) + 9] = alt >> 8; + buffer->Data[(buffer->Count) + 10] = alt; + buffer->Count += LPP_GPS_SIZE; // 11 + return; +} + + +void CayenneLPP::addAnalogOutput(uint8_t channel, double value) // sLoRa_Message *buffer, +{ + int16_t val; + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (int16_t) (value * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_OUTPUT; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val; + buffer->Count += LPP_ANALOG_OUTPUT_SIZE; // 4 + return; +} + + +void CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_OUTPUT; + buffer->Data[(buffer->Count) + 2] = value; + + // Add the added length to the buffer counter. + buffer->Count += LPP_DIGITAL_OUTPUT_SIZE; +} + + + +void CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_INPUT; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_DIGITAL_INPUT_SIZE; +} + + + +void CayenneLPP::addAnalogInput(uint8_t channel, float value) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ANALOG_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) (value * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT_SIZE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_ANALOG_INPUT_SIZE; +} + + + +void CayenneLPP::addLuminosity(uint8_t channel, float lux) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_LUMINOSITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) lux; + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT_SIZE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_LUMINOSITY_SIZE; +} + +void CayenneLPP::addPresence(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_PRESENCE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_PRESENCE; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_PRESENCE_SIZE; +} + +void CayenneLPP::addTemperature(uint8_t channel, float celsius) +{ + uint16_t temp; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_TEMPERATURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + temp = (uint16_t) (celsius * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_TEMPERATURE; + buffer->Data[(buffer->Count) + 2] = temp >> 8; + buffer->Data[(buffer->Count) + 3] = temp >> 0; + buffer->Count += LPP_TEMPERATURE_SIZE; +} + +void CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) //sLoRa_Message *buffer, +{ + uint16_t humidity; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_RELATIVE_HUMIDITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + humidity = (uint16_t) (rh * 2.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_RELATIVE_HUMIDITY; + buffer->Data[(buffer->Count) + 2] = humidity; + buffer->Count += LPP_RELATIVE_HUMIDITY_SIZE; +} + +void CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) +{ + uint16_t x_axis, y_axis, z_axis; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ACCELEROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to unsigned integers. + x_axis = (uint16_t) (x * 1000.0); + y_axis = (uint16_t) (y * 1000.0); + z_axis = (uint16_t) (z * 1000.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ACCELEROMETER; + buffer->Data[(buffer->Count) + 2] = x_axis >> 8; + buffer->Data[(buffer->Count) + 3] = x_axis >> 0; + buffer->Data[(buffer->Count) + 4] = y_axis >> 8; + buffer->Data[(buffer->Count) + 5] = y_axis >> 0; + buffer->Data[(buffer->Count) + 6] = z_axis >> 8; + buffer->Data[(buffer->Count) + 7] = z_axis >> 0; + buffer->Count += LPP_ACCELEROMETER_SIZE; +} + +void CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) +{ + int16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_BAROMETRIC_PRESSURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t)(hpa * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_BAROMETRIC_PRESSURE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_BAROMETRIC_PRESSURE_SIZE; +} + +void CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) +{ + + int16_t vx, vy, vz; + + // Check for invalid input pointer or if adding this data will cause the message to be larger then the maximum payload size. + if((buffer == 0) || ((buffer->Count + LPP_GYROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to signed integers + vx = (int16_t)(x * 100.0); + vy = (int16_t)(y * 100.0); + vz = (int16_t)(z * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GYROMETER; + buffer->Data[(buffer->Count) + 2] = vx >> 8; + buffer->Data[(buffer->Count) + 3] = vx >> 0; + buffer->Data[(buffer->Count) + 4] = vy >> 8; + buffer->Data[(buffer->Count) + 5] = vy >> 0; + buffer->Data[(buffer->Count) + 6] = vz >> 8; + buffer->Data[(buffer->Count) + 7] = vz >> 0; + buffer->Count += LPP_GYROMETER_SIZE; +} diff --git a/examples/SaveImgToFlash/Cayenne_LPP.h b/examples/SaveImgToFlash/Cayenne_LPP.h new file mode 100644 index 0000000..59b67f2 --- /dev/null +++ b/examples/SaveImgToFlash/Cayenne_LPP.h @@ -0,0 +1,78 @@ +/* + * Cayenne_LPP.h + * + * Created: 4-10-2017 11:45:55 + * Author: adri + */ + + +#ifndef CAYENNE_LPP_H_ +#define CAYENNE_LPP_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + + #include "lorawan_def.h" + + /****************************************************************************************** + DEFINITIONS + ******************************************************************************************/ + + #define LPP_DIGITAL_INPUT 0 // 1 byte + #define LPP_DIGITAL_OUTPUT 1 // 1 byte + #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE 102 // 1 byte, 1 + #define LPP_TEMPERATURE 103 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS 136 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + + // Data ID + Data Type + Data Size + #define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte + #define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte + #define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE_SIZE 3 // 1 byte, 1 + #define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + class CayenneLPP + { + public: + CayenneLPP (sLoRa_Message *buffer_Ptr); + ~CayenneLPP (); + + void clearBuffer (void); + void addGPS (uint8_t channel, double latitude, double longitude, double altitude); + void addAnalogOutput (uint8_t channel, double value); + void addDigitalOutput (uint8_t channel, uint8_t value); + void addDigitalInput (uint8_t channel, uint8_t value); + void addAnalogInput (uint8_t channel, float value); + void addLuminosity (uint8_t channel, float lux); + void addPresence (uint8_t channel, uint8_t value); + void addTemperature (uint8_t channel, float celsius); + void addRelativeHumidity (uint8_t channel, float rh); + void addAccelerometer (uint8_t channel, float x, float y, float z); + void addBarometricPressure (uint8_t channel, float hpa); + void addGyrometer (uint8_t channel, float x, float y, float z); + + private: + sLoRa_Message *buffer; + }; + +#endif /* CAYENNE_LPP_H_ */ diff --git a/examples/SaveImgToFlash/DS2401.cpp b/examples/SaveImgToFlash/DS2401.cpp new file mode 100644 index 0000000..1431eec --- /dev/null +++ b/examples/SaveImgToFlash/DS2401.cpp @@ -0,0 +1,185 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include "Arduino.h" +#include "DS2401.h" +#include "lorapaper.h" + +/* +***************************************************************************************** +* Description: Function is used to read the all the bytes provide by the DS2401 +* +* Arguments: *DS_Bytes pointer to an array of 8 uint8_ts +***************************************************************************************** +*/ +bool DS_Read(uint8_t *DS_Bytes) +{ + uint8_t i; + + //Preform reset pulse + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(700); + pinMode(DS2401,INPUT); + delayMicroseconds(700); + + //Send command 0x33 LSB + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + + //Read DS bytes + for (i = 0 ; i < RAM_SIZE ; i++) + { + DS_Bytes[i] = DS_ReadByte(); + } + + //Shutdown DS2401 + digitalWrite(DS2401, HIGH); + + // Check whether the CRC matches with the last byte from the DS2401. + return DS_CheckCRC(DS_Bytes); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 1 to the DS2401 +***************************************************************************************** +*/ +void DS_WR1(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(80); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 0 to the DS2401 +***************************************************************************************** +*/ +void DS_WR0(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(80); + pinMode(DS2401,INPUT); + delayMicroseconds(2); +} + +/* +***************************************************************************************** +* Description: Function is used to read the one byte provided by the DS2401 +* +* Return: Returns the byte received from the DS2401 +***************************************************************************************** +*/ +uint8_t DS_ReadByte(void) +{ + uint8_t DS_Byte = 0; + uint8_t i, t = 1; + + for (i = 0 ; i < 8 ; i++) + { + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(10); + + if(digitalRead(DS2401) == 1) + { + DS_Byte |= t; + } + + t = t << 1; + delayMicroseconds(80); + } + return DS_Byte; +} + +/* +***************************************************************************************** +* Description : This function does a CRC check on the received data from the DS2401 +* Arguments : *DS_bytes pointer to the arry that holds the DS bytes +* Return : Returns 0x01 when CRC check is OK +***************************************************************************************** +*/ +bool DS_CheckCRC(uint8_t *DS_bytes) +{ + uint8_t DS_current_byte = 0x00; + uint8_t DS_crc = 0x00; + uint8_t DS_Polynom = 0x0C; + uint8_t DS_crc_carry = 0x00; + uint8_t i = 0x00; + uint8_t j = 0x00; + + /*Loop for all 6 DS bytes*/ + for (i = 0 ; i < 7 ; i++) + { + DS_current_byte = DS_bytes[i]; //Get first byte + + /*Calculate CRC for all bits*/ + for (j = 0 ; j < 8 ; j++) + { + /*XOR bit 0 with bit 0 of current CRC*/ + if ((DS_crc & 0x01) != (DS_current_byte & 0x01)) + { + DS_crc_carry = 0x80; + } + else + { + DS_crc_carry = 0x00; + } + + /*shift CRC*/ + DS_crc = DS_crc >> 1; + + /*Check for carry*/ + if (DS_crc_carry == 0x80) + { + DS_crc = DS_crc | DS_crc_carry; + DS_crc = DS_crc ^ DS_Polynom; + } + + DS_current_byte = DS_current_byte >> 1; + } + } + + // Check whether the calculated CRC and the last received byte match. When they do retrun true to indicate that the received bytes are valid otherwise return false + if (DS_crc == DS_bytes[7]) + { + return true; + } + return false; +} diff --git a/examples/SaveImgToFlash/DS2401.h b/examples/SaveImgToFlash/DS2401.h new file mode 100644 index 0000000..c03503c --- /dev/null +++ b/examples/SaveImgToFlash/DS2401.h @@ -0,0 +1,47 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef DS2401_H +#define DS2401_H + + #include + #include + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RAM_SIZE 8 + + /********************************************************************************************* + FUNCTION PORTOTYPES + *********************************************************************************************/ + bool DS_Read (uint8_t *DS_Bytes); + bool DS_CheckCRC (uint8_t *DS_bytes); + void DS_WR1 (void); + void DS_WR0 (void); + uint8_t DS_ReadByte (void); + +#endif diff --git a/examples/SaveImgToFlash/Encrypt.cpp b/examples/SaveImgToFlash/Encrypt.cpp new file mode 100644 index 0000000..afd095b --- /dev/null +++ b/examples/SaveImgToFlash/Encrypt.cpp @@ -0,0 +1,404 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "Encrypt.h" +#include "AES-128.h" +#include +#include "timers.h" +#include "lorawan_def.h" + +/* +***************************************************************************************** +* INCLUDE GLOBAL VARIABLES +***************************************************************************************** +*/ + +/* +***************************************************************************************** +* Description : Function used to encrypt and decrypt the data in a LoRaWAN data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data to de/encrypt +* *Session_Data pointer to sLoRa_Session structure +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Encrypt_Payload(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i = 0x00; + uint8_t j; + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + uint8_t Block_A[16]; + + if((data==0) || (Key==0) || (lenght == 0) || (Message == 0)) + { + return; + } + + //Calculate number of blocks + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + for(i = 0x00; 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; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_A[5] = UPSTREAM_DIR; + } + else + { + Block_A[5] = DOWNSTREAM_DIR; + } + + Block_A[6] = Message->DevAddr[3]; + Block_A[7] = Message->DevAddr[2]; + Block_A[8] = Message->DevAddr[1]; + Block_A[9] = Message->DevAddr[0]; + + Block_A[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_A[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_A[12] = 0x00; //Frame counter upper Bytes + Block_A[13] = 0x00; + Block_A[14] = 0x00; + Block_A[15] = i + 1; + + //Calculate S + AES_Encrypt(Block_A, Key); + + //Check for last block + if(i != (Number_of_Blocks - 1)) + { + for(j = 0; j < 16; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + else + { + if(Incomplete_Block_Size == 0) + { + Incomplete_Block_Size = 16; + } + for(j = 0; j < Incomplete_Block_Size; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + } +} + +/* +***************************************************************************************** +* Description : Function used to build a the data that is used for calculating the MIC of a data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data +* *Session_Data pointer to sLoRa_Session sturct +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Construct_Data_MIC(uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message) +{ + uint8_t i; + uint8_t MIC_Data[80], MIC_lenght; + uint8_t Block_B[16]; + + //Construct Block B + Block_B[0] = 0x49; + Block_B[1] = 0x00; + Block_B[2] = 0x00; + Block_B[3] = 0x00; + Block_B[4] = 0x00; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_B[5] = UPSTREAM_DIR; + } + else + { + Block_B[5] = DOWNSTREAM_DIR; + } + + Block_B[6] = Message->DevAddr[3]; + Block_B[7] = Message->DevAddr[2]; + Block_B[8] = Message->DevAddr[1]; + Block_B[9] = Message->DevAddr[0]; + + Block_B[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_B[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_B[12] = 0x00; //Frame counter upper bytes + Block_B[13] = 0x00; + Block_B[14] = 0x00; + Block_B[15] = lenght; + + //Copy Block B into MIC data + for(i = 0x00; i < 16; i++) + { + MIC_Data[i] = Block_B[i]; + } + + //Add data to it + for(i = 0x00; i < lenght; i++) + { + MIC_Data[i + 16] = data[i]; + } + + //Calculate the correct buffer length + MIC_lenght = 16 + lenght; + + //Calculate the MIC + Calculate_MIC(&MIC_Data[0], MIC_lenght, Session_Data->NwkSKey, Message); +} + +/* +***************************************************************************************** +* Description : Function used to calculate the MIC of data +* +* Arguments : *Buffer pointer to the buffer cointaining the data the MIC should be calculated from +* *Key pointer to key used for the MIC calculation +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Calculate_MIC(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i, j; + uint8_t Key_K1[16] = {0}; + uint8_t Key_K2[16] = {0}; + uint8_t Old_Data[16] = {0}; + uint8_t New_Data[16] = {0}; + + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + //Calculate number of Blocks and blocksize of last block + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + + //if there is an incomplete block at the end add 1 to the number of blocks + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + Generate_Keys(Key, Key_K1, Key_K2); + + //Preform full calculating until n-1 messsage blocks + for(j = 0x0; j < (Number_of_Blocks - 1); j++) + { + //Copy data into array + for(i = 0; i < 16; i++) + { + New_Data[i] = data[(j*16)+i]; + } + + //Preform XOR with old data + XOR(New_Data,Old_Data); + + //Preform AES encryption + AES_Encrypt(New_Data,Key); + + //Copy New_Data to Old_Data + for(i = 0; i < 16; i++) + { + Old_Data[i] = New_Data[i]; + } + } + + //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[((Number_of_Blocks -1)*16)+i]; + } + + //Preform XOR with Key 1 + XOR(New_Data, Key_K1); + + //Preform XOR with old data + XOR(New_Data, Old_Data); + + //Preform last AES routine + AES_Encrypt(New_Data, Key); + } + else + { + //Copy the remaining data and fill the rest + for(i = 0; i < 16; i++) + { + if(i < Incomplete_Block_Size) + { + New_Data[i] = data[((Number_of_Blocks -1)*16)+i]; + } + 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, Key); + } + + Message->MIC[0] = New_Data[0]; + Message->MIC[1] = New_Data[1]; + Message->MIC[2] = New_Data[2]; + Message->MIC[3] = New_Data[3]; +} + +/* +***************************************************************************************** +* Description : Function used to generate keys for the MIC calculation +* +* Arguments : *Key pointer to key used for the MIC calculation +* *K1 pointer to Key1 +* *K2 pointer ot Key2 +***************************************************************************************** +*/ +void Generate_Keys(uint8_t *Key, uint8_t *K1, uint8_t *K2) +{ + uint8_t i; + uint8_t MSB_Key; + + //Encrypt the zeros in K1 with the NwkSkey + AES_Encrypt(K1,Key); + + //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 Shift_Left(uint8_t *Data) +{ + uint8_t i; + uint8_t Overflow = 0; + //uint8_t 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 XOR(uint8_t *New_Data,uint8_t *Old_Data) +{ + uint8_t i; + + for(i = 0; i < 16; i++) + { + New_Data[i] = New_Data[i] ^ Old_Data[i]; + } +} diff --git a/examples/SaveImgToFlash/Encrypt.h b/examples/SaveImgToFlash/Encrypt.h new file mode 100644 index 0000000..23d29be --- /dev/null +++ b/examples/SaveImgToFlash/Encrypt.h @@ -0,0 +1,50 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef ENCRYPT_H +#define ENCRYPT_H + + /* + ******************************************************************************************** + * INCLUDES + ******************************************************************************************** + */ + #include + #include "lorawan_def.h" + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + void Construct_Data_MIC (uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message); + void Calculate_MIC (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Encrypt_Payload (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Generate_Keys (uint8_t *Key, uint8_t *K1, uint8_t *K2); + void Shift_Left (uint8_t *Data); + void XOR (uint8_t *New_Data,uint8_t *Old_Data); + +#endif diff --git a/examples/SaveImgToFlash/I2C.cpp b/examples/SaveImgToFlash/I2C.cpp new file mode 100644 index 0000000..2cd9b3b --- /dev/null +++ b/examples/SaveImgToFlash/I2C.cpp @@ -0,0 +1,66 @@ +/* + * I2C.c + * + * Created: 22-9-2017 08:48:32 + * Author: adri + */ +#include +#include "I2C.h" + + +void I2C_init (void) +{ + Wire.begin(); +} + + +void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data); + Wire.endTransmission(); +} + + +void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data, lenght); + Wire.endTransmission(); +} + + + +uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register) +{ + uint8_t retVal; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom(slaveAddress, (uint8_t)1); + retVal = Wire.read(); + Wire.endTransmission(); + return retVal; +} + + +void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + uint8_t retVal, i; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)slaveAddress, lenght); + + // Read until all bytes have been retrieved. + for( i = 0 ; i < lenght ; i++) + { + data[i] = Wire.read(); + } + + Wire.endTransmission(); +} diff --git a/examples/SaveImgToFlash/I2C.h b/examples/SaveImgToFlash/I2C.h new file mode 100644 index 0000000..f329aeb --- /dev/null +++ b/examples/SaveImgToFlash/I2C.h @@ -0,0 +1,27 @@ +/* + * I2C.h + * + * Created: 22-9-2017 08:48:47 + * Author: adri + */ + + +#ifndef I2C_H_ +#define I2C_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + #include + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void I2C_init (void); + void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data); + void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register); + void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + +#endif /* I2C_H_ */ diff --git a/examples/SaveImgToFlash/LoRaMAC.cpp b/examples/SaveImgToFlash/LoRaMAC.cpp new file mode 100644 index 0000000..620b716 --- /dev/null +++ b/examples/SaveImgToFlash/LoRaMAC.cpp @@ -0,0 +1,794 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include +#include +#include +#include "spi_functions.h" +#include "AES-128.h" +#include "RFM95.h" +#include "Encrypt.h" +#include "LoRaMAC.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** + FUNCTIONS +******************************************************************************************/ +LORAMAC::LORAMAC(sLoRaWAN *LoRaPTR) +{ + // Check if the pointer is not invalid. + if(LoRaPTR != NULL) + { + // Copy the LoRa structure to the pointer. + lora = LoRaPTR; + } + else + { + //Re-Initialize the RFM95 module to set it to a lowe power state. + RFM_Init(lora); + + lora = 0; + } +} + +LORAMAC::~LORAMAC() +{ + lora = NULL; +} + + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::init(void) +{ + // Check if the pointer is not invalid. + if(lora == NULL) + { + return; + } + + // Initialize the random number generation. + Init_DevNonce_Generation(); + + //Initialize the RFM95 module + RFM_Init(lora); + +} + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::OTAA_connect(void) +{ + uint8_t attempt = 0; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check if the activation method is set to ABP. If so OTAA join procedure is not required. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + return; + } + + // Set the flag for OTAA done to false. + lora->OTAA.OTAAdone = false; + + // Continue to retry the OTAA connection + while(lora->OTAA.OTAAdone == false) + { + // Wait ten seconds before attempting to connect with LoRaWAN + /* + delay(10000); + Serial.print("Sending Join Request: "); + Serial.println(attempt++, DEC); + */ + // Transmit the join request to the back-end. + LoRa_Send_JoinReq(); + + // Enable the ms tick and clear the delay variable to time the JOIN_DELAY + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for join delay 1 + while(lora->timeslot < JOIN_DELAY_1) + {} + + // Receive the Join Accept from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&lora->OTAA.TxChDr, RECEIVE_RX_TIMEOUT_MS); + + // Check if the JOIN_ACCEPT message was received in time slot 1 + if(lora->OTAA.OTAAdone == true) + { + disable_ms_tick(); + return; // No need to wait for Time slot 2. + } + + // Wait for join delay 2 + while(lora->timeslot < JOIN_DELAY_2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->OTAA.RxChDr.channel = CH10_869_525; + lora->OTAA.RxChDr.datarate = SF12_BW125kHz; + LORA_Receive_Data(&lora->OTAA.RxChDr, RECEIVE_RX_TIMEOUT_MS); + + disable_ms_tick(); + + // Check if the JOIN_ACCEPT message was received in time slot 2 + if(lora->OTAA.OTAAdone == true) + { + return; // No need to wait for Time slot 2. + } + else + { + // Increment the OTAA channel for the next join attempt + switch(lora->OTAA.TxChDr.channel) + { + case CH00_868_100: + lora->OTAA.TxChDr.channel = CH01_868_300; + break; + case CH01_868_300: + lora->OTAA.TxChDr.channel = CH02_868_500; + break; + case CH02_868_500: + default: + lora->OTAA.TxChDr.channel = CH00_868_100; + break; + } + } + } +} + + +/****************************************************************************************** +* Description : Function for transmitting data Confirmed Up and then receive the back-ends +* reply. +* Arguments : lora pointer to the complete LoRaWAN structure. +******************************************************************************************/ +void LORAMAC::LORA_send_and_receive (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Set the TX message to confirmed up, so the back-end must reply. Copy the TX settings as the LORA_Send_Data will increment the Tx channel when channel hopping is enabled. + lora->TX.Confirmation = CONFIRMED; + lora->Session.RxChDr.channel = lora->Session.TxChDr.channel; + lora->Session.RxChDr.datarate = lora->Session.TxChDr.datarate; + + // Transmit data in the TX buffer. + LORA_Send_Data(); + + // Enable the ms tick and clear the delay variable to time the RECEIVE_DELAY1 + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for time slot 1 + while(lora->timeslot < RECEIVE_DELAY1) + {} + + // Receive the back-end reply from the gateway with the settings from the Transmission. + if(LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_RX_TIMEOUT_MS) == RX_MESSAGE) + { + // On success, disable the ms tick timer and return with the data. + disable_ms_tick(); + return; // No need to wait for Receive slot 2. + } + + // Wait for time slot 2 before + while(lora->timeslot < RECEIVE_DELAY2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->Session.RxChDr.channel = CH10_869_525; + lora->Session.RxChDr.datarate = SF09_BW125kHz; + + // Disable the timer. + disable_ms_tick(); + + // Receive the reply from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_DELAY2); +} + + +/* +***************************************************************************************** +* Description : Function that is used to build a LoRaWAN data message and then transmit it. +* +* Arguments : *Data_Tx pointer to tranmit buffer +* *Session_Data pointer to sLoRa_Session structure +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LORA_Send_Data(void) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check whether the data payload is within the array size. + if(lora->TX.Count > LORA_FIFO_SIZE) + { + lora->TX.Count = LORA_FIFO_SIZE; + } + + lora->TX.Frame_Port = 1; //Frame port always 1 for now + lora->TX.Frame_Control = 0; + + //Load device address from session data into the message + lora->TX.DevAddr[0] = lora->Session.DevAddr[0]; + lora->TX.DevAddr[1] = lora->Session.DevAddr[1]; + lora->TX.DevAddr[2] = lora->Session.DevAddr[2]; + lora->TX.DevAddr[3] = lora->Session.DevAddr[3]; + + //Load the frame counter from the session data into the message + lora->TX.Frame_Counter = lora->Session.frame_counter_up; + + //Set confirmed or unconfirmed bit + if(lora->TX.Confirmation == CONFIRMED) + { + lora->TX.MAC_Header = CONFIRMED_DATA_UP; // 0x80 + } + //Confirmed + else + { + lora->TX.MAC_Header = UNCONFIRMED_DATA_UP; // 0x40 + } + + /* Build the LoRaWAN Package */ + + // Load mac header + RFM_Data[0] = (uint8_t)lora->TX.MAC_Header; + + //Load the device address in reverse order + RFM_Data[1] = lora->TX.DevAddr[3]; + RFM_Data[2] = lora->TX.DevAddr[2]; + RFM_Data[3] = lora->TX.DevAddr[1]; + RFM_Data[4] = lora->TX.DevAddr[0]; + + // Load the frame control + RFM_Data[5] = lora->TX.Frame_Control; + + //Load frame counter + RFM_Data[6] = (uint8_t)(lora->Session.frame_counter_up >> 0); + RFM_Data[7] = (uint8_t)(lora->Session.frame_counter_up >> 8); + + //Set data counter to 8 + RFM_Counter = 8; + + //If there is data load the Frame_Port field, encrypt the data and load the data + if(lora->TX.Count != 0) + { + //Load Frame port field + RFM_Data[8] = lora->TX.Frame_Port; + + //Raise package counter for the Frame Port field + RFM_Counter++; + + // Copy the data into the RFM buffer. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.Data[0]), lora->TX.Count); + + // Encrypt the copied data in the RFM_Data array, so the original contents of the TX.Data array are not overwritten by the encryption. + Encrypt_Payload(&(RFM_Data[RFM_Counter]), lora->TX.Count, &(lora->Session.AppSKey[0]), &(lora->TX)); + + //Add data Length to package counter + RFM_Counter += lora->TX.Count; + } + + //Calculate MIC + Construct_Data_MIC(&(RFM_Data[0]), RFM_Counter, &(lora->Session), &(lora->TX)); + + // Load the calculated MIC in the RFM transmit array. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.MIC[0]), 4); + + //Add MIC length to RFM package length + RFM_Counter += 4; + + // Send Package to the RFM and transmit the data. + RFM_Send_Package(RFM_Data, RFM_Counter, &(lora->Session.TxChDr), lora->Session.Transmit_Power, &(lora->CH_list)); + + // Raise Frame counter + if(lora->Session.frame_counter_up != UINT16_MAX) + { + //Raise frame counter + lora->Session.frame_counter_up++; + } + else + { + // End of session is reached. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // Clear the frame counter when using ABP + lora->Session.frame_counter_up = 0; + } + else + { + // When using OTAA begin a new session to reset the frame counter. + + } + } + + //Change channel for next message if hopping is activated + LORA_increment_tx_channel(); +} + + +/* +***************************************************************************************** +* Description : Function that is used to receive a LoRaWAN message and retrieve the data from the RFM +* Also checks on CRC, MIC and Device Address +* This function is used for Class A and C motes. +* +* Arguments : *Data_Rx pointer to receive buffer +* *Session_Data pointer to sLoRa_Session struct +* *OTAA_Data pointer to sLoRa_OTAA struct +* *Message_Rx pointer to sLoRa_Message struct used for the received message information +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +RFM_RETVAL LORAMAC::LORA_Receive_Data(eDR_CH *RxSettings, uint16_t Timeout_ms) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i, N; + uint8_t Frame_Options_Length; + uint8_t Data_Location; + uint32_t frequency; + + // Check for invalid lora pointer + if(lora == NULL) + { + return LORA_POINTER_INVALID; + } + + + // If it is a type A device switch RFM to single receive + if(lora->Mote_Class == CLASS_A) + { + lora->RX.retVal = RFM_Single_Receive(RxSettings, Timeout_ms, &(lora->CH_list)); + } + else + { + //Switch RFM to standby + RFM_Switch_Mode(0x01); + lora->RX.retVal = NEW_MESSAGE; + } + + // If there is a message received get the data from the RFM + if(lora->RX.retVal == NEW_MESSAGE) + { + lora->RX.retVal = RFM_Get_Package(&RFM_Data[0], &RFM_Counter); + + //If mote class C switch RFM back to continuous receive + if(lora->Mote_Class == CLASS_C) + { + //Switch RFM to Continuous Receive + RFM_Continuous_Receive(RxSettings, &(lora->CH_list)); + } + } + + /* Clear the DIO# pins of the RFM, then switch the RFM to sleep */ + SPI_Write(RFM_NSS, 0x12, 0xE0); + SPI_Write(RFM_NSS, 0x01, 0x80); + + // If CRC is not OK, return with CRC_NOK to indicate that the received messages is incomplete. + if(lora->RX.retVal != CRC_OK) + { + return CRC_NOK; + } + + // Get the MAC_Header + lora->RX.MAC_Header = (eMESSAGE_TYPES) RFM_Data[0]; + + //Join Accept message + switch(lora->RX.MAC_Header) // 0x20 + { + ///------------------------------------------------------------------------------------------------------------------------------------------------------ + case JOIN_ACCEPT: + + // Decrypt the data + for(i = 0 ; i < ((RFM_Counter - 1) >> 4) ; i++) + { + AES_Encrypt(&(RFM_Data[(i << 4)+1]), lora->OTAA.AppKey); + } + + // Calculate MIC + RFM_Counter -= 4; + + // Get MIC + Calculate_MIC(RFM_Data, RFM_Counter, lora->OTAA.AppKey, &(lora->RX)); + + // Check if the calculated and received MIC match or not. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC), 4) == 0) + { + lora->RX.retVal = MIC_OK; + } + else + { + // When the MIC is NOK, return the MIC_NOK_OTAA + return MIC_NOK_OTAA; + } + + // Get AppNonce + lora->OTAA.AppNonce[0] = RFM_Data[1]; + lora->OTAA.AppNonce[1] = RFM_Data[2]; + lora->OTAA.AppNonce[2] = RFM_Data[3]; + + // Get Net ID + lora->OTAA.NetID[0] = RFM_Data[4]; + lora->OTAA.NetID[1] = RFM_Data[5]; + lora->OTAA.NetID[2] = RFM_Data[6]; + + // Get session Device address + lora->Session.DevAddr[3] = RFM_Data[7]; + lora->Session.DevAddr[2] = RFM_Data[8]; + lora->Session.DevAddr[1] = RFM_Data[9]; + lora->Session.DevAddr[0] = RFM_Data[10]; + + // Get the DLsettings + lora->CH_list.rx1_dr_offset = (RFM_Data[11] & 0x70) >> 4; + lora->CH_list.rx2_dr = RFM_Data[11] & 0x0F; + + // Get the RXDelay + lora->CH_list.rx_delay = RFM_Data[12]; + + // Calculate Network Session Key + lora->Session.NwkSKey[0] = 0x01; + + // Load AppNonce + lora->Session.NwkSKey[1] = lora->OTAA.AppNonce[0]; + lora->Session.NwkSKey[2] = lora->OTAA.AppNonce[1]; + lora->Session.NwkSKey[3] = lora->OTAA.AppNonce[2]; + + // Load NetID + lora->Session.NwkSKey[4] = lora->OTAA.NetID[0]; + lora->Session.NwkSKey[5] = lora->OTAA.NetID[1]; + lora->Session.NwkSKey[6] = lora->OTAA.NetID[2]; + + // Load Dev Nonce + lora->Session.NwkSKey[7] = lora->OTAA.DevNonce[0]; + lora->Session.NwkSKey[8] = lora->OTAA.DevNonce[1]; + + // Pad with zeros + lora->Session.NwkSKey[9] = 0x00; + lora->Session.NwkSKey[10] = 0x00; + lora->Session.NwkSKey[11] = 0x00; + lora->Session.NwkSKey[12] = 0x00; + lora->Session.NwkSKey[13] = 0x00; + lora->Session.NwkSKey[14] = 0x00; + lora->Session.NwkSKey[15] = 0x00; + + // Copy to AppSkey + memcpy(lora->Session.AppSKey, lora->Session.NwkSKey, 16); + + //Change first byte of AppSKey + lora->Session.AppSKey[0] = 0x02; + + // Calculate the keys + AES_Encrypt(lora->Session.NwkSKey, lora->OTAA.AppKey); + AES_Encrypt(lora->Session.AppSKey, lora->OTAA.AppKey); + + // Reset Frame counters + lora->Session.frame_counter_up = 0; + lora->Session.frame_counter_down = 0; + + // Check for the optional CF list in the JOin Accept message + if(RFM_Counter > 13) + { + //printStringAndHex("Join Accept Message: ", RFM_Data, RFM_Counter); + // Calculate the number of frequencies + N = (RFM_Counter - 14) / 3; + + // Maximum of 5 frequencies + if(N > CFLIST_FREQUENCIES_MAX) + { + N = CFLIST_FREQUENCIES_MAX; + } + + //Serial.println("CFlist"); + + // Retrieve all frequencies and print them to the serial port. + for( i = 0 ; i < N ; i++) + { + // Construct the frequency value and multiply the value with 100 to get the frequency in Hertz. + frequency = (((uint32_t)(RFM_Data[13 + (i*3)]) << 0) | ((uint32_t)(RFM_Data[14 + (i*3)]) << 8) | ((uint32_t)(RFM_Data[15 + (i*3)]) << 16)) * 100; + + + // Check if the frequency is between the 867.0MHz and 868.0 MHz and the boundaries of the channel list haven't been reached yet. + if((frequency > 867000000) && (frequency < 868000000) && (lora->CH_list.index <= CFLIST_FREQUENCIES_MAX)) + { + // Add the retrieved frequency to the list of channel settings and increment the index; + lora->CH_list.channel[(lora->CH_list.index)] = calculate_frequency_settings(frequency); + lora->CH_list.index += 1; + } + } + } + + // Print the received variables + /* + printStringAndHex("Device Address: ", lora->Session.DevAddr, 4); + printStringAndHex("Network Session Key: ", lora->Session.NwkSKey, 16); + printStringAndHex("Application Session Key: ", lora->Session.AppSKey, 16); +*/ + //Clear Data counter + lora->RX.Count = 0; + lora->OTAA.OTAAdone = true; + lora->RX.retVal = OTAA_COMPLETE; + break; + + ///------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + case UNCONFIRMED_DATA_UP: + case UNCONFIRMED_DATA_DOWN: + case CONFIRMED_DATA_UP: + case CONFIRMED_DATA_DOWN: + + //Get device address from received data + lora->RX.DevAddr[0] = RFM_Data[4]; + lora->RX.DevAddr[1] = RFM_Data[3]; + lora->RX.DevAddr[2] = RFM_Data[2]; + lora->RX.DevAddr[3] = RFM_Data[1]; + + //Get frame control field + lora->RX.Frame_Control = RFM_Data[5]; + + //Get the frame counter by combining two bytes + lora->RX.Frame_Counter = ((uint16_t)RFM_Data[7] << 8) | (uint16_t)RFM_Data[6]; + + //Lower Package length with 4 to remove MIC length + RFM_Counter -= 4; + + //Calculate MIC + Construct_Data_MIC(&RFM_Data[0], RFM_Counter, &(lora->Session), &(lora->RX)); + + // Compare the calculated MIC and the Received MIC. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC[0]), 4) != 0) + { + lora->RX.retVal = MIC_NOK_MESSAGE; + return MIC_NOK_MESSAGE; + } + + // Compare the Received message's device address and the session device address to check whether this message was intended for this mote. + if(memcmp(lora->RX.DevAddr, lora->Session.DevAddr, 4) != 0) + { + //Return when ADDRESS_NOK + lora->RX.retVal = ADDRESS_NOK; + return ADDRESS_NOK; + } + + //Get length of frame options field + Frame_Options_Length = (lora->RX.Frame_Control & 0x0F); + + //Add length of frame options field to data location + Data_Location = 8 + Frame_Options_Length; + + //Check if there's is data in the package of not. OTherwise there might be + if(RFM_Counter == Data_Location) + { + // The Received number of bytes and the data start location match, so there's no data in the received message. + RFM_Counter = 0; + } + else + { + //Get port field when there is data + lora->RX.Frame_Port = RFM_Data[8]; + + //Calculate the amount of data in the package + lora->RX.Count = (RFM_Counter - Data_Location - 1); + + //Correct the data location by 1 for the Fport field + Data_Location++; + + // Copy and decrypt the data + memcpy(lora->RX.Data, &(RFM_Data[Data_Location]), lora->RX.Count); + + if(lora->RX.Frame_Port == 0) + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.NwkSKey, &(lora->RX)); + } + else + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.AppSKey, &(lora->RX)); + } + } + lora->RX.retVal = RX_MESSAGE; + break; + ///------------------------------------------------------------------------------------------------------------------------------------------------------- + default: + lora->RX.retVal = MAC_HEADER_NOK; + break; + } + // Return the message status + return lora->RX.retVal; +} + +/****************************************************************************************** +* Description : Function that is used to generate device nonce used in the join request function +* This is based on a pseudo random function in the Arduino library +* +* Arguments : *Devnonce pointer to the devnonce arry of withc is uint8_t[2] +******************************************************************************************/ +void LORAMAC::Generate_DevNonce(uint8_t *DevNonce) +{ + uint16_t RandNumber; + + // Check for invalid lora pointer + if(DevNonce == NULL) + { + return; + } + // Generate a random number between 0x0000 to 0xFFFF + RandNumber = random(0xFFFF); + //Serial.println(RandNumber, DEC); + + // Return the Dev Nonce value. + DevNonce[0] = (uint8_t)(RandNumber >> 0); + DevNonce[1] = (uint8_t)(RandNumber >> 8); +} + +/* +***************************************************************************************** +* Description : Function that is used to send a join request to a network. +* +* Arguments : *OTAA_Data pointer to sLoRa_OTAA struct +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LoRa_Send_JoinReq(void) +{ + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + //Initialize RFM data buffer + uint8_t data[23], lenght; + + // Set the OTAA done to false, so the higher layer code can detect if OTAA has been completed successfully + lora->OTAA.OTAAdone = false; + lora->TX.MAC_Header = JOIN_REQUEST; //Join request 0x00 + + //Construct OTAA Request message + //Load Header in package + data[0] = lora->TX.MAC_Header; + + //Load AppEUI in package + for(i = 0 ; i < 8 ; i++) + { + data[i+1] = lora->OTAA.AppEUI[7-i]; + } + + //Load DevEUI in package + for(i = 0; i < 8; i++) + { + data[i+9] = lora->OTAA.DevEUI[7-i]; + } + + //Generate DevNonce + Generate_DevNonce(&(lora->OTAA.DevNonce[0])); + + //Load DevNonce in package + data[17] = lora->OTAA.DevNonce[0]; + data[18] = lora->OTAA.DevNonce[1]; + + //Set length of package + lenght = 19; + + //Get MIC + Calculate_MIC(data, lenght, lora->OTAA.AppKey, &(lora->TX)); + + //Load MIC in package + memcpy(&(data[19]), &(lora->TX.MIC[0]), 4); + + //Set lenght of package to the right length + lenght = 23; + + //Send Package + RFM_Send_Package(data, lenght, &(lora->OTAA.TxChDr), lora->OTAA.Transmit_Power, &(lora->CH_list)); +} + + + +/******************************************************************************************************************************************************** +* Description : Initialize the random number generation seed, so all generated random numbers are new when generated. +********************************************************************************************************************************************************/ +#define EEADDR 0x3FF +void LORAMAC::Init_DevNonce_Generation(void) +{ + uint8_t RandSeed; + unsigned int addr = 0; + + RandSeed = eeprom_read_byte((uint8_t*)addr); + //Serial.print("Random Seed: "); + //Serial.println(RandSeed, HEX); + + eeprom_write_byte((uint8_t*)addr, (RandSeed+1)); + + // Initialize the Random seed with the retrieved seed value + randomSeed(RandSeed); +} + + +/******************************************************************************************************************************************************** +* Description : Increment the transmit channel according to the connection methode and the available CF list from the Over The Air Activation. +********************************************************************************************************************************************************/ +void LORAMAC::LORA_increment_tx_channel (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Check whether channel hopping is enabled or not. + if(lora->CH_list.channel_hopping_on == false) + { + return; + } + + // Increment the Transmit channel + lora->Session.TxChDr.channel = (eLoRaWAN_CHANNELS)(lora->Session.TxChDr.channel + 1); + + // Depending on the activation method, increment the transmission channel to a maximum channel + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // With ABP only three channels are available + if(lora->Session.TxChDr.channel > CH02_868_500) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } + else + { + // Check if the incremented channel is above the number of received channels from OTAA + if(lora->Session.TxChDr.channel > ((eLoRaWAN_CHANNELS) CH02_868_500 + (lora->CH_list.index))) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } +} diff --git a/examples/SaveImgToFlash/LoRaMAC.h b/examples/SaveImgToFlash/LoRaMAC.h new file mode 100644 index 0000000..541f01a --- /dev/null +++ b/examples/SaveImgToFlash/LoRaMAC.h @@ -0,0 +1,62 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef LORAMAC_H +#define LORAMAC_H + + /********************************************************************************************* + * INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + /********************************************************************************************* + * TYPE DEFINITION + *********************************************************************************************/ + + /****************************************************************************************** + * FUNCTION PROTOTYPES + ******************************************************************************************/ + class LORAMAC + { + public: + LORAMAC(sLoRaWAN *LoRaPTR); + ~LORAMAC(); + + void init (void); + void OTAA_connect (void); + void LORA_Send_Data (void); + void LORA_send_and_receive (void); + void LORA_increment_tx_channel (void); + RFM_RETVAL LORA_Receive_Data (eDR_CH *RxSettings, uint16_t Timeout_ms); + void LoRa_Send_JoinReq (void); + void Init_DevNonce_Generation (void); + void Generate_DevNonce (uint8_t *DevNonce); + + private: + sLoRaWAN *lora; // Pointer to the LoRaWAN structure with all the settings and information + }; +#endif diff --git a/examples/SaveImgToFlash/LowPower.cpp b/examples/SaveImgToFlash/LowPower.cpp new file mode 100644 index 0000000..6aff586 --- /dev/null +++ b/examples/SaveImgToFlash/LowPower.cpp @@ -0,0 +1,1195 @@ +/******************************************************************************* +* LowPower Library +* Version: 1.80 +* Date: 04-10-2018 +* Author: Lim Phang Moh +* Company: Rocket Scream Electronics +* Website: www.rocketscream.com +* +* This is a lightweight low power library for Arduino. +* +* This library is licensed under Creative Commons Attribution-ShareAlike 3.0 +* Unported License. +* +* Revision Description +* ======== =========== +* 1.80 Added support for ATmega88 and ATmega168P. PowerExtStandby() +* modified because not supported on Atmega88 / Atmega168 +* Contributed by mrguen. +* 1.70 Added support for ATmega644P and ATmega1284P. +* Contributed by alexreinert. +* 1.60 Added support for ATmega256RFR2. Contributed by Rodmg. +* 1.50 Fixed compiler optimization (Arduino IDE 1.6.x branch) on BOD enable +* function that causes the function to be over optimized. +* 1.40 Added support for ATSAMD21G18A. +* Library format compliant with Arduino IDE 1.5.x. +* 1.30 Added support for ATMega168, ATMega2560, ATMega1280 & ATMega32U4. +* Tested to work with Arduino IDE 1.0.1 - 1.0.4. +* 1.20 Remove typo error in idle method for checking whether Timer 0 was +* turned off. +* Remove dependecy on WProgram.h which is not required. +* Tested to work with Arduino IDE 1.0. +* 1.10 Added #ifndef for sleep_bod_disable() for compatibility with future +* Arduino IDE release. +* 1.00 Initial public release. +*******************************************************************************/ +#if defined (__AVR__) + #include + #include + #include + #include +#elif defined (__arm__) + +#else + #error "Processor architecture is not supported." +#endif + +#include "LowPower.h" + +#if defined (__AVR__) +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#ifndef sleep_bod_disable +#define sleep_bod_disable() \ +do { \ + unsigned char tempreg; \ + __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ + "ori %[tempreg], %[bods_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" "\n\t" \ + "andi %[tempreg], %[not_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" \ + : [tempreg] "=&d" (tempreg) \ + : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ + [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ + [not_bodse] "i" (~_BV(BODSE))); \ +} while (0) +#endif +#endif + +#define lowPowerBodOn(mode) \ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); + +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#define lowPowerBodOff(mode)\ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sleep_bod_disable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); +#endif + +// Some macros is still missing from AVR GCC distribution for ATmega32U4 +#if defined __AVR_ATmega32U4__ + // Timer 4 PRR bit is currently not defined in iom32u4.h + #ifndef PRTIM4 + #define PRTIM4 4 + #endif + + // Timer 4 power reduction macro is not defined currently in power.h + #ifndef power_timer4_disable + #define power_timer4_disable() (PRR1 |= (uint8_t)(1 << PRTIM4)) + #endif + + #ifndef power_timer4_enable + #define power_timer4_enable() (PRR1 &= (uint8_t)~(1 << PRTIM4)) + #endif +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega328P/168 into idle state. Please make sure you +* understand the implication and result of disabling module. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 8. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega32U4 into idle state. Please make sure you +* understand the implication and result of disabling module. +* Take note that Timer 2 is not available and USART0 is replaced +* with USART1 on ATmega32U4. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 4. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 5. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 6. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 7. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 8. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +* 10.usb USB module disable control: +* (a) USB_OFF - Turn off USB module +* (b) USB_ON - Leave USB module in its default state +*******************************************************************************/ +#if defined __AVR_ATmega32U4__ +void LowPowerClass::idle(period_t period, adc_t adc, + timer4_t timer4, timer3_t timer3, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb) +{ + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (twi == TWI_OFF) power_twi_disable(); + if (usb == USB_OFF) power_usb_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (twi == TWI_OFF) power_twi_enable(); + if (usb == USB_OFF) power_usb_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega644P & ATmega1284P into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra USART 1 compared to an ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 8. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega644P__) || defined (__AVR_ATmega1284P__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega2560 & ATmega1280 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 3, 2, 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart3 USART3 module disable control: +* (a) USART3_OFF - Turn off USART3 module +* (b) USART3_ON - Leave USART3 module in its default state +* +* 11.usart2 USART2 module disable control: +* (a) USART2_OFF - Turn off USART2 module +* (b) USART2_ON - Leave USART2 module in its default state +* +* 12.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 13.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 14.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart3 == USART3_OFF) power_usart3_disable(); + if (usart2 == USART2_OFF) power_usart2_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart3 == USART3_OFF) power_usart3_enable(); + if (usart2 == USART2_OFF) power_usart2_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega256RFR2 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 11.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 12.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega256RFR2__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + + +/******************************************************************************* +* Name: adcNoiseReduction +* Description: Putting microcontroller into ADC noise reduction state. This is +* a very useful state when using the ADC to achieve best and low +* noise signal. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::adcNoiseReduction(period_t period, adc_t adc, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_ADC); + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + } + #endif +} + +/******************************************************************************* +* Name: powerDown +* Description: Putting microcontroller into power down state. This is +* the lowest current consumption state. Use this together with +* external pin interrupt to wake up through external event +* triggering (example: RTC clockout pin, SD card detect pin). +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerDown(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_DOWN); + #else + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerSave +* Description: Putting microcontroller into power save state. This is +* the lowest current consumption state after power down. +* Use this state together with an external 32.768 kHz crystal (but +* 8/16 MHz crystal/resonator need to be removed) to provide an +* asynchronous clock source to Timer 2. Please take note that +* Timer 2 is also used by the Arduino core for PWM operation. +* Please refer to wiring.c for explanation. Removal of the external +* 8/16 MHz crystal/resonator requires the microcontroller to run +* on its internal RC oscillator which is not so accurate for time +* critical operation. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerSave(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_SAVE); + #else + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: powerStandby +* Description: Putting microcontroller into power standby state. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerStandby(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_STANDBY); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerExtStandby +* Description: Putting microcontroller into power extended standby state. This +* is different from the power standby state as it has the +* capability to run Timer 2 asynchronously. +* Not implemented on Atmega88 and Atmega168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerExtStandby(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + + #if defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) // SLEEP_MODE_EXT_STANDBY not implemented on Atmega88 / Atmega168 + #else + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_EXT_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + } + #endif + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: ISR (WDT_vect) +* Description: Watchdog Timer interrupt service routine. This routine is +* required to allow automatic WDIF and WDIE bit clearance in +* hardware. +* +*******************************************************************************/ +ISR (WDT_vect) +{ + // WDIE & WDIF is cleared in hardware upon entering this ISR + wdt_disable(); +} + +#elif defined (__arm__) +#if defined (__SAMD21G18A__) +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into idle mode. This is the lowest current +* consumption mode. Requires separate handling of clock and +* peripheral management (disabling and shutting down) to achieve +* the desired current consumption. +* +* Argument Description +* ========= =========== +* 1. idleMode Idle mode level (0, 1, 2) where IDLE_2 level provide lowest +* current consumption in this mode. +* +*******************************************************************************/ +void LowPowerClass::idle(idle_t idleMode) +{ + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + PM->SLEEP.reg = idleMode; + __DSB(); + __WFI(); +} + +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into standby mode. This is the lowest current +* consumption mode. Use this together with the built-in RTC (use +* RTCZero library) or external pin interrupt to wake up through +* external event triggering. +* +* Argument Description +* ========= =========== +* 1. NIL +* +*******************************************************************************/ +void LowPowerClass::standby() +{ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + __DSB(); + __WFI(); +} + +#else + #error "Please ensure chosen MCU is ATSAMD21G18A." +#endif +#else + #error "Processor architecture is not supported." +#endif + +LowPowerClass LowPower; diff --git a/examples/SaveImgToFlash/LowPower.h b/examples/SaveImgToFlash/LowPower.h new file mode 100644 index 0000000..5a8864b --- /dev/null +++ b/examples/SaveImgToFlash/LowPower.h @@ -0,0 +1,173 @@ +#ifndef LowPower_h +#define LowPower_h + +#include "Arduino.h" + +enum period_t +{ + SLEEP_15MS, + SLEEP_30MS, + SLEEP_60MS, + SLEEP_120MS, + SLEEP_250MS, + SLEEP_500MS, + SLEEP_1S, + SLEEP_2S, + SLEEP_4S, + SLEEP_8S, + SLEEP_FOREVER +}; + +enum bod_t +{ + BOD_OFF, + BOD_ON +}; + +enum adc_t +{ + ADC_OFF, + ADC_ON +}; + +enum timer5_t +{ + TIMER5_OFF, + TIMER5_ON +}; + +enum timer4_t +{ + TIMER4_OFF, + TIMER4_ON +}; + +enum timer3_t +{ + TIMER3_OFF, + TIMER3_ON +}; + +enum timer2_t +{ + TIMER2_OFF, + TIMER2_ON +}; + +enum timer1_t +{ + TIMER1_OFF, + TIMER1_ON +}; + +enum timer0_t +{ + TIMER0_OFF, + TIMER0_ON +}; + +enum spi_t +{ + SPI_OFF, + SPI_ON +}; + +enum usart0_t +{ + USART0_OFF, + USART0_ON +}; + +enum usart1_t +{ + USART1_OFF, + USART1_ON +}; + +enum usart2_t +{ + USART2_OFF, + USART2_ON +}; + +enum usart3_t +{ + USART3_OFF, + USART3_ON +}; + +enum twi_t +{ + TWI_OFF, + TWI_ON +}; + +enum usb_t +{ + USB_OFF, + USB_ON +}; + +enum idle_t +{ + IDLE_0, + IDLE_1, + IDLE_2 +}; + +class LowPowerClass +{ + public: + #if defined (__AVR__) + + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega644P__ || defined (__AVR_ATmega1284P__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega2560__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega256RFR2__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega32U4__ + void idle(period_t period, adc_t adc, timer4_t timer4, + timer3_t timer3, timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb); + #else + #error "Please ensure chosen MCU is either 88, 168, 168P, 328P, 32U4, 2560 or 256RFR2." + #endif + void adcNoiseReduction(period_t period, adc_t adc, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerDown(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerSave(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerStandby(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerExtStandby(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + + #elif defined (__arm__) + + #if defined (__SAMD21G18A__) + void idle(idle_t idleMode); + void standby(); + #else + #error "Please ensure chosen MCU is ATSAMD21G18A." + #endif + + #else + + #error "Processor architecture is not supported." + + #endif +}; + +extern LowPowerClass LowPower; +#endif diff --git a/examples/SaveImgToFlash/PL_microEPD44.cpp b/examples/SaveImgToFlash/PL_microEPD44.cpp new file mode 100644 index 0000000..75e4728 --- /dev/null +++ b/examples/SaveImgToFlash/PL_microEPD44.cpp @@ -0,0 +1,416 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#include "LowPower.h" +#include "PL_microEPD44.h" +#include +#include "spi_functions.h" + +PL_microEPD::PL_microEPD(int8_t _cs, int8_t _rst, int8_t _busy) : Adafruit_GFX(EPD_WIDTH, +EPD_HEIGHT) { + cs = _cs; + rst = _rst; + busy = _busy; +} + +// PUBLIC + +// ****************************************************************************************** +// BEGIN - Resetting UC8156 driver IC and configuring all sorts of behind-the-scenes-settings +// By default (WHITEERASE=TRUE) a clear screen update is triggered once to erase the screen. +// ****************************************************************************************** +void PL_microEPD::begin(bool whiteErase) { + pinMode(cs, OUTPUT); + pinMode(busy, INPUT); + pinMode(rst, OUTPUT); + + delay(10); + + digitalWrite(rst, HIGH); + delay(5); + pinMode(9, OUTPUT); // VDD for EPD + digitalWrite(9, HIGH); // Enable VDD for UC8156 + delay(5); + digitalWrite(rst, LOW); // Trigger global reset + delay(5); + digitalWrite(rst, HIGH); + delay(5); + + delay(10); + + waitForBusyInactive(); + + _width=24; _height=24; nextline= _width/4; _buffersize=_width*_height/4; + _EPDwidth=148; _EPDheight=72; + writeRegister(EPD_PANELSETTING, 0x12, -1, -1, -1); + writeRegister(EPD_WRITEPXRECTSET, 0, 31, 0, 23); + writeRegister(EPD_VCOMCONFIG, 0x00, 0x00, 0x24, 0x07); + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); + writeRegister(EPD_LOADMONOWF, 0x60, -1, -1, -1); + writeRegister(EPD_INTTEMPERATURE, 0x0A, -1, -1, -1); + writeRegister(EPD_BOOSTSETTING, 0x22, 0x37, -1, -1); // DC 30%/30% (Normal/Softstart), 125KHz, 16384 CC + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + setTextColor(EPD_BLACK); //Set text color to black as default + + if (whiteErase) + WhiteErase(); //Start with a white refresh if TRUE + clear(); +} + +// ************************************************************************************ +// CLEAR - Erases the image buffer and triggers an image update and sets the cursor +// back to the origin coordinates (0,0). +// ************************************************************************************ +void PL_microEPD::clearBuffer() { + memset(buffer, 0xFF, sizeof(buffer)); + setCursor(0,0); +} + +void PL_microEPD::clear(int c) { + writeRegister(EPD_WRITEPXRECTSET, 0, 71, 0, 147); + digitalWrite(cs, LOW); + SPI.transfer(0x10); + if (c==EPD_BLACK) + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0x00); + else + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + clearBuffer(); +} + +void PL_microEPD::setCursorSegment(int x, int y) { + if (x < (_EPDwidth - _width) && y < (_EPDheight - _height)) { + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + } else { + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + writeRegister(EPD_PIXELACESSPOS, 0, 0, -1, -1); + } +} + +// ************************************************************************************ +// DRAWPIXEL - Draws pixel in the memory buffer at position X, Y with the value of the +// parameter color (2 bit value). +// ************************************************************************************ +void PL_microEPD::drawPixel(int16_t x, int16_t y, uint16_t color) { + + if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height) || (color>4 )) return; + x+=2; + uint8_t pixels = buffer[x/4 + (y) * nextline]; + switch (x%4) { //2-bit grayscale dot + case 0: buffer[x/4 + (y) * nextline] = (pixels & 0x3F) | ((uint8_t)color << 6); break; + case 1: buffer[x/4 + (y) * nextline] = (pixels & 0xCF) | ((uint8_t)color << 4); break; + case 2: buffer[x/4 + (y) * nextline] = (pixels & 0xF3) | ((uint8_t)color << 2); break; + case 3: buffer[x/4 + (y) * nextline] = (pixels & 0xFC) | (uint8_t)color; break; + } +} + +// ************************************************************************************ +// UPDATE - Triggers an image update based on the content written in the image buffer. +// There are three different updateModes supported: EPD_UPD_FULL(0) is set by default, +// achieves four greyelevels, takes about 800ms and refreshes all pixels. This is the +// update mode having the best image quality. EPD_UPD_PART(1) is a variant of the +// previous one but only changing pixels are refreshed. This results in less flickering +// for the price of a slightly higher pixel to pixel crosstalk. EPD_UPD_MONO(2) is +// again a variant of the previous update mode but only about 250ms long. this allows +// slightly faster and more responsive updates for the price of only two greylevels +// being supported (EPD_BLACK and EPD_WHITE). Depending on your application it is +// recommended to insert a full update EPD_UPD_FULL(0) after a couple of mono updates +// to increase the image quality. +// THIS KIND OF DISPLAY IS NOT SUITED FOR LONG RUNNING ANIMATIONS OR APPLICATIONS WITH +// CONTINUOUSLY HIGH UPDATE RATES. AS A RULE OF THUMB PLEASE TRIGGER UPDATES IN AVERAGE +// NOT FASTER THAN MINUTELY (OR RUN BACK2BACK UPDATES NOT LONGER AS ONE HOUR PER DAY.) +// ************************************************************************************ +void PL_microEPD::update(int updateMode) { + if (updateMode==3) + powerOn(0); + //powerOn(1); + else + powerOn(); + switch (updateMode) { + case 0: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x00, -1, -1, -1); + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); + break; + case 2: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated + break; + case 3: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegister(EPD_DRIVERVOLTAGE, 0x00, 0x88, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + //writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x07, -1, -1, -1); //Only changing pixels updated (delta mode) + LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); + LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF); + break; + } + powerOff(); +} + + +// ************************************************************************************ +// SETVBORDERCOLOR - Sets the color of the VBorder around the active area. By default +// this is set to White (matching to the Paperino FrontCover) and should not be changed +// ************************************************************************************ +void PL_microEPD::setVBorderColor(int color) { + if (color==3) writeRegister(EPD_BORDERSETTING, 0xF7, -1, -1, -1); // + if (color==0) writeRegister(EPD_BORDERSETTING, 0x07, -1, -1, -1); // + update(EPD_UPD_PART); + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); // +} + +// PRIVATE + +// ************************************************************************************ +// POWERON - Activates the defined high voltages needed to update the screen. The +// command should always be called before triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOn(bool lowpower) { + waitForBusyInactive(); + writeRegister(EPD_SETRESOLUTION, 0, 239, 0, 147); + writeRegister(EPD_TCOMTIMING, 0x67, 0x55, -1, -1); // GAP=115µs; S2G=G2S=85µs @1MHz + writeRegister(EPD_POWERSEQUENCE, 0x00, 0x00, 0x00, -1); + if (lowpower) + writeRegister(EPD_POWERCONTROL, 0x81, -1, -1, -1); // TCON 500KHz; Internal clock auto-enable + else + writeRegister(EPD_POWERCONTROL, 0xC1, -1, -1, -1); // TCON 1MHz; Internal clock always enabled + while (readRegister(0x15) == 0) {} // Wait until Internal Pump is ready +} + + +// ************************************************************************************ +// POWEROFF - Deactivates the high voltages needed to update the screen. The +// command should always be called after triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOff() { + writeRegister(EPD_POWERCONTROL, 0xD0, -1, -1, -1); + waitForBusyInactive(); + writeRegister(EPD_POWERCONTROL, 0xC0, -1, -1, -1); + waitForBusyInactive(); +} + + +// ************************************************************************************ +// Fills a segment of 24 x 24 px size with text, supporting textSize(<=4) +// ************************************************************************************ +void PL_microEPD::printText(String text, int x, int y, int size){ + int i=0, xo=0, yo=0; + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + int ys = y, xs = x; + setTextSize(size); + clearBuffer(); + + while (i _width) + xs -= size*6; + x -=size*6; + i += 1; + } +} + +void PL_microEPD::fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + clearBuffer(); + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + fillRect(0, _height-h, w, _height, color); + writeRegister(EPD_WRITEPXRECTSET, y - _height, y -1, x - _width, x -1); + writeRegister(EPD_PIXELACESSPOS, y - _height, x - _width, -1, -1); + writeBuffer(); +} + +void PL_microEPD::drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h){ + clearBuffer(); + x = _EPDwidth-_width-x; + y = _EPDheight-_height-y; + drawBitmap(0, 0, bitmap, w, h, 0); // WHITE=3 or BLACK=1 + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + writeBuffer(); +} + +// ************************************************************************************ +// WRITEBUFFER - Sends the content of the memory buffer to the UC8156 driver IC. +// ************************************************************************************ +void PL_microEPD::writeBuffer(boolean previousRAM){ + if (previousRAM) + writeRegister(EPD_DATENTRYMODE, 0x37, -1, -1, -1); + else + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + //SPI_Read_Array(cs, 0x10, buffer, _buffersize); //(write)this clears the buffer..(?) + + digitalWrite(cs, LOW); + SPI.transfer(0x10); + for (int i=0; i < _buffersize; i++) + SPI.transfer(buffer[i]); + digitalWrite(cs, HIGH); + + waitForBusyInactive(); +} + + +// ************************************************************************************ +// WRITE REGISTER - Sets register ADDRESS to value VAL1 (optional: VAL2, VAL3, VAL4) +// ************************************************************************************ +void PL_microEPD::writeRegister(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + waitForBusyInactive(); +} + +void PL_microEPD::writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + //waitForBusyInactive(EPD_TMG_SR2); +} +// ************************************************************************************ +// READREGISTER - Returning the value of the register at the specified address +// ************************************************************************************ +byte PL_microEPD::readRegister(char address){ + byte data; + digitalWrite(cs, LOW); + SPI.transfer(address | EPD_REGREAD); + data = SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + waitForBusyInactive(); + return data; // can be improved +} + + +// ************************************************************************************ +// WHITE ERASE - Triggers two white updates to erase the screen and set back previous +// ghosting. Recommended after each power cycling. +// ************************************************************************************ +void PL_microEPD::WhiteErase() { + clear(EPD_WHITE); + update(); +} + +void PL_microEPD::saveFBToFlash(int address) { + flash_eraseSector(0); + writeRegister(EPD_WRITEPXRECTSET, 0, 69, 0, 147); + writeRegister(EPD_PIXELACESSPOS, 0, 0, -1, -1); + for (int i=0; i<15; i++) { + digitalWrite(cs, LOW); + SPI.transfer(0x11 | EPD_REGREAD); + for (int j=0; j < sizeof(buffer); j++) //144 bytes + buffer[j] = SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + waitForBusyInactive(); + flash_write(address+i*256, buffer, sizeof(buffer)); + } +} + +void PL_microEPD::saveImgToFlash(int address) { + flash_eraseSector(address); + flash_eraseSector(address+4096); + int c=0; + for (int i=0; i<18; i++) { + digitalWrite(cs, LOW); + for (int j=0; j < sizeof(buffer); j++) { //144 bytes + buffer[j] = pgm_read_byte_near(pic + c); + c++; + } + digitalWrite(cs, HIGH); + waitForBusyInactive(); + flash_write(address+i*256, buffer, sizeof(buffer)); + }} + +void PL_microEPD::loadFromFlash(int address, bool toPreviousBuffer) { + writeRegister(EPD_WRITEPXRECTSET, 0, 69, 0, 147); + if (toPreviousBuffer) + writeRegister(EPD_DATENTRYMODE, 0x37, -1, -1, -1); //Previous buffer @UC8156 + writeRegister(EPD_PIXELACESSPOS, 0, 0, -1, -1); + + for (int i=0; i<18; i++) { + memset(buffer, 0xFF, sizeof(buffer)); + flash_read(address+i*256, buffer, 144); + digitalWrite(cs, LOW); + SPI.transfer(0x10); + for (int j=0; j < sizeof(buffer); j++) + if (i==0 && j==0) {} + else + SPI.transfer(buffer[j]); + digitalWrite(cs, HIGH); + Serial.println(); + waitForBusyInactive(); + } +} + + +// ************************************************************************************ +// WAITFORBUSYINACTIVE - Sensing to ‘Busy’ pin to detect the UC8156 driver status. +// Function returns only after driver IC is free again for listening to new commands. +// ************************************************************************************ +void PL_microEPD::waitForBusyInactive(){ + while (digitalRead(busy) == LOW) {} +} + +// ************************************************************************************ +// DEEPSLEEP - Putting the UC8156 in deep sleep mode with less than 1µA current @3.3V. +// Reset pin toggling needed to wakeup the driver IC again. +// ************************************************************************************ +void PL_microEPD::deepSleep(void) { + writeRegister(0x21, 0xff, 0xff, 0xff, 0xff); +} + +void PL_microEPD::end(void) { + digitalWrite(rst, HIGH); // Turn VDD for EPD off and put remaining + delay(5); + digitalWrite(9, LOW); // GPIOs LOW to minimize power consumption + delay(5); + digitalWrite(rst, LOW); // to around 1µA + pinMode(busy, OUTPUT); + digitalWrite(busy, LOW); + digitalWrite(cs, LOW); +} diff --git a/examples/SaveImgToFlash/PL_microEPD44.h b/examples/SaveImgToFlash/PL_microEPD44.h new file mode 100644 index 0000000..ece865b --- /dev/null +++ b/examples/SaveImgToFlash/PL_microEPD44.h @@ -0,0 +1,103 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#ifndef PL_microEPD_h +#define PL_microEPD_h + +#include "Adafruit_GFX.h" +#include "spi_flash.h" +#include "progmem.h" + +#define EPD_WIDTH (146) +#define EPD_HEIGHT (240) + +#define EPD_BLACK 0x00 +#define EPD_DGRAY 0x01 +#define EPD_LGRAY 0x02 +#define EPD_WHITE 0x03 + +#define EPD_UPD_FULL 0x00 // Triggers a Full update, 4 GL, 800ms +#define EPD_UPD_PART 0x01 // Triggers a Partial update, 4 GL, 800ms +#define EPD_UPD_MONO 0x02 // Triggers a Partial Mono update, 2 GL, 250ms +#define EPD_UPD_LOWP 0x03 // Triggers a Partial Mono update, 2 GL, 250ms, low power + +#define EPD_REVISION 0x00 // Revision, Read only +#define EPD_PANELSETTING 0x01 +#define EPD_DRIVERVOLTAGE 0x02 +#define EPD_POWERCONTROL 0x03 +#define EPD_BOOSTSETTING 0x04 +#define EPD_INTERVALSETTING 0x05 +#define EPD_TCOMTIMING 0x06 +#define EPD_INTTEMPERATURE 0x07 +#define EPD_SETRESOLUTION 0x0C +#define EPD_WRITEPXRECTSET 0x0D +#define EPD_PIXELACESSPOS 0x0E +#define EPD_DATENTRYMODE 0x0F +#define EPD_BYPASSRAM 0x12 +#define EPD_DISPLAYENGINE 0x14 +#define EPD_VCOMCONFIG 0x18 +#define EPD_BORDERSETTING 0x1D +#define EPD_POWERSEQUENCE 0x1F +#define EPD_SOFTWARERESET 0x20 +#define EPD_PROGRAMMTP 0x40 +#define EPD_MTPADDRESSSETTING 0x41 +#define EPD_LOADMONOWF 0x44 +#define EPD_REGREAD 0x80 // Instruction R/W bit set HIGH for data READ + +#define ADDR_PIC1 0 // Adress of Pic1..Pic7 +#define ADDR_PIC2 8192 +#define ADDR_PIC3 16384 +#define ADDR_PIC4 24576 +#define ADDR_PIC5 32768 +#define ADDR_PIC6 40960 +#define ADDR_PIC7 49152 +#define ADDR_FRAMEBUFFER 57344 // Reserved for saving framebuffer content during power-poff + +class PL_microEPD : public Adafruit_GFX { + +public: + PL_microEPD(int8_t _cs, int8_t _rst=-1, int8_t _busy=-1); + + void begin(bool erase=true); + void clearBuffer(); + void clear(int c=EPD_WHITE); + void drawPixel(int16_t x, int16_t y, uint16_t color); + void update(int updateMode=EPD_UPD_FULL); + void setVBorderColor(int color); + void powerOn(bool lowpower=0); + void powerOff(void); + void writeRegister(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + void writeBuffer(boolean previousRAM = false); + void printText(String text, int x, int y, int s); + void setCursorSegment(int x, int y); + void drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h); + void fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + byte buffer[144]; //buffer for 24x24 character (size 2) + void saveFBToFlash(int address=ADDR_FRAMEBUFFER); + void saveImgToFlash(int address=ADDR_PIC1); + void loadFromFlash(int address=ADDR_FRAMEBUFFER, bool toPreviousBuffer=true); + void waitForBusyInactive(); + void deepSleep(void); + void end(void); + +private: + int _EPDsize, _EPDwidth, _EPDheight, _buffersize; + int cs, rst, busy; + int nextline; + void writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + byte readRegister(char address); + void scrambleBuffer(void); + //void writeBuffer(boolean previousRAM = false); + void WhiteErase(void); + +}; + +#endif diff --git a/examples/SaveImgToFlash/RFM95.cpp b/examples/SaveImgToFlash/RFM95.cpp new file mode 100644 index 0000000..fb117d6 --- /dev/null +++ b/examples/SaveImgToFlash/RFM95.cpp @@ -0,0 +1,583 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "spi_functions.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** +* Description: Function used to initialize the RFM module on startup +******************************************************************************************/ +void RFM_Init(sLoRaWAN *lora) +{ + //Switch RFM to sleep + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x00); + + //Wait until RFM is in sleep mode + delay(10); + + //Set RFM in LoRa mode + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x80); + + //Switch RFM to standby + RFM_Switch_Mode(0x01); + + //Set channel to channel 0 868.100 MHz + RFM_Change_Channel(CH00_868_100, &(lora->CH_list)); + + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected. + */ + RFM_Set_Output_Power(0x0F); + + //Switch LNA boost on + SPI_Write(RFM_NSS, 0x0C, 0x23); + + //Set RFM To datarate 0 SF12 BW 125 kHz + RFM_Change_Datarate((eLoRaWAN_DATARATES)0x00, RECEIVE_RX_TIMEOUT_MS); + + //Rx Timeout set to 37 symbols + SPI_Write(RFM_NSS, 0x1F, 0x25); + + //Preamble length set to 8 symbols + //0x0008 + 4 = 12 + SPI_Write(RFM_NSS, 0x20, 0x00); + SPI_Write(RFM_NSS, 0x21, 0x08); + + //Set LoRa sync word + SPI_Write(RFM_NSS, 0x39, 0x34); + + //Set FIFO pointers + //Tx base address + SPI_Write(RFM_NSS, 0x0E, 0x80); + + //Rx base address + SPI_Write(RFM_NSS, 0x0F, 0x00); + + // Switch RFM to sleep + SPI_Write(RFM_NSS, 0x01, 0x80); +} + +/****************************************************************************************** +* Description : Function for sending a package with the RFM +* +* Arguments : *RFM_Tx_Package pointer to buffer with data and counter of data +* *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Send_Package(uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list) +{ + uint8_t i; + uint8_t RFM_Tx_Location = 0; + + //Set RFM in Standby mode + RFM_Switch_Mode(0x01); + + //Switch Datarate + RFM_Change_Datarate(TxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Switch Channel + RFM_Change_Channel(TxSettings->channel, list); + + //Switch RFM_DIO0 to TxDone + SPI_Write(RFM_NSS, 0x40, 0x40); + + //Set IQ to normal values + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + + // Set the Output Power + RFM_Set_Output_Power(TxPower); + + //Set payload length to the right length + SPI_Write(RFM_NSS, 0x22, lenght); + + //Get location of Tx part of FiFo + RFM_Tx_Location = SPI_Read(RFM_NSS, 0x0E); + + //Set SPI pointer to start of Tx part in FiFo + SPI_Write(RFM_NSS, 0x0D, RFM_Tx_Location); + + // Write the Payload to FiFo + SPI_Write_Array(RFM_NSS, 0x00, data, lenght); + + ////Set NSS pin Low to start communication + //digitalWrite(RFM_NSS, LOW); +// + ////Send Address with MSB 1 to make it a Write command + //SPI.transfer(0x00 | 0x80); + // + ////Send the data array + //SPI.transfer(data, lenght); +// + ////Set NSS pin High to end communication + //digitalWrite(RFM_NSS, HIGH); + + + //Switch RFM to Tx + SPI_Write(RFM_NSS, 0x01, 0x83); + + //Wait for TxDone + while(digitalRead(RFM_DIO0) == LOW) + {} + + //Clear interrupt + SPI_Write(RFM_NSS, 0x12, 0x08); +} + +/****************************************************************************************** +* Description : Function to switch RFM to single receive mode, used for Class A motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Single_Receive(eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list) +{ + RFM_RETVAL Message_Status = INIT; + + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, timeout_ms); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch RFM to Single reception + RFM_Switch_Mode(0x06); + + //Wait until RxDone or Timeout + //Wait until timeout or RxDone interrupt + while((digitalRead(RFM_DIO0) == LOW) && (digitalRead(RFM_DIO1) == LOW)) + { + } + + //Check for Timeout + if(digitalRead(RFM_DIO1) == HIGH) + { + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + Message_Status = RX_TIMEOUT; + //Serial.println("RX Timeout"); + } + + //Check for RxDone + if(digitalRead(RFM_DIO0) == HIGH) + { + Message_Status = NEW_MESSAGE; + } + + return Message_Status; +} + + +/****************************************************************************************** +* Description : Function to switch RFM to continuous receive mode, used for Class C motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Continuous_Receive(eDR_CH * RxSettings, eCHANNEL_LIST *list) +{ + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch to continuous receive + RFM_Switch_Mode(0x05); +} + +/****************************************************************************************** +* Description : Function to retrieve a message received by the RFM +* +* Arguments : *RFM_Rx_Package pointer to sBuffer struct containing the data received +* and number of bytes received +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Get_Package(uint8_t *data, uint8_t *counter) +{ + uint8_t i; + uint8_t RFM_Interrupts = 0; + uint8_t RFM_Package_Location = 0; + RFM_RETVAL Message_Status; + + // Get the RFM's interrupt status register value + RFM_Interrupts = SPI_Read(RFM_NSS, 0x12); + + // UART_Send_Data(RFM_Interrupts,0x01); + + // Check if the CRC of the received package is correct. + if((RFM_Interrupts & 0x20) != 0x20) + { + Message_Status = CRC_OK; + } + else + { + Message_Status = CRC_NOK; + } + + RFM_Package_Location = SPI_Read(RFM_NSS, 0x10); /*Read start position of received package*/ + (*counter) = SPI_Read(RFM_NSS, 0x13); /*Read length of received package*/ + + SPI_Write(RFM_NSS, 0x0D, RFM_Package_Location); /*Set SPI pointer to start of package*/ + + // Read the FIFO from the RFM in a single read operation. + SPI_Read_Array(RFM_NSS, 0x00, data, (*counter)); + + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + return Message_Status; +} + + + + +/****************************************************************************************** +* Description : Function to change the datarate of the RFM module. Setting the following +* register: Spreading factor, Bandwidth and low datarate optimization. +* +* Arguments : Datarate the datarate to set +******************************************************************************************/ +void RFM_Change_Datarate(eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms) +{ + uint16_t TimeOutSymbols; + + /* Determine the new Timeout Symbol Time depending on the Datarate. */ + switch(Datarate) + { + //------------------------------------------------------------------------------------------------------------- + case SF12_BW125kHz: //SF12 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 5); // Divide the Timeout Time by 32 (Symbol time = 32.77ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF11_BW125kHz: //SF11 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 4); // Divide the Timeout Time by 16 (Symbol time = 16.38ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF10_BW125kHz: //SF10 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 3); // Divide the Timeout Time by 8 (Symbol time = 8.19ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF09_BW125kHz: //SF9 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 2); // Divide the timeout time by 4 (Symbol time = 4.10ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF08_BW125kHz: //SF8 BW 125 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 2.05ms) + break; + + //------------------------------------------------------------------------------------------------------------- + default: + case SF07_BW125kHz: //SF7 BW 125 kHz + TimeOutSymbols = (timeout_ms); // Copy the timeout value directly (Symbol time = 1.02ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF07_BW250kHz: //SF7 BW 250 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 0.5ms) + break; + } + + /* Limit the Timeout to 1023 symbols, since the Time-out symbols register has a maximum size of 10 bits. 2^10 = 1024. */ + if(TimeOutSymbols > 1023) + { + TimeOutSymbols = 1023; + } + + /* Set the Datarate, CRC settings, the number of symbol for the required timeout and the Low Datarate optimizer On */ + switch(Datarate) + { + case SF12_BW125kHz: //SF12 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xC4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF12, CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF11_BW125kHz: //SF11 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xB4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF11 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF10_BW125kHz: //SF10 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xA4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF10 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF09_BW125kHz: //SF9 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x94 | ((uint8_t) (TimeOutSymbols >> 8))); //SF9 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF08_BW125kHz: //SF8 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x84 | ((uint8_t) (TimeOutSymbols >> 8))); //SF8 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW125kHz: //SF7 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW250kHz: //SF7 BW 250kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x82); //250 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + } +} + +/****************************************************************************************** +* Description : Function to change the channel of the RFM module. Setting the following +* register: Channel +* Arguments : Channel the channel to set +******************************************************************************************/ +void RFM_Change_Channel(eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list) +{ + uint32_t hex; + // Check if the given pointer is not invalid. + if(list == NULL) + { + return; + } + + switch(Channel) + { + default: + case CH00_868_100: //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x06); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH01_868_300: //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x13); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH02_868_500: //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x20); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH03_867_100: //Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xC6); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH04_867_300: //Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xD3); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH05_867_500: //Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xE0); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH06_867_700: //Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xEC); + SPI_Write(RFM_NSS, 0x08,0xF1); + break; + case CH07_867_900: //Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xF9); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CH10_869_525: //Receive channel 869.525 MHz / 61.035 Hz = 14246334 = 0xD961BE + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x61); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CFLIST_INDEX_1: + if(list->index >= 1) + { + hex = list->channel[0]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_2: + if(list->index >= 2) + { + hex = list->channel[1]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_3: + if(list->index >= 3) + { + hex = list->channel[2]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_4: + if(list->index >= 4) + { + hex = list->channel[3]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_5: + if(list->index >= 5) + { + hex = list->channel[4]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + } +} + + +/****************************************************************************************** +* Description : Function to calculate the RFM register value for the given frequency. +* Arguments : Transmit frequency in Hertz. +******************************************************************************************/ +uint32_t calculate_frequency_settings (uint32_t frequency_Hz) +{ + double calc; + calc = (double)frequency_Hz / 61.035; + Serial.print("Freq: "); + Serial.print((uint32_t) frequency_Hz); + Serial.print('\t'); + Serial.println((uint32_t) calc); + return calc; +} + +/****************************************************************************************** +* Description : Function to change the operation mode of the RFM. Switching mode and wait +* for mode ready flag +* DO NOT USE FOR SLEEP +* +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_Switch_Mode(uint8_t Mode) +{ + Mode |= 0x80; //Set high bit for LoRa mode + + //Switch mode on RFM module + SPI_Write(RFM_NSS, 0x01,Mode); + + //Wait on mode ready + while(digitalRead(RFM_DIO5) == LOW) + { + } +} + + +/****************************************************************************************** +* Description : +* Function to change the IQ bits in the RFM95, which will allow the RFM module to send messages with the same LoRa modulation +* as a gateway transmitter or receive message from motes just like a gateway would be able to do. +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_SetIQ(IQ_FUNCTION function) +{ + switch(function) + { + //########################################################################### + case GATEWAY_TRANSMITTER: + // IQ set to gateway transmission + SPI_Write(RFM_NSS, 0x33, 0x26); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + case MOTE_TRANSCEIVER: + // IQ set to default Mote transceiver + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + default: + case GATEWAY_RECEIVER: + //Invert IQ for receiving messages from gateway + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + break; + } +} + + +/************************************************************************************************************************************************************** +* Description : +* Function to set the transmit power of the RFM95. +* Arguments : power 0x00 - 0x0F to set the output power. Pout = 17-(15-power) +**************************************************************************************************************************************************************/ +void RFM_Set_Output_Power(uint8_t power) +{ + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected on the RFM board. Limit the output power to the maximum and set + * the output power with the last 4 bits of the register. + */ + SPI_Write(RFM_NSS, 0x09, 0xF0 | (power & 0x0F)); +} diff --git a/examples/SaveImgToFlash/RFM95.h b/examples/SaveImgToFlash/RFM95.h new file mode 100644 index 0000000..e9c9164 --- /dev/null +++ b/examples/SaveImgToFlash/RFM95.h @@ -0,0 +1,78 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef RFM95_H +#define RFM95_H + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RECEIVE_RX_TIMEOUT_MS 500 + + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + MOTE_TRANSCEIVER, // Default settings for the RFM to function as a mote which can send messages to and receive messages from a gateway. + GATEWAY_TRANSMITTER, // IQ settings to let the RFM95 behave as an Gateway sending messages to a mote. + GATEWAY_RECEIVER // IQ settings to let the RFM95 behave as an Gateway receiving messages from a mote. + }IQ_FUNCTION; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + void RFM_Init (sLoRaWAN *lora); + void RFM_Send_Package (uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Single_Receive (eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list); + void RFM_Continuous_Receive (eDR_CH * RxSettings, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Get_Package (uint8_t *data, uint8_t *counter); + void RFM_Change_Datarate (eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms); + void RFM_Change_Channel (eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list); + uint32_t calculate_frequency_settings (uint32_t frequency_Hz); + void RFM_Switch_Mode (uint8_t Mode); + void RFM_SetIQ (IQ_FUNCTION function); + void RFM_Set_Output_Power (uint8_t power); + + /* SPI functions */ + uint8_t RFM_Read (uint8_t RFM_Address); + void RFM_Read_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + void RFM_Write (uint8_t RFM_Address, uint8_t RFM_Data); + void RFM_Write_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + +#endif diff --git a/examples/SaveImgToFlash/SaveImgToFlash.ino b/examples/SaveImgToFlash/SaveImgToFlash.ino new file mode 100644 index 0000000..cedb59c --- /dev/null +++ b/examples/SaveImgToFlash/SaveImgToFlash.ino @@ -0,0 +1,30 @@ +/* + 1) Create a Grayscale picture with a resolution of 148x70 pixel + 2) Go to https://littlevgl.com/image-to-c-array and upload this image + 3) Fillout Name: "pic", select color format: "Indexed 4 colors", Output format "C array", Convert! + 4) Open pic.c in an editor and select the complete array-stream, excluding only the first 4 line swith color definition + 5) Insert the image bytestream in the file progmen.h + 6) Use the function saveToFlash to store the picture in the external flash memory +*/ +#include "lorapaper.h" +#include "spi_flash.h" +#include "PL_microEPD44.h" + +PL_microEPD epd(EPD_CS, EPD_RST, EPD_BUSY); //Initialize the EPD. + +void setup(void) { + analogReference(EXTERNAL); // use AREF for reference voltage + SPI.begin(); // Initialize the SPI port + SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); + + pinMode(SPI_FLASH_CS, OUTPUT); + digitalWrite(SPI_FLASH_CS, HIGH); + + epd.begin(); // Turn ON & initialize 1.1" EPD + epd.saveImgToFlash(ADDR_PIC5); // Save an image define in progmem.h to external Flash + epd.loadFromFlash(ADDR_PIC5, 0); // Load an image from external flash + epd.update(); +} + +void loop(){ +} diff --git a/examples/SaveImgToFlash/encoder.h b/examples/SaveImgToFlash/encoder.h new file mode 100644 index 0000000..2104bec --- /dev/null +++ b/examples/SaveImgToFlash/encoder.h @@ -0,0 +1,32 @@ +/* +// This is the code you will need to add to the 'encoder' section in the TTN backend to let the +// weather forecast demo run properly. + +function Encoder(object, port) { + // Encode downlink messages sent as + // object to an array or buffer of bytes. + var bytes = [9]; + + bytes[0] = parseInt(object.temperature); + bytes[2] = parseInt(object.humidity); + bytes[4] = parseInt(object.date); + bytes[6] = parseInt(object.hour); + bytes[8] = parseInt(object.mins); + bytes[10] = parseInt(object.icon); + + bytes[12] = parseInt(object.rainPrecipProb0); + bytes[14] = parseInt(object.rainPrecipProb1); + bytes[16] = parseInt(object.rainPrecipProb2); + bytes[18] = parseInt(object.rainPrecipProb3); + bytes[20] = parseInt(object.rainPrecipProb4); + bytes[22] = parseInt(object.rainPrecipProb5); + bytes[24] = parseInt(object.rainPrecipProb6); + bytes[26] = parseInt(object.rainPrecipProb7); + bytes[28] = parseInt(object.rainPrecipProb8); + bytes[30] = parseInt(object.rainPrecipProb9); + bytes[32] = parseInt(object.rainPrecipProb10); + bytes[34] = parseInt(object.rainPrecipProb11); + + return bytes; +} +*/ diff --git a/examples/SaveImgToFlash/lorapaper.h b/examples/SaveImgToFlash/lorapaper.h new file mode 100644 index 0000000..12c7ff5 --- /dev/null +++ b/examples/SaveImgToFlash/lorapaper.h @@ -0,0 +1,38 @@ +#ifndef LORAPAPER_DEMOBOARD_H_ +#define LORAPAPER_DEMOBOARD_H_ + +/****************************************************************************************** + DEFINES +/******************************************************************************************/ + +#include +#include "I2C.h" +#include "spi_functions.h" +#include "progmem.h" +#include "LowPower.h" + + +#define EPD_RST A2 +#define EPD_BUSY A1 +#define EPD_CS A3 +#define HV_BST 9 +#define SW_TFT A0 +#define DS2401 2 // One wire pin for the DS2401 +#define RTC_MFP 3 +#define RFM_DIO0 4 +#define RFM_DIO1 5 +#define RFM_DIO5 6 +#define RFM_DIO2 7 +#define RFM_NSS 10 +#define SPI_FLASH_CS 8 + + +/****************************************************************************************** + STRUCTURES +******************************************************************************************/ +typedef struct{ + uint8_t Counter = 0; + uint16_t LoRaWAN_message_interval = 1; // Variable to set the number of Timer2 timer ticks between LoRaWAN messages. +}sAPP; + +#endif diff --git a/examples/SaveImgToFlash/lorawan_def.h b/examples/SaveImgToFlash/lorawan_def.h new file mode 100644 index 0000000..55669ef --- /dev/null +++ b/examples/SaveImgToFlash/lorawan_def.h @@ -0,0 +1,386 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Struct.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + + +#ifndef STRUCT_H +#define STRUCT_H + + #include + #include + + #define LORA_FIFO_SIZE 64 + #define CFLIST_FREQUENCIES_MAX 5 + #define DEFAULT_FREQUENCIES 3 + #define LORAWAN_MAX_PAYLOAD 52 + + /********************************************************************************************* + ENUMERATION DEFENITIONS + *********************************************************************************************/ + // LoRaWAN Mote class specification + typedef enum + { + CLASS_A, + //CLASS_B, + CLASS_C + }eLoRaWAN_MOTE_CLASS; + + // Enumeration to set the used back-end and use the TX and RX settings for that particular back-end. + typedef enum + { + MANUAL, // Manual mode for setting the TX and RX channels and data rate for OTAA and data messages + SEMTECH, // Semtech's the patent holder for the LoRaWAN chips. + The_Things_Network, // The Things Industries back-end, which is only functional for OTAA motes. ABP is barely supported and the service may have some downtime from time-to-time for maintenance. + KPN // The Dutch full-coverage LoRaWAN network provider in the Netherlands, which has LoRaWAN localization capabilities, but it's services come at a cost. + }eBACKENDS; + + /* MAC HEADER types */ + typedef enum + { + JOIN_REQUEST = 0x00, // 0000 0000 + JOIN_ACCEPT = 0x20, // 0010 0000 + UNCONFIRMED_DATA_UP = 0x40, // 0100 0000 + UNCONFIRMED_DATA_DOWN = 0x60, // 0110 0000 + CONFIRMED_DATA_UP = 0x80, // 1000 0000 + CONFIRMED_DATA_DOWN = 0xA0, // 1010 0000 + CONFIRMED_BITMASK = 0x80, + UNCONFIRMED_BITMASK = 0x40, + INIT_VAL = 0x00 + }eMESSAGE_TYPES; + + // Receive delay enumeration for OTAA Join Delay and message delays. + typedef enum + { + JOIN_DELAY_1 = 4900, + JOIN_DELAY_2 = 5900, + RECEIVE_DELAY1 = 900, + RECEIVE_DELAY2 = 1900 + }eRECEIVE_DELAY; + + + // Direction enumeration for the MIC calculation. See page 20, chapter 4.3.3 of the Lora Specification 1.02 + typedef enum + { + UPSTREAM_DIR = 0, // Uplink (Away from mote) + DOWNSTREAM_DIR = 1 // Downlink (towards the mote) + }eDIRECTION; + + // LoRaWAN Datarates + typedef enum + { + SF12_BW125kHz = 0x01, + SF11_BW125kHz = 0x02, + SF10_BW125kHz = 0x03, + SF09_BW125kHz = 0x04, + SF08_BW125kHz = 0x05, + SF07_BW125kHz = 0x06, + SF07_BW250kHz = 0x07, + SF09_BW500kHz = 0x08, + SF_BITMASK = 0x03 + }eLoRaWAN_DATARATES; + + // Channel enumeration + typedef enum + { + CH00_868_100, + CH01_868_300, + CH02_868_500, + CFLIST_INDEX_1, + CFLIST_INDEX_2, + CFLIST_INDEX_3, + CFLIST_INDEX_4, + CFLIST_INDEX_5, + CH03_867_100, + CH04_867_300, + CH05_867_500, + CH06_867_700, + CH07_867_900, + CH10_869_525 + }eLoRaWAN_CHANNELS; + + // Confirmed and unconfirmed messages type enumeration. + typedef enum + { + UNCONFIRMED, + CONFIRMED + }eCONFIRMATION; + + // Activation methods + typedef enum + { + ACTIVATION_BY_PERSONALISATION, + OVER_THE_AIR_ACTIVATION + }eMOTE_NETWORK_JOIN; + + //return values for the RFM95 + typedef enum + { + INIT, //0 Initialization error return code + CRC_OK, //1 CRC of the received message is incorrect, indicating an incomplete or corrupted message + CRC_NOK, //2 CRC of the received message is correct + MIC_OK, //3 Message Integrity Checksum is correct. + MIC_NOK_OTAA, //4 MIC of the OTAA message is incorrect. + MIC_NOK_MESSAGE, //5 MIC of the received message is incorrect. + ADDRESS_OK, //6 Device address of the received message matches that of this LoRaWAN mote. + ADDRESS_NOK, //7 The device address of the received message doesn't match to the dev address of this mote. + MAC_HEADER_NOK, //8 Message header has an incorrect value, not matching any LoRaWAN headers + RX_TIMEOUT, //9 The RFM hasn't receive any message and got a time-out instead. + OTAA_POINTER, //10 Over The Air Activation structure has an incorrect pointer + RX_MESSAGE, //11 Message received. + OTAA_COMPLETE, //12 OTAA has been successfully completed. + NEW_MESSAGE, //13 When a new message has been received. + DEFAULT_MAC, //14 When a message is received with an invalid MAC header, this return value will be send back. + LORA_POINTER_INVALID//15 Invalid Lora pointer + }RFM_RETVAL; + + + + /********************************************************************************************* + STRUCT DEFENITIONS + *********************************************************************************************/ + + // Structure for both datarate and channel as they are alway used together. + typedef struct + { + eLoRaWAN_DATARATES datarate; + eLoRaWAN_CHANNELS channel; + }eDR_CH; + + // Structure for all the RFM channel settings retrieved from the OTAA connection and the default settings + typedef struct + { + uint32_t channel[CFLIST_FREQUENCIES_MAX]; + uint8_t index; + uint8_t rx1_dr_offset; + uint8_t rx2_dr; + uint8_t rx_delay; + bool channel_hopping_on; + }eCHANNEL_LIST; + + + /* + Structure used to store session data of a LoRaWAN session if ABP is used, the following parameters must be supplied by the user + if OTAA is used, the OTAA JOIN procedure will produce the parameters for the session. + */ + typedef struct + { + uint8_t NwkSKey[16]; // The Network Session Key is used to encrypt the payload + uint8_t AppSKey[16]; // The Application Session Key is used to encrypt the payload + uint8_t DevAddr[4]; // The Device Address is used to identify the messages send and send to this Mote + uint16_t frame_counter_down; // Frame counter for messages received from the back-end + uint16_t frame_counter_up; // Frame counter for messages send to the back-end. + eRECEIVE_DELAY Receive_delay; // Wait time between transmitting an confirmed LoRaWAN message and listening to the Back-end's downlink. + uint8_t Transmit_Power; // Transmission power settings 0x00 to 0x0F. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_Session; + + + /* + Structure for connecting with Over The Air Activation to the back-end to retrieve session settings. + */ + typedef struct + { + uint8_t DevEUI[8]; // Device EUI. Unique number to identify the Mote with on the back-end. + uint8_t AppEUI[16]; // Application Key used to encrypt and calculate the session parameters with. + uint8_t AppKey[16]; // Application EUI, used to specify which Application the MOte belongs to. + uint8_t DevNonce[2]; // DevNonce is a random value that can only be used once for a join_request. The back-end will store all previously used values. + uint8_t AppNonce[3]; + uint8_t NetID[3]; + bool OTAAdone; // Boolean for indicating if the OTAA procedure was succesfull; + eRECEIVE_DELAY JoinDelay; // Join Delay for waiting an given amount of time between the JOIN and Accept messages + uint8_t Transmit_Power; // Transmission power settings for the OTAA connection. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_OTAA; + + + // Structure to store information of a LoRaWAN message to transmit or received + typedef struct + { + eMESSAGE_TYPES MAC_Header; // Message type header. + uint8_t DevAddr[4]; // Device address number used to identify the Mote with when communicating + uint8_t Frame_Control; // + uint16_t Frame_Counter; // Frame Counter of the message used for determining whether the message is received more than once. + uint8_t Frame_Port; // Frame port + uint8_t Frame_Options[15]; // Array for the frame options data, if available + uint8_t MIC[4]; // Array for the calculated result for the MIC + eCONFIRMATION Confirmation; // Either it an UNCONFIRMED or CONFIRMED message up or downstream. + RFM_RETVAL retVal; // Returns status value for receiving. Indicates whether a timeout, MIC NOK or other error status occured. + uint8_t Count; // Index count for the data array + uint8_t Data[LORA_FIFO_SIZE]; // Transmit and receive data array. + }sLoRa_Message; + + + //Structure used for storing settings of the mote + typedef struct + { + eLoRaWAN_MOTE_CLASS Mote_Class = CLASS_A; // Mote Class, only CLASS A or CLASS C are supported. + eMOTE_NETWORK_JOIN activation_method = ACTIVATION_BY_PERSONALISATION; + //eMOTE_NETWORK_JOIN activation_method = OVER_THE_AIR_ACTIVATION; // Variable used to specify whether ABP or OTAA is used for activation of the mote on the back-end. + eBACKENDS back_end = SEMTECH; // Variable to identify which back-end is used and to provide automatic OTAA and data messages channel, datarate and timeslot configuration. + bool Channel_Hopping_enabled = false; // Enables channel hopping when set to true, the channel won't be changed when set to false + volatile uint16_t timeslot; // Timing variable for timeslot 1 and 2. + + // List of additional channels in hexadecimal format pre-calculated for the RFM frequency register + eCHANNEL_LIST CH_list = + { + .channel = {0}, + .index = 0, + .rx1_dr_offset = 0, + .rx2_dr = 0, + .rx_delay = 0, + .channel_hopping_on = true + }; + + /* Session parameters for the current session */ + sLoRa_Session Session = + { + //.NwkSKey = {0x07, 0x18, 0x37, 0x73, 0x92, 0xA4, 0xCD, 0xC8, 0x68, 0xB3, 0xAB, 0x8C, 0xB3, 0x49, 0xEC, 0xB6}, + //.NwkSKey = {0xA0, 0x5B, 0x04, 0x27, 0xBC, 0xE8, 0x68, 0x61, 0xFC, 0xE3, 0xC9, 0x82, 0x43, 0x22, 0x50, 0x95}, + //.NwkSKey = {0x46, 0xB9, 0x00, 0x68, 0x46, 0xA6, 0x38, 0x38, 0xC0, 0x1E, 0xA1, 0x89, 0x98, 0xA8, 0x64, 0x49}, + //.NwkSKey = {0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}, + //.NwkSKey = {0x9D, 0x89, 0x88, 0x07, 0x44, 0x80, 0xAD, 0x9B, 0xF1, 0x3E, 0x7D, 0x7D, 0xF8, 0x6C, 0x97, 0xA3}, + //.NwkSKey = {0xB6, 0xB7, 0x01, 0x1D, 0xC7, 0x5F, 0x6A, 0x8B, 0x27, 0x36, 0xD9, 0xEE, 0x18, 0x29, 0x10, 0x03}, + //.NwkSKey = {0xC6, 0x0E, 0x1B, 0x62, 0x21, 0x72, 0xDF, 0x58, 0x2C, 0x94, 0x60, 0xE0, 0xA8, 0x3F, 0xC9, 0xFA}, + //.NwkSKey = {0x5F, 0xFE, 0xD7, 0xCD, 0x39, 0xCB, 0xA3, 0x05, 0x81, 0x6D, 0x66, 0x51, 0x98, 0x72, 0x41, 0xB1}, + //.NwkSKey = {0xB7, 0x95, 0x61, 0xDB, 0x1C, 0xF9, 0x3C, 0x8D, 0xBF, 0xF2, 0x7D, 0x8E, 0x61, 0xD8, 0x70, 0x6F}, + //.NwkSKey = {0x70, 0x12, 0x46, 0x98, 0xC5, 0xE0, 0x11, 0x7C, 0xD4, 0x5C, 0x32, 0xCE, 0xA2, 0x11, 0x07, 0x15}, + .NwkSKey = {0x76, 0x9A, 0x6E, 0x60, 0xA5, 0xC0, 0xE3, 0xC9, 0x8F, 0x78, 0xB0, 0x89, 0xCF, 0xC1, 0x3D, 0xE4}, + //.NwkSKey = {0x7D, 0xA6, 0x9F, 0xD0, 0x9C, 0xCC, 0x72, 0x82, 0x06, 0x62, 0x90, 0x59, 0x15, 0x84, 0x29, 0x4C}, + //.NwkSKey = {0xC4, 0x35, 0x99, 0xB7, 0xD9, 0x6E, 0x85, 0x62, 0x4A, 0x1A, 0x75, 0x2F, 0x14, 0xF9, 0x49, 0x6B}, + //.NwkSKey = {0x7C, 0x47, 0xCA, 0xAD, 0xC5, 0x26, 0x41, 0x19, 0xEB, 0x16, 0x07, 0xED, 0xE6, 0xB6, 0x6D, 0xC8}, + //.NwkSKey = {0x38, 0x53, 0xD1, 0xF5, 0x58, 0x74, 0xD0, 0xC1, 0x93, 0x76, 0x81, 0x8B, 0x64, 0xC9, 0xF9, 0x1C}, + //.NwkSKey = {0xA6, 0x5F, 0xEF, 0xC0, 0x47, 0xB1, 0xC2, 0xD0, 0x28, 0x40, 0x0D, 0xDE, 0xCE, 0xA4, 0xDC, 0x2F}, + //.NwkSKey = {0x83, 0x62, 0xC7, 0xE5, 0xA3, 0xC9, 0x4A, 0x33, 0x0A, 0x43, 0xC3, 0xF9, 0x4E, 0x15, 0x9E, 0x5D}, + //.NwkSKey = {0xC1, 0x01, 0xF2, 0x71, 0x2C, 0x60, 0xB8, 0x0C, 0x46, 0xA9, 0x8C, 0xA4, 0x87, 0x45, 0x40, 0x7D}, + //.NwkSKey = {0x78, 0x87, 0x29, 0x2F, 0x32, 0x9F, 0x4F, 0xBE, 0x45, 0x1D, 0x34, 0xB3, 0x5D, 0xE1, 0xFA, 0x71}, + //.NwkSKey = {0x45, 0x1A, 0xEE, 0x9C, 0x30, 0xC5, 0xAA, 0x8C, 0x2D, 0xB4, 0xA4, 0xEB, 0x37, 0xE2, 0x0B, 0x9B}, + //.NwkSKey = {0x40, 0x6A, 0xB7, 0x95, 0x52, 0x26, 0xB2, 0x10, 0x31, 0x15, 0x11, 0x03, 0xAC, 0x08, 0xB3, 0x36}, + //.NwkSKey = {0x9E, 0x13, 0x34, 0xCB, 0x39, 0x39, 0xA4, 0x8B, 0x02, 0x91, 0xA4, 0xA4, 0xB8, 0x36, 0x31, 0x26}, + //.AppSKey = {0xBC, 0xDC, 0x20, 0x30, 0x55, 0x09, 0xEA, 0xC9, 0x3B, 0x65, 0x19, 0xD4, 0xA5, 0xC0, 0xD8, 0x27}, + //.AppSKey = {0x89, 0xE8, 0x41, 0xDA, 0x62, 0xE7, 0x50, 0xCA, 0xBE, 0xEF, 0x2A, 0xAA, 0x19, 0x31, 0xF9, 0x25}, + //.AppSKey = {0xDD, 0x09, 0x11, 0x07, 0x73, 0x04, 0xE5, 0x9D, 0xE6, 0x3A, 0x24, 0xE7, 0x9E, 0x25, 0xF4, 0x2F}, + //.AppSKey = {0x6C, 0x61, 0x57, 0xE8, 0xF4, 0x0A, 0x7B, 0xE0, 0x8F, 0x71, 0x7B, 0x07, 0x01, 0x30, 0x58, 0xC5}, + //.AppSKey = {0x0B, 0xF2, 0x13, 0x20, 0xCC, 0xB0, 0x99, 0xF3, 0xBE, 0x0B, 0x96, 0xCE, 0x59, 0x66, 0x98, 0x94}, + //.AppSKey = {0x2F, 0x79, 0x4C, 0x52, 0xC1, 0x93, 0x10, 0x2D, 0xAC, 0x4F, 0x03, 0x4D, 0x87, 0x0D, 0x0A, 0xA8}, + //.AppSKey = {0xFB, 0xC3, 0x0C, 0x40, 0xDF, 0x26, 0xE7, 0x94, 0x0A, 0x8A, 0x04, 0x95, 0x75, 0x4D, 0xCB, 0x02}, + //.AppSKey = {0xAD, 0x90, 0x26, 0xF7, 0xFF, 0x85, 0xA3, 0x32, 0x76, 0x61, 0x10, 0x5B, 0xE8, 0x16, 0x75, 0xCC}, + //.AppSKey = {0xD5, 0x9B, 0x18, 0xB0, 0xB6, 0x7F, 0x60, 0x7D, 0xE9, 0x23, 0x4C, 0x70, 0x6F, 0xB5, 0xFF, 0xC1}, + //.AppSKey = {0xCC, 0x49, 0x22, 0xDE, 0xEE, 0xDA, 0xC9, 0x57, 0x2B, 0x5E, 0xD6, 0x23, 0x69, 0xF5, 0xBF, 0xC6}, + //.AppSKey = {0x20, 0xC5, 0x79, 0x9F, 0x6E, 0xE8, 0x51, 0x51, 0x5C, 0x5D, 0x27, 0xA4, 0x8B, 0x3D, 0x99, 0x67}, + //.AppSKey = {0x0F, 0x0A, 0xD0, 0x0A, 0x8A, 0x57, 0x1B, 0x28, 0xF9, 0xED, 0xB4, 0xBB, 0x40, 0xD1, 0x11, 0xC8}, + .AppSKey = {0x68, 0xFE, 0xB9, 0x8D, 0x78, 0x96, 0x51, 0x8C, 0x45, 0xD5, 0x67, 0xB9, 0x4B, 0x00, 0x27, 0x4C}, + //.AppSKey = {0x24, 0x47, 0x0E, 0xF6, 0xC5, 0xCD, 0x34, 0x9F, 0xAD, 0xED, 0x30, 0xEB, 0xC6, 0x02, 0xBC, 0x2F}, + //.AppSKey = {0xE6, 0x3E, 0x71, 0x0D, 0x49, 0x2E, 0xBB, 0x60, 0x6F, 0x04, 0x8E, 0xB3, 0x71, 0xFA, 0x3A, 0x89}, + //.AppSKey = {0x8C, 0xA4, 0x33, 0x5C, 0x0C, 0x56, 0x6B, 0x56, 0x47, 0x7B, 0x05, 0xD7, 0xCC, 0x11, 0xC1, 0x98}, + //.AppSKey = {0x48, 0x24, 0x9C, 0x8A, 0x25, 0xFF, 0xD2, 0xA4, 0xAB, 0xF6, 0xC0, 0x36, 0x73, 0x3B, 0x33, 0xB9}, + //.AppSKey = {0x3E, 0x36, 0x9A, 0x80, 0x93, 0x9A, 0xAD, 0x8B, 0x61, 0xDA, 0x3D, 0xE1, 0xE8, 0x1F, 0x0A, 0x0A}, + //.AppSKey = {0xA7, 0x55, 0xF5, 0xF7, 0x25, 0x3B, 0x98, 0x7E, 0x3A, 0x68, 0xA1, 0x68, 0x95, 0x97, 0xD4, 0x65}, + //.AppSKey = {0xE6, 0x5E, 0xCF, 0x63, 0x06, 0x79, 0x3E, 0xA2, 0x09, 0x35, 0xA7, 0x28, 0x88, 0x69, 0x82, 0xDB}, + //.AppSKey = {0x15, 0x0F, 0x46, 0x45, 0x0F, 0xB5, 0x76, 0xF8, 0xBA, 0xA2, 0x1D, 0xFB, 0x3D, 0xA5, 0x84, 0x81}, + //.DevAddr = {0x3A, 0x12, 0x34, 0x56}, + //.DevAddr = {0x26, 0x01, 0x14, 0xF4}, + //.DevAddr = {0x26, 0x01, 0x15, 0x16}, + //.DevAddr = {0x26, 0x01, 0x13, 0x4F}, + //.DevAddr = {0x26, 0x01, 0x1E, 0x04}, + //.DevAddr = {0x26, 0x01, 0x17, 0x9F}, + .DevAddr = {0x26, 0x01, 0x15, 0xE3}, + //.DevAddr = {0x26, 0x01, 0x1B, 0x47}, + //.DevAddr = {0x26, 0x01, 0x1E, 0xDA}, + //.DevAddr = {0x26, 0x01, 0x13, 0x27}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x3A}, + //.DevAddr = {0x26, 0x01, 0x18, 0xE4}, + //.DevAddr = {0x26, 0x01, 0x10, 0x0F}, + //.DevAddr = {0x26, 0x01, 0x15, 0x01}, + //.DevAddr = {0x26, 0x01, 0x12, 0x0B}, + //.DevAddr = {0x26, 0x01, 0x11, 0x67}, + //.DevAddr = {0x26, 0x01, 0x1E, 0x8C}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x25}, + //.DevAddr = {0x26, 0x01, 0x1C, 0x6F}, + //.DevAddr = {0x26, 0x01, 0x10, 0x52}, + //.DevAddr = {0x26, 0x01, 0x18, 0xAC}, + //.DevAddr = {0x26, 0x01, 0x1D, 0x67}, + //.DevAddr = {0x26, 0x01, 0x1A, 0xCA}, + .frame_counter_down = 0, + .frame_counter_up = 0, + .Receive_delay = RECEIVE_DELAY2, + .Transmit_Power = 0x0F, + .TxChDr = {SF09_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* OTAA configuration and encryption parameters */ + sLoRa_OTAA OTAA = + { + .DevEUI = {0}, // If the DS2401 is used, set the DEV EUI to zero. + .AppEUI = {0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x01, 0x80, 0x13}, // TTN Ideetron Application EUI, change this to your own APP EUI code + //.AppEUI = {0x01, 0xF7, 0x03, 0x0E, 0x19, 0x00, 0x00, 0x55}, // TTN Ideetron Application EUI, change this to your own APP EUI code + //.AppKey = {0x0B, 0x32, 0x3C, 0x2F, 0xDD, 0xF3, 0x47, 0xB3, 0xDA, 0x20, 0x4F, 0x20, 0xE0, 0x13, 0xAA, 0x45}, + .AppKey = {0xB3, 0x23, 0xC2, 0xFD, 0xDF, 0x34, 0x7B, 0x3D, 0xA2, 0x04, 0xF2, 0x0E, 0x01, 0x3A, 0xA4, 0x59}, + .DevNonce = {0}, + .AppNonce = {0}, + .NetID = {0}, + .OTAAdone = false, + .JoinDelay = JOIN_DELAY_2, + .Transmit_Power = 0x0F, + .TxChDr = {SF10_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* Message structure for both receive and transmitting */ + sLoRa_Message TX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + sLoRa_Message RX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + }sLoRaWAN; + + + +#endif diff --git a/examples/SaveImgToFlash/mcp7940.cpp b/examples/SaveImgToFlash/mcp7940.cpp new file mode 100644 index 0000000..d352542 --- /dev/null +++ b/examples/SaveImgToFlash/mcp7940.cpp @@ -0,0 +1,322 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: mcp7940.h +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 22-09-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include "HardwareSerial.h" +#include "I2C.h" +#include "mcp7940.h" + +// private functions +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII); +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal); +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal); + + + +/* + @brief + Initialize the mcp7940 Real Time Clock (RTC) with the compilation strings to set the current date and time. Also configures the RTC to generate an + interrupt on the next minute roll-over with the alarm 0 function of the RTC. The RTC will pull the MFP pin low, which is held high with an internal + pull-up on pin D3. + @parameters + *TimeDate Pointer to a structure where the current time will be written to once converted from the compiler __BUILD__ and __TIME__ strings. + alarm_in_x_minutes The number of minutes from the current time on which the RTC will generate an interrupt. +*/ +void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes) +{ + uint8_t date [] = __DATE__; // Sep 27 2017 + uint8_t time [] = __TIME__; // 13:50:25 + + // Enable the internal pull-up on pin D3 by setting D3 bit in PORTD and DDRD as the MFP of the RTC is open drain and using an internal pull-up is more efficient. + PORTD |= 0x08; + DDRD &= ~0x08; + + //Serial.println((char*)date); + //Serial.println((char*)time); + + // Convert the compilation time and date to decimal numbers in order to set the RTC time. + TimeDate->hours = convert_ASCII_to_decimal(&(time[0])); + TimeDate->minutes = convert_ASCII_to_decimal(&(time[3])); + TimeDate->seconds = convert_ASCII_to_decimal(&(time[6])); + TimeDate->day = convert_ASCII_to_decimal(&(date[4])); + TimeDate->year = convert_ASCII_to_decimal(&(date[9])); + TimeDate->weekDay = 1; // There's no weekday in the time and date compiler string, so always set it to 1 upon start. + + // Compare the string of three letters to determine the current month as a decimal value + if(memcmp(date, "Jan", 3) == 0) + { + TimeDate->month = 1; + }else if (memcmp(date, "Feb", 3) == 0) + { + TimeDate->month = 2; + }else if (memcmp(date, "Mar", 3) == 0) + { + TimeDate->month = 3; + }else if (memcmp(date, "Apr", 3) == 0) + { + TimeDate->month = 4; + }else if (memcmp(date, "May", 3) == 0) + { + TimeDate->month = 5; + }else if (memcmp(date, "Jun", 3) == 0) + { + TimeDate->month = 6; + }else if (memcmp(date, "Jul", 3) == 0) + { + TimeDate->month = 7; + }else if (memcmp(date, "Aug", 3) == 0) + { + TimeDate->month = 8; + }else if (memcmp(date, "Sep", 3) == 0) + { + TimeDate->month = 9; + }else if (memcmp(date, "Oct", 3) == 0) + { + TimeDate->month = 10; + }else if (memcmp(date, "Nov", 3) == 0) + { + TimeDate->month = 11; + }else if (memcmp(date, "Dec", 3) == 0) + { + TimeDate->month = 12; + } + //Serial.print("Converted: "); + //mcp7940_print(TimeDate); + + // Write the converted time and date settings to the RTC and print it to the serial port. + mcp7940_set_time_and_date(TimeDate); + mcp7940_print(TimeDate); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); // Disable the square wave output on the RTC and Alarm 0 + + // Reload alarm 0 to generate an interrupt over x minutes + if(alarm_in_x_minutes > 0) + { + mcp7940_reset_minute_alarm(alarm_in_x_minutes); + } + + // Enable Alarm 0, set the MFP pin to idle HIGH, active LOW. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x10); + + // read back the set time and date in order to check if the time and date was set properly. + mcp7940_read_time_and_date(TimeDate); + + // Enable the Alarm to generate an external interrupt on a falling edge of INT1 + EICRA = 0x08; // The falling edge of INT1 generates an interrupt request. + EIMSK = 0x02; // Enable the external interrupt +} + + +/* + @brief + Function to disable the RTC and all alarms. +*/ +void mcp7940_disable (void) +{ + // Disable all alarms and square wave outputs. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); + + // Disable the RTC by clearing bit 7 of the RTC_SEC register, which will stop the 32kHz crystal. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); +} + + + + + +/* + @brief + After the RTC's alarm has triggered, the alarm must be re-configured to generate an alarm again after alarm_in_x_minutes minutes. + @parameters + alarm_in_x_minutes The number of minutes from now when the RTC must generate an interrupt again. +*/ +void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes) +{ + uint8_t value, current_time_minutes, new_time_minutes; + + if(alarm_in_x_minutes > 59) + { + alarm_in_x_minutes = 59; + } + + // Read the current minutes register to calculate the match value for the minute register. + current_time_minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + + // Calculate the new time settings. + new_time_minutes = current_time_minutes + alarm_in_x_minutes; + + // Check whether the additional number of minutes and the current time settings will be larger than 59. if larger, subtract 59 from the sum to calculate the new time + if(new_time_minutes > 59) + { + // Subtract 59 from the sum of the current time in minutes and the added number of minutes. + new_time_minutes -= 59; + } + + // Rewrite the Alarm 0 minutes register. + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_MIN, convert_decimal_to_binary(MIN_MASK, new_time_minutes)); + + /* + Set MFP pin High on Alarm interrupt on minutes match and clear the ALM0IF (Alarm interrupt Flag) now that the Alarm 0 minutes register is different + from the current time or the interrupt would re-trigger. Read the register first to keep the current weekday value and add the three bits 2:0 to + the value written to the register. + */ + value = I2C_read_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY); + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY, 0x10 | (value & 0x07)); +} + + + +/* + @brief + Function to read the time and date from the RTC, convert it to decimal values and store the values in the reference structure. + @parameters + TimeDate pointer to the structure where all the retrieved time and date must be stored. +*/ +void mcp7940_read_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + return; + + // Read the Time and date from the RTC and convert it to a time and date in decimal values. + TimeDate->seconds = convert_binary_to_decimal(SEC_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_SEC)); + TimeDate->minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + TimeDate->hours = convert_binary_to_decimal(HOUR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR)); + TimeDate->weekDay = WEEKDAY_MASK & I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_WEEKDAY); + + TimeDate->day = convert_binary_to_decimal(DATE_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_DATE)); + TimeDate->month = convert_binary_to_decimal(MONTH_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH)); + TimeDate->year = convert_binary_to_decimal(YEAR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR)); +} + + +/* + @brief + Function to write the referenced time and date in the TimeDate structure to the RTC + @parameters + TimeDate pointer to the structure where the new time and date settings are stored. +*/ +void mcp7940_set_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + { + return; + } + + // Disable the oscillator to stop the RTC while the time and date are configured to prevent roll-overs from occurring while writing new values. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MIN, convert_decimal_to_binary(MIN_MASK, TimeDate->minutes)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR, 0x40 | convert_decimal_to_binary(HOUR_MASK, TimeDate->hours)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, WEEKDAY_MASK, convert_decimal_to_binary(WEEKDAY_MASK, TimeDate->weekDay)); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_DATE, convert_decimal_to_binary(DATE_MASK, TimeDate->day)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH, convert_decimal_to_binary(MONTH_MASK, TimeDate->month)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR, convert_decimal_to_binary(YEAR_MASK, TimeDate->year)); + + // Write the seconds as last since, we'll reactivate the oscillator with this write operation as well. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x80 | convert_decimal_to_binary(SEC_MASK, TimeDate->seconds)); +} + + + +/* + @brief + Convert the RTC's tens and ones binary format to a decimal number. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New binary value which needs to be converted to decimal +*/ +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal) +{ + return (((regVal & mask) >> 4) * 10) + (regVal & 0x0F); +} + + +/* + @brief + Convert a decimal value to binary tens and ones values. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New decimal value which needs to be converted to binary +*/ +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal) +{ + uint8_t tens, ones; + tens = decimal / 10; + ones = decimal % 10; + return ((mask &(tens << 4)) | (ones & 0x0F)); +} + +/* + @brief + Converts two ASCII characters to a single decimal value to convert the compilers __BUILD__ and __TIME__ string to decimal values. + @parameters + ASCII pointer to the first ASCII character. +*/ +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII) +{ + uint8_t retVal = 0; + + if((ASCII[0] >= '0') && (ASCII[0] <= '9')) + { + retVal = (ASCII[0] - '0') * 10; + } + + if((ASCII[1] >= '0') && (ASCII[1] <= '9')) + { + retVal += (ASCII[1] - '0'); + } + + return retVal; +} + +/* + @brief + Prints the referenced structure in a human readable format to the serial port "hh:mm:ss day:n dd/mm/yy" +*/ +void mcp7940_print(sTimeDate *TimeDate) +{ + Serial.print(TimeDate->hours, DEC); + Serial.print(':'); + Serial.print(TimeDate->minutes, DEC); + Serial.print(':'); + Serial.print(TimeDate->seconds, DEC); + Serial.print(" day:"); + Serial.print(TimeDate->weekDay, DEC); + + Serial.print('\t'); + + Serial.print(TimeDate->day, DEC); + Serial.print('/'); + Serial.print(TimeDate->month, DEC); + Serial.print('/'); + Serial.println(TimeDate->year, DEC); +} diff --git a/examples/SaveImgToFlash/mcp7940.h b/examples/SaveImgToFlash/mcp7940.h new file mode 100644 index 0000000..70f622e --- /dev/null +++ b/examples/SaveImgToFlash/mcp7940.h @@ -0,0 +1,85 @@ +/* + * MCP7940.h + * + * Created: 22-9-2017 08:21:21 + * Author: adri + */ + + +#ifndef MCP7940_H_ +#define MCP7940_H_ + + //#define MCP7940_SLAVE_ADDRESS 0xDE + #define MCP7940_SLAVE_ADDRESS 0x6F + /****************************************************************************************** + ENUMERATION + ******************************************************************************************/ + typedef enum + { + SEC_MASK = 0x70, + MIN_MASK = 0x70, + HOUR_MASK = 0x10, + WEEKDAY_MASK= 0x07, + DATE_MASK = 0x30, + MONTH_MASK = 0x10, + YEAR_MASK = 0xF0 + }eMASKS; + + typedef enum + { + // Timekeeping + RTC_SEC = 0x00, + RTC_MIN = 0x01, + RTC_HOUR = 0x02, + RTC_WEEKDAY = 0x03, + RTC_DATE = 0x04, + RTC_MONTH = 0x05, + RTC_YEAR = 0x06, + CONTROL = 0x07, + OSCTRIM = 0x08, + + // Alarms 0 + ALARM0_SEC = 0x0A, + ALARM0_MIN = 0x0B, + ALARM0_HOUR = 0x0C, + ALARM0_WEEKDAY = 0x0D, + ALARM0_DATE = 0x0E, + ALARM0_MONTH = 0x0F, + + + // Alarms 1 + ALARM1_SEC = 0x11, + ALARM1_MIN = 0x12, + ALARM1_HOUR = 0x13, + ALARM1_WEEKDAY = 0x14, + ALARM1_DATE = 0x15, + ALARM1_MONTH = 0x16 + }eMCP7940_REGISTERS; + + /****************************************************************************************** + Structures + ******************************************************************************************/ + typedef struct + { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day; + uint8_t month; + uint8_t year; + uint8_t weekDay; + }sTimeDate; + + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes); + void mcp7940_disable (void); + void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes); + void mcp7940_read_time_and_date (sTimeDate *TimeDate); + void mcp7940_set_time_and_date (sTimeDate *TimeDate); + void mcp7940_print (sTimeDate *TimeDate); + +#endif /* MCP7940_H_ */ diff --git a/examples/SaveImgToFlash/progmem.h b/examples/SaveImgToFlash/progmem.h new file mode 100644 index 0000000..ea04c97 --- /dev/null +++ b/examples/SaveImgToFlash/progmem.h @@ -0,0 +1,797 @@ +/* + PIC1 SyncingPic + PIC2 TestPic + PIC3 GL test image + PIC4 TTN Logo + PIC5 PL Logo + PIC6 (free) + PIC7 (free) +*/ + +#ifndef PROGMEM_h +#define PROGMEM_h + +const byte pic [] PROGMEM= { // Icon example1, you will need to use + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xff, + 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xff, 0xff, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x7f, 0xfd, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x55, 0x5f, 0xff, 0x7f, 0xff, 0xff, 0xfd, 0x7f, 0xff, 0xfd, 0x57, 0xfd, 0x55, 0x55, 0x7f, 0x5f, 0xff, 0xd5, 0x5f, 0xff, 0xff, 0x5f, 0xff, 0xff, 0xf5, 0x5f, 0xff, 0xff, 0xd5, 0x5f, 0xff, 0x5f, 0xff, 0xd5, 0x5f, 0xff, + 0xfd, 0x00, 0x01, 0xfd, 0x0f, 0xff, 0xff, 0xf4, 0x2f, 0xff, 0xd0, 0x02, 0x7c, 0x00, 0x00, 0x1d, 0x0f, 0xfd, 0x80, 0x09, 0xff, 0xff, 0x07, 0xff, 0xff, 0x60, 0x02, 0x7f, 0xfd, 0x80, 0x09, 0xfd, 0x0f, 0xfd, 0x80, 0x09, 0xff, + 0xfd, 0x0a, 0xa0, 0xbd, 0x0f, 0xff, 0xff, 0xf8, 0x07, 0xff, 0x42, 0x68, 0xbe, 0xa8, 0x2a, 0xbd, 0x0f, 0xf8, 0x2a, 0x82, 0xff, 0xff, 0x07, 0xff, 0xfd, 0x0a, 0xa0, 0x1f, 0xf4, 0x2a, 0xa2, 0xfd, 0x0f, 0xf8, 0x29, 0x80, 0xff, + 0xfd, 0x0f, 0xf4, 0x1d, 0x2f, 0xff, 0xff, 0xd0, 0x8b, 0xff, 0x8b, 0xfd, 0x7f, 0xfc, 0x1f, 0xfd, 0x0f, 0xd0, 0x7f, 0xd9, 0xff, 0xff, 0x07, 0xff, 0xf4, 0x2f, 0xf6, 0x27, 0xd0, 0x9f, 0xf5, 0xfd, 0x0f, 0xd0, 0x7f, 0xd9, 0xff, + 0xfd, 0x0f, 0xfc, 0x1d, 0x2f, 0xff, 0xff, 0xd2, 0x49, 0xff, 0x89, 0xff, 0xff, 0xfc, 0x1f, 0xfd, 0x0f, 0xc2, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xf8, 0xbf, 0xfd, 0x87, 0xe2, 0x7f, 0xff, 0xfd, 0x0f, 0xe2, 0xff, 0xff, 0xff, + 0xfd, 0x0f, 0xfc, 0x1d, 0x2f, 0xff, 0xff, 0x41, 0xe1, 0xff, 0x42, 0x97, 0xff, 0xfc, 0x1f, 0xfd, 0x0f, 0x41, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xd0, 0x7f, 0xff, 0x4b, 0xe1, 0xff, 0x55, 0xfd, 0x0f, 0x41, 0xff, 0xff, 0xff, + 0xfd, 0x05, 0x58, 0x3d, 0x0f, 0xff, 0xff, 0x4b, 0xd2, 0x7f, 0xd8, 0x02, 0x7f, 0xfc, 0x1f, 0xfd, 0x0f, 0x43, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xd2, 0x7f, 0xff, 0x4b, 0x61, 0xfe, 0x00, 0xbd, 0x0f, 0x43, 0xff, 0xff, 0xff, + 0xfd, 0x00, 0x00, 0x7d, 0x0f, 0xff, 0xfd, 0x09, 0x58, 0x7f, 0xfd, 0x60, 0x1f, 0xfc, 0x1f, 0xfd, 0x0f, 0x43, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xd0, 0x7f, 0xff, 0x4b, 0xe1, 0xfd, 0xa8, 0xbd, 0x0f, 0x41, 0xff, 0xff, 0xff, + 0xfd, 0x0a, 0xa7, 0xfd, 0x0f, 0xff, 0xfd, 0x00, 0x00, 0xbf, 0xff, 0xf6, 0x1f, 0xfc, 0x1f, 0xfd, 0x0f, 0xc1, 0xff, 0xff, 0xff, 0xff, 0x07, 0xff, 0xf8, 0xbf, 0xff, 0x8b, 0xe2, 0xff, 0xf4, 0xbd, 0x0f, 0xc1, 0xff, 0xff, 0xff, + 0xfd, 0x0f, 0xff, 0xfd, 0x0f, 0xff, 0xf6, 0x2a, 0xa8, 0x1f, 0x5f, 0xfe, 0x1f, 0xfc, 0x1f, 0xfd, 0x0f, 0xe0, 0x7f, 0xf5, 0xff, 0xff, 0x07, 0xff, 0xf4, 0x1f, 0xfd, 0x07, 0xd0, 0x7f, 0xf4, 0xbd, 0x0f, 0xe0, 0x7f, 0xf5, 0xff, + 0xfd, 0x0f, 0xff, 0xfd, 0x05, 0x55, 0x74, 0xbf, 0xfd, 0x2f, 0x89, 0x74, 0x1f, 0xfe, 0x1f, 0xfd, 0x0f, 0xf0, 0x25, 0x40, 0xff, 0xff, 0x09, 0x55, 0x7e, 0x09, 0xd8, 0x1f, 0xf8, 0x25, 0x60, 0xbd, 0x0f, 0xf8, 0x25, 0x60, 0xff, + 0xfd, 0x0f, 0xff, 0xfd, 0x00, 0x00, 0x78, 0x7f, 0xfd, 0x87, 0x40, 0x00, 0xbf, 0xfc, 0x1f, 0xfd, 0x0f, 0xfe, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0xbf, 0x80, 0x00, 0x7f, 0xfe, 0x00, 0x02, 0x7d, 0x0f, 0xfe, 0x00, 0x01, 0xff, + 0xfd, 0xaf, 0xff, 0xfd, 0xaa, 0xaa, 0x79, 0xff, 0xff, 0x67, 0xf6, 0xa9, 0xff, 0xfd, 0xbf, 0xfd, 0xaf, 0xff, 0x6a, 0x9f, 0xff, 0xff, 0xaa, 0xaa, 0x7f, 0xda, 0xa7, 0xff, 0xff, 0xda, 0xa7, 0xfd, 0x6f, 0xff, 0x6a, 0x9f, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +/* +const unsigned char wIcon_sunny [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x01, 0x80, 0x30, + 0x00, 0x80, 0x20, + 0x00, 0x1f, 0x00, + 0x01, 0xb1, 0x80, + 0x0e, 0x70, 0x80, + 0x18, 0x18, 0xc0, + 0x10, 0x0c, 0x4c, + 0x30, 0x0f, 0x80, + 0x20, 0x00, 0xc0, + 0x20, 0x00, 0x40, + 0x20, 0x00, 0x40, + 0x30, 0x00, 0x40, + 0x10, 0x00, 0x40, + 0x0c, 0x00, 0xc0, + 0x07, 0xff, 0x80, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + + +const unsigned char wIcon_cloudy [] PROGMEM= { // Icon example2, you will need to use + 0x00, 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x01, 0x80, 0x80, 0xc0, + 0x01, 0xc0, 0x01, 0xc0, + 0x00, 0xe0, 0x01, 0xc0, + 0x00, 0x41, 0xc1, 0x80, + 0x00, 0x07, 0xf0, 0x00, + 0x00, 0x0f, 0xfc, 0x00, + 0x07, 0xfc, 0x1c, 0x00, + 0x1f, 0xfc, 0x0e, 0x00, + 0x3c, 0x1e, 0x06, 0x00, + 0x38, 0x07, 0x06, 0x3c, + 0x70, 0x03, 0xe6, 0x3c, + 0x60, 0x01, 0xfe, 0x00, + 0xe0, 0x01, 0xfe, 0x00, + 0xc0, 0x00, 0x0e, 0x00, + 0xc0, 0x00, 0x07, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xe0, 0x00, 0x03, 0x00, + 0x60, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x03, 0x00, + 0x38, 0x00, 0x07, 0x00, + 0x3c, 0x00, 0x0e, 0x00, + 0x1f, 0xff, 0xfc, 0x00, + 0x07, 0xff, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_01 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x08, 0x00, + 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, + 0x0c, 0x18, 0x30, + 0x0e, 0x00, 0x70, + 0x06, 0x00, 0x60, + 0x00, 0x7e, 0x00, + 0x00, 0xff, 0x00, + 0x01, 0xc3, 0x80, + 0x01, 0x81, 0x80, + 0x31, 0x80, 0xcc, + 0x79, 0x80, 0xde, + 0x01, 0x81, 0x80, + 0x01, 0x81, 0x80, + 0x00, 0xe7, 0x00, + 0x00, 0x7f, 0x00, + 0x02, 0x1c, 0x60, + 0x06, 0x00, 0x70, + 0x0c, 0x00, 0x30, + 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_02 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x60, 0x00, + 0x01, 0xe0, 0x00, + 0x03, 0xe0, 0x00, + 0x07, 0x30, 0x00, + 0x06, 0x38, 0x00, + 0x0c, 0x1e, 0x00, + 0x0c, 0x0f, 0x80, + 0x0c, 0x03, 0x80, + 0x06, 0x03, 0x00, + 0x07, 0x06, 0x00, + 0x03, 0xfe, 0x00, + 0x01, 0xf8, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_03 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x0c, 0x00, + 0x00, 0x0c, 0x00, + 0x02, 0x04, 0x18, + 0x03, 0x00, 0x30, + 0x01, 0x0c, 0x20, + 0x00, 0x3f, 0x80, + 0x07, 0xf1, 0x80, + 0x1f, 0xe0, 0xc0, + 0x38, 0x30, 0xc0, + 0x60, 0x1e, 0x4e, + 0x60, 0x0f, 0xc0, + 0xc0, 0x01, 0xc0, + 0xc0, 0x00, 0x60, + 0xc0, 0x00, 0x60, + 0x40, 0x00, 0x60, + 0x60, 0x00, 0x60, + 0x60, 0x00, 0x60, + 0x38, 0x00, 0xc0, + 0x1f, 0xff, 0x80, + 0x07, 0xfe, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_04 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x00, 0x01, 0xc0, + 0x00, 0x07, 0xc0, + 0x00, 0x0f, 0xc0, + 0x00, 0x1c, 0xe0, + 0x07, 0xf8, 0x70, + 0x1f, 0xf8, 0x3e, + 0x38, 0x1c, 0x1e, + 0x30, 0x0e, 0x0e, + 0x60, 0x07, 0xec, + 0x60, 0x03, 0xfc, + 0xc0, 0x00, 0x38, + 0xc0, 0x00, 0x1c, + 0xc0, 0x00, 0x0c, + 0x60, 0x00, 0x0c, + 0x60, 0x00, 0x0c, + 0x70, 0x00, 0x0c, + 0x38, 0x00, 0x1c, + 0x1c, 0x00, 0x38, + 0x0f, 0xff, 0xf0, + 0x03, 0xff, 0xc0, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_05 [] PROGMEM= { // Icon example1, you will need to use +0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x07, 0xf0, 0x00, + 0x0f, 0xf8, 0x00, + 0x1e, 0x3c, 0x00, + 0x38, 0x0e, 0x00, + 0x70, 0x07, 0x00, + 0x60, 0x03, 0xf0, + 0x60, 0x03, 0xf8, + 0xc0, 0x00, 0x1c, + 0xc0, 0x00, 0x0e, + 0xc0, 0x00, 0x06, + 0xc0, 0x00, 0x06, + 0xc0, 0x00, 0x06, + 0x60, 0x00, 0x06, + 0x60, 0x00, 0x06, + 0x70, 0x00, 0x06, + 0x38, 0x00, 0x0e, + 0x1e, 0x00, 0x3c, + 0x0f, 0xff, 0xf8, + 0x07, 0xff, 0xf0, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_06 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x03, 0xf0, 0x00, + 0x0f, 0xfc, 0x00, + 0x1c, 0x0e, 0x00, + 0x38, 0x07, 0x00, + 0x30, 0x03, 0xf0, + 0x70, 0x01, 0xf8, + 0x60, 0x00, 0x1c, + 0x60, 0x00, 0x0e, + 0x60, 0x00, 0x06, + 0x60, 0x00, 0x06, + 0x71, 0x81, 0x86, + 0x31, 0x81, 0x86, + 0x39, 0x99, 0x8e, + 0x19, 0x99, 0x9c, + 0x08, 0x18, 0x18, + 0x00, 0x18, 0x00, + 0x01, 0x81, 0x80, + 0x01, 0x81, 0x80, + 0x01, 0x99, 0x80, + 0x01, 0x99, 0x80, + 0x00, 0x18, 0x00, + 0x00, 0x18, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_07 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x0f, 0xf0, 0x00, + 0x1f, 0xf8, 0x00, + 0x38, 0x1c, 0x00, + 0x70, 0x0e, 0x00, + 0x60, 0x07, 0xe0, + 0xc0, 0x03, 0xf8, + 0xc0, 0x00, 0x18, + 0xc0, 0x00, 0x0c, + 0xc0, 0x00, 0x0c, + 0xc0, 0x00, 0x0c, + 0x63, 0x01, 0x0c, + 0x63, 0x01, 0x0c, + 0x33, 0x11, 0x1c, + 0x33, 0x11, 0x38, + 0x03, 0x11, 0x30, + 0x00, 0x11, 0x00, + 0x00, 0x10, 0x00, + 0x00, 0x10, 0x00, + 0x03, 0x03, 0x00, + 0x03, 0x01, 0x00, + 0x00, 0x30, 0x00, + 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, +}; + + +const unsigned char wIcon_08 [] PROGMEM= { // Icon example1, you will need to use + 0x01, 0xc0, 0x00, + 0x0f, 0xf8, 0x00, + 0x1f, 0xfc, 0x00, + 0x38, 0x0e, 0x00, + 0x70, 0x07, 0x00, + 0x60, 0x03, 0xf0, + 0xe0, 0x03, 0xf8, + 0xc0, 0x00, 0x1c, + 0xc0, 0x00, 0x0e, + 0xc0, 0x00, 0x06, + 0xc0, 0x00, 0x06, + 0xe0, 0x00, 0x06, + 0x60, 0x00, 0x06, + 0x71, 0x01, 0x8e, + 0x33, 0x01, 0x9c, + 0x10, 0x10, 0x1c, + 0x00, 0x18, 0x10, + 0x01, 0x00, 0x00, + 0x03, 0x01, 0x80, + 0x01, 0x00, 0x00, + 0x00, 0x18, 0x00, + 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_09 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x03, 0x80, + 0x00, 0x03, 0xc0, + 0x00, 0x00, 0xcc, + 0x00, 0x00, 0xde, + 0x3f, 0xff, 0xc6, + 0x7f, 0xff, 0x86, + 0xe0, 0x00, 0x3e, + 0xc0, 0x00, 0x3e, + 0xff, 0xfe, 0x00, + 0x7f, 0xff, 0x80, + 0x00, 0x01, 0xc0, + 0x00, 0x00, 0xc0, + 0x00, 0x00, 0xc0, + 0x00, 0x03, 0xc0, + 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char wIcon_10 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x07, 0xf0, 0x00, + 0x00, 0x00, 0x00, + 0x1f, 0xfe, 0x00, + 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xf0, + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xfc, + 0x00, 0x00, 0x00, + 0xff, 0xff, 0xfe, + 0x00, 0x00, 0x00, + 0x7f, 0xff, 0xfe, + 0x00, 0x00, 0x00, + 0x3f, 0xff, 0xfc, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + +const unsigned char testPic [] PROGMEM= { // Icon example1, you will need to use +0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x3f, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xff, 0xff, 0xf0, + 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xfc, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xff, 0xfc, 0xcf, 0xcf, 0xcc, 0xff, 0xfc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x3f, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0xcf, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x30, 0x3c, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf3, 0xf3, 0x3f, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x33, 0x3c, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0x33, 0x3f, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x30, 0x3c, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x33, 0x3f, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xcc, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcf, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xfc, 0xff, 0xf0, + 0x3f, 0xf3, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0x3f, 0xc0, + 0xff, 0xff, 0xc0, 0xc0, 0xfc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xf0, 0x30, 0x3f, 0xff, 0xf0, + 0x3f, 0xff, 0xcf, 0xcc, 0xf3, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0xf3, 0xf3, 0x3f, 0xff, 0xc0, + 0xf0, 0x03, 0xc0, 0xcc, 0xfc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xf0, 0x33, 0x3c, 0x00, 0xf0, + 0x3f, 0xff, 0xfc, 0xcc, 0xf3, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0xff, 0x33, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xc0, 0xc0, 0xfc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xf0, 0x30, 0x3f, 0xff, 0xf0, + 0x3f, 0xf3, 0xff, 0xff, 0xf3, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0xff, 0xff, 0xff, 0x3f, 0xc0, + 0xff, 0xcf, 0xff, 0xff, 0xfc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xff, 0xff, 0xfc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x53, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x5c, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x33, 0x3f, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xcc, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xac, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa3, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcf, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0xff, 0xff, 0xfc, 0xff, 0xf0, + 0x3f, 0xf3, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0xff, 0xff, 0xff, 0x3f, 0xc0, + 0xff, 0xff, 0xcc, 0x0c, 0x0f, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0x30, 0x30, 0x3f, 0xff, 0xf0, + 0x3f, 0xff, 0xcc, 0xcc, 0xcf, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0x33, 0x33, 0x3f, 0xff, 0xc0, + 0xf0, 0x03, 0xcc, 0xcc, 0xcf, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0x33, 0x33, 0x3c, 0x00, 0xf0, + 0x3f, 0xff, 0xcc, 0xcc, 0xcf, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaf, 0x33, 0x33, 0x3f, 0xff, 0xc0, + 0xff, 0xff, 0xcc, 0x0c, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x30, 0x30, 0x3f, 0xff, 0xf0, + 0x3f, 0xf3, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0x3f, 0xc0, + 0xff, 0xcf, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xfc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x33, 0x3f, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xcc, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcf, 0xff, 0xf0, + 0x3f, 0xff, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x30, 0x3f, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf3, 0xf3, 0x3c, 0xcc, 0xcc, 0xff, 0xf0, + 0x33, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x33, 0x3f, 0x33, 0x33, 0x3c, 0xc0, + 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0x33, 0x3c, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x30, 0x3f, 0x33, 0x33, 0x3f, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xfc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0x33, 0x33, 0xff, 0xc0, + 0xf0, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xcc, 0xcc, 0xcc, 0xf0, 0xf0, + 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0x33, 0x33, 0x33, 0xff, 0xc0, + 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xcf, 0xcc, 0xcc, 0xcc, 0xff, 0xf0, + 0x3f, 0xf3, 0xff, 0x33, 0x3f, 0xff, 0xff, 0xff, 0x33, 0x3f, 0xf3, 0x33, 0xcf, 0x33, 0x3f, 0xf3, 0x3f, 0xc0, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xf0, + 0x33, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xff, 0xfc, 0xc0, + 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xf0, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x30, + }; + +const unsigned char testPic_r [] PROGMEM= { // Icon example1, you will need to use +0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x3c, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xfc, + 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, + 0x33, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, + 0xff, 0xff, 0xff, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0xff, 0xf3, 0x3f, 0x3f, 0x33, 0xff, 0xf3, 0x3f, 0xff, 0xff, 0xff, 0xf3, 0x3f, 0xff, 0x33, 0xf3, 0xf3, 0x3f, 0xff, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0xff, 0xf3, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xf3, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x03, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x30, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xcf, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x03, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x03, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf3, 0xf3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x03, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x00, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x33, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x3f, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xcc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x03, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x00, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf3, 0xf3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf0, 0x03, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xf0, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x0f, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0xfc, + 0xff, 0xff, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x33, 0x3c, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xfc, 0xcc, + 0xff, 0xff, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xff, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xf0, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0x0f, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x33, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xcc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xf0, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0x0f, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x33, 0xcc, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xfc, 0xcf, 0xcc, + 0xff, 0xf3, 0x3f, 0xff, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0x33, 0xff, + 0x3f, 0xcc, 0xfc, 0xc0, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x30, 0x3c, 0xcf, 0xfc, + 0xff, 0xff, 0xfc, 0xcc, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x33, 0x3f, 0xff, 0xff, + 0x3f, 0xff, 0xfc, 0x0c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x03, 0x3f, 0xff, 0xfc, + 0xf0, 0x03, 0xff, 0xff, 0xf3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa5, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc0, 0x0f, + 0x3f, 0xff, 0xfc, 0x00, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x3f, 0xff, 0xfc, + 0xff, 0xff, 0xfc, 0xfc, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x3f, 0x3f, 0xff, 0xff, + 0x3f, 0xcc, 0xfc, 0x00, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x03, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x3c, 0xcf, 0xfc, + 0xff, 0xf3, 0x3f, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0x33, 0xff, + 0x33, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x03, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x30, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xfc, 0xcf, 0xcc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf3, 0xf3, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x33, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x03, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x03, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x03, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xf0, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf3, 0xf3, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x3f, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x0f, + 0x3f, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf0, 0x03, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0x00, 0x3c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xff, + 0x3f, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xff, 0xff, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xff, 0xfc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcf, 0xfc, + 0xff, 0xf3, 0x33, 0x33, 0x33, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x3f, 0x3f, 0x33, 0x33, 0x33, 0x33, 0x33, 0xff, 0x33, 0x33, 0x33, 0x33, 0x33, 0xf3, 0xf3, 0x33, 0x33, 0x33, 0x33, 0x3f, 0xf3, 0x33, 0x33, 0x33, 0x33, 0xff, + 0x33, 0xcf, 0xfc, 0xcc, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xff, 0xcc, 0xcf, 0x3c, 0xcc, 0xff, 0xcc, 0xcf, 0xff, 0xff, 0xff, 0xcc, 0xcf, 0xfc, 0xcc, 0xf3, 0xcc, 0xcf, 0xfc, 0xcc, 0xff, 0xff, 0xff, 0xfc, 0xcc, 0xff, 0xff, 0xcc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x3f, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0xfc, + 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, 0x3f, 0xcf, 0xf3, 0xfc, 0xff, + 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + }; + +const unsigned char testGL15 [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + + +*/ + +#endif diff --git a/examples/SaveImgToFlash/spi_flash.cpp b/examples/SaveImgToFlash/spi_flash.cpp new file mode 100644 index 0000000..0a4b9ac --- /dev/null +++ b/examples/SaveImgToFlash/spi_flash.cpp @@ -0,0 +1,192 @@ +#include "Arduino.h" +#include +#include "spi_functions.h" +#include "spi_flash.h" +#include "lorapaper.h" + + +void flash_ID (sFLASH_ID * ID) +{ + if(ID == NULL) + { + return; + } + + flash_release_power_down(); + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_MANUFACTURERE_ID); // 0x90 + + //Send 0x000000 for address + SPI.transfer(0x00); + SPI.transfer(0x00); + SPI.transfer(0x00); + + //Get Manufacture ID + ID->manufacturerID = SPI.transfer(0x00); + + //Get device ID + ID->deviceID = SPI.transfer(0x00); + + //Set CS pin HIGH + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +void flash_power_down (void) +{ + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_POWER_DOWN); // 0xB9 + + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_release_power_down (void) +{ + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction and four dummy bytes + SPI.transfer(FLASH_RELEASE_POWER_DOWN); // 0xAB + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // ID7:ID0 will repeat this until SPI transfer has finished. + + digitalWrite(SPI_FLASH_CS, HIGH); + + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_eraseSector(uint32_t address) { + flash_release_power_down(); + flash_write_enable(); + digitalWrite(SPI_FLASH_CS, LOW); + SPI.transfer(FLASH_SECTOR_ERASE); + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + digitalWrite(SPI_FLASH_CS, HIGH); + while(flash_status() & R_W_IN_PROGRESS) {} +} + +void flash_write(uint32_t address, uint8_t *data, uint16_t n) +{ + uint8_t i; + + flash_release_power_down(); + + // Enable writing to the Flash Chip + flash_write_enable(); + + //delay(1); + + // Set CS low + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_PAGE_PROGRAM); // 0x02 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + //SPI.transfer(i); // writes the value of i to the + SPI.transfer(data[i]); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); + + //delay(2000); + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_read(uint32_t address, uint8_t *data, uint16_t n) +{ + uint16_t i; + + flash_release_power_down(); + + // Set CS low again + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_READ_DATA); // 0x03 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + data[i] = SPI.transfer(0x00); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_enable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_ENABLE); // 0x06 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_disable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_DISABLE); // 0x04 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +uint8_t flash_status (void) +{ + uint8_t status = 0; + + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_READ_STATUS); // 0x05 + + // Read the status byte + status = SPI.transfer(0x00); + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); + + return status; +} diff --git a/examples/SaveImgToFlash/spi_flash.h b/examples/SaveImgToFlash/spi_flash.h new file mode 100644 index 0000000..846e3ec --- /dev/null +++ b/examples/SaveImgToFlash/spi_flash.h @@ -0,0 +1,78 @@ +// spi_flash.h + +#ifndef _SPI_FLASH_h +#define _SPI_FLASH_h + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define SPI_FLASH_PAGE_SIZE 256 + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + FLASH_WRITE_ENABLE = 0x06, + FLASH_WRITE_EN_VOLATILE = 0x50, + FLASH_WRITE_DISABLE = 0x04, + FLASH_READ_STATUS = 0x05, + FLASH_WRITE_STATUS = 0x01, + FLASH_READ_DATA = 0x03, + FLASH_FAST_READ = 0x0B, + FLASH_FAST_READ_DUAL = 0x3B, + FLASH_READ_DUAL_IO = 0xBB, + FLASH_PAGE_PROGRAM = 0x02, + FLASH_SECTOR_ERASE = 0x20, + FLASH_BLOCK_ERASE_32K = 0x52, + FLASH_BLOCK_ERASE_64K = 0xD8, + FLASH_CHIP_ERASE = 0xC7, + FLASH_CHIP_ERASE2 = 0x60, + FLASH_POWER_DOWN = 0xB9, + FLASH_RELEASE_POWER_DOWN= 0xAB, + FLASH_MANUFACTURERE_ID = 0x90, + FLASH_ID_DUAL_IO = 0x92, + FLASH_JEDEC_ID = 0x9F, + FLASH_READ_UNIQUE_ID = 0x4B + }eFLASH_COMMANDS; + + typedef enum + { + REGISTER_PROTECT = 0x80, + TOP_BOT_PROTECT = 0x20, + BLOCK_PROTECT = 0x1C, + WRITE_ENABLE_LATCH = 0x02, + R_W_IN_PROGRESS = 0x01 + }eSTATUS_BITS; + + /****************************************************************************************** + STRUCTURE + ******************************************************************************************/ + typedef struct + { + uint8_t deviceID; + uint8_t manufacturerID; + }sFLASH_ID; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void flash_ID (sFLASH_ID * ID); + void flash_power_down (void); + void flash_release_power_down (void); + void flash_write (uint32_t address, uint8_t *data, uint16_t n); + void flash_read (uint32_t address, uint8_t *data, uint16_t n); + void flash_write_enable (void); + void flash_write_disable (void); + uint8_t flash_status (void); + void flash_eraseSector (uint32_t address); + + +#endif diff --git a/examples/SaveImgToFlash/spi_functions.cpp b/examples/SaveImgToFlash/spi_functions.cpp new file mode 100644 index 0000000..73b435f --- /dev/null +++ b/examples/SaveImgToFlash/spi_functions.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "spi_functions.h" + +/****************************************************************************************** +* Description : Function that reads a register and returns the value +* Arguments : Address of register to be read +* Returns : Value of the register +******************************************************************************************/ + +uint8_t SPI_Read(uint8_t CS_pin, uint8_t register_Address) { + uint8_t RFM_Data; + + digitalWrite(CS_pin, LOW); // Set NSS pin low to start SPI communication + SPI.transfer(register_Address); // Send Address + RFM_Data = SPI.transfer(0x00); // Send 0x00 to be able to receive the answer from the RFM + digitalWrite(CS_pin, HIGH); // Set NSS high to end communication + return RFM_Data; // Return received data +} + +/****************************************************************************************** +* Description: Function that writes a register with the given value +* Arguments: Address of the register to be written +* Data Data to be written to the register +******************************************************************************************/ +void SPI_Write(uint8_t CS_pin, uint8_t register_Address, uint8_t Data) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a writ command + SPI.transfer(Data); //Send Data + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + + +/********************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* Lenght: The number of bytes needed to transmit +**********************************************************************************************/ +void SPI_Write_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a Write command + SPI.transfer(Data, lenght); //Send the data array + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + +/***************************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the RFM register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* lenght: The number of bytes needed to transmit +*****************************************************************************************************/ +void SPI_Read_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set Chip select pin low to start SPI communication + SPI.transfer(register_Address); //Send the register Address and then read the contents of the receive buffer in the RFM + SPI.transfer(Data, lenght); //Set NSS high to end communication + digitalWrite(CS_pin, HIGH); +} diff --git a/examples/SaveImgToFlash/spi_functions.h b/examples/SaveImgToFlash/spi_functions.h new file mode 100644 index 0000000..f9e7a88 --- /dev/null +++ b/examples/SaveImgToFlash/spi_functions.h @@ -0,0 +1,17 @@ +#ifndef _SPI_Functions_h +#define _SPI_Functions_h + + #if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" + #else + #include "WProgram.h" + #endif + + #include + + void SPI_Write (uint8_t CS_pin, uint8_t register_Address, uint8_t Data); + uint8_t SPI_Read (uint8_t CS_pin, uint8_t register_Address); + void SPI_Write_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + void SPI_Read_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + +#endif diff --git a/examples/SaveImgToFlash/timers.cpp b/examples/SaveImgToFlash/timers.cpp new file mode 100644 index 0000000..ab4b098 --- /dev/null +++ b/examples/SaveImgToFlash/timers.cpp @@ -0,0 +1,68 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: timers.cpp +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "avr/sleep.h" +#include "Arduino.h" +#include "timers.h" + + +void disable_ms_tick (void) +{ + TCCR1B = 0x00; // Disable Timer 1 +} + + +/* + @brief Configures the timer to create an interrupt every millisecond for timing purposes. +*/ +void enable_ms_tick (void) +{ + // Disable the Timer before configuring + disable_ms_tick(); + + // Use T1 since it's a 16 bit timer, but Timer 1 uses the same prescaler, so be aware. + TCCR1A = 0x00; + + // Clear the timer counter. + TCNT1 = 0; + + // (16MHz / 1) / 1000 = 16000 Ticks per millisecond. + OCR1A = 16000; + + // Enable Output compare A Match interrupts + TIMSK1 = 0x02; + + // Enable Timer1 by setting a non-zero prescaler. + TCCR1B = 0x09; // 0x00x0 1001: use CTC mode and set the prescaler to 1/1 +} diff --git a/examples/SaveImgToFlash/timers.h b/examples/SaveImgToFlash/timers.h new file mode 100644 index 0000000..157a7ae --- /dev/null +++ b/examples/SaveImgToFlash/timers.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Waitloop.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef TIMERS_H +#define TIMERS_H + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + + #include + + void disable_ms_tick (void); + void enable_ms_tick (void); + +#endif diff --git a/examples/WeatherForecastExample/AES-128.cpp b/examples/WeatherForecastExample/AES-128.cpp new file mode 100644 index 0000000..786ca2b --- /dev/null +++ b/examples/WeatherForecastExample/AES-128.cpp @@ -0,0 +1,284 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#include +#include +#include "AES-128.h" + + +uint8_t 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} +}; + + + + +/* +***************************************************************************************** +* Title : AES_Encrypt +* Description : +***************************************************************************************** +*/ +void AES_Encrypt(uint8_t *Data, uint8_t *Key) +{ + uint8_t Row, Column, Round = 0; + uint8_t Round_Key[16]; + uint8_t 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 AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]) +{ + uint8_t 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 : +***************************************************************************************** +*/ +uint8_t AES_Sub_Byte(uint8_t Byte) +{ + return S_Table [ ((Byte >> 4) & 0x0F) ] [ ((Byte >> 0) & 0x0F) ]; +} // AES_Sub_Byte + + +/* +***************************************************************************************** +* Title : AES_Shift_Rows +* Description : +***************************************************************************************** +*/ +void AES_Shift_Rows(uint8_t (*State)[4]) +{ + uint8_t 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 AES_Mix_Collums(uint8_t (*State)[4]) +{ + uint8_t Row,Collum; + uint8_t 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 AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key) +{ + uint8_t i, j, b, Rcon; + uint8_t Temp[4]; + + + //Calculate Rcon + Rcon = 0x01; + while(Round != 1) + { + b = Rcon & 0x80; + Rcon = Rcon << 1; + + if(b == 0x80) + { + Rcon ^= 0x1b; + } + Round--; + } + + // Calculate first Temp + // Copy laste byte from previous key and subsitute the byte, but shift the array contents around by 1. + Temp[0] = AES_Sub_Byte( Round_Key[12 + 1] ); + Temp[1] = AES_Sub_Byte( Round_Key[12 + 2] ); + Temp[2] = AES_Sub_Byte( Round_Key[12 + 3] ); + Temp[3] = AES_Sub_Byte( Round_Key[12 + 0] ); + + // XOR with Rcon + Temp[0] ^= Rcon; + + // Calculate new key + for(i = 0; i < 4; i++) + { + for(j = 0; j < 4; j++) + { + Round_Key[j + (i << 2)] ^= Temp[j]; + Temp[j] = Round_Key[j + (i << 2)]; + } + } +} // AES_Calculate_Round_Key + + + + + diff --git a/examples/WeatherForecastExample/AES-128.h b/examples/WeatherForecastExample/AES-128.h new file mode 100644 index 0000000..848907d --- /dev/null +++ b/examples/WeatherForecastExample/AES-128.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: AES-128.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#ifndef AES128_H +#define AES128_H + + #include + + /************************************************************************************** + * FUNCTION + **************************************************************************************/ + + void AES_Encrypt(uint8_t *Data, uint8_t *Key); + void AES_Add_Round_Key(uint8_t *Round_Key, uint8_t (*State)[4]); + uint8_t AES_Sub_Byte(uint8_t Byte); + void AES_Shift_Rows(uint8_t (*State)[4]); + void AES_Mix_Collums(uint8_t (*State)[4]); + void AES_Calculate_Round_Key(uint8_t Round, uint8_t *Round_Key); + void Send_State(); + +#endif diff --git a/examples/WeatherForecastExample/Cayenne_LPP.cpp b/examples/WeatherForecastExample/Cayenne_LPP.cpp new file mode 100644 index 0000000..b88969d --- /dev/null +++ b/examples/WeatherForecastExample/Cayenne_LPP.cpp @@ -0,0 +1,289 @@ +/* + * Cayenne_LPP.c + * + * Created: 4-10-2017 11:45:10 + * Author: adri + */ +#include +#include "Cayenne_LPP.h" + + +CayenneLPP::CayenneLPP(sLoRa_Message *buffer_Ptr) +{ + if(buffer_Ptr != 0) + { + buffer = buffer_Ptr; + } +} + +CayenneLPP::~CayenneLPP() +{ + buffer = 0; +} + + +void CayenneLPP::clearBuffer (void) +{ + if(buffer == 0) + { + return; + } + + // Clear the buffer and the number of bytes in it. + memset(&(buffer->Data[0]), 0, LORA_FIFO_SIZE); + buffer->Count = 0; +} + + +void CayenneLPP::addGPS(uint8_t channel, double latitude, double longitude, double altitude) +{ + if((buffer == 0) || ((buffer->Count + LPP_GPS_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + int32_t lat = (int32_t)(latitude * 10000.0); + int32_t lon = (int32_t)(longitude * 10000.0); + int32_t alt = (int32_t)(altitude * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GPS; + buffer->Data[(buffer->Count) + 2] = lat >> 16; + buffer->Data[(buffer->Count) + 3] = lat >> 8; + buffer->Data[(buffer->Count) + 4] = lat; + buffer->Data[(buffer->Count) + 5] = lon >> 16; + buffer->Data[(buffer->Count) + 6] = lon >> 8; + buffer->Data[(buffer->Count) + 7] = lon; + buffer->Data[(buffer->Count) + 8] = alt >> 16; + buffer->Data[(buffer->Count) + 9] = alt >> 8; + buffer->Data[(buffer->Count) + 10] = alt; + buffer->Count += LPP_GPS_SIZE; // 11 + return; +} + + +void CayenneLPP::addAnalogOutput(uint8_t channel, double value) // sLoRa_Message *buffer, +{ + int16_t val; + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (int16_t) (value * 100.0); + + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_OUTPUT; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val; + buffer->Count += LPP_ANALOG_OUTPUT_SIZE; // 4 + return; +} + + +void CayenneLPP::addDigitalOutput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_OUTPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_OUTPUT; + buffer->Data[(buffer->Count) + 2] = value; + + // Add the added length to the buffer counter. + buffer->Count += LPP_DIGITAL_OUTPUT_SIZE; +} + + + +void CayenneLPP::addDigitalInput(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_DIGITAL_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_DIGITAL_INPUT; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_DIGITAL_INPUT_SIZE; +} + + + +void CayenneLPP::addAnalogInput(uint8_t channel, float value) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ANALOG_INPUT_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) (value * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_ANALOG_INPUT_SIZE; +} + + + +void CayenneLPP::addLuminosity(uint8_t channel, float lux) +{ + uint16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_LUMINOSITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t) lux; + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ANALOG_INPUT_SIZE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_LUMINOSITY_SIZE; +} + +void CayenneLPP::addPresence(uint8_t channel, uint8_t value) +{ + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_PRESENCE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_PRESENCE; + buffer->Data[(buffer->Count) + 2] = value; + buffer->Count += LPP_PRESENCE_SIZE; +} + +void CayenneLPP::addTemperature(uint8_t channel, float celsius) +{ + uint16_t temp; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_TEMPERATURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + temp = (uint16_t) (celsius * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_TEMPERATURE; + buffer->Data[(buffer->Count) + 2] = temp >> 8; + buffer->Data[(buffer->Count) + 3] = temp >> 0; + buffer->Count += LPP_TEMPERATURE_SIZE; +} + +void CayenneLPP::addRelativeHumidity(uint8_t channel, float rh) //sLoRa_Message *buffer, +{ + uint16_t humidity; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_RELATIVE_HUMIDITY_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + humidity = (uint16_t) (rh * 2.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_RELATIVE_HUMIDITY; + buffer->Data[(buffer->Count) + 2] = humidity; + buffer->Count += LPP_RELATIVE_HUMIDITY_SIZE; +} + +void CayenneLPP::addAccelerometer(uint8_t channel, float x, float y, float z) +{ + uint16_t x_axis, y_axis, z_axis; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_ACCELEROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to unsigned integers. + x_axis = (uint16_t) (x * 1000.0); + y_axis = (uint16_t) (y * 1000.0); + z_axis = (uint16_t) (z * 1000.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_ACCELEROMETER; + buffer->Data[(buffer->Count) + 2] = x_axis >> 8; + buffer->Data[(buffer->Count) + 3] = x_axis >> 0; + buffer->Data[(buffer->Count) + 4] = y_axis >> 8; + buffer->Data[(buffer->Count) + 5] = y_axis >> 0; + buffer->Data[(buffer->Count) + 6] = z_axis >> 8; + buffer->Data[(buffer->Count) + 7] = z_axis >> 0; + buffer->Count += LPP_ACCELEROMETER_SIZE; +} + +void CayenneLPP::addBarometricPressure(uint8_t channel, float hpa) +{ + int16_t val; + + // Check for invalid input pointer. + if((buffer == 0) || ((buffer->Count + LPP_BAROMETRIC_PRESSURE_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + val = (uint16_t)(hpa * 10.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_BAROMETRIC_PRESSURE; + buffer->Data[(buffer->Count) + 2] = val >> 8; + buffer->Data[(buffer->Count) + 3] = val >> 0; + buffer->Count += LPP_BAROMETRIC_PRESSURE_SIZE; +} + +void CayenneLPP::addGyrometer(uint8_t channel, float x, float y, float z) +{ + + int16_t vx, vy, vz; + + // Check for invalid input pointer or if adding this data will cause the message to be larger then the maximum payload size. + if((buffer == 0) || ((buffer->Count + LPP_GYROMETER_SIZE) > LORAWAN_MAX_PAYLOAD)) + { + return; + } + + // Convert the floats to signed integers + vx = (int16_t)(x * 100.0); + vy = (int16_t)(y * 100.0); + vz = (int16_t)(z * 100.0); + + // Add the digital value to the Transmit buffer. + buffer->Data[(buffer->Count) + 0] = channel; + buffer->Data[(buffer->Count) + 1] = LPP_GYROMETER; + buffer->Data[(buffer->Count) + 2] = vx >> 8; + buffer->Data[(buffer->Count) + 3] = vx >> 0; + buffer->Data[(buffer->Count) + 4] = vy >> 8; + buffer->Data[(buffer->Count) + 5] = vy >> 0; + buffer->Data[(buffer->Count) + 6] = vz >> 8; + buffer->Data[(buffer->Count) + 7] = vz >> 0; + buffer->Count += LPP_GYROMETER_SIZE; +} diff --git a/examples/WeatherForecastExample/Cayenne_LPP.h b/examples/WeatherForecastExample/Cayenne_LPP.h new file mode 100644 index 0000000..59b67f2 --- /dev/null +++ b/examples/WeatherForecastExample/Cayenne_LPP.h @@ -0,0 +1,78 @@ +/* + * Cayenne_LPP.h + * + * Created: 4-10-2017 11:45:55 + * Author: adri + */ + + +#ifndef CAYENNE_LPP_H_ +#define CAYENNE_LPP_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + + #include "lorawan_def.h" + + /****************************************************************************************** + DEFINITIONS + ******************************************************************************************/ + + #define LPP_DIGITAL_INPUT 0 // 1 byte + #define LPP_DIGITAL_OUTPUT 1 // 1 byte + #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE 102 // 1 byte, 1 + #define LPP_TEMPERATURE 103 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS 136 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + + // Data ID + Data Type + Data Size + #define LPP_DIGITAL_INPUT_SIZE 3 // 1 byte + #define LPP_DIGITAL_OUTPUT_SIZE 3 // 1 byte + #define LPP_ANALOG_INPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_ANALOG_OUTPUT_SIZE 4 // 2 bytes, 0.01 signed + #define LPP_LUMINOSITY_SIZE 4 // 2 bytes, 1 lux unsigned + #define LPP_PRESENCE_SIZE 3 // 1 byte, 1 + #define LPP_TEMPERATURE_SIZE 4 // 2 bytes, 0.1�C signed + #define LPP_RELATIVE_HUMIDITY_SIZE 3 // 1 byte, 0.5% unsigned + #define LPP_ACCELEROMETER_SIZE 8 // 2 bytes per axis, 0.001G + #define LPP_BAROMETRIC_PRESSURE_SIZE 4 // 2 bytes 0.1 hPa Unsigned + #define LPP_GYROMETER_SIZE 8 // 2 bytes per axis, 0.01 �/s + #define LPP_GPS_SIZE 11 // 3 byte lon/lat 0.0001 �, 3 bytes alt 0.01 meter + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + class CayenneLPP + { + public: + CayenneLPP (sLoRa_Message *buffer_Ptr); + ~CayenneLPP (); + + void clearBuffer (void); + void addGPS (uint8_t channel, double latitude, double longitude, double altitude); + void addAnalogOutput (uint8_t channel, double value); + void addDigitalOutput (uint8_t channel, uint8_t value); + void addDigitalInput (uint8_t channel, uint8_t value); + void addAnalogInput (uint8_t channel, float value); + void addLuminosity (uint8_t channel, float lux); + void addPresence (uint8_t channel, uint8_t value); + void addTemperature (uint8_t channel, float celsius); + void addRelativeHumidity (uint8_t channel, float rh); + void addAccelerometer (uint8_t channel, float x, float y, float z); + void addBarometricPressure (uint8_t channel, float hpa); + void addGyrometer (uint8_t channel, float x, float y, float z); + + private: + sLoRa_Message *buffer; + }; + +#endif /* CAYENNE_LPP_H_ */ diff --git a/examples/WeatherForecastExample/DS2401.cpp b/examples/WeatherForecastExample/DS2401.cpp new file mode 100644 index 0000000..1431eec --- /dev/null +++ b/examples/WeatherForecastExample/DS2401.cpp @@ -0,0 +1,185 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include "Arduino.h" +#include "DS2401.h" +#include "lorapaper.h" + +/* +***************************************************************************************** +* Description: Function is used to read the all the bytes provide by the DS2401 +* +* Arguments: *DS_Bytes pointer to an array of 8 uint8_ts +***************************************************************************************** +*/ +bool DS_Read(uint8_t *DS_Bytes) +{ + uint8_t i; + + //Preform reset pulse + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(700); + pinMode(DS2401,INPUT); + delayMicroseconds(700); + + //Send command 0x33 LSB + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + DS_WR1(); + DS_WR1(); + DS_WR0(); + DS_WR0(); + + //Read DS bytes + for (i = 0 ; i < RAM_SIZE ; i++) + { + DS_Bytes[i] = DS_ReadByte(); + } + + //Shutdown DS2401 + digitalWrite(DS2401, HIGH); + + // Check whether the CRC matches with the last byte from the DS2401. + return DS_CheckCRC(DS_Bytes); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 1 to the DS2401 +***************************************************************************************** +*/ +void DS_WR1(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(80); +} + +/* +***************************************************************************************** +* Description: Function is used to write a logical 0 to the DS2401 +***************************************************************************************** +*/ +void DS_WR0(void) +{ + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(80); + pinMode(DS2401,INPUT); + delayMicroseconds(2); +} + +/* +***************************************************************************************** +* Description: Function is used to read the one byte provided by the DS2401 +* +* Return: Returns the byte received from the DS2401 +***************************************************************************************** +*/ +uint8_t DS_ReadByte(void) +{ + uint8_t DS_Byte = 0; + uint8_t i, t = 1; + + for (i = 0 ; i < 8 ; i++) + { + pinMode(DS2401,OUTPUT); + digitalWrite(DS2401,LOW); + delayMicroseconds(2); + pinMode(DS2401,INPUT); + delayMicroseconds(10); + + if(digitalRead(DS2401) == 1) + { + DS_Byte |= t; + } + + t = t << 1; + delayMicroseconds(80); + } + return DS_Byte; +} + +/* +***************************************************************************************** +* Description : This function does a CRC check on the received data from the DS2401 +* Arguments : *DS_bytes pointer to the arry that holds the DS bytes +* Return : Returns 0x01 when CRC check is OK +***************************************************************************************** +*/ +bool DS_CheckCRC(uint8_t *DS_bytes) +{ + uint8_t DS_current_byte = 0x00; + uint8_t DS_crc = 0x00; + uint8_t DS_Polynom = 0x0C; + uint8_t DS_crc_carry = 0x00; + uint8_t i = 0x00; + uint8_t j = 0x00; + + /*Loop for all 6 DS bytes*/ + for (i = 0 ; i < 7 ; i++) + { + DS_current_byte = DS_bytes[i]; //Get first byte + + /*Calculate CRC for all bits*/ + for (j = 0 ; j < 8 ; j++) + { + /*XOR bit 0 with bit 0 of current CRC*/ + if ((DS_crc & 0x01) != (DS_current_byte & 0x01)) + { + DS_crc_carry = 0x80; + } + else + { + DS_crc_carry = 0x00; + } + + /*shift CRC*/ + DS_crc = DS_crc >> 1; + + /*Check for carry*/ + if (DS_crc_carry == 0x80) + { + DS_crc = DS_crc | DS_crc_carry; + DS_crc = DS_crc ^ DS_Polynom; + } + + DS_current_byte = DS_current_byte >> 1; + } + } + + // Check whether the calculated CRC and the last received byte match. When they do retrun true to indicate that the received bytes are valid otherwise return false + if (DS_crc == DS_bytes[7]) + { + return true; + } + return false; +} diff --git a/examples/WeatherForecastExample/DS2401.h b/examples/WeatherForecastExample/DS2401.h new file mode 100644 index 0000000..c03503c --- /dev/null +++ b/examples/WeatherForecastExample/DS2401.h @@ -0,0 +1,47 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: DS2401.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 13-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef DS2401_H +#define DS2401_H + + #include + #include + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RAM_SIZE 8 + + /********************************************************************************************* + FUNCTION PORTOTYPES + *********************************************************************************************/ + bool DS_Read (uint8_t *DS_Bytes); + bool DS_CheckCRC (uint8_t *DS_bytes); + void DS_WR1 (void); + void DS_WR0 (void); + uint8_t DS_ReadByte (void); + +#endif diff --git a/examples/WeatherForecastExample/Encrypt.cpp b/examples/WeatherForecastExample/Encrypt.cpp new file mode 100644 index 0000000..afd095b --- /dev/null +++ b/examples/WeatherForecastExample/Encrypt.cpp @@ -0,0 +1,404 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "Encrypt.h" +#include "AES-128.h" +#include +#include "timers.h" +#include "lorawan_def.h" + +/* +***************************************************************************************** +* INCLUDE GLOBAL VARIABLES +***************************************************************************************** +*/ + +/* +***************************************************************************************** +* Description : Function used to encrypt and decrypt the data in a LoRaWAN data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data to de/encrypt +* *Session_Data pointer to sLoRa_Session structure +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Encrypt_Payload(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i = 0x00; + uint8_t j; + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + uint8_t Block_A[16]; + + if((data==0) || (Key==0) || (lenght == 0) || (Message == 0)) + { + return; + } + + //Calculate number of blocks + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + for(i = 0x00; 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; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_A[5] = UPSTREAM_DIR; + } + else + { + Block_A[5] = DOWNSTREAM_DIR; + } + + Block_A[6] = Message->DevAddr[3]; + Block_A[7] = Message->DevAddr[2]; + Block_A[8] = Message->DevAddr[1]; + Block_A[9] = Message->DevAddr[0]; + + Block_A[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_A[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_A[12] = 0x00; //Frame counter upper Bytes + Block_A[13] = 0x00; + Block_A[14] = 0x00; + Block_A[15] = i + 1; + + //Calculate S + AES_Encrypt(Block_A, Key); + + //Check for last block + if(i != (Number_of_Blocks - 1)) + { + for(j = 0; j < 16; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + else + { + if(Incomplete_Block_Size == 0) + { + Incomplete_Block_Size = 16; + } + for(j = 0; j < Incomplete_Block_Size; j++) + { + data[(i*16)+j] ^= Block_A[j]; + } + } + } +} + +/* +***************************************************************************************** +* Description : Function used to build a the data that is used for calculating the MIC of a data message +* +* Arguments : *Buffer pointer to the buffer cointaining the data +* *Session_Data pointer to sLoRa_Session sturct +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Construct_Data_MIC(uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message) +{ + uint8_t i; + uint8_t MIC_Data[80], MIC_lenght; + uint8_t Block_B[16]; + + //Construct Block B + Block_B[0] = 0x49; + Block_B[1] = 0x00; + Block_B[2] = 0x00; + Block_B[3] = 0x00; + Block_B[4] = 0x00; + + if((Message->MAC_Header == CONFIRMED_DATA_UP) || (Message->MAC_Header == UNCONFIRMED_DATA_UP)) + { + Block_B[5] = UPSTREAM_DIR; + } + else + { + Block_B[5] = DOWNSTREAM_DIR; + } + + Block_B[6] = Message->DevAddr[3]; + Block_B[7] = Message->DevAddr[2]; + Block_B[8] = Message->DevAddr[1]; + Block_B[9] = Message->DevAddr[0]; + + Block_B[10] = (uint8_t)(Message->Frame_Counter >> 0); + Block_B[11] = (uint8_t)(Message->Frame_Counter >> 8); + + Block_B[12] = 0x00; //Frame counter upper bytes + Block_B[13] = 0x00; + Block_B[14] = 0x00; + Block_B[15] = lenght; + + //Copy Block B into MIC data + for(i = 0x00; i < 16; i++) + { + MIC_Data[i] = Block_B[i]; + } + + //Add data to it + for(i = 0x00; i < lenght; i++) + { + MIC_Data[i + 16] = data[i]; + } + + //Calculate the correct buffer length + MIC_lenght = 16 + lenght; + + //Calculate the MIC + Calculate_MIC(&MIC_Data[0], MIC_lenght, Session_Data->NwkSKey, Message); +} + +/* +***************************************************************************************** +* Description : Function used to calculate the MIC of data +* +* Arguments : *Buffer pointer to the buffer cointaining the data the MIC should be calculated from +* *Key pointer to key used for the MIC calculation +* *Message pointer to sLoRa_Message struct containing the message specific variables +***************************************************************************************** +*/ +void Calculate_MIC(uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message) +{ + uint8_t i, j; + uint8_t Key_K1[16] = {0}; + uint8_t Key_K2[16] = {0}; + uint8_t Old_Data[16] = {0}; + uint8_t New_Data[16] = {0}; + + uint8_t Number_of_Blocks = 0x00; + uint8_t Incomplete_Block_Size = 0x00; + + //Calculate number of Blocks and blocksize of last block + Number_of_Blocks = lenght / 16; + Incomplete_Block_Size = lenght % 16; + + //if there is an incomplete block at the end add 1 to the number of blocks + if(Incomplete_Block_Size != 0) + { + Number_of_Blocks++; + } + + Generate_Keys(Key, Key_K1, Key_K2); + + //Preform full calculating until n-1 messsage blocks + for(j = 0x0; j < (Number_of_Blocks - 1); j++) + { + //Copy data into array + for(i = 0; i < 16; i++) + { + New_Data[i] = data[(j*16)+i]; + } + + //Preform XOR with old data + XOR(New_Data,Old_Data); + + //Preform AES encryption + AES_Encrypt(New_Data,Key); + + //Copy New_Data to Old_Data + for(i = 0; i < 16; i++) + { + Old_Data[i] = New_Data[i]; + } + } + + //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[((Number_of_Blocks -1)*16)+i]; + } + + //Preform XOR with Key 1 + XOR(New_Data, Key_K1); + + //Preform XOR with old data + XOR(New_Data, Old_Data); + + //Preform last AES routine + AES_Encrypt(New_Data, Key); + } + else + { + //Copy the remaining data and fill the rest + for(i = 0; i < 16; i++) + { + if(i < Incomplete_Block_Size) + { + New_Data[i] = data[((Number_of_Blocks -1)*16)+i]; + } + 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, Key); + } + + Message->MIC[0] = New_Data[0]; + Message->MIC[1] = New_Data[1]; + Message->MIC[2] = New_Data[2]; + Message->MIC[3] = New_Data[3]; +} + +/* +***************************************************************************************** +* Description : Function used to generate keys for the MIC calculation +* +* Arguments : *Key pointer to key used for the MIC calculation +* *K1 pointer to Key1 +* *K2 pointer ot Key2 +***************************************************************************************** +*/ +void Generate_Keys(uint8_t *Key, uint8_t *K1, uint8_t *K2) +{ + uint8_t i; + uint8_t MSB_Key; + + //Encrypt the zeros in K1 with the NwkSkey + AES_Encrypt(K1,Key); + + //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 Shift_Left(uint8_t *Data) +{ + uint8_t i; + uint8_t Overflow = 0; + //uint8_t 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 XOR(uint8_t *New_Data,uint8_t *Old_Data) +{ + uint8_t i; + + for(i = 0; i < 16; i++) + { + New_Data[i] = New_Data[i] ^ Old_Data[i]; + } +} diff --git a/examples/WeatherForecastExample/Encrypt.h b/examples/WeatherForecastExample/Encrypt.h new file mode 100644 index 0000000..23d29be --- /dev/null +++ b/examples/WeatherForecastExample/Encrypt.h @@ -0,0 +1,50 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Encrypt.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 09-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef ENCRYPT_H +#define ENCRYPT_H + + /* + ******************************************************************************************** + * INCLUDES + ******************************************************************************************** + */ + #include + #include "lorawan_def.h" + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + void Construct_Data_MIC (uint8_t *data, uint8_t lenght, sLoRa_Session *Session_Data, sLoRa_Message *Message); + void Calculate_MIC (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Encrypt_Payload (uint8_t *data, uint8_t lenght, uint8_t *Key, sLoRa_Message *Message); + void Generate_Keys (uint8_t *Key, uint8_t *K1, uint8_t *K2); + void Shift_Left (uint8_t *Data); + void XOR (uint8_t *New_Data,uint8_t *Old_Data); + +#endif diff --git a/examples/WeatherForecastExample/I2C.cpp b/examples/WeatherForecastExample/I2C.cpp new file mode 100644 index 0000000..2cd9b3b --- /dev/null +++ b/examples/WeatherForecastExample/I2C.cpp @@ -0,0 +1,66 @@ +/* + * I2C.c + * + * Created: 22-9-2017 08:48:32 + * Author: adri + */ +#include +#include "I2C.h" + + +void I2C_init (void) +{ + Wire.begin(); +} + + +void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data); + Wire.endTransmission(); +} + + +void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.write(data, lenght); + Wire.endTransmission(); +} + + + +uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register) +{ + uint8_t retVal; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom(slaveAddress, (uint8_t)1); + retVal = Wire.read(); + Wire.endTransmission(); + return retVal; +} + + +void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght) +{ + uint8_t retVal, i; + + Wire.beginTransmission(slaveAddress); + Wire.write(Register); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)slaveAddress, lenght); + + // Read until all bytes have been retrieved. + for( i = 0 ; i < lenght ; i++) + { + data[i] = Wire.read(); + } + + Wire.endTransmission(); +} diff --git a/examples/WeatherForecastExample/I2C.h b/examples/WeatherForecastExample/I2C.h new file mode 100644 index 0000000..f329aeb --- /dev/null +++ b/examples/WeatherForecastExample/I2C.h @@ -0,0 +1,27 @@ +/* + * I2C.h + * + * Created: 22-9-2017 08:48:47 + * Author: adri + */ + + +#ifndef I2C_H_ +#define I2C_H_ + + /****************************************************************************************** + INCLUDE FILES + ******************************************************************************************/ + #include + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void I2C_init (void); + void I2C_write_register (uint8_t slaveAddress, uint8_t Register, uint8_t data); + void I2C_write_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + uint8_t I2C_read_register (uint8_t slaveAddress, uint8_t Register); + void I2C_read_array (uint8_t slaveAddress, uint8_t Register, uint8_t *data, uint8_t lenght); + +#endif /* I2C_H_ */ diff --git a/examples/WeatherForecastExample/LoRaMAC.cpp b/examples/WeatherForecastExample/LoRaMAC.cpp new file mode 100644 index 0000000..620b716 --- /dev/null +++ b/examples/WeatherForecastExample/LoRaMAC.cpp @@ -0,0 +1,794 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include +#include +#include +#include "spi_functions.h" +#include "AES-128.h" +#include "RFM95.h" +#include "Encrypt.h" +#include "LoRaMAC.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** + FUNCTIONS +******************************************************************************************/ +LORAMAC::LORAMAC(sLoRaWAN *LoRaPTR) +{ + // Check if the pointer is not invalid. + if(LoRaPTR != NULL) + { + // Copy the LoRa structure to the pointer. + lora = LoRaPTR; + } + else + { + //Re-Initialize the RFM95 module to set it to a lowe power state. + RFM_Init(lora); + + lora = 0; + } +} + +LORAMAC::~LORAMAC() +{ + lora = NULL; +} + + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::init(void) +{ + // Check if the pointer is not invalid. + if(lora == NULL) + { + return; + } + + // Initialize the random number generation. + Init_DevNonce_Generation(); + + //Initialize the RFM95 module + RFM_Init(lora); + +} + +/****************************************************************************************** +* Description : +* Arguments : +******************************************************************************************/ +void LORAMAC::OTAA_connect(void) +{ + uint8_t attempt = 0; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check if the activation method is set to ABP. If so OTAA join procedure is not required. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + return; + } + + // Set the flag for OTAA done to false. + lora->OTAA.OTAAdone = false; + + // Continue to retry the OTAA connection + while(lora->OTAA.OTAAdone == false) + { + // Wait ten seconds before attempting to connect with LoRaWAN + /* + delay(10000); + Serial.print("Sending Join Request: "); + Serial.println(attempt++, DEC); + */ + // Transmit the join request to the back-end. + LoRa_Send_JoinReq(); + + // Enable the ms tick and clear the delay variable to time the JOIN_DELAY + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for join delay 1 + while(lora->timeslot < JOIN_DELAY_1) + {} + + // Receive the Join Accept from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&lora->OTAA.TxChDr, RECEIVE_RX_TIMEOUT_MS); + + // Check if the JOIN_ACCEPT message was received in time slot 1 + if(lora->OTAA.OTAAdone == true) + { + disable_ms_tick(); + return; // No need to wait for Time slot 2. + } + + // Wait for join delay 2 + while(lora->timeslot < JOIN_DELAY_2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->OTAA.RxChDr.channel = CH10_869_525; + lora->OTAA.RxChDr.datarate = SF12_BW125kHz; + LORA_Receive_Data(&lora->OTAA.RxChDr, RECEIVE_RX_TIMEOUT_MS); + + disable_ms_tick(); + + // Check if the JOIN_ACCEPT message was received in time slot 2 + if(lora->OTAA.OTAAdone == true) + { + return; // No need to wait for Time slot 2. + } + else + { + // Increment the OTAA channel for the next join attempt + switch(lora->OTAA.TxChDr.channel) + { + case CH00_868_100: + lora->OTAA.TxChDr.channel = CH01_868_300; + break; + case CH01_868_300: + lora->OTAA.TxChDr.channel = CH02_868_500; + break; + case CH02_868_500: + default: + lora->OTAA.TxChDr.channel = CH00_868_100; + break; + } + } + } +} + + +/****************************************************************************************** +* Description : Function for transmitting data Confirmed Up and then receive the back-ends +* reply. +* Arguments : lora pointer to the complete LoRaWAN structure. +******************************************************************************************/ +void LORAMAC::LORA_send_and_receive (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Set the TX message to confirmed up, so the back-end must reply. Copy the TX settings as the LORA_Send_Data will increment the Tx channel when channel hopping is enabled. + lora->TX.Confirmation = CONFIRMED; + lora->Session.RxChDr.channel = lora->Session.TxChDr.channel; + lora->Session.RxChDr.datarate = lora->Session.TxChDr.datarate; + + // Transmit data in the TX buffer. + LORA_Send_Data(); + + // Enable the ms tick and clear the delay variable to time the RECEIVE_DELAY1 + enable_ms_tick(); + lora->timeslot = 0; + + // Wait for time slot 1 + while(lora->timeslot < RECEIVE_DELAY1) + {} + + // Receive the back-end reply from the gateway with the settings from the Transmission. + if(LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_RX_TIMEOUT_MS) == RX_MESSAGE) + { + // On success, disable the ms tick timer and return with the data. + disable_ms_tick(); + return; // No need to wait for Receive slot 2. + } + + // Wait for time slot 2 before + while(lora->timeslot < RECEIVE_DELAY2) + {} + + // Configure the channel and datarate for Join delay 2 time slot. + lora->Session.RxChDr.channel = CH10_869_525; + lora->Session.RxChDr.datarate = SF09_BW125kHz; + + // Disable the timer. + disable_ms_tick(); + + // Receive the reply from the Server with the Receive settings from the Transmit + LORA_Receive_Data(&(lora->Session.RxChDr), RECEIVE_DELAY2); +} + + +/* +***************************************************************************************** +* Description : Function that is used to build a LoRaWAN data message and then transmit it. +* +* Arguments : *Data_Tx pointer to tranmit buffer +* *Session_Data pointer to sLoRa_Session structure +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LORA_Send_Data(void) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + // Check whether the data payload is within the array size. + if(lora->TX.Count > LORA_FIFO_SIZE) + { + lora->TX.Count = LORA_FIFO_SIZE; + } + + lora->TX.Frame_Port = 1; //Frame port always 1 for now + lora->TX.Frame_Control = 0; + + //Load device address from session data into the message + lora->TX.DevAddr[0] = lora->Session.DevAddr[0]; + lora->TX.DevAddr[1] = lora->Session.DevAddr[1]; + lora->TX.DevAddr[2] = lora->Session.DevAddr[2]; + lora->TX.DevAddr[3] = lora->Session.DevAddr[3]; + + //Load the frame counter from the session data into the message + lora->TX.Frame_Counter = lora->Session.frame_counter_up; + + //Set confirmed or unconfirmed bit + if(lora->TX.Confirmation == CONFIRMED) + { + lora->TX.MAC_Header = CONFIRMED_DATA_UP; // 0x80 + } + //Confirmed + else + { + lora->TX.MAC_Header = UNCONFIRMED_DATA_UP; // 0x40 + } + + /* Build the LoRaWAN Package */ + + // Load mac header + RFM_Data[0] = (uint8_t)lora->TX.MAC_Header; + + //Load the device address in reverse order + RFM_Data[1] = lora->TX.DevAddr[3]; + RFM_Data[2] = lora->TX.DevAddr[2]; + RFM_Data[3] = lora->TX.DevAddr[1]; + RFM_Data[4] = lora->TX.DevAddr[0]; + + // Load the frame control + RFM_Data[5] = lora->TX.Frame_Control; + + //Load frame counter + RFM_Data[6] = (uint8_t)(lora->Session.frame_counter_up >> 0); + RFM_Data[7] = (uint8_t)(lora->Session.frame_counter_up >> 8); + + //Set data counter to 8 + RFM_Counter = 8; + + //If there is data load the Frame_Port field, encrypt the data and load the data + if(lora->TX.Count != 0) + { + //Load Frame port field + RFM_Data[8] = lora->TX.Frame_Port; + + //Raise package counter for the Frame Port field + RFM_Counter++; + + // Copy the data into the RFM buffer. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.Data[0]), lora->TX.Count); + + // Encrypt the copied data in the RFM_Data array, so the original contents of the TX.Data array are not overwritten by the encryption. + Encrypt_Payload(&(RFM_Data[RFM_Counter]), lora->TX.Count, &(lora->Session.AppSKey[0]), &(lora->TX)); + + //Add data Length to package counter + RFM_Counter += lora->TX.Count; + } + + //Calculate MIC + Construct_Data_MIC(&(RFM_Data[0]), RFM_Counter, &(lora->Session), &(lora->TX)); + + // Load the calculated MIC in the RFM transmit array. + memcpy(&(RFM_Data[RFM_Counter]), &(lora->TX.MIC[0]), 4); + + //Add MIC length to RFM package length + RFM_Counter += 4; + + // Send Package to the RFM and transmit the data. + RFM_Send_Package(RFM_Data, RFM_Counter, &(lora->Session.TxChDr), lora->Session.Transmit_Power, &(lora->CH_list)); + + // Raise Frame counter + if(lora->Session.frame_counter_up != UINT16_MAX) + { + //Raise frame counter + lora->Session.frame_counter_up++; + } + else + { + // End of session is reached. + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // Clear the frame counter when using ABP + lora->Session.frame_counter_up = 0; + } + else + { + // When using OTAA begin a new session to reset the frame counter. + + } + } + + //Change channel for next message if hopping is activated + LORA_increment_tx_channel(); +} + + +/* +***************************************************************************************** +* Description : Function that is used to receive a LoRaWAN message and retrieve the data from the RFM +* Also checks on CRC, MIC and Device Address +* This function is used for Class A and C motes. +* +* Arguments : *Data_Rx pointer to receive buffer +* *Session_Data pointer to sLoRa_Session struct +* *OTAA_Data pointer to sLoRa_OTAA struct +* *Message_Rx pointer to sLoRa_Message struct used for the received message information +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +RFM_RETVAL LORAMAC::LORA_Receive_Data(eDR_CH *RxSettings, uint16_t Timeout_ms) +{ + uint8_t RFM_Data[LORA_FIFO_SIZE], RFM_Counter; + uint8_t i, N; + uint8_t Frame_Options_Length; + uint8_t Data_Location; + uint32_t frequency; + + // Check for invalid lora pointer + if(lora == NULL) + { + return LORA_POINTER_INVALID; + } + + + // If it is a type A device switch RFM to single receive + if(lora->Mote_Class == CLASS_A) + { + lora->RX.retVal = RFM_Single_Receive(RxSettings, Timeout_ms, &(lora->CH_list)); + } + else + { + //Switch RFM to standby + RFM_Switch_Mode(0x01); + lora->RX.retVal = NEW_MESSAGE; + } + + // If there is a message received get the data from the RFM + if(lora->RX.retVal == NEW_MESSAGE) + { + lora->RX.retVal = RFM_Get_Package(&RFM_Data[0], &RFM_Counter); + + //If mote class C switch RFM back to continuous receive + if(lora->Mote_Class == CLASS_C) + { + //Switch RFM to Continuous Receive + RFM_Continuous_Receive(RxSettings, &(lora->CH_list)); + } + } + + /* Clear the DIO# pins of the RFM, then switch the RFM to sleep */ + SPI_Write(RFM_NSS, 0x12, 0xE0); + SPI_Write(RFM_NSS, 0x01, 0x80); + + // If CRC is not OK, return with CRC_NOK to indicate that the received messages is incomplete. + if(lora->RX.retVal != CRC_OK) + { + return CRC_NOK; + } + + // Get the MAC_Header + lora->RX.MAC_Header = (eMESSAGE_TYPES) RFM_Data[0]; + + //Join Accept message + switch(lora->RX.MAC_Header) // 0x20 + { + ///------------------------------------------------------------------------------------------------------------------------------------------------------ + case JOIN_ACCEPT: + + // Decrypt the data + for(i = 0 ; i < ((RFM_Counter - 1) >> 4) ; i++) + { + AES_Encrypt(&(RFM_Data[(i << 4)+1]), lora->OTAA.AppKey); + } + + // Calculate MIC + RFM_Counter -= 4; + + // Get MIC + Calculate_MIC(RFM_Data, RFM_Counter, lora->OTAA.AppKey, &(lora->RX)); + + // Check if the calculated and received MIC match or not. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC), 4) == 0) + { + lora->RX.retVal = MIC_OK; + } + else + { + // When the MIC is NOK, return the MIC_NOK_OTAA + return MIC_NOK_OTAA; + } + + // Get AppNonce + lora->OTAA.AppNonce[0] = RFM_Data[1]; + lora->OTAA.AppNonce[1] = RFM_Data[2]; + lora->OTAA.AppNonce[2] = RFM_Data[3]; + + // Get Net ID + lora->OTAA.NetID[0] = RFM_Data[4]; + lora->OTAA.NetID[1] = RFM_Data[5]; + lora->OTAA.NetID[2] = RFM_Data[6]; + + // Get session Device address + lora->Session.DevAddr[3] = RFM_Data[7]; + lora->Session.DevAddr[2] = RFM_Data[8]; + lora->Session.DevAddr[1] = RFM_Data[9]; + lora->Session.DevAddr[0] = RFM_Data[10]; + + // Get the DLsettings + lora->CH_list.rx1_dr_offset = (RFM_Data[11] & 0x70) >> 4; + lora->CH_list.rx2_dr = RFM_Data[11] & 0x0F; + + // Get the RXDelay + lora->CH_list.rx_delay = RFM_Data[12]; + + // Calculate Network Session Key + lora->Session.NwkSKey[0] = 0x01; + + // Load AppNonce + lora->Session.NwkSKey[1] = lora->OTAA.AppNonce[0]; + lora->Session.NwkSKey[2] = lora->OTAA.AppNonce[1]; + lora->Session.NwkSKey[3] = lora->OTAA.AppNonce[2]; + + // Load NetID + lora->Session.NwkSKey[4] = lora->OTAA.NetID[0]; + lora->Session.NwkSKey[5] = lora->OTAA.NetID[1]; + lora->Session.NwkSKey[6] = lora->OTAA.NetID[2]; + + // Load Dev Nonce + lora->Session.NwkSKey[7] = lora->OTAA.DevNonce[0]; + lora->Session.NwkSKey[8] = lora->OTAA.DevNonce[1]; + + // Pad with zeros + lora->Session.NwkSKey[9] = 0x00; + lora->Session.NwkSKey[10] = 0x00; + lora->Session.NwkSKey[11] = 0x00; + lora->Session.NwkSKey[12] = 0x00; + lora->Session.NwkSKey[13] = 0x00; + lora->Session.NwkSKey[14] = 0x00; + lora->Session.NwkSKey[15] = 0x00; + + // Copy to AppSkey + memcpy(lora->Session.AppSKey, lora->Session.NwkSKey, 16); + + //Change first byte of AppSKey + lora->Session.AppSKey[0] = 0x02; + + // Calculate the keys + AES_Encrypt(lora->Session.NwkSKey, lora->OTAA.AppKey); + AES_Encrypt(lora->Session.AppSKey, lora->OTAA.AppKey); + + // Reset Frame counters + lora->Session.frame_counter_up = 0; + lora->Session.frame_counter_down = 0; + + // Check for the optional CF list in the JOin Accept message + if(RFM_Counter > 13) + { + //printStringAndHex("Join Accept Message: ", RFM_Data, RFM_Counter); + // Calculate the number of frequencies + N = (RFM_Counter - 14) / 3; + + // Maximum of 5 frequencies + if(N > CFLIST_FREQUENCIES_MAX) + { + N = CFLIST_FREQUENCIES_MAX; + } + + //Serial.println("CFlist"); + + // Retrieve all frequencies and print them to the serial port. + for( i = 0 ; i < N ; i++) + { + // Construct the frequency value and multiply the value with 100 to get the frequency in Hertz. + frequency = (((uint32_t)(RFM_Data[13 + (i*3)]) << 0) | ((uint32_t)(RFM_Data[14 + (i*3)]) << 8) | ((uint32_t)(RFM_Data[15 + (i*3)]) << 16)) * 100; + + + // Check if the frequency is between the 867.0MHz and 868.0 MHz and the boundaries of the channel list haven't been reached yet. + if((frequency > 867000000) && (frequency < 868000000) && (lora->CH_list.index <= CFLIST_FREQUENCIES_MAX)) + { + // Add the retrieved frequency to the list of channel settings and increment the index; + lora->CH_list.channel[(lora->CH_list.index)] = calculate_frequency_settings(frequency); + lora->CH_list.index += 1; + } + } + } + + // Print the received variables + /* + printStringAndHex("Device Address: ", lora->Session.DevAddr, 4); + printStringAndHex("Network Session Key: ", lora->Session.NwkSKey, 16); + printStringAndHex("Application Session Key: ", lora->Session.AppSKey, 16); +*/ + //Clear Data counter + lora->RX.Count = 0; + lora->OTAA.OTAAdone = true; + lora->RX.retVal = OTAA_COMPLETE; + break; + + ///------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + case UNCONFIRMED_DATA_UP: + case UNCONFIRMED_DATA_DOWN: + case CONFIRMED_DATA_UP: + case CONFIRMED_DATA_DOWN: + + //Get device address from received data + lora->RX.DevAddr[0] = RFM_Data[4]; + lora->RX.DevAddr[1] = RFM_Data[3]; + lora->RX.DevAddr[2] = RFM_Data[2]; + lora->RX.DevAddr[3] = RFM_Data[1]; + + //Get frame control field + lora->RX.Frame_Control = RFM_Data[5]; + + //Get the frame counter by combining two bytes + lora->RX.Frame_Counter = ((uint16_t)RFM_Data[7] << 8) | (uint16_t)RFM_Data[6]; + + //Lower Package length with 4 to remove MIC length + RFM_Counter -= 4; + + //Calculate MIC + Construct_Data_MIC(&RFM_Data[0], RFM_Counter, &(lora->Session), &(lora->RX)); + + // Compare the calculated MIC and the Received MIC. + if(memcmp(&(RFM_Data[RFM_Counter]), &(lora->RX.MIC[0]), 4) != 0) + { + lora->RX.retVal = MIC_NOK_MESSAGE; + return MIC_NOK_MESSAGE; + } + + // Compare the Received message's device address and the session device address to check whether this message was intended for this mote. + if(memcmp(lora->RX.DevAddr, lora->Session.DevAddr, 4) != 0) + { + //Return when ADDRESS_NOK + lora->RX.retVal = ADDRESS_NOK; + return ADDRESS_NOK; + } + + //Get length of frame options field + Frame_Options_Length = (lora->RX.Frame_Control & 0x0F); + + //Add length of frame options field to data location + Data_Location = 8 + Frame_Options_Length; + + //Check if there's is data in the package of not. OTherwise there might be + if(RFM_Counter == Data_Location) + { + // The Received number of bytes and the data start location match, so there's no data in the received message. + RFM_Counter = 0; + } + else + { + //Get port field when there is data + lora->RX.Frame_Port = RFM_Data[8]; + + //Calculate the amount of data in the package + lora->RX.Count = (RFM_Counter - Data_Location - 1); + + //Correct the data location by 1 for the Fport field + Data_Location++; + + // Copy and decrypt the data + memcpy(lora->RX.Data, &(RFM_Data[Data_Location]), lora->RX.Count); + + if(lora->RX.Frame_Port == 0) + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.NwkSKey, &(lora->RX)); + } + else + { + Encrypt_Payload(lora->RX.Data, lora->RX.Count, lora->Session.AppSKey, &(lora->RX)); + } + } + lora->RX.retVal = RX_MESSAGE; + break; + ///------------------------------------------------------------------------------------------------------------------------------------------------------- + default: + lora->RX.retVal = MAC_HEADER_NOK; + break; + } + // Return the message status + return lora->RX.retVal; +} + +/****************************************************************************************** +* Description : Function that is used to generate device nonce used in the join request function +* This is based on a pseudo random function in the Arduino library +* +* Arguments : *Devnonce pointer to the devnonce arry of withc is uint8_t[2] +******************************************************************************************/ +void LORAMAC::Generate_DevNonce(uint8_t *DevNonce) +{ + uint16_t RandNumber; + + // Check for invalid lora pointer + if(DevNonce == NULL) + { + return; + } + // Generate a random number between 0x0000 to 0xFFFF + RandNumber = random(0xFFFF); + //Serial.println(RandNumber, DEC); + + // Return the Dev Nonce value. + DevNonce[0] = (uint8_t)(RandNumber >> 0); + DevNonce[1] = (uint8_t)(RandNumber >> 8); +} + +/* +***************************************************************************************** +* Description : Function that is used to send a join request to a network. +* +* Arguments : *OTAA_Data pointer to sLoRa_OTAA struct +* *LoRa_Settings pointer to sSetting struct +***************************************************************************************** +*/ +void LORAMAC::LoRa_Send_JoinReq(void) +{ + uint8_t i; + + // Check for invalid lora pointer + if(lora == NULL) + { + return; + } + + //Initialize RFM data buffer + uint8_t data[23], lenght; + + // Set the OTAA done to false, so the higher layer code can detect if OTAA has been completed successfully + lora->OTAA.OTAAdone = false; + lora->TX.MAC_Header = JOIN_REQUEST; //Join request 0x00 + + //Construct OTAA Request message + //Load Header in package + data[0] = lora->TX.MAC_Header; + + //Load AppEUI in package + for(i = 0 ; i < 8 ; i++) + { + data[i+1] = lora->OTAA.AppEUI[7-i]; + } + + //Load DevEUI in package + for(i = 0; i < 8; i++) + { + data[i+9] = lora->OTAA.DevEUI[7-i]; + } + + //Generate DevNonce + Generate_DevNonce(&(lora->OTAA.DevNonce[0])); + + //Load DevNonce in package + data[17] = lora->OTAA.DevNonce[0]; + data[18] = lora->OTAA.DevNonce[1]; + + //Set length of package + lenght = 19; + + //Get MIC + Calculate_MIC(data, lenght, lora->OTAA.AppKey, &(lora->TX)); + + //Load MIC in package + memcpy(&(data[19]), &(lora->TX.MIC[0]), 4); + + //Set lenght of package to the right length + lenght = 23; + + //Send Package + RFM_Send_Package(data, lenght, &(lora->OTAA.TxChDr), lora->OTAA.Transmit_Power, &(lora->CH_list)); +} + + + +/******************************************************************************************************************************************************** +* Description : Initialize the random number generation seed, so all generated random numbers are new when generated. +********************************************************************************************************************************************************/ +#define EEADDR 0x3FF +void LORAMAC::Init_DevNonce_Generation(void) +{ + uint8_t RandSeed; + unsigned int addr = 0; + + RandSeed = eeprom_read_byte((uint8_t*)addr); + //Serial.print("Random Seed: "); + //Serial.println(RandSeed, HEX); + + eeprom_write_byte((uint8_t*)addr, (RandSeed+1)); + + // Initialize the Random seed with the retrieved seed value + randomSeed(RandSeed); +} + + +/******************************************************************************************************************************************************** +* Description : Increment the transmit channel according to the connection methode and the available CF list from the Over The Air Activation. +********************************************************************************************************************************************************/ +void LORAMAC::LORA_increment_tx_channel (void) +{ + // Check for invalid pointer + if(lora == NULL) + { + return; + } + + // Check whether channel hopping is enabled or not. + if(lora->CH_list.channel_hopping_on == false) + { + return; + } + + // Increment the Transmit channel + lora->Session.TxChDr.channel = (eLoRaWAN_CHANNELS)(lora->Session.TxChDr.channel + 1); + + // Depending on the activation method, increment the transmission channel to a maximum channel + if(lora->activation_method == ACTIVATION_BY_PERSONALISATION) + { + // With ABP only three channels are available + if(lora->Session.TxChDr.channel > CH02_868_500) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } + else + { + // Check if the incremented channel is above the number of received channels from OTAA + if(lora->Session.TxChDr.channel > ((eLoRaWAN_CHANNELS) CH02_868_500 + (lora->CH_list.index))) + { + lora->Session.TxChDr.channel = CH00_868_100; + } + } +} diff --git a/examples/WeatherForecastExample/LoRaMAC.h b/examples/WeatherForecastExample/LoRaMAC.h new file mode 100644 index 0000000..541f01a --- /dev/null +++ b/examples/WeatherForecastExample/LoRaMAC.h @@ -0,0 +1,62 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: LoRaMAC.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef LORAMAC_H +#define LORAMAC_H + + /********************************************************************************************* + * INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + /********************************************************************************************* + * TYPE DEFINITION + *********************************************************************************************/ + + /****************************************************************************************** + * FUNCTION PROTOTYPES + ******************************************************************************************/ + class LORAMAC + { + public: + LORAMAC(sLoRaWAN *LoRaPTR); + ~LORAMAC(); + + void init (void); + void OTAA_connect (void); + void LORA_Send_Data (void); + void LORA_send_and_receive (void); + void LORA_increment_tx_channel (void); + RFM_RETVAL LORA_Receive_Data (eDR_CH *RxSettings, uint16_t Timeout_ms); + void LoRa_Send_JoinReq (void); + void Init_DevNonce_Generation (void); + void Generate_DevNonce (uint8_t *DevNonce); + + private: + sLoRaWAN *lora; // Pointer to the LoRaWAN structure with all the settings and information + }; +#endif diff --git a/examples/WeatherForecastExample/LowPower.cpp b/examples/WeatherForecastExample/LowPower.cpp new file mode 100644 index 0000000..6aff586 --- /dev/null +++ b/examples/WeatherForecastExample/LowPower.cpp @@ -0,0 +1,1195 @@ +/******************************************************************************* +* LowPower Library +* Version: 1.80 +* Date: 04-10-2018 +* Author: Lim Phang Moh +* Company: Rocket Scream Electronics +* Website: www.rocketscream.com +* +* This is a lightweight low power library for Arduino. +* +* This library is licensed under Creative Commons Attribution-ShareAlike 3.0 +* Unported License. +* +* Revision Description +* ======== =========== +* 1.80 Added support for ATmega88 and ATmega168P. PowerExtStandby() +* modified because not supported on Atmega88 / Atmega168 +* Contributed by mrguen. +* 1.70 Added support for ATmega644P and ATmega1284P. +* Contributed by alexreinert. +* 1.60 Added support for ATmega256RFR2. Contributed by Rodmg. +* 1.50 Fixed compiler optimization (Arduino IDE 1.6.x branch) on BOD enable +* function that causes the function to be over optimized. +* 1.40 Added support for ATSAMD21G18A. +* Library format compliant with Arduino IDE 1.5.x. +* 1.30 Added support for ATMega168, ATMega2560, ATMega1280 & ATMega32U4. +* Tested to work with Arduino IDE 1.0.1 - 1.0.4. +* 1.20 Remove typo error in idle method for checking whether Timer 0 was +* turned off. +* Remove dependecy on WProgram.h which is not required. +* Tested to work with Arduino IDE 1.0. +* 1.10 Added #ifndef for sleep_bod_disable() for compatibility with future +* Arduino IDE release. +* 1.00 Initial public release. +*******************************************************************************/ +#if defined (__AVR__) + #include + #include + #include + #include +#elif defined (__arm__) + +#else + #error "Processor architecture is not supported." +#endif + +#include "LowPower.h" + +#if defined (__AVR__) +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#ifndef sleep_bod_disable +#define sleep_bod_disable() \ +do { \ + unsigned char tempreg; \ + __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ + "ori %[tempreg], %[bods_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" "\n\t" \ + "andi %[tempreg], %[not_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" \ + : [tempreg] "=&d" (tempreg) \ + : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ + [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ + [not_bodse] "i" (~_BV(BODSE))); \ +} while (0) +#endif +#endif + +#define lowPowerBodOn(mode) \ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); + +// Only Pico Power devices can change BOD settings through software +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) +#define lowPowerBodOff(mode)\ +do { \ + set_sleep_mode(mode); \ + cli(); \ + sleep_enable(); \ + sleep_bod_disable(); \ + sei(); \ + sleep_cpu(); \ + sleep_disable(); \ + sei(); \ +} while (0); +#endif + +// Some macros is still missing from AVR GCC distribution for ATmega32U4 +#if defined __AVR_ATmega32U4__ + // Timer 4 PRR bit is currently not defined in iom32u4.h + #ifndef PRTIM4 + #define PRTIM4 4 + #endif + + // Timer 4 power reduction macro is not defined currently in power.h + #ifndef power_timer4_disable + #define power_timer4_disable() (PRR1 |= (uint8_t)(1 << PRTIM4)) + #endif + + #ifndef power_timer4_enable + #define power_timer4_enable() (PRR1 &= (uint8_t)~(1 << PRTIM4)) + #endif +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega328P/168 into idle state. Please make sure you +* understand the implication and result of disabling module. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 8. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega32U4 into idle state. Please make sure you +* understand the implication and result of disabling module. +* Take note that Timer 2 is not available and USART0 is replaced +* with USART1 on ATmega32U4. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 4. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 5. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 6. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 7. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 8. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +* 10.usb USB module disable control: +* (a) USB_OFF - Turn off USB module +* (b) USB_ON - Leave USB module in its default state +*******************************************************************************/ +#if defined __AVR_ATmega32U4__ +void LowPowerClass::idle(period_t period, adc_t adc, + timer4_t timer4, timer3_t timer3, + timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb) +{ + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (twi == TWI_OFF) power_twi_disable(); + if (usb == USB_OFF) power_usb_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (twi == TWI_OFF) power_twi_enable(); + if (usb == USB_OFF) power_usb_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega644P & ATmega1284P into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra USART 1 compared to an ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 4. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 5. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 6. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 7. usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 8. usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 9. twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega644P__) || defined (__AVR_ATmega1284P__) +void LowPowerClass::idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega2560 & ATmega1280 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 3, 2, 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart3 USART3 module disable control: +* (a) USART3_OFF - Turn off USART3 module +* (b) USART3_ON - Leave USART3 module in its default state +* +* 11.usart2 USART2 module disable control: +* (a) USART2_OFF - Turn off USART2 module +* (b) USART2_ON - Leave USART2 module in its default state +* +* 12.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 13.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 14.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega2560__) || defined (__AVR_ATmega1280__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart3 == USART3_OFF) power_usart3_disable(); + if (usart2 == USART2_OFF) power_usart2_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart3 == USART3_OFF) power_usart3_enable(); + if (usart2 == USART2_OFF) power_usart2_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + +/******************************************************************************* +* Name: idle +* Description: Putting ATmega256RFR2 into idle state. Please make sure +* you understand the implication and result of disabling module. +* Take note that extra Timer 5, 4, 3 compared to an ATmega328P/168. +* Also take note that extra USART 1 compared to an +* ATmega328P/168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control: +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer5 Timer 5 module disable control: +* (a) TIMER5_OFF - Turn off Timer 5 module +* (b) TIMER5_ON - Leave Timer 5 module in its default state +* +* 4. timer4 Timer 4 module disable control: +* (a) TIMER4_OFF - Turn off Timer 4 module +* (b) TIMER4_ON - Leave Timer 4 module in its default state +* +* 5. timer3 Timer 3 module disable control: +* (a) TIMER3_OFF - Turn off Timer 3 module +* (b) TIMER3_ON - Leave Timer 3 module in its default state +* +* 6. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +* 7. timer1 Timer 1 module disable control: +* (a) TIMER1_OFF - Turn off Timer 1 module +* (b) TIMER1_ON - Leave Timer 1 module in its default state +* +* 8. timer0 Timer 0 module disable control: +* (a) TIMER0_OFF - Turn off Timer 0 module +* (b) TIMER0_ON - Leave Timer 0 module in its default state +* +* 9. spi SPI module disable control: +* (a) SPI_OFF - Turn off SPI module +* (b) SPI_ON - Leave SPI module in its default state +* +* 10.usart1 USART1 module disable control: +* (a) USART1_OFF - Turn off USART1 module +* (b) USART1_ON - Leave USART1 module in its default state +* +* 11.usart0 USART0 module disable control: +* (a) USART0_OFF - Turn off USART0 module +* (b) USART0_ON - Leave USART0 module in its default state +* +* 12.twi TWI module disable control: +* (a) TWI_OFF - Turn off TWI module +* (b) TWI_ON - Leave TWI module in its default state +* +*******************************************************************************/ +#if defined (__AVR_ATmega256RFR2__) +void LowPowerClass::idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + + power_timer2_disable(); + } + + if (adc == ADC_OFF) + { + ADCSRA &= ~(1 << ADEN); + power_adc_disable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_disable(); + if (timer4 == TIMER4_OFF) power_timer4_disable(); + if (timer3 == TIMER3_OFF) power_timer3_disable(); + if (timer1 == TIMER1_OFF) power_timer1_disable(); + if (timer0 == TIMER0_OFF) power_timer0_disable(); + if (spi == SPI_OFF) power_spi_disable(); + if (usart1 == USART1_OFF) power_usart1_disable(); + if (usart0 == USART0_OFF) power_usart0_disable(); + if (twi == TWI_OFF) power_twi_disable(); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_IDLE); + + if (adc == ADC_OFF) + { + power_adc_enable(); + ADCSRA |= (1 << ADEN); + } + + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + power_timer2_enable(); + } + + if (timer5 == TIMER5_OFF) power_timer5_enable(); + if (timer4 == TIMER4_OFF) power_timer4_enable(); + if (timer3 == TIMER3_OFF) power_timer3_enable(); + if (timer1 == TIMER1_OFF) power_timer1_enable(); + if (timer0 == TIMER0_OFF) power_timer0_enable(); + if (spi == SPI_OFF) power_spi_enable(); + if (usart1 == USART1_OFF) power_usart1_enable(); + if (usart0 == USART0_OFF) power_usart0_enable(); + if (twi == TWI_OFF) power_twi_enable(); +} +#endif + + +/******************************************************************************* +* Name: adcNoiseReduction +* Description: Putting microcontroller into ADC noise reduction state. This is +* a very useful state when using the ADC to achieve best and low +* noise signal. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::adcNoiseReduction(period_t period, adc_t adc, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + lowPowerBodOn(SLEEP_MODE_ADC); + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + + } + #endif +} + +/******************************************************************************* +* Name: powerDown +* Description: Putting microcontroller into power down state. This is +* the lowest current consumption state. Use this together with +* external pin interrupt to wake up through external event +* triggering (example: RTC clockout pin, SD card detect pin). +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerDown(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_DOWN); + #else + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_DOWN); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerSave +* Description: Putting microcontroller into power save state. This is +* the lowest current consumption state after power down. +* Use this state together with an external 32.768 kHz crystal (but +* 8/16 MHz crystal/resonator need to be removed) to provide an +* asynchronous clock source to Timer 2. Please take note that +* Timer 2 is also used by the Arduino core for PWM operation. +* Please refer to wiring.c for explanation. Removal of the external +* 8/16 MHz crystal/resonator requires the microcontroller to run +* on its internal RC oscillator which is not so accurate for time +* critical operation. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerSave(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_PWR_SAVE); + #else + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_PWR_SAVE); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: powerStandby +* Description: Putting microcontroller into power standby state. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. Turning off the ADC module is +* basically removing the purpose of this low power mode. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerStandby(period_t period, adc_t adc, bod_t bod) +{ + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_STANDBY); + } + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); +} + +/******************************************************************************* +* Name: powerExtStandby +* Description: Putting microcontroller into power extended standby state. This +* is different from the power standby state as it has the +* capability to run Timer 2 asynchronously. +* Not implemented on Atmega88 and Atmega168. +* +* Argument Description +* ========= =========== +* 1. period Duration of low power mode. Use SLEEP_FOREVER to use other wake +* up resource: +* (a) SLEEP_15MS - 15 ms sleep +* (b) SLEEP_30MS - 30 ms sleep +* (c) SLEEP_60MS - 60 ms sleep +* (d) SLEEP_120MS - 120 ms sleep +* (e) SLEEP_250MS - 250 ms sleep +* (f) SLEEP_500MS - 500 ms sleep +* (g) SLEEP_1S - 1 s sleep +* (h) SLEEP_2S - 2 s sleep +* (i) SLEEP_4S - 4 s sleep +* (j) SLEEP_8S - 8 s sleep +* (k) SLEEP_FOREVER - Sleep without waking up through WDT +* +* 2. adc ADC module disable control. +* (a) ADC_OFF - Turn off ADC module +* (b) ADC_ON - Leave ADC module in its default state +* +* 3. bod Brown Out Detector (BOD) module disable control: +* (a) BOD_OFF - Turn off BOD module +* (b) BOD_ON - Leave BOD module in its default state +* +* 4. timer2 Timer 2 module disable control: +* (a) TIMER2_OFF - Turn off Timer 2 module +* (b) TIMER2_ON - Leave Timer 2 module in its default state +* +*******************************************************************************/ +void LowPowerClass::powerExtStandby(period_t period, adc_t adc, bod_t bod, + timer2_t timer2) +{ + // Temporary clock source variable + unsigned char clockSource = 0; + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (TCCR2B & CS22) clockSource |= (1 << CS22); + if (TCCR2B & CS21) clockSource |= (1 << CS21); + if (TCCR2B & CS20) clockSource |= (1 << CS20); + + // Remove the clock source to shutdown Timer2 + TCCR2B &= ~(1 << CS22); + TCCR2B &= ~(1 << CS21); + TCCR2B &= ~(1 << CS20); + } + #endif + + if (adc == ADC_OFF) ADCSRA &= ~(1 << ADEN); + + if (period != SLEEP_FOREVER) + { + wdt_enable(period); + WDTCSR |= (1 << WDIE); + } + + + #if defined (__AVR_ATmega88__) || defined (__AVR_ATmega168__) // SLEEP_MODE_EXT_STANDBY not implemented on Atmega88 / Atmega168 + #else + if (bod == BOD_OFF) + { + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168P__) + lowPowerBodOff(SLEEP_MODE_EXT_STANDBY); + #else + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + #endif + } + else + { + lowPowerBodOn(SLEEP_MODE_EXT_STANDBY); + } + #endif + + if (adc == ADC_OFF) ADCSRA |= (1 << ADEN); + + #if !defined(__AVR_ATmega32U4__) + if (timer2 == TIMER2_OFF) + { + if (clockSource & CS22) TCCR2B |= (1 << CS22); + if (clockSource & CS21) TCCR2B |= (1 << CS21); + if (clockSource & CS20) TCCR2B |= (1 << CS20); + } + #endif +} + +/******************************************************************************* +* Name: ISR (WDT_vect) +* Description: Watchdog Timer interrupt service routine. This routine is +* required to allow automatic WDIF and WDIE bit clearance in +* hardware. +* +*******************************************************************************/ +ISR (WDT_vect) +{ + // WDIE & WDIF is cleared in hardware upon entering this ISR + wdt_disable(); +} + +#elif defined (__arm__) +#if defined (__SAMD21G18A__) +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into idle mode. This is the lowest current +* consumption mode. Requires separate handling of clock and +* peripheral management (disabling and shutting down) to achieve +* the desired current consumption. +* +* Argument Description +* ========= =========== +* 1. idleMode Idle mode level (0, 1, 2) where IDLE_2 level provide lowest +* current consumption in this mode. +* +*******************************************************************************/ +void LowPowerClass::idle(idle_t idleMode) +{ + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + PM->SLEEP.reg = idleMode; + __DSB(); + __WFI(); +} + +/******************************************************************************* +* Name: standby +* Description: Putting SAMD21G18A into standby mode. This is the lowest current +* consumption mode. Use this together with the built-in RTC (use +* RTCZero library) or external pin interrupt to wake up through +* external event triggering. +* +* Argument Description +* ========= =========== +* 1. NIL +* +*******************************************************************************/ +void LowPowerClass::standby() +{ + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + __DSB(); + __WFI(); +} + +#else + #error "Please ensure chosen MCU is ATSAMD21G18A." +#endif +#else + #error "Processor architecture is not supported." +#endif + +LowPowerClass LowPower; diff --git a/examples/WeatherForecastExample/LowPower.h b/examples/WeatherForecastExample/LowPower.h new file mode 100644 index 0000000..5a8864b --- /dev/null +++ b/examples/WeatherForecastExample/LowPower.h @@ -0,0 +1,173 @@ +#ifndef LowPower_h +#define LowPower_h + +#include "Arduino.h" + +enum period_t +{ + SLEEP_15MS, + SLEEP_30MS, + SLEEP_60MS, + SLEEP_120MS, + SLEEP_250MS, + SLEEP_500MS, + SLEEP_1S, + SLEEP_2S, + SLEEP_4S, + SLEEP_8S, + SLEEP_FOREVER +}; + +enum bod_t +{ + BOD_OFF, + BOD_ON +}; + +enum adc_t +{ + ADC_OFF, + ADC_ON +}; + +enum timer5_t +{ + TIMER5_OFF, + TIMER5_ON +}; + +enum timer4_t +{ + TIMER4_OFF, + TIMER4_ON +}; + +enum timer3_t +{ + TIMER3_OFF, + TIMER3_ON +}; + +enum timer2_t +{ + TIMER2_OFF, + TIMER2_ON +}; + +enum timer1_t +{ + TIMER1_OFF, + TIMER1_ON +}; + +enum timer0_t +{ + TIMER0_OFF, + TIMER0_ON +}; + +enum spi_t +{ + SPI_OFF, + SPI_ON +}; + +enum usart0_t +{ + USART0_OFF, + USART0_ON +}; + +enum usart1_t +{ + USART1_OFF, + USART1_ON +}; + +enum usart2_t +{ + USART2_OFF, + USART2_ON +}; + +enum usart3_t +{ + USART3_OFF, + USART3_ON +}; + +enum twi_t +{ + TWI_OFF, + TWI_ON +}; + +enum usb_t +{ + USB_OFF, + USB_ON +}; + +enum idle_t +{ + IDLE_0, + IDLE_1, + IDLE_2 +}; + +class LowPowerClass +{ + public: + #if defined (__AVR__) + + #if defined (__AVR_ATmega328P__) || defined (__AVR_ATmega168__) || defined (__AVR_ATmega168P__) || defined (__AVR_ATmega88__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega644P__ || defined (__AVR_ATmega1284P__) + void idle(period_t period, adc_t adc, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega2560__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart3_t usart3, usart2_t usart2, usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega256RFR2__ + void idle(period_t period, adc_t adc, timer5_t timer5, + timer4_t timer4, timer3_t timer3, timer2_t timer2, + timer1_t timer1, timer0_t timer0, spi_t spi, + usart1_t usart1, + usart0_t usart0, twi_t twi); + #elif defined __AVR_ATmega32U4__ + void idle(period_t period, adc_t adc, timer4_t timer4, + timer3_t timer3, timer1_t timer1, timer0_t timer0, + spi_t spi, usart1_t usart1, twi_t twi, usb_t usb); + #else + #error "Please ensure chosen MCU is either 88, 168, 168P, 328P, 32U4, 2560 or 256RFR2." + #endif + void adcNoiseReduction(period_t period, adc_t adc, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerDown(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerSave(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + void powerStandby(period_t period, adc_t adc, bod_t bod) __attribute__((optimize("-O1"))); + void powerExtStandby(period_t period, adc_t adc, bod_t bod, timer2_t timer2) __attribute__((optimize("-O1"))); + + #elif defined (__arm__) + + #if defined (__SAMD21G18A__) + void idle(idle_t idleMode); + void standby(); + #else + #error "Please ensure chosen MCU is ATSAMD21G18A." + #endif + + #else + + #error "Processor architecture is not supported." + + #endif +}; + +extern LowPowerClass LowPower; +#endif diff --git a/examples/WeatherForecastExample/PL_microEPD44.cpp b/examples/WeatherForecastExample/PL_microEPD44.cpp new file mode 100644 index 0000000..6343c9a --- /dev/null +++ b/examples/WeatherForecastExample/PL_microEPD44.cpp @@ -0,0 +1,365 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#include "LowPower.h" +#include "PL_microEPD44.h" +#include +#include "spi_functions.h" + +PL_microEPD::PL_microEPD(int8_t _cs, int8_t _rst, int8_t _busy) : Adafruit_GFX(EPD_WIDTH, +EPD_HEIGHT) { + cs = _cs; + rst = _rst; + busy = _busy; +} + +// PUBLIC + +// ****************************************************************************************** +// BEGIN - Resetting UC8156 driver IC and configuring all sorts of behind-the-scenes-settings +// By default (WHITEERASE=TRUE) a clear screen update is triggered once to erase the screen. +// ****************************************************************************************** +void PL_microEPD::begin(bool whiteErase) { + pinMode(cs, OUTPUT); + pinMode(busy, INPUT); + pinMode(rst, OUTPUT); + + delay(10); + + digitalWrite(rst, HIGH); + delay(5); + pinMode(9, OUTPUT); // VDD for EPD + digitalWrite(9, HIGH); // Enable VDD for UC8156 + delay(5); + digitalWrite(rst, LOW); // Trigger global reset + delay(5); + digitalWrite(rst, HIGH); + delay(5); + + delay(10); + + waitForBusyInactive(); + + _width=24; _height=24; nextline= _width/4; _buffersize=_width*_height/4; + _EPDwidth=148; _EPDheight=72; + writeRegister(EPD_PANELSETTING, 0x12, -1, -1, -1); + writeRegister(EPD_WRITEPXRECTSET, 0, 31, 0, 23); + writeRegister(EPD_VCOMCONFIG, 0x00, 0x00, 0x24, 0x07); + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); + writeRegister(EPD_LOADMONOWF, 0x60, -1, -1, -1); + writeRegister(EPD_INTTEMPERATURE, 0x0A, -1, -1, -1); + writeRegister(EPD_BOOSTSETTING, 0x22, 0x37, -1, -1); // DC 30%/30% (Normal/Softstart), 125KHz, 16384 CC + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + setTextColor(EPD_BLACK); //Set text color to black as default + + if (whiteErase) + WhiteErase(); //Start with a white refresh if TRUE + clear(); +} + +// ************************************************************************************ +// CLEAR - Erases the image buffer and triggers an image update and sets the cursor +// back to the origin coordinates (0,0). +// ************************************************************************************ +void PL_microEPD::clearBuffer() { + memset(buffer, 0xFF, sizeof(buffer)); + setCursor(0,0); +} + +void PL_microEPD::clear(int c) { + writeRegister(EPD_WRITEPXRECTSET, 0, 71, 0, 147); + digitalWrite(cs, LOW); + SPI.transfer(0x10); + if (c==EPD_BLACK) + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0x00); + else + for (int x=0; x < _EPDwidth*_EPDheight/4; x++) + SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + clearBuffer(); +} + +void PL_microEPD::setCursorSegment(int x, int y) { + if (x < (_EPDwidth - _width) && y < (_EPDheight - _height)) { + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + } else { + writeRegister(EPD_WRITEPXRECTSET, 0, _height-1, 0, _width-1); + writeRegister(EPD_PIXELACESSPOS, 0, 0, -1, -1); + } +} + +// ************************************************************************************ +// DRAWPIXEL - Draws pixel in the memory buffer at position X, Y with the value of the +// parameter color (2 bit value). +// ************************************************************************************ +void PL_microEPD::drawPixel(int16_t x, int16_t y, uint16_t color) { + + if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height) || (color>4 )) return; + x+=2; + uint8_t pixels = buffer[x/4 + (y) * nextline]; + switch (x%4) { //2-bit grayscale dot + case 0: buffer[x/4 + (y) * nextline] = (pixels & 0x3F) | ((uint8_t)color << 6); break; + case 1: buffer[x/4 + (y) * nextline] = (pixels & 0xCF) | ((uint8_t)color << 4); break; + case 2: buffer[x/4 + (y) * nextline] = (pixels & 0xF3) | ((uint8_t)color << 2); break; + case 3: buffer[x/4 + (y) * nextline] = (pixels & 0xFC) | (uint8_t)color; break; + } +} + +// ************************************************************************************ +// UPDATE - Triggers an image update based on the content written in the image buffer. +// There are three different updateModes supported: EPD_UPD_FULL(0) is set by default, +// achieves four greyelevels, takes about 800ms and refreshes all pixels. This is the +// update mode having the best image quality. EPD_UPD_PART(1) is a variant of the +// previous one but only changing pixels are refreshed. This results in less flickering +// for the price of a slightly higher pixel to pixel crosstalk. EPD_UPD_MONO(2) is +// again a variant of the previous update mode but only about 250ms long. this allows +// slightly faster and more responsive updates for the price of only two greylevels +// being supported (EPD_BLACK and EPD_WHITE). Depending on your application it is +// recommended to insert a full update EPD_UPD_FULL(0) after a couple of mono updates +// to increase the image quality. +// THIS KIND OF DISPLAY IS NOT SUITED FOR LONG RUNNING ANIMATIONS OR APPLICATIONS WITH +// CONTINUOUSLY HIGH UPDATE RATES. AS A RULE OF THUMB PLEASE TRIGGER UPDATES IN AVERAGE +// NOT FASTER THAN MINUTELY (OR RUN BACK2BACK UPDATES NOT LONGER AS ONE HOUR PER DAY.) +// ************************************************************************************ +void PL_microEPD::update(int updateMode) { + if (updateMode==3) + powerOn(0); + //powerOn(1); + else + powerOn(); + switch (updateMode) { + case 0: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x00, -1, -1, -1); + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); + break; + case 2: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+17V/-25V, Vsource=+/-15V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated + break; + case 3: + writeRegister(EPD_DRIVERVOLTAGE, 0x25, 0xFF, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + writeRegister(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x03, -1, -1, -1); //Only changing pixels updated (delta mode) + //writeRegister(EPD_DRIVERVOLTAGE, 0x00, 0x88, -1, -1); //Vgate=+15V/-20V, Vsource=+/-12V + //writeRegister(EPD_PROGRAMMTP, 0x02, -1, -1, -1); //Use short mono waveform + //writeRegisterNoWait(EPD_DISPLAYENGINE, 0x07, -1, -1, -1); //Only changing pixels updated (delta mode) + LowPower.powerDown(SLEEP_250MS, ADC_OFF, BOD_OFF); + LowPower.powerDown(SLEEP_60MS, ADC_OFF, BOD_OFF); + break; + } + powerOff(); +} + + +// ************************************************************************************ +// SETVBORDERCOLOR - Sets the color of the VBorder around the active area. By default +// this is set to White (matching to the Paperino FrontCover) and should not be changed +// ************************************************************************************ +void PL_microEPD::setVBorderColor(int color) { + if (color==3) writeRegister(EPD_BORDERSETTING, 0xF7, -1, -1, -1); // + if (color==0) writeRegister(EPD_BORDERSETTING, 0x07, -1, -1, -1); // + update(EPD_UPD_PART); + writeRegister(EPD_BORDERSETTING, 0x04, -1, -1, -1); // +} + +// PRIVATE + +// ************************************************************************************ +// POWERON - Activates the defined high voltages needed to update the screen. The +// command should always be called before triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOn(bool lowpower) { + waitForBusyInactive(); + writeRegister(EPD_SETRESOLUTION, 0, 239, 0, 147); + writeRegister(EPD_TCOMTIMING, 0x67, 0x55, -1, -1); // GAP=115µs; S2G=G2S=85µs @1MHz + writeRegister(EPD_POWERSEQUENCE, 0x00, 0x00, 0x00, -1); + if (lowpower) + writeRegister(EPD_POWERCONTROL, 0x81, -1, -1, -1); // TCON 500KHz; Internal clock auto-enable + else + writeRegister(EPD_POWERCONTROL, 0xC1, -1, -1, -1); // TCON 1MHz; Internal clock always enabled + while (readRegister(0x15) == 0) {} // Wait until Internal Pump is ready +} + + +// ************************************************************************************ +// POWEROFF - Deactivates the high voltages needed to update the screen. The +// command should always be called after triggering an image update. +// ************************************************************************************ +void PL_microEPD::powerOff() { + writeRegister(EPD_POWERCONTROL, 0xD0, -1, -1, -1); + waitForBusyInactive(); + writeRegister(EPD_POWERCONTROL, 0xC0, -1, -1, -1); + waitForBusyInactive(); +} + + +// ************************************************************************************ +// Fills a segment of 24 x 24 px size with text, supporting textSize(<=4) +// ************************************************************************************ +void PL_microEPD::printText(String text, int x, int y, int size){ + int i=0, xo=0, yo=0; + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + int ys = y, xs = x; + setTextSize(size); + clearBuffer(); + + while (i _width) + xs -= size*6; + x -=size*6; + i += 1; + } +} + +void PL_microEPD::fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + clearBuffer(); + x = _EPDwidth-x; // todo: Enable 12px @right column + y = _EPDheight-y-2; + fillRect(0, _height-h, w, _height, color); + writeRegister(EPD_WRITEPXRECTSET, y - _height, y -1, x - _width, x -1); + writeRegister(EPD_PIXELACESSPOS, y - _height, x - _width, -1, -1); + writeBuffer(); +} + +void PL_microEPD::drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h){ + clearBuffer(); + x = _EPDwidth-_width-x; + y = _EPDheight-_height-y; + drawBitmap(0, 0, bitmap, w, h, 0); // WHITE=3 or BLACK=1 + writeRegister(EPD_WRITEPXRECTSET, y, y + _height-1, x, x + _width-1); + writeRegister(EPD_PIXELACESSPOS, y, x, -1, -1); + writeBuffer(); +} + +// ************************************************************************************ +// WRITEBUFFER - Sends the content of the memory buffer to the UC8156 driver IC. +// ************************************************************************************ +void PL_microEPD::writeBuffer(boolean previousRAM){ + if (previousRAM) + writeRegister(EPD_DATENTRYMODE, 0x37, -1, -1, -1); + else + writeRegister(EPD_DATENTRYMODE, 0x27, -1, -1, -1); + //SPI_Read_Array(cs, 0x10, buffer, _buffersize); //(write)this clears the buffer..(?) + + digitalWrite(cs, LOW); + SPI.transfer(0x10); + for (int i=0; i < _buffersize; i++) + SPI.transfer(buffer[i]); + digitalWrite(cs, HIGH); + + waitForBusyInactive(); +} + + +// ************************************************************************************ +// WRITE REGISTER - Sets register ADDRESS to value VAL1 (optional: VAL2, VAL3, VAL4) +// ************************************************************************************ +void PL_microEPD::writeRegister(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + waitForBusyInactive(); +} + +void PL_microEPD::writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, + int16_t val3, int16_t val4) { + digitalWrite(cs, LOW); + SPI.transfer(address); + if (val1!=-1) SPI.transfer((byte)val1); + if (val2!=-1) SPI.transfer((byte)val2); + if (val3!=-1) SPI.transfer((byte)val3); + if (val4!=-1) SPI.transfer((byte)val4); + digitalWrite(cs, HIGH); + //waitForBusyInactive(EPD_TMG_SR2); +} +// ************************************************************************************ +// READREGISTER - Returning the value of the register at the specified address +// ************************************************************************************ +byte PL_microEPD::readRegister(char address){ + byte data; + digitalWrite(cs, LOW); + SPI.transfer(address | EPD_REGREAD); + data = SPI.transfer(0xFF); + digitalWrite(cs, HIGH); + waitForBusyInactive(); + return data; // can be improved +} + + +// ************************************************************************************ +// WHITE ERASE - Triggers two white updates to erase the screen and set back previous +// ghosting. Recommended after each power cycling. +// ************************************************************************************ +void PL_microEPD::WhiteErase() { + clear(EPD_WHITE); + update(); +} + + +// ************************************************************************************ +// WAITFORBUSYINACTIVE - Sensing to ‘Busy’ pin to detect the UC8156 driver status. +// Function returns only after driver IC is free again for listening to new commands. +// ************************************************************************************ +void PL_microEPD::waitForBusyInactive(){ + while (digitalRead(busy) == LOW) {} +} + +// ************************************************************************************ +// DEEPSLEEP - Putting the UC8156 in deep sleep mode with less than 1µA current @3.3V. +// Reset pin toggling needed to wakeup the driver IC again. +// ************************************************************************************ +void PL_microEPD::deepSleep(void) { + writeRegister(0x21, 0xff, 0xff, 0xff, 0xff); +} + +void PL_microEPD::end(void) { + digitalWrite(rst, HIGH); // Turn VDD for EPD off and put remaining + delay(5); + digitalWrite(9, LOW); // GPIOs LOW to minimize power consumption + delay(5); + digitalWrite(rst, LOW); // to around 1µA + pinMode(busy, OUTPUT); + digitalWrite(busy, LOW); + digitalWrite(cs, LOW); +} diff --git a/examples/WeatherForecastExample/PL_microEPD44.h b/examples/WeatherForecastExample/PL_microEPD44.h new file mode 100644 index 0000000..76cec5f --- /dev/null +++ b/examples/WeatherForecastExample/PL_microEPD44.h @@ -0,0 +1,89 @@ +/* ***************************************************************************************** +PL_MICROEPD - A Universal Hardware Library for 1.1”, 1.4", 2.1" and 3.1" E-Paper displays +(EPDs) from Plastic Logic based on UC8156 driver IC. The communication is SPI-based, for more +information about hook-up and tutorials please check: https://github.com/RobPo/Paperino. + +Created by Robert Poser, Feb 9th 2018, Dresden/Germany. Released under BSD license +(3-clause BSD license), check license.md for more information. + +We invested time and resources providing this open source code, please support Paperino and +open source hardware by purchasing this product @Crowd_supply @Watterott @Plasticlogic +***************************************************************************************** */ +#ifndef PL_microEPD_h +#define PL_microEPD_h + +#include "Adafruit_GFX.h" + +#define EPD_WIDTH (146) +#define EPD_HEIGHT (240) + +#define EPD_BLACK 0x00 +#define EPD_DGRAY 0x01 +#define EPD_LGRAY 0x02 +#define EPD_WHITE 0x03 + +#define EPD_UPD_FULL 0x00 // Triggers a Full update, 4 GL, 800ms +#define EPD_UPD_PART 0x01 // Triggers a Partial update, 4 GL, 800ms +#define EPD_UPD_MONO 0x02 // Triggers a Partial Mono update, 2 GL, 250ms +#define EPD_UPD_LOWP 0x03 // Triggers a Partial Mono update, 2 GL, 250ms, low power + +#define EPD_REVISION 0x00 // Revision, Read only +#define EPD_PANELSETTING 0x01 +#define EPD_DRIVERVOLTAGE 0x02 +#define EPD_POWERCONTROL 0x03 +#define EPD_BOOSTSETTING 0x04 +#define EPD_INTERVALSETTING 0x05 +#define EPD_TCOMTIMING 0x06 +#define EPD_INTTEMPERATURE 0x07 +#define EPD_SETRESOLUTION 0x0C +#define EPD_WRITEPXRECTSET 0x0D +#define EPD_PIXELACESSPOS 0x0E +#define EPD_DATENTRYMODE 0x0F +#define EPD_BYPASSRAM 0x12 +#define EPD_DISPLAYENGINE 0x14 +#define EPD_VCOMCONFIG 0x18 +#define EPD_BORDERSETTING 0x1D +#define EPD_POWERSEQUENCE 0x1F +#define EPD_SOFTWARERESET 0x20 +#define EPD_PROGRAMMTP 0x40 +#define EPD_MTPADDRESSSETTING 0x41 +#define EPD_LOADMONOWF 0x44 +#define EPD_REGREAD 0x80 // Instruction R/W bit set HIGH for data READ + +class PL_microEPD : public Adafruit_GFX { + +public: + PL_microEPD(int8_t _cs, int8_t _rst=-1, int8_t _busy=-1); + + void begin(bool erase=true); + void clearBuffer(); + void clear(int c=EPD_WHITE); + void drawPixel(int16_t x, int16_t y, uint16_t color); + void update(int updateMode=EPD_UPD_FULL); + void setVBorderColor(int color); + void powerOn(bool lowpower=0); + void powerOff(void); + void writeRegister(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + void writeBuffer(boolean previousRAM = false); + void printText(String text, int x, int y, int s); + void setCursorSegment(int x, int y); + void drawBitmapLM(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h); + void fillRectLM(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); + byte buffer[144]; //buffer for 24x24 character (size 2) + void waitForBusyInactive(); + void deepSleep(void); + void end(void); + +private: + int _EPDsize, _EPDwidth, _EPDheight, _buffersize; + int cs, rst, busy; + int nextline; + void writeRegisterNoWait(uint8_t address, int16_t val1, int16_t val2, int16_t val3, int16_t val4); + byte readRegister(char address); + void scrambleBuffer(void); + //void writeBuffer(boolean previousRAM = false); + void WhiteErase(void); + +}; + +#endif diff --git a/examples/WeatherForecastExample/RFM95.cpp b/examples/WeatherForecastExample/RFM95.cpp new file mode 100644 index 0000000..fb117d6 --- /dev/null +++ b/examples/WeatherForecastExample/RFM95.cpp @@ -0,0 +1,583 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.cpp +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "spi_functions.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "lorapaper.h" + +/****************************************************************************************** +* Description: Function used to initialize the RFM module on startup +******************************************************************************************/ +void RFM_Init(sLoRaWAN *lora) +{ + //Switch RFM to sleep + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x00); + + //Wait until RFM is in sleep mode + delay(10); + + //Set RFM in LoRa mode + //DONT USE Switch mode function + SPI_Write(RFM_NSS, 0x01, 0x80); + + //Switch RFM to standby + RFM_Switch_Mode(0x01); + + //Set channel to channel 0 868.100 MHz + RFM_Change_Channel(CH00_868_100, &(lora->CH_list)); + + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected. + */ + RFM_Set_Output_Power(0x0F); + + //Switch LNA boost on + SPI_Write(RFM_NSS, 0x0C, 0x23); + + //Set RFM To datarate 0 SF12 BW 125 kHz + RFM_Change_Datarate((eLoRaWAN_DATARATES)0x00, RECEIVE_RX_TIMEOUT_MS); + + //Rx Timeout set to 37 symbols + SPI_Write(RFM_NSS, 0x1F, 0x25); + + //Preamble length set to 8 symbols + //0x0008 + 4 = 12 + SPI_Write(RFM_NSS, 0x20, 0x00); + SPI_Write(RFM_NSS, 0x21, 0x08); + + //Set LoRa sync word + SPI_Write(RFM_NSS, 0x39, 0x34); + + //Set FIFO pointers + //Tx base address + SPI_Write(RFM_NSS, 0x0E, 0x80); + + //Rx base address + SPI_Write(RFM_NSS, 0x0F, 0x00); + + // Switch RFM to sleep + SPI_Write(RFM_NSS, 0x01, 0x80); +} + +/****************************************************************************************** +* Description : Function for sending a package with the RFM +* +* Arguments : *RFM_Tx_Package pointer to buffer with data and counter of data +* *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Send_Package(uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list) +{ + uint8_t i; + uint8_t RFM_Tx_Location = 0; + + //Set RFM in Standby mode + RFM_Switch_Mode(0x01); + + //Switch Datarate + RFM_Change_Datarate(TxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Switch Channel + RFM_Change_Channel(TxSettings->channel, list); + + //Switch RFM_DIO0 to TxDone + SPI_Write(RFM_NSS, 0x40, 0x40); + + //Set IQ to normal values + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + + // Set the Output Power + RFM_Set_Output_Power(TxPower); + + //Set payload length to the right length + SPI_Write(RFM_NSS, 0x22, lenght); + + //Get location of Tx part of FiFo + RFM_Tx_Location = SPI_Read(RFM_NSS, 0x0E); + + //Set SPI pointer to start of Tx part in FiFo + SPI_Write(RFM_NSS, 0x0D, RFM_Tx_Location); + + // Write the Payload to FiFo + SPI_Write_Array(RFM_NSS, 0x00, data, lenght); + + ////Set NSS pin Low to start communication + //digitalWrite(RFM_NSS, LOW); +// + ////Send Address with MSB 1 to make it a Write command + //SPI.transfer(0x00 | 0x80); + // + ////Send the data array + //SPI.transfer(data, lenght); +// + ////Set NSS pin High to end communication + //digitalWrite(RFM_NSS, HIGH); + + + //Switch RFM to Tx + SPI_Write(RFM_NSS, 0x01, 0x83); + + //Wait for TxDone + while(digitalRead(RFM_DIO0) == LOW) + {} + + //Clear interrupt + SPI_Write(RFM_NSS, 0x12, 0x08); +} + +/****************************************************************************************** +* Description : Function to switch RFM to single receive mode, used for Class A motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Single_Receive(eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list) +{ + RFM_RETVAL Message_Status = INIT; + + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, timeout_ms); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch RFM to Single reception + RFM_Switch_Mode(0x06); + + //Wait until RxDone or Timeout + //Wait until timeout or RxDone interrupt + while((digitalRead(RFM_DIO0) == LOW) && (digitalRead(RFM_DIO1) == LOW)) + { + } + + //Check for Timeout + if(digitalRead(RFM_DIO1) == HIGH) + { + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + Message_Status = RX_TIMEOUT; + //Serial.println("RX Timeout"); + } + + //Check for RxDone + if(digitalRead(RFM_DIO0) == HIGH) + { + Message_Status = NEW_MESSAGE; + } + + return Message_Status; +} + + +/****************************************************************************************** +* Description : Function to switch RFM to continuous receive mode, used for Class C motes +* +* Arguments : *LoRa_Settings pointer to sSettings struct +******************************************************************************************/ +void RFM_Continuous_Receive(eDR_CH * RxSettings, eCHANNEL_LIST *list) +{ + //Change DIO 0 back to RxDone + SPI_Write(RFM_NSS, 0x40, 0x00); + + //Invert IQ Back + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + + //Change Datarate + RFM_Change_Datarate(RxSettings->datarate, RECEIVE_RX_TIMEOUT_MS); + + //Change Channel + RFM_Change_Channel(RxSettings->channel, list); + + //Switch to continuous receive + RFM_Switch_Mode(0x05); +} + +/****************************************************************************************** +* Description : Function to retrieve a message received by the RFM +* +* Arguments : *RFM_Rx_Package pointer to sBuffer struct containing the data received +* and number of bytes received +* +* Return : RFM_RETVAL Status of the received message +******************************************************************************************/ +RFM_RETVAL RFM_Get_Package(uint8_t *data, uint8_t *counter) +{ + uint8_t i; + uint8_t RFM_Interrupts = 0; + uint8_t RFM_Package_Location = 0; + RFM_RETVAL Message_Status; + + // Get the RFM's interrupt status register value + RFM_Interrupts = SPI_Read(RFM_NSS, 0x12); + + // UART_Send_Data(RFM_Interrupts,0x01); + + // Check if the CRC of the received package is correct. + if((RFM_Interrupts & 0x20) != 0x20) + { + Message_Status = CRC_OK; + } + else + { + Message_Status = CRC_NOK; + } + + RFM_Package_Location = SPI_Read(RFM_NSS, 0x10); /*Read start position of received package*/ + (*counter) = SPI_Read(RFM_NSS, 0x13); /*Read length of received package*/ + + SPI_Write(RFM_NSS, 0x0D, RFM_Package_Location); /*Set SPI pointer to start of package*/ + + // Read the FIFO from the RFM in a single read operation. + SPI_Read_Array(RFM_NSS, 0x00, data, (*counter)); + + //Clear interrupt register + SPI_Write(RFM_NSS, 0x12, 0xE0); + + return Message_Status; +} + + + + +/****************************************************************************************** +* Description : Function to change the datarate of the RFM module. Setting the following +* register: Spreading factor, Bandwidth and low datarate optimization. +* +* Arguments : Datarate the datarate to set +******************************************************************************************/ +void RFM_Change_Datarate(eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms) +{ + uint16_t TimeOutSymbols; + + /* Determine the new Timeout Symbol Time depending on the Datarate. */ + switch(Datarate) + { + //------------------------------------------------------------------------------------------------------------- + case SF12_BW125kHz: //SF12 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 5); // Divide the Timeout Time by 32 (Symbol time = 32.77ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF11_BW125kHz: //SF11 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 4); // Divide the Timeout Time by 16 (Symbol time = 16.38ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF10_BW125kHz: //SF10 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 3); // Divide the Timeout Time by 8 (Symbol time = 8.19ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF09_BW125kHz: //SF9 BW 125 kHz + TimeOutSymbols = (timeout_ms >> 2); // Divide the timeout time by 4 (Symbol time = 4.10ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF08_BW125kHz: //SF8 BW 125 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 2.05ms) + break; + + //------------------------------------------------------------------------------------------------------------- + default: + case SF07_BW125kHz: //SF7 BW 125 kHz + TimeOutSymbols = (timeout_ms); // Copy the timeout value directly (Symbol time = 1.02ms) + break; + + //------------------------------------------------------------------------------------------------------------- + case SF07_BW250kHz: //SF7 BW 250 kHz + TimeOutSymbols = (timeout_ms << 1); // Multiply the Timeout Time by 2 (Symbol time = 0.5ms) + break; + } + + /* Limit the Timeout to 1023 symbols, since the Time-out symbols register has a maximum size of 10 bits. 2^10 = 1024. */ + if(TimeOutSymbols > 1023) + { + TimeOutSymbols = 1023; + } + + /* Set the Datarate, CRC settings, the number of symbol for the required timeout and the Low Datarate optimizer On */ + switch(Datarate) + { + case SF12_BW125kHz: //SF12 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xC4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF12, CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF11_BW125kHz: //SF11 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xB4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF11 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x0C); //Low datarate optimization on AGC auto on + break; + case SF10_BW125kHz: //SF10 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0xA4 | ((uint8_t) (TimeOutSymbols >> 8))); //SF10 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF09_BW125kHz: //SF9 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x94 | ((uint8_t) (TimeOutSymbols >> 8))); //SF9 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF08_BW125kHz: //SF8 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x84 | ((uint8_t) (TimeOutSymbols >> 8))); //SF8 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW125kHz: //SF7 BW 125 kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x72); //125 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + case SF07_BW250kHz: //SF7 BW 250kHz + SPI_Write(RFM_NSS, 0x1F, (uint8_t) (TimeOutSymbols >> 0)); + SPI_Write(RFM_NSS, 0x1E, 0x74 | ((uint8_t) (TimeOutSymbols >> 8))); //SF7 CRC On, and bits 9:8 of the Timeout Symbols + SPI_Write(RFM_NSS, 0x1D, 0x82); //250 kHz 4/5 coding rate explicit header mode + SPI_Write(RFM_NSS, 0x26, 0x04); //Low datarate optimization off AGC auto on + break; + } +} + +/****************************************************************************************** +* Description : Function to change the channel of the RFM module. Setting the following +* register: Channel +* Arguments : Channel the channel to set +******************************************************************************************/ +void RFM_Change_Channel(eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list) +{ + uint32_t hex; + // Check if the given pointer is not invalid. + if(list == NULL) + { + return; + } + + switch(Channel) + { + default: + case CH00_868_100: //Channel 0 868.100 MHz / 61.035 Hz = 14222987 = 0xD9068B + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x06); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH01_868_300: //Channel 1 868.300 MHz / 61.035 Hz = 14226264 = 0xD91358 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x13); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH02_868_500: //Channel 2 868.500 MHz / 61.035 Hz = 14229540 = 0xD92024 + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x20); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH03_867_100: //Channel 3 867.100 MHz / 61.035 Hz = 14206603 = 0xD8C68B + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xC6); + SPI_Write(RFM_NSS, 0x08,0x8B); + break; + case CH04_867_300: //Channel 4 867.300 MHz / 61.035 Hz = 14209880 = 0xD8D358 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xD3); + SPI_Write(RFM_NSS, 0x08,0x58); + break; + case CH05_867_500: //Channel 5 867.500 MHz / 61.035 Hz = 14213156 = 0xD8E024 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xE0); + SPI_Write(RFM_NSS, 0x08,0x24); + break; + case CH06_867_700: //Channel 6 867.700 MHz / 61.035 Hz = 14216433 = 0xD8ECF1 + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xEC); + SPI_Write(RFM_NSS, 0x08,0xF1); + break; + case CH07_867_900: //Channel 7 867.900 MHz / 61.035 Hz = 14219710 = 0xD8F9BE + SPI_Write(RFM_NSS, 0x06,0xD8); + SPI_Write(RFM_NSS, 0x07,0xF9); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CH10_869_525: //Receive channel 869.525 MHz / 61.035 Hz = 14246334 = 0xD961BE + SPI_Write(RFM_NSS, 0x06,0xD9); + SPI_Write(RFM_NSS, 0x07,0x61); + SPI_Write(RFM_NSS, 0x08,0xBE); + break; + case CFLIST_INDEX_1: + if(list->index >= 1) + { + hex = list->channel[0]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_2: + if(list->index >= 2) + { + hex = list->channel[1]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_3: + if(list->index >= 3) + { + hex = list->channel[2]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + case CFLIST_INDEX_4: + if(list->index >= 4) + { + hex = list->channel[3]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + + case CFLIST_INDEX_5: + if(list->index >= 5) + { + hex = list->channel[4]; + SPI_Write(RFM_NSS, 0x06, hex >> 16); + SPI_Write(RFM_NSS, 0x07, hex >> 8); + SPI_Write(RFM_NSS, 0x08, hex >> 0); + } + break; + } +} + + +/****************************************************************************************** +* Description : Function to calculate the RFM register value for the given frequency. +* Arguments : Transmit frequency in Hertz. +******************************************************************************************/ +uint32_t calculate_frequency_settings (uint32_t frequency_Hz) +{ + double calc; + calc = (double)frequency_Hz / 61.035; + Serial.print("Freq: "); + Serial.print((uint32_t) frequency_Hz); + Serial.print('\t'); + Serial.println((uint32_t) calc); + return calc; +} + +/****************************************************************************************** +* Description : Function to change the operation mode of the RFM. Switching mode and wait +* for mode ready flag +* DO NOT USE FOR SLEEP +* +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_Switch_Mode(uint8_t Mode) +{ + Mode |= 0x80; //Set high bit for LoRa mode + + //Switch mode on RFM module + SPI_Write(RFM_NSS, 0x01,Mode); + + //Wait on mode ready + while(digitalRead(RFM_DIO5) == LOW) + { + } +} + + +/****************************************************************************************** +* Description : +* Function to change the IQ bits in the RFM95, which will allow the RFM module to send messages with the same LoRa modulation +* as a gateway transmitter or receive message from motes just like a gateway would be able to do. +* Arguments : Mode the mode to set +******************************************************************************************/ +void RFM_SetIQ(IQ_FUNCTION function) +{ + switch(function) + { + //########################################################################### + case GATEWAY_TRANSMITTER: + // IQ set to gateway transmission + SPI_Write(RFM_NSS, 0x33, 0x26); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + case MOTE_TRANSCEIVER: + // IQ set to default Mote transceiver + SPI_Write(RFM_NSS, 0x33, 0x27); + SPI_Write(RFM_NSS, 0x3B, 0x1D); + break; + + //########################################################################### + default: + case GATEWAY_RECEIVER: + //Invert IQ for receiving messages from gateway + SPI_Write(RFM_NSS, 0x33, 0x67); + SPI_Write(RFM_NSS, 0x3B, 0x19); + break; + } +} + + +/************************************************************************************************************************************************************** +* Description : +* Function to set the transmit power of the RFM95. +* Arguments : power 0x00 - 0x0F to set the output power. Pout = 17-(15-power) +**************************************************************************************************************************************************************/ +void RFM_Set_Output_Power(uint8_t power) +{ + /* + * Always enable the PA_BOOST pin by setting bit 7 = 1, since the RFO pin is not connected on the RFM board. Limit the output power to the maximum and set + * the output power with the last 4 bits of the register. + */ + SPI_Write(RFM_NSS, 0x09, 0xF0 | (power & 0x0F)); +} diff --git a/examples/WeatherForecastExample/RFM95.h b/examples/WeatherForecastExample/RFM95.h new file mode 100644 index 0000000..e9c9164 --- /dev/null +++ b/examples/WeatherForecastExample/RFM95.h @@ -0,0 +1,78 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: RFM95.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef RFM95_H +#define RFM95_H + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + #include "lorawan_def.h" + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define RECEIVE_RX_TIMEOUT_MS 500 + + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + MOTE_TRANSCEIVER, // Default settings for the RFM to function as a mote which can send messages to and receive messages from a gateway. + GATEWAY_TRANSMITTER, // IQ settings to let the RFM95 behave as an Gateway sending messages to a mote. + GATEWAY_RECEIVER // IQ settings to let the RFM95 behave as an Gateway receiving messages from a mote. + }IQ_FUNCTION; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + + void RFM_Init (sLoRaWAN *lora); + void RFM_Send_Package (uint8_t *data, uint8_t lenght, eDR_CH *TxSettings, uint16_t TxPower, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Single_Receive (eDR_CH * RxSettings, uint16_t timeout_ms, eCHANNEL_LIST *list); + void RFM_Continuous_Receive (eDR_CH * RxSettings, eCHANNEL_LIST *list); + RFM_RETVAL RFM_Get_Package (uint8_t *data, uint8_t *counter); + void RFM_Change_Datarate (eLoRaWAN_DATARATES Datarate, uint16_t timeout_ms); + void RFM_Change_Channel (eLoRaWAN_CHANNELS Channel, eCHANNEL_LIST *list); + uint32_t calculate_frequency_settings (uint32_t frequency_Hz); + void RFM_Switch_Mode (uint8_t Mode); + void RFM_SetIQ (IQ_FUNCTION function); + void RFM_Set_Output_Power (uint8_t power); + + /* SPI functions */ + uint8_t RFM_Read (uint8_t RFM_Address); + void RFM_Read_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + void RFM_Write (uint8_t RFM_Address, uint8_t RFM_Data); + void RFM_Write_Array (uint8_t RFM_Address, uint8_t *RFM_Data, uint8_t lenght); + +#endif diff --git a/examples/WeatherForecastExample/WeatherForecastExample.ino b/examples/WeatherForecastExample/WeatherForecastExample.ino new file mode 100644 index 0000000..093bb05 --- /dev/null +++ b/examples/WeatherForecastExample/WeatherForecastExample.ino @@ -0,0 +1,159 @@ +/**************************************************************************************** +* This example demonstrates a simple weatherforcest demo. The supercap voltage v_scap is +* measured minutely while the ATmega328p processor is in deep sleep all remaining time. +* Triggering is done via external RTC to minimize current consumption during deep sleep +* phase. IF the voltage is charged above a certain limit (ie 4.2V) and a max. number of +* allowed Lora syncs is not exceeded, a download of is triggered from the TTN console +* and the recieved content is printed on the Epaper Screen. +/**************************************************************************************** +* File: WeatherForecastExample.ino +* Author: Robert Poser +* Created on: 19-12-2019 +* Supported Hardware: LoraPaper (with RFM95, PV cell, supercap & 1.1" EPD) +* +* Libraries used in this sketch are based on the LoRaWAN stack from IDEETRON/NEXUS, for +* more infos please check this great source: https://github.com/Ideetron/Nexus-Low-Power +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +****************************************************************************************/ + +#include "lorapaper.h" +#include "mcp7940.h" +#include "DS2401.h" +#include "spi_flash.h" +#include "RFM95.h" +#include "LoRaMAC.h" +#include "lorawan_def.h" +#include "Cayenne_LPP.h" +#include "PL_microEPD44.h" + +/******** GLOBAL VARIABLES ****************************************************************/ +sAPP app; // Application variable +sLoRaWAN lora; // See the lorapaper.h file for the settings +sTimeDate TimeDate; // RTC time and date variables +LORAMAC lorawan (&lora); // Initialize the LoRaWAN stack. +CayenneLPP LPP(&(lora.TX)); // Initialize the Low Power Protocol functions +PL_microEPD epd(EPD_CS, EPD_RST, EPD_BUSY); //Initialize the EPD. + +volatile bool RTC_ALARM = false; // Interrupt variable +uint16_t v_scap, ndr, sync_max = 10; // V_Supercap, counter of failed downloads & max_syncs + +/********* INTERRUPTS *********************************************************************/ +ISR(INT1_vect) { // Interrupt vector for the alarm of the MCP7940 Real Time + RTC_ALARM = true; // Clock. Do not use I2C functions or long delays +} // here. + +ISR(TIMER1_COMPA_vect) { // Interrupt vector for Timer1 which is used to time the Join + lora.timeslot++; // and Receive windows for timeslot 1 and timelsot 2 Increment +} // the timeslot counter variable for timing the + +/********* MAIN ***************************************************************************/ +void setup(void) { + analogReference(EXTERNAL); // use AREF for reference voltage + SPI.begin(); // Initialize the SPI port + SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); + + pinMode(A7, INPUT); // To measure V_Scap + pinMode(SW_TFT, OUTPUT); // Switch for V_Scap + pinMode(DS2401, OUTPUT); // Authenticating IC DS2401p+ + pinMode(RTC_MFP, INPUT); // RTC MCP7940 + pinMode(RFM_DIO0, INPUT); // RFM95W DIO0 + pinMode(RFM_DIO1, INPUT); // RFM95W DIO1 + pinMode(RFM_DIO5, INPUT); // RFM95W DIO5 + pinMode(RFM_DIO2, INPUT); // RFM95W DIO2 (not really needed) + pinMode(RFM_NSS, OUTPUT); // RFM95W NSS = CS + digitalWrite(DS2401, HIGH); // to save power... + digitalWrite(SW_TFT, HIGH); // to save power... + + PCMSK1 = 0; // Disable Pin change interrupts + PCICR = 0; // Disable Pin Change Interrupts + //delay(10); // Power on delay for the RFM module + I2C_init(); // Initialize I2C pins + flash_power_down(); // To save power... + sei(); // Enable Global Interrupts + + epd.begin(); // Turn ON & initialize 1.1" EPD + epd.printText("Syncing with TTN...", 16, 20, 1); + epd.update(); // Update EPD + epd.end(); // to save power... + digitalWrite(RFM_NSS, LOW); // to save power... + SPI.endTransaction(); // to save power... + SPI.end(); // to save power... + + v_scap = analogRead(A7); // 1st Dummy-read which always delivers strange values...(to skip) + + mcp7940_init(&TimeDate, app.LoRaWAN_message_interval); // Generate minutely interrupt +} + +void loop(){ + if(RTC_ALARM == true){ // Catch the minute alarm from the RTC. + RTC_ALARM = false; // Clear the boolean. + + mcp7940_reset_minute_alarm(app.LoRaWAN_message_interval); + mcp7940_read_time_and_date(&TimeDate); + + digitalWrite(SW_TFT, LOW); // Turn ON voltage divider + delay(1); // To stabilize analogRead + v_scap = analogRead(A7); // Measure V_scap + digitalWrite(SW_TFT, HIGH); // Turn OFF voltage divider + + //if (v_scap >= 320) { // Proceed only if (Vscap > 4,2V)--> DEFAULT! + if (v_scap >= 0) { // Always proceed --> Use this for debugging! + LPP.clearBuffer(); // Form a payload according to the LPP standard to + LPP.addDigitalOutput(0x00, app.Counter); + LPP.addAnalogInput(0x00, v_scap*3.3/1023*4); + app.Counter += 1; // Increase app.Counter + + SPI.begin(); + SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0)); + epd.begin(false); // Turn ON EPD without refresh to save power + lorawan.init(); // Init the RFM95 module + + if (app.Counter< sync_max) { // Limit to 10 syncs to fullfill TTNs daily download limit + lora.RX.Data[6] = 25; // Dummy value to check lateron if downlink data was received + lorawan.LORA_send_and_receive(); + } + + if (lora.RX.Data[6] == 25) // Downlink data received? + ndr++; // if not increase nodatareceived (ndr) counter + + epd.printText("1Day Forecast ", 1, 2, 1); // Following lines look a bit complex; due to + epd.printText(String(lora.RX.Data[3]), 107, 2, 1); // memory constraints we can only write + epd.printText(":", 119, 2, 1); // smallfractions of the screen in one go + if (int(lora.RX.Data[4]) < 10) { // generally here we have some air for + epd.printText("0", 125, 2, 1); // improvement in the future + epd.printText(String(lora.RX.Data[4]), 131, 2, 1); + } else + epd.printText(String(lora.RX.Data[4]), 125, 2, 1); + + epd.printText(String(lora.RX.Data[0]), 11, 16, 3); + epd.printText("o", 53, 12, 2); + epd.printText("C", 65, 16, 3); + epd.drawBitmapLM(90, 15, wIcon_sunny, 24, 24); // Just to demonstrate how to write little + epd.printText(String(lora.RX.Data[1]), 11, 44, 3); // icons; here it will always be the same + epd.printText("%", 65, 44, 3); // independent of the weather forecast :-) + epd.printText(String(app.Counter), 124, 30, 1); + epd.fillRectLM(90, 40, 1, (int)lora.RX.Data[6], EPD_BLACK); // Rain probability of the next + epd.fillRectLM(92, 40, 1, (int)lora.RX.Data[7], EPD_BLACK); // 12hrs... to be improved + epd.fillRectLM(94, 40, 1, (int)lora.RX.Data[8], EPD_BLACK); + epd.fillRectLM(96, 40, 1, (int)lora.RX.Data[9], EPD_BLACK); + epd.fillRectLM(98, 40, 1, (int)lora.RX.Data[10], EPD_BLACK); + epd.fillRectLM(100, 40, 1, (int)lora.RX.Data[11], EPD_BLACK); + epd.fillRectLM(102, 40, 1, (int)lora.RX.Data[12], EPD_BLACK); + epd.fillRectLM(104, 40, 1, (int)lora.RX.Data[13], EPD_BLACK); + epd.fillRectLM(106, 40, 1, (int)lora.RX.Data[14], EPD_BLACK); + epd.fillRectLM(108, 40, 1, (int)lora.RX.Data[15], EPD_BLACK); + epd.fillRectLM(110, 40, 1, (int)lora.RX.Data[16], EPD_BLACK); + epd.fillRectLM(112, 40, 1, (int)lora.RX.Data[17], EPD_BLACK); + epd.printText(String(ndr), 124, 40, 1); // Plot how many downlinks were empty + + epd.update(); // Send the framebuffer and do the update + epd.end(); // To save power... + digitalWrite(RFM_NSS, LOW); // To save power... + SPI.endTransaction(); // To save power... + SPI.end(); // To save power... + } + } else + LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF); // To save power... +} diff --git a/examples/WeatherForecastExample/encoder.h b/examples/WeatherForecastExample/encoder.h new file mode 100644 index 0000000..2104bec --- /dev/null +++ b/examples/WeatherForecastExample/encoder.h @@ -0,0 +1,32 @@ +/* +// This is the code you will need to add to the 'encoder' section in the TTN backend to let the +// weather forecast demo run properly. + +function Encoder(object, port) { + // Encode downlink messages sent as + // object to an array or buffer of bytes. + var bytes = [9]; + + bytes[0] = parseInt(object.temperature); + bytes[2] = parseInt(object.humidity); + bytes[4] = parseInt(object.date); + bytes[6] = parseInt(object.hour); + bytes[8] = parseInt(object.mins); + bytes[10] = parseInt(object.icon); + + bytes[12] = parseInt(object.rainPrecipProb0); + bytes[14] = parseInt(object.rainPrecipProb1); + bytes[16] = parseInt(object.rainPrecipProb2); + bytes[18] = parseInt(object.rainPrecipProb3); + bytes[20] = parseInt(object.rainPrecipProb4); + bytes[22] = parseInt(object.rainPrecipProb5); + bytes[24] = parseInt(object.rainPrecipProb6); + bytes[26] = parseInt(object.rainPrecipProb7); + bytes[28] = parseInt(object.rainPrecipProb8); + bytes[30] = parseInt(object.rainPrecipProb9); + bytes[32] = parseInt(object.rainPrecipProb10); + bytes[34] = parseInt(object.rainPrecipProb11); + + return bytes; +} +*/ diff --git a/examples/WeatherForecastExample/lorapaper.h b/examples/WeatherForecastExample/lorapaper.h new file mode 100644 index 0000000..12c7ff5 --- /dev/null +++ b/examples/WeatherForecastExample/lorapaper.h @@ -0,0 +1,38 @@ +#ifndef LORAPAPER_DEMOBOARD_H_ +#define LORAPAPER_DEMOBOARD_H_ + +/****************************************************************************************** + DEFINES +/******************************************************************************************/ + +#include +#include "I2C.h" +#include "spi_functions.h" +#include "progmem.h" +#include "LowPower.h" + + +#define EPD_RST A2 +#define EPD_BUSY A1 +#define EPD_CS A3 +#define HV_BST 9 +#define SW_TFT A0 +#define DS2401 2 // One wire pin for the DS2401 +#define RTC_MFP 3 +#define RFM_DIO0 4 +#define RFM_DIO1 5 +#define RFM_DIO5 6 +#define RFM_DIO2 7 +#define RFM_NSS 10 +#define SPI_FLASH_CS 8 + + +/****************************************************************************************** + STRUCTURES +******************************************************************************************/ +typedef struct{ + uint8_t Counter = 0; + uint16_t LoRaWAN_message_interval = 1; // Variable to set the number of Timer2 timer ticks between LoRaWAN messages. +}sAPP; + +#endif diff --git a/examples/WeatherForecastExample/lorawan_def.h b/examples/WeatherForecastExample/lorawan_def.h new file mode 100644 index 0000000..250e336 --- /dev/null +++ b/examples/WeatherForecastExample/lorawan_def.h @@ -0,0 +1,321 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Struct.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 06-02-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + + +#ifndef STRUCT_H +#define STRUCT_H + + #include + #include + + #define LORA_FIFO_SIZE 64 + #define CFLIST_FREQUENCIES_MAX 5 + #define DEFAULT_FREQUENCIES 3 + #define LORAWAN_MAX_PAYLOAD 52 + + /********************************************************************************************* + ENUMERATION DEFENITIONS + *********************************************************************************************/ + // LoRaWAN Mote class specification + typedef enum + { + CLASS_A, + //CLASS_B, + CLASS_C + }eLoRaWAN_MOTE_CLASS; + + // Enumeration to set the used back-end and use the TX and RX settings for that particular back-end. + typedef enum + { + MANUAL, // Manual mode for setting the TX and RX channels and data rate for OTAA and data messages + SEMTECH, // Semtech's the patent holder for the LoRaWAN chips. + The_Things_Network, // The Things Industries back-end, which is only functional for OTAA motes. ABP is barely supported and the service may have some downtime from time-to-time for maintenance. + KPN // The Dutch full-coverage LoRaWAN network provider in the Netherlands, which has LoRaWAN localization capabilities, but it's services come at a cost. + }eBACKENDS; + + /* MAC HEADER types */ + typedef enum + { + JOIN_REQUEST = 0x00, // 0000 0000 + JOIN_ACCEPT = 0x20, // 0010 0000 + UNCONFIRMED_DATA_UP = 0x40, // 0100 0000 + UNCONFIRMED_DATA_DOWN = 0x60, // 0110 0000 + CONFIRMED_DATA_UP = 0x80, // 1000 0000 + CONFIRMED_DATA_DOWN = 0xA0, // 1010 0000 + CONFIRMED_BITMASK = 0x80, + UNCONFIRMED_BITMASK = 0x40, + INIT_VAL = 0x00 + }eMESSAGE_TYPES; + + // Receive delay enumeration for OTAA Join Delay and message delays. + typedef enum + { + JOIN_DELAY_1 = 4900, + JOIN_DELAY_2 = 5900, + RECEIVE_DELAY1 = 900, + RECEIVE_DELAY2 = 1900 + }eRECEIVE_DELAY; + + + // Direction enumeration for the MIC calculation. See page 20, chapter 4.3.3 of the Lora Specification 1.02 + typedef enum + { + UPSTREAM_DIR = 0, // Uplink (Away from mote) + DOWNSTREAM_DIR = 1 // Downlink (towards the mote) + }eDIRECTION; + + // LoRaWAN Datarates + typedef enum + { + SF12_BW125kHz = 0x01, + SF11_BW125kHz = 0x02, + SF10_BW125kHz = 0x03, + SF09_BW125kHz = 0x04, + SF08_BW125kHz = 0x05, + SF07_BW125kHz = 0x06, + SF07_BW250kHz = 0x07, + SF09_BW500kHz = 0x08, + SF_BITMASK = 0x03 + }eLoRaWAN_DATARATES; + + // Channel enumeration + typedef enum + { + CH00_868_100, + CH01_868_300, + CH02_868_500, + CFLIST_INDEX_1, + CFLIST_INDEX_2, + CFLIST_INDEX_3, + CFLIST_INDEX_4, + CFLIST_INDEX_5, + CH03_867_100, + CH04_867_300, + CH05_867_500, + CH06_867_700, + CH07_867_900, + CH10_869_525 + }eLoRaWAN_CHANNELS; + + // Confirmed and unconfirmed messages type enumeration. + typedef enum + { + UNCONFIRMED, + CONFIRMED + }eCONFIRMATION; + + // Activation methods + typedef enum + { + ACTIVATION_BY_PERSONALISATION, + OVER_THE_AIR_ACTIVATION + }eMOTE_NETWORK_JOIN; + + //return values for the RFM95 + typedef enum + { + INIT, //0 Initialization error return code + CRC_OK, //1 CRC of the received message is incorrect, indicating an incomplete or corrupted message + CRC_NOK, //2 CRC of the received message is correct + MIC_OK, //3 Message Integrity Checksum is correct. + MIC_NOK_OTAA, //4 MIC of the OTAA message is incorrect. + MIC_NOK_MESSAGE, //5 MIC of the received message is incorrect. + ADDRESS_OK, //6 Device address of the received message matches that of this LoRaWAN mote. + ADDRESS_NOK, //7 The device address of the received message doesn't match to the dev address of this mote. + MAC_HEADER_NOK, //8 Message header has an incorrect value, not matching any LoRaWAN headers + RX_TIMEOUT, //9 The RFM hasn't receive any message and got a time-out instead. + OTAA_POINTER, //10 Over The Air Activation structure has an incorrect pointer + RX_MESSAGE, //11 Message received. + OTAA_COMPLETE, //12 OTAA has been successfully completed. + NEW_MESSAGE, //13 When a new message has been received. + DEFAULT_MAC, //14 When a message is received with an invalid MAC header, this return value will be send back. + LORA_POINTER_INVALID//15 Invalid Lora pointer + }RFM_RETVAL; + + + + /********************************************************************************************* + STRUCT DEFENITIONS + *********************************************************************************************/ + + // Structure for both datarate and channel as they are alway used together. + typedef struct + { + eLoRaWAN_DATARATES datarate; + eLoRaWAN_CHANNELS channel; + }eDR_CH; + + // Structure for all the RFM channel settings retrieved from the OTAA connection and the default settings + typedef struct + { + uint32_t channel[CFLIST_FREQUENCIES_MAX]; + uint8_t index; + uint8_t rx1_dr_offset; + uint8_t rx2_dr; + uint8_t rx_delay; + bool channel_hopping_on; + }eCHANNEL_LIST; + + + /* + Structure used to store session data of a LoRaWAN session if ABP is used, the following parameters must be supplied by the user + if OTAA is used, the OTAA JOIN procedure will produce the parameters for the session. + */ + typedef struct + { + uint8_t NwkSKey[16]; // The Network Session Key is used to encrypt the payload + uint8_t AppSKey[16]; // The Application Session Key is used to encrypt the payload + uint8_t DevAddr[4]; // The Device Address is used to identify the messages send and send to this Mote + uint16_t frame_counter_down; // Frame counter for messages received from the back-end + uint16_t frame_counter_up; // Frame counter for messages send to the back-end. + eRECEIVE_DELAY Receive_delay; // Wait time between transmitting an confirmed LoRaWAN message and listening to the Back-end's downlink. + uint8_t Transmit_Power; // Transmission power settings 0x00 to 0x0F. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_Session; + + + /* + Structure for connecting with Over The Air Activation to the back-end to retrieve session settings. + */ + typedef struct + { + uint8_t DevEUI[8]; // Device EUI. Unique number to identify the Mote with on the back-end. + uint8_t AppEUI[16]; // Application Key used to encrypt and calculate the session parameters with. + uint8_t AppKey[16]; // Application EUI, used to specify which Application the MOte belongs to. + uint8_t DevNonce[2]; // DevNonce is a random value that can only be used once for a join_request. The back-end will store all previously used values. + uint8_t AppNonce[3]; + uint8_t NetID[3]; + bool OTAAdone; // Boolean for indicating if the OTAA procedure was succesfull; + eRECEIVE_DELAY JoinDelay; // Join Delay for waiting an given amount of time between the JOIN and Accept messages + uint8_t Transmit_Power; // Transmission power settings for the OTAA connection. + eDR_CH TxChDr; // Transmit channel and datarate settings + eDR_CH RxChDr; // Receive channel and datarate settings + }sLoRa_OTAA; + + + // Structure to store information of a LoRaWAN message to transmit or received + typedef struct + { + eMESSAGE_TYPES MAC_Header; // Message type header. + uint8_t DevAddr[4]; // Device address number used to identify the Mote with when communicating + uint8_t Frame_Control; // + uint16_t Frame_Counter; // Frame Counter of the message used for determining whether the message is received more than once. + uint8_t Frame_Port; // Frame port + uint8_t Frame_Options[15]; // Array for the frame options data, if available + uint8_t MIC[4]; // Array for the calculated result for the MIC + eCONFIRMATION Confirmation; // Either it an UNCONFIRMED or CONFIRMED message up or downstream. + RFM_RETVAL retVal; // Returns status value for receiving. Indicates whether a timeout, MIC NOK or other error status occured. + uint8_t Count; // Index count for the data array + uint8_t Data[LORA_FIFO_SIZE]; // Transmit and receive data array. + }sLoRa_Message; + + + //Structure used for storing settings of the mote + typedef struct + { + eLoRaWAN_MOTE_CLASS Mote_Class = CLASS_A; // Mote Class, only CLASS A or CLASS C are supported. + eMOTE_NETWORK_JOIN activation_method = ACTIVATION_BY_PERSONALISATION; + //eMOTE_NETWORK_JOIN activation_method = OVER_THE_AIR_ACTIVATION; // Variable used to specify whether ABP or OTAA is used for activation of the mote on the back-end. + eBACKENDS back_end = SEMTECH; // Variable to identify which back-end is used and to provide automatic OTAA and data messages channel, datarate and timeslot configuration. + bool Channel_Hopping_enabled = false; // Enables channel hopping when set to true, the channel won't be changed when set to false + volatile uint16_t timeslot; // Timing variable for timeslot 1 and 2. + + // List of additional channels in hexadecimal format pre-calculated for the RFM frequency register + eCHANNEL_LIST CH_list = + { + .channel = {0}, + .index = 0, + .rx1_dr_offset = 0, + .rx2_dr = 0, + .rx_delay = 0, + .channel_hopping_on = true + }; + + /* Session parameters for the current session */ + sLoRa_Session Session = + { + .NwkSKey = {0x9E, 0x13, 0x34, 0xCB, 0x39, 0x39, 0xA4, 0x8B, 0x02, 0x91, 0xA4, 0xA4, 0xB8, 0x36, 0x31, 0x26}, + .AppSKey = {0xBC, 0xDC, 0x20, 0x30, 0x55, 0x09, 0xEA, 0xC9, 0x3B, 0x65, 0x19, 0xD4, 0xA5, 0xC0, 0xD8, 0x27}, + .DevAddr = {0x26, 0x01, 0x10, 0x52}, + .frame_counter_down = 0, + .frame_counter_up = 0, + .Receive_delay = RECEIVE_DELAY2, + .Transmit_Power = 0x0F, + .TxChDr = {SF09_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* OTAA configuration and encryption parameters */ + sLoRa_OTAA OTAA = + { + .DevEUI = {0}, // If the DS2401 is used, set the DEV EUI to zero. + .AppEUI = {0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x01, 0x80, 0x13}, // TTN Ideetron Application EUI, change this to your own APP EUI code + .AppKey = {0xB3, 0x23, 0xC2, 0xFD, 0xDF, 0x34, 0x7B, 0x3D, 0xA2, 0x04, 0xF2, 0x0E, 0x01, 0x3A, 0xA4, 0x59}, + .DevNonce = {0}, + .AppNonce = {0}, + .NetID = {0}, + .OTAAdone = false, + .JoinDelay = JOIN_DELAY_2, + .Transmit_Power = 0x0F, + .TxChDr = {SF10_BW125kHz, CH00_868_100}, + .RxChDr = {SF09_BW125kHz, CH10_869_525} + }; + + /* Message structure for both receive and transmitting */ + sLoRa_Message TX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + sLoRa_Message RX = + { + .MAC_Header = INIT_VAL, + .DevAddr = {0}, + .Frame_Control = 0, + .Frame_Counter = 0, + .Frame_Port = 0, + .Frame_Options = {0}, + .MIC = {0}, + .Confirmation = CONFIRMED, + .retVal = INIT, + .Count = 0, + .Data = {0} + }; + }sLoRaWAN; + + + +#endif diff --git a/examples/WeatherForecastExample/mcp7940.cpp b/examples/WeatherForecastExample/mcp7940.cpp new file mode 100644 index 0000000..d352542 --- /dev/null +++ b/examples/WeatherForecastExample/mcp7940.cpp @@ -0,0 +1,322 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: mcp7940.h +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +* Created on: 22-09-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ +#include "HardwareSerial.h" +#include "I2C.h" +#include "mcp7940.h" + +// private functions +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII); +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal); +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal); + + + +/* + @brief + Initialize the mcp7940 Real Time Clock (RTC) with the compilation strings to set the current date and time. Also configures the RTC to generate an + interrupt on the next minute roll-over with the alarm 0 function of the RTC. The RTC will pull the MFP pin low, which is held high with an internal + pull-up on pin D3. + @parameters + *TimeDate Pointer to a structure where the current time will be written to once converted from the compiler __BUILD__ and __TIME__ strings. + alarm_in_x_minutes The number of minutes from the current time on which the RTC will generate an interrupt. +*/ +void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes) +{ + uint8_t date [] = __DATE__; // Sep 27 2017 + uint8_t time [] = __TIME__; // 13:50:25 + + // Enable the internal pull-up on pin D3 by setting D3 bit in PORTD and DDRD as the MFP of the RTC is open drain and using an internal pull-up is more efficient. + PORTD |= 0x08; + DDRD &= ~0x08; + + //Serial.println((char*)date); + //Serial.println((char*)time); + + // Convert the compilation time and date to decimal numbers in order to set the RTC time. + TimeDate->hours = convert_ASCII_to_decimal(&(time[0])); + TimeDate->minutes = convert_ASCII_to_decimal(&(time[3])); + TimeDate->seconds = convert_ASCII_to_decimal(&(time[6])); + TimeDate->day = convert_ASCII_to_decimal(&(date[4])); + TimeDate->year = convert_ASCII_to_decimal(&(date[9])); + TimeDate->weekDay = 1; // There's no weekday in the time and date compiler string, so always set it to 1 upon start. + + // Compare the string of three letters to determine the current month as a decimal value + if(memcmp(date, "Jan", 3) == 0) + { + TimeDate->month = 1; + }else if (memcmp(date, "Feb", 3) == 0) + { + TimeDate->month = 2; + }else if (memcmp(date, "Mar", 3) == 0) + { + TimeDate->month = 3; + }else if (memcmp(date, "Apr", 3) == 0) + { + TimeDate->month = 4; + }else if (memcmp(date, "May", 3) == 0) + { + TimeDate->month = 5; + }else if (memcmp(date, "Jun", 3) == 0) + { + TimeDate->month = 6; + }else if (memcmp(date, "Jul", 3) == 0) + { + TimeDate->month = 7; + }else if (memcmp(date, "Aug", 3) == 0) + { + TimeDate->month = 8; + }else if (memcmp(date, "Sep", 3) == 0) + { + TimeDate->month = 9; + }else if (memcmp(date, "Oct", 3) == 0) + { + TimeDate->month = 10; + }else if (memcmp(date, "Nov", 3) == 0) + { + TimeDate->month = 11; + }else if (memcmp(date, "Dec", 3) == 0) + { + TimeDate->month = 12; + } + //Serial.print("Converted: "); + //mcp7940_print(TimeDate); + + // Write the converted time and date settings to the RTC and print it to the serial port. + mcp7940_set_time_and_date(TimeDate); + mcp7940_print(TimeDate); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); // Disable the square wave output on the RTC and Alarm 0 + + // Reload alarm 0 to generate an interrupt over x minutes + if(alarm_in_x_minutes > 0) + { + mcp7940_reset_minute_alarm(alarm_in_x_minutes); + } + + // Enable Alarm 0, set the MFP pin to idle HIGH, active LOW. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x10); + + // read back the set time and date in order to check if the time and date was set properly. + mcp7940_read_time_and_date(TimeDate); + + // Enable the Alarm to generate an external interrupt on a falling edge of INT1 + EICRA = 0x08; // The falling edge of INT1 generates an interrupt request. + EIMSK = 0x02; // Enable the external interrupt +} + + +/* + @brief + Function to disable the RTC and all alarms. +*/ +void mcp7940_disable (void) +{ + // Disable all alarms and square wave outputs. + I2C_write_register(MCP7940_SLAVE_ADDRESS, CONTROL, 0x00); + + // Disable the RTC by clearing bit 7 of the RTC_SEC register, which will stop the 32kHz crystal. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); +} + + + + + +/* + @brief + After the RTC's alarm has triggered, the alarm must be re-configured to generate an alarm again after alarm_in_x_minutes minutes. + @parameters + alarm_in_x_minutes The number of minutes from now when the RTC must generate an interrupt again. +*/ +void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes) +{ + uint8_t value, current_time_minutes, new_time_minutes; + + if(alarm_in_x_minutes > 59) + { + alarm_in_x_minutes = 59; + } + + // Read the current minutes register to calculate the match value for the minute register. + current_time_minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + + // Calculate the new time settings. + new_time_minutes = current_time_minutes + alarm_in_x_minutes; + + // Check whether the additional number of minutes and the current time settings will be larger than 59. if larger, subtract 59 from the sum to calculate the new time + if(new_time_minutes > 59) + { + // Subtract 59 from the sum of the current time in minutes and the added number of minutes. + new_time_minutes -= 59; + } + + // Rewrite the Alarm 0 minutes register. + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_MIN, convert_decimal_to_binary(MIN_MASK, new_time_minutes)); + + /* + Set MFP pin High on Alarm interrupt on minutes match and clear the ALM0IF (Alarm interrupt Flag) now that the Alarm 0 minutes register is different + from the current time or the interrupt would re-trigger. Read the register first to keep the current weekday value and add the three bits 2:0 to + the value written to the register. + */ + value = I2C_read_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY); + I2C_write_register(MCP7940_SLAVE_ADDRESS, ALARM0_WEEKDAY, 0x10 | (value & 0x07)); +} + + + +/* + @brief + Function to read the time and date from the RTC, convert it to decimal values and store the values in the reference structure. + @parameters + TimeDate pointer to the structure where all the retrieved time and date must be stored. +*/ +void mcp7940_read_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + return; + + // Read the Time and date from the RTC and convert it to a time and date in decimal values. + TimeDate->seconds = convert_binary_to_decimal(SEC_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_SEC)); + TimeDate->minutes = convert_binary_to_decimal(MIN_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MIN)); + TimeDate->hours = convert_binary_to_decimal(HOUR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR)); + TimeDate->weekDay = WEEKDAY_MASK & I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_WEEKDAY); + + TimeDate->day = convert_binary_to_decimal(DATE_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_DATE)); + TimeDate->month = convert_binary_to_decimal(MONTH_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH)); + TimeDate->year = convert_binary_to_decimal(YEAR_MASK, I2C_read_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR)); +} + + +/* + @brief + Function to write the referenced time and date in the TimeDate structure to the RTC + @parameters + TimeDate pointer to the structure where the new time and date settings are stored. +*/ +void mcp7940_set_time_and_date (sTimeDate *TimeDate) +{ + uint8_t regVal; + + // Check the given pointer + if(TimeDate == 0) + { + return; + } + + // Disable the oscillator to stop the RTC while the time and date are configured to prevent roll-overs from occurring while writing new values. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x00); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MIN, convert_decimal_to_binary(MIN_MASK, TimeDate->minutes)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_HOUR, 0x40 | convert_decimal_to_binary(HOUR_MASK, TimeDate->hours)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, WEEKDAY_MASK, convert_decimal_to_binary(WEEKDAY_MASK, TimeDate->weekDay)); + + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_DATE, convert_decimal_to_binary(DATE_MASK, TimeDate->day)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_MONTH, convert_decimal_to_binary(MONTH_MASK, TimeDate->month)); + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_YEAR, convert_decimal_to_binary(YEAR_MASK, TimeDate->year)); + + // Write the seconds as last since, we'll reactivate the oscillator with this write operation as well. + I2C_write_register(MCP7940_SLAVE_ADDRESS, RTC_SEC, 0x80 | convert_decimal_to_binary(SEC_MASK, TimeDate->seconds)); +} + + + +/* + @brief + Convert the RTC's tens and ones binary format to a decimal number. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New binary value which needs to be converted to decimal +*/ +uint8_t convert_binary_to_decimal (uint8_t mask, uint8_t regVal) +{ + return (((regVal & mask) >> 4) * 10) + (regVal & 0x0F); +} + + +/* + @brief + Convert a decimal value to binary tens and ones values. + @parameters + mask Mask value for bits 7:4, since the time and date registers of the RTC do not use all bits or additional functionalities are included in the bits + for the tens values, which should not be converted to decimal values. + regVal New decimal value which needs to be converted to binary +*/ +uint8_t convert_decimal_to_binary (uint8_t mask, uint8_t decimal) +{ + uint8_t tens, ones; + tens = decimal / 10; + ones = decimal % 10; + return ((mask &(tens << 4)) | (ones & 0x0F)); +} + +/* + @brief + Converts two ASCII characters to a single decimal value to convert the compilers __BUILD__ and __TIME__ string to decimal values. + @parameters + ASCII pointer to the first ASCII character. +*/ +uint8_t convert_ASCII_to_decimal (uint8_t *ASCII) +{ + uint8_t retVal = 0; + + if((ASCII[0] >= '0') && (ASCII[0] <= '9')) + { + retVal = (ASCII[0] - '0') * 10; + } + + if((ASCII[1] >= '0') && (ASCII[1] <= '9')) + { + retVal += (ASCII[1] - '0'); + } + + return retVal; +} + +/* + @brief + Prints the referenced structure in a human readable format to the serial port "hh:mm:ss day:n dd/mm/yy" +*/ +void mcp7940_print(sTimeDate *TimeDate) +{ + Serial.print(TimeDate->hours, DEC); + Serial.print(':'); + Serial.print(TimeDate->minutes, DEC); + Serial.print(':'); + Serial.print(TimeDate->seconds, DEC); + Serial.print(" day:"); + Serial.print(TimeDate->weekDay, DEC); + + Serial.print('\t'); + + Serial.print(TimeDate->day, DEC); + Serial.print('/'); + Serial.print(TimeDate->month, DEC); + Serial.print('/'); + Serial.println(TimeDate->year, DEC); +} diff --git a/examples/WeatherForecastExample/mcp7940.h b/examples/WeatherForecastExample/mcp7940.h new file mode 100644 index 0000000..70f622e --- /dev/null +++ b/examples/WeatherForecastExample/mcp7940.h @@ -0,0 +1,85 @@ +/* + * MCP7940.h + * + * Created: 22-9-2017 08:21:21 + * Author: adri + */ + + +#ifndef MCP7940_H_ +#define MCP7940_H_ + + //#define MCP7940_SLAVE_ADDRESS 0xDE + #define MCP7940_SLAVE_ADDRESS 0x6F + /****************************************************************************************** + ENUMERATION + ******************************************************************************************/ + typedef enum + { + SEC_MASK = 0x70, + MIN_MASK = 0x70, + HOUR_MASK = 0x10, + WEEKDAY_MASK= 0x07, + DATE_MASK = 0x30, + MONTH_MASK = 0x10, + YEAR_MASK = 0xF0 + }eMASKS; + + typedef enum + { + // Timekeeping + RTC_SEC = 0x00, + RTC_MIN = 0x01, + RTC_HOUR = 0x02, + RTC_WEEKDAY = 0x03, + RTC_DATE = 0x04, + RTC_MONTH = 0x05, + RTC_YEAR = 0x06, + CONTROL = 0x07, + OSCTRIM = 0x08, + + // Alarms 0 + ALARM0_SEC = 0x0A, + ALARM0_MIN = 0x0B, + ALARM0_HOUR = 0x0C, + ALARM0_WEEKDAY = 0x0D, + ALARM0_DATE = 0x0E, + ALARM0_MONTH = 0x0F, + + + // Alarms 1 + ALARM1_SEC = 0x11, + ALARM1_MIN = 0x12, + ALARM1_HOUR = 0x13, + ALARM1_WEEKDAY = 0x14, + ALARM1_DATE = 0x15, + ALARM1_MONTH = 0x16 + }eMCP7940_REGISTERS; + + /****************************************************************************************** + Structures + ******************************************************************************************/ + typedef struct + { + uint8_t seconds; + uint8_t minutes; + uint8_t hours; + uint8_t day; + uint8_t month; + uint8_t year; + uint8_t weekDay; + }sTimeDate; + + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void mcp7940_init (sTimeDate *TimeDate, uint8_t alarm_in_x_minutes); + void mcp7940_disable (void); + void mcp7940_reset_minute_alarm (uint8_t alarm_in_x_minutes); + void mcp7940_read_time_and_date (sTimeDate *TimeDate); + void mcp7940_set_time_and_date (sTimeDate *TimeDate); + void mcp7940_print (sTimeDate *TimeDate); + +#endif /* MCP7940_H_ */ diff --git a/examples/WeatherForecastExample/progmem.h b/examples/WeatherForecastExample/progmem.h new file mode 100644 index 0000000..c6b972a --- /dev/null +++ b/examples/WeatherForecastExample/progmem.h @@ -0,0 +1,60 @@ +const unsigned char wIcon_sunny [] PROGMEM= { // Icon example1, you will need to use + 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x00, 0x04, 0x00, + 0x01, 0x80, 0x30, + 0x00, 0x80, 0x20, + 0x00, 0x1f, 0x00, + 0x01, 0xb1, 0x80, + 0x0e, 0x70, 0x80, + 0x18, 0x18, 0xc0, + 0x10, 0x0c, 0x4c, + 0x30, 0x0f, 0x80, + 0x20, 0x00, 0xc0, + 0x20, 0x00, 0x40, + 0x20, 0x00, 0x40, + 0x30, 0x00, 0x40, + 0x10, 0x00, 0x40, + 0x0c, 0x00, 0xc0, + 0x07, 0xff, 0x80, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, +}; + + +const unsigned char wIcon_cloudy [] PROGMEM= { // Icon example2, you will need to use + 0x00, 0x00, 0x00, 0x00, // the SPI flash to store more icons + 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0xc0, 0x00, + 0x01, 0x80, 0x80, 0xc0, + 0x01, 0xc0, 0x01, 0xc0, + 0x00, 0xe0, 0x01, 0xc0, + 0x00, 0x41, 0xc1, 0x80, + 0x00, 0x07, 0xf0, 0x00, + 0x00, 0x0f, 0xfc, 0x00, + 0x07, 0xfc, 0x1c, 0x00, + 0x1f, 0xfc, 0x0e, 0x00, + 0x3c, 0x1e, 0x06, 0x00, + 0x38, 0x07, 0x06, 0x3c, + 0x70, 0x03, 0xe6, 0x3c, + 0x60, 0x01, 0xfe, 0x00, + 0xe0, 0x01, 0xfe, 0x00, + 0xc0, 0x00, 0x0e, 0x00, + 0xc0, 0x00, 0x07, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xc0, 0x00, 0x03, 0x00, + 0xe0, 0x00, 0x03, 0x00, + 0x60, 0x00, 0x03, 0x00, + 0x70, 0x00, 0x03, 0x00, + 0x38, 0x00, 0x07, 0x00, + 0x3c, 0x00, 0x0e, 0x00, + 0x1f, 0xff, 0xfc, 0x00, + 0x07, 0xff, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; diff --git a/examples/WeatherForecastExample/spi_flash.cpp b/examples/WeatherForecastExample/spi_flash.cpp new file mode 100644 index 0000000..d0d718f --- /dev/null +++ b/examples/WeatherForecastExample/spi_flash.cpp @@ -0,0 +1,180 @@ +#include "Arduino.h" +#include +#include "spi_functions.h" +#include "spi_flash.h" +#include "lorapaper.h" + + +void flash_ID (sFLASH_ID * ID) +{ + if(ID == NULL) + { + return; + } + + flash_release_power_down(); + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_MANUFACTURERE_ID); // 0x90 + + //Send 0x000000 for address + SPI.transfer(0x00); + SPI.transfer(0x00); + SPI.transfer(0x00); + + //Get Manufacture ID + ID->manufacturerID = SPI.transfer(0x00); + + //Get device ID + ID->deviceID = SPI.transfer(0x00); + + //Set CS pin HIGH + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +void flash_power_down (void) +{ + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} + + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction + SPI.transfer(FLASH_POWER_DOWN); // 0xB9 + + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_release_power_down (void) +{ + //Set CS pin low + digitalWrite(SPI_FLASH_CS, LOW); + + //Send instruction and four dummy bytes + SPI.transfer(FLASH_RELEASE_POWER_DOWN); // 0xAB + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // Dummy + SPI.transfer(0x00); // ID7:ID0 will repeat this until SPI transfer has finished. + + digitalWrite(SPI_FLASH_CS, HIGH); + + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_write(uint32_t address, uint8_t *data, uint16_t n) +{ + uint8_t i; + + flash_release_power_down(); + + // Enable writing to the Flash Chip + flash_write_enable(); + + //delay(1); + + // Set CS low + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_PAGE_PROGRAM); // 0x02 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + //SPI.transfer(i); // writes the value of i to the + SPI.transfer(data[i]); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); + + //delay(2000); + // Wait for the SPI flash IC to be ready for further instructions. + while(flash_status() & R_W_IN_PROGRESS) + {} +} + +void flash_read(uint32_t address, uint8_t *data, uint16_t n) +{ + uint16_t i; + + flash_release_power_down(); + + // Set CS low again + digitalWrite(SPI_FLASH_CS, LOW); + + // Send page program instruction + SPI.transfer(FLASH_READ_DATA); // 0x03 + + // Send address + SPI.transfer((uint8_t) (address >> 16)); + SPI.transfer((uint8_t) (address >> 8)); + SPI.transfer((uint8_t) (address >> 0)); + + // Write DS bytes + for(i = 0 ; i < n ; i++) + { + data[i] = SPI.transfer(0x00); + } + + // Set Cs pin high + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_enable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_ENABLE); // 0x06 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + +void flash_write_disable (void) +{ + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_WRITE_DISABLE); // 0x04 + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); +} + + +uint8_t flash_status (void) +{ + uint8_t status = 0; + + // Set write enable latch + digitalWrite(SPI_FLASH_CS, LOW); + + // Send Write enable latch instruction + SPI.transfer(FLASH_READ_STATUS); // 0x05 + + // Read the status byte + status = SPI.transfer(0x00); + + // Set CS high again + digitalWrite(SPI_FLASH_CS, HIGH); + + return status; +} diff --git a/examples/WeatherForecastExample/spi_flash.h b/examples/WeatherForecastExample/spi_flash.h new file mode 100644 index 0000000..04d3ed8 --- /dev/null +++ b/examples/WeatherForecastExample/spi_flash.h @@ -0,0 +1,76 @@ +// spi_flash.h + +#ifndef _SPI_FLASH_h +#define _SPI_FLASH_h + + /********************************************************************************************* + INCLUDES + *********************************************************************************************/ + #include + + + /********************************************************************************************* + DEFINITIONS + *********************************************************************************************/ + #define SPI_FLASH_PAGE_SIZE 256 + + /****************************************************************************************** + ENUMERATIONS + ******************************************************************************************/ + typedef enum + { + FLASH_WRITE_ENABLE = 0x06, + FLASH_WRITE_EN_VOLATILE = 0x50, + FLASH_WRITE_DISABLE = 0x04, + FLASH_READ_STATUS = 0x05, + FLASH_WRITE_STATUS = 0x01, + FLASH_READ_DATA = 0x03, + FLASH_FAST_READ = 0x0B, + FLASH_FAST_READ_DUAL = 0x3B, + FLASH_READ_DUAL_IO = 0xBB, + FLASH_PAGE_PROGRAM = 0x02, + FLASH_SECTOR_ERASE = 0x20, + FLASH_BLOCK_ERASE_32K = 0x52, + FLASH_BLOCK_ERASE_64K = 0xD8, + FLASH_CHIP_ERASE = 0xC7, + FLASH_CHIP_ERASE2 = 0x60, + FLASH_POWER_DOWN = 0xB9, + FLASH_RELEASE_POWER_DOWN= 0xAB, + FLASH_MANUFACTURERE_ID = 0x90, + FLASH_ID_DUAL_IO = 0x92, + FLASH_JEDEC_ID = 0x9F, + FLASH_READ_UNIQUE_ID = 0x4B + }eFLASH_COMMANDS; + + typedef enum + { + REGISTER_PROTECT = 0x80, + TOP_BOT_PROTECT = 0x20, + BLOCK_PROTECT = 0x1C, + WRITE_ENABLE_LATCH = 0x02, + R_W_IN_PROGRESS = 0x01 + }eSTATUS_BITS; + + /****************************************************************************************** + STRUCTURE + ******************************************************************************************/ + typedef struct + { + uint8_t deviceID; + uint8_t manufacturerID; + }sFLASH_ID; + + + /****************************************************************************************** + FUNCTION PROTOTYPES + ******************************************************************************************/ + void flash_ID (sFLASH_ID * ID); + void flash_power_down (void); + void flash_release_power_down (void); + void flash_write (uint32_t address, uint8_t *data, uint16_t n); + void flash_read (uint32_t address, uint8_t *data, uint16_t n); + void flash_write_enable (void); + void flash_write_disable (void); + uint8_t flash_status (void); + +#endif diff --git a/examples/WeatherForecastExample/spi_functions.cpp b/examples/WeatherForecastExample/spi_functions.cpp new file mode 100644 index 0000000..73b435f --- /dev/null +++ b/examples/WeatherForecastExample/spi_functions.cpp @@ -0,0 +1,63 @@ +#include +#include +#include "LoRaMAC.h" +#include "RFM95.h" +#include "Arduino.h" +#include "timers.h" +#include "lorawan_def.h" +#include "spi_functions.h" + +/****************************************************************************************** +* Description : Function that reads a register and returns the value +* Arguments : Address of register to be read +* Returns : Value of the register +******************************************************************************************/ + +uint8_t SPI_Read(uint8_t CS_pin, uint8_t register_Address) { + uint8_t RFM_Data; + + digitalWrite(CS_pin, LOW); // Set NSS pin low to start SPI communication + SPI.transfer(register_Address); // Send Address + RFM_Data = SPI.transfer(0x00); // Send 0x00 to be able to receive the answer from the RFM + digitalWrite(CS_pin, HIGH); // Set NSS high to end communication + return RFM_Data; // Return received data +} + +/****************************************************************************************** +* Description: Function that writes a register with the given value +* Arguments: Address of the register to be written +* Data Data to be written to the register +******************************************************************************************/ +void SPI_Write(uint8_t CS_pin, uint8_t register_Address, uint8_t Data) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a writ command + SPI.transfer(Data); //Send Data + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + + +/********************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* Lenght: The number of bytes needed to transmit +**********************************************************************************************/ +void SPI_Write_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set NSS pin Low to start communication + SPI.transfer(register_Address | 0x80); //Send Address with MSB 1 to make it a Write command + SPI.transfer(Data, lenght); //Send the data array + digitalWrite(CS_pin, HIGH); //Set NSS pin High to end communication +} + +/***************************************************************************************************** +* Description: Function that writes an array of data to the RFM register +* Arguments: Address of the RFM register to be written to. +* Data: Pointer to the Data array to be written to, starting on to the given register address +* lenght: The number of bytes needed to transmit +*****************************************************************************************************/ +void SPI_Read_Array(uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght) { + digitalWrite(CS_pin, LOW); //Set Chip select pin low to start SPI communication + SPI.transfer(register_Address); //Send the register Address and then read the contents of the receive buffer in the RFM + SPI.transfer(Data, lenght); //Set NSS high to end communication + digitalWrite(CS_pin, HIGH); +} diff --git a/examples/WeatherForecastExample/spi_functions.h b/examples/WeatherForecastExample/spi_functions.h new file mode 100644 index 0000000..f9e7a88 --- /dev/null +++ b/examples/WeatherForecastExample/spi_functions.h @@ -0,0 +1,17 @@ +#ifndef _SPI_Functions_h +#define _SPI_Functions_h + + #if defined(ARDUINO) && ARDUINO >= 100 + #include "Arduino.h" + #else + #include "WProgram.h" + #endif + + #include + + void SPI_Write (uint8_t CS_pin, uint8_t register_Address, uint8_t Data); + uint8_t SPI_Read (uint8_t CS_pin, uint8_t register_Address); + void SPI_Write_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + void SPI_Read_Array (uint8_t CS_pin, uint8_t register_Address, uint8_t *Data, uint8_t lenght); + +#endif diff --git a/examples/WeatherForecastExample/timers.cpp b/examples/WeatherForecastExample/timers.cpp new file mode 100644 index 0000000..ab4b098 --- /dev/null +++ b/examples/WeatherForecastExample/timers.cpp @@ -0,0 +1,68 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: timers.cpp +* Author: Adri Verhoef +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +/* +***************************************************************************************** +* INCLUDE FILES +***************************************************************************************** +*/ +#include +#include "avr/sleep.h" +#include "Arduino.h" +#include "timers.h" + + +void disable_ms_tick (void) +{ + TCCR1B = 0x00; // Disable Timer 1 +} + + +/* + @brief Configures the timer to create an interrupt every millisecond for timing purposes. +*/ +void enable_ms_tick (void) +{ + // Disable the Timer before configuring + disable_ms_tick(); + + // Use T1 since it's a 16 bit timer, but Timer 1 uses the same prescaler, so be aware. + TCCR1A = 0x00; + + // Clear the timer counter. + TCNT1 = 0; + + // (16MHz / 1) / 1000 = 16000 Ticks per millisecond. + OCR1A = 16000; + + // Enable Output compare A Match interrupts + TIMSK1 = 0x02; + + // Enable Timer1 by setting a non-zero prescaler. + TCCR1B = 0x09; // 0x00x0 1001: use CTC mode and set the prescaler to 1/1 +} diff --git a/examples/WeatherForecastExample/timers.h b/examples/WeatherForecastExample/timers.h new file mode 100644 index 0000000..157a7ae --- /dev/null +++ b/examples/WeatherForecastExample/timers.h @@ -0,0 +1,43 @@ +/****************************************************************************************** +* Copyright 2017 Ideetron B.V. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Lesser General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this program. If not, see . +******************************************************************************************/ +/**************************************************************************************** +* File: Waitloop.h +* Author: Gerben den Hartog +* Company: Ideetron B.V. +* Website: http://www.ideetron.nl/LoRa +* E-mail: info@ideetron.nl +****************************************************************************************/ +/**************************************************************************************** +* Created on: 06-01-2017 +* Supported Hardware: ID150119-02 Nexus board with RFM95 +****************************************************************************************/ + +#ifndef TIMERS_H +#define TIMERS_H + + /* + ***************************************************************************************** + * FUNCTION PROTOTYPES + ***************************************************************************************** + */ + + #include + + void disable_ms_tick (void); + void enable_ms_tick (void); + +#endif diff --git a/examples/readme.md b/examples/readme.md new file mode 100644 index 0000000..6819cc7 --- /dev/null +++ b/examples/readme.md @@ -0,0 +1,32 @@ +Example: MinimalTemplate +------------------- +This MinimalTemplate demonstrates a simple counter, being updated everytime if there is sufficient energy harvested. The supercap voltage v_scap is measured minutely while the ATmega328p processor is in deep sleep all remaining time. Triggering is done via external RTC to minimize current consumption during deep sleep phase. IF the voltage is charged above a certain limit (ie 4.2V), an image update is triggered. + +Example: WeatherForecast +------------------- + +### 1) Register LoraPaper at your TTN Console + +Now its time to add LoraPaper as new device to the TTN Console. After login, please select 'application' and 'register device'. Please add the Device ID and Device EUI in the fields as provided with LoraPaper and select 'Register'. Then please reselect your newly added device and open the 'settings' view again. You will now see the new generated keys, please copy Device Adress, Network Session Key and the App Session Key since they will be added into the source code lateron. Finally, select 'ABP' as activation methode and deactivate 'frame counter checks'. + +### 2) Schedule downloadable data at your TTN console with NodeRED + +There are several ways to add data to the TTN console, which can then be downloaded to your node once it comes online the next time. In this example the shown Node-RED flow is implemented; you can continue to use it or build your own flow. + +![noderead2](https://user-images.githubusercontent.com/21104467/71321637-c1accb80-24bc-11ea-906a-3cce3634e421.jpg) + +Requesting a new weather API call can be triggered via a predefined timer (timer, i.e. hourly) or after the node came online and just downloaded the present weather data (ttn uplink). With this setup we assure that there is always weather data available, no matter when the node came back online the last time. The orange box extract payload now parses the received html code and extracts only the relevant weather data we are interested in. As a result, the data size decreases from several kilobytes down to less than ten bytes. This payload is now configured in ttn-downlink to be downloaded to our weather station when it is online next time. + +![scheduled](https://user-images.githubusercontent.com/21104467/71321656-2a944380-24bd-11ea-957f-c4cb6a82c611.jpg) + +### 3) Add the Encoder function to your TTN backend + +Next we need to add an encoder function, which converts the weather data to a bytestream which can be sent during the next donwlink message. Please fill in the data from the file 'encoder.h' to the field in Application/Payload Format/Encoder. + +### 4) Modify & Upload The Source Code + +Open the example 'WeatherForecast' and select the tab 'lorawan_def.h'. Here you will need to add your previously generated keys: + +![slora](https://user-images.githubusercontent.com/21104467/71319850-f1030e80-24a3-11ea-84f9-7d1ee86cc57c.jpg) + +After compiling and successful upload you should be able to receive the first data! diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..ec72cd6 --- /dev/null +++ b/readme.md @@ -0,0 +1,52 @@ +LoraPaper Arduino Library +=============================================================== + +Welcome to the docs! This is an Arduino Library for LoraPaper, a TTN-connected 1.1 E-Paper node which is powered by ambient light and thus energy-autark. + + +![lorapaper2](https://user-images.githubusercontent.com/21104467/71319613-09bdf500-24a1-11ea-9b56-77f6731cf4ea.png) +[*LoraPaper*](https://twitter.com/Paperino_io) + +Documentation +-------------- +This is the place to get started! We have divided the documentation into the following sections: + + +* **[Examples](https://github.com/RobPo/lorapaper/tree/master/examples)** - Ready to use examples for your own inspiration. +* **[Reference](https://github.com/RobPo/lorapaper/tree/master/datasheets)** - Datasheets. +* **[Hardware](https://github.com/RobPo/lorapaper/tree/master/schematics)** - Schematic. + +How To Use +------------------- + +### Installation + +Please download and start the latest Arduino IDE. Select "Arduino Mini" in the menu Tools/Board; if not available please download this board from the Boards Manager. Please select "Atmega328p" in the Tools/Processor menu. Now please download and import the examples of this repository. + +### Hardware hookup + +Connect your FTTI programmer to LoraPaper through the pins exposed on the bottom side; either directly (left) or through a breadboard and with soldered pin row (right): +![ftdi2](https://user-images.githubusercontent.com/21104467/71321525-9de88600-24ba-11ea-95f3-0afbbc627986.jpg) + + +Examples +------------------- +These demo projects are for your inspiration! What will you implement with LoraPaper? Tell us, well love to add your project here! + +![weatherforecast](https://user-images.githubusercontent.com/21104467/71322107-f3756080-24c3-11ea-96c5-fdd6a71fff85.jpg) + +License Information +------------------- + +This library is **open source**! + +Libraries used in this sketch are based on the LoRaWAN stack from IDEETRON/NEXUS, for more infos please check this great source: https://github.com/Ideetron/Nexus-Low-Power + +Libraries used in this sketch around the ePaper and the Example sketches are created by Robert Poser, Dez 19th 2019, Dresden/Germany. + +Released under GNU Lesser General Public License, either version 3 of the License, or (at your option) any later version, check license.md for more information. + +We invested time and resources providing this source code, please support open source hardware and software @Ideetron, @Adafruit, @Watterott and others. + +If you like this project please [follow us on Twitter](https://twitter.com/paperino_io). +Having problems or have awesome suggestions? Contact us: paperino.display@gmail.com. diff --git a/schematics/LoraPaper_v1.pdf b/schematics/LoraPaper_v1.pdf new file mode 100644 index 0000000..370e68b Binary files /dev/null and b/schematics/LoraPaper_v1.pdf differ