From 726a20dad9696ed86408106061a9e0be31995b5c Mon Sep 17 00:00:00 2001 From: d00616 Date: Sun, 14 May 2017 11:53:01 +0200 Subject: [PATCH] Add nrf5 platform (#830) * Add NVRAM support This is an EEPROM replacement based on MCU's internal Flash memory. The NVRAM class is designed to be stateless. Each operation is written to flash immediately. The last 16k(nRF51)/32k(nRF52) of Flash memory are managed by the VirtualPage class. This class allows writing a single emulated 4k page up 40,000 times. The NVRAM class is a log based byte-wise storage allowing up to 40,000,000 writes depending on the number of used addresses. Each four allocated addresses from the beginning of the NVRAM reduce the write log by one. When first 256 bytes of the emulated storage are used, there is an average writing capacity of 145,000 cycles per byte. When the write log is full, the NVRAM is compressed into a new flash page. After a new page is ready, the old page is released. This process can take up to 4500 ms and cannot be interrupted. The NVRAM functionality can easily be ported to ESP8266 or SAMD by providing a Flash interface. * Add NRF5 platform support This is the support of the Nordic Semiconductor NRF51 (nrf51822, nrf51422) and NRF52 (nrf52832) platform. Based on arduino-nRF5 (https://github.com/sandeepmistry/arduino-nRF5). The included radio driver is compatible with the NRF24 devices. Using a SoftDevice (BLE)is not supported and must be removed before flashing MySensors with a "mass_erase" operation. --- MyConfig.h | 99 ++++ MySensors.h | 33 +- core/MyCapabilities.h | 4 +- core/MyTransport.h | 3 + drivers/NRF5/Flash.cpp | 148 ++++++ drivers/NRF5/Radio.cpp | 88 ++++ drivers/NRF5/Radio.h | 82 ++++ drivers/NRF5/Radio_ESB.cpp | 678 ++++++++++++++++++++++++++ drivers/NRF5/Radio_ESB.h | 157 ++++++ drivers/NRF5/nrf5_wiring_constants.h | 42 ++ drivers/NRF5/nrf5_wiring_digital.c | 96 ++++ drivers/NRF5/wdt.h | 185 +++++++ drivers/NVM/Flash.h | 180 +++++++ drivers/NVM/NVRAM.cpp | 342 +++++++++++++ drivers/NVM/NVRAM.h | 113 +++++ drivers/NVM/README.md | 51 ++ drivers/NVM/VirtualPage.cpp | 342 +++++++++++++ drivers/NVM/VirtualPage.h | 139 ++++++ hal/architecture/MyHwNRF5.cpp | 436 +++++++++++++++++ hal/architecture/MyHwNRF5.h | 120 +++++ hal/architecture/MyMainNRF5.cpp | 69 +++ hal/transport/MyTransportHAL.h | 3 + hal/transport/MyTransportNRF5_ESB.cpp | 158 ++++++ keywords.txt | 13 +- library.json | 4 +- library.properties | 2 +- 26 files changed, 3576 insertions(+), 11 deletions(-) create mode 100644 drivers/NRF5/Flash.cpp create mode 100644 drivers/NRF5/Radio.cpp create mode 100644 drivers/NRF5/Radio.h create mode 100644 drivers/NRF5/Radio_ESB.cpp create mode 100644 drivers/NRF5/Radio_ESB.h create mode 100644 drivers/NRF5/nrf5_wiring_constants.h create mode 100644 drivers/NRF5/nrf5_wiring_digital.c create mode 100644 drivers/NRF5/wdt.h create mode 100644 drivers/NVM/Flash.h create mode 100644 drivers/NVM/NVRAM.cpp create mode 100644 drivers/NVM/NVRAM.h create mode 100644 drivers/NVM/README.md create mode 100644 drivers/NVM/VirtualPage.cpp create mode 100644 drivers/NVM/VirtualPage.h create mode 100644 hal/architecture/MyHwNRF5.cpp create mode 100644 hal/architecture/MyHwNRF5.h create mode 100644 hal/architecture/MyMainNRF5.cpp create mode 100644 hal/transport/MyTransportNRF5_ESB.cpp diff --git a/MyConfig.h b/MyConfig.h index 7873e8f..4960801 100644 --- a/MyConfig.h +++ b/MyConfig.h @@ -94,6 +94,7 @@ // Selecting uplink transport layer is optional (for a gateway node). //#define MY_RADIO_RF24 +//#define MY_RADIO_NRF5_ESB //#define MY_RADIO_RFM69 //#define MY_RADIO_RFM95 //#define MY_RS485 @@ -668,6 +669,100 @@ #define MY_SOFT_SPI_MOSI_PIN (15) #endif +/********************************** +* NRF5_ESB Driver Defaults +* The NRF5_ESB driver is compatible +* to RF24 +***********************************/ + +/** + * @def MY_NRF5_ESB_ENABLE_ENCRYPTION + * @brief Enables RF24 compatible ncryption (all nodes and gateway must have + * this enabled, and all must be personalized with the same AES key) + * This is compatible to MY_RF24_ENABLE_ENCRYPTION. Because the Initialization + * Vector is always 0, the Encryption is weak. + */ +//#define MY_NRF5_ESB_ENABLE_ENCRYPTION + +/** + * @def MY_DEBUG_VERBOSE_NRF5_ESB + * @brief Enable MY_DEBUG_VERBOSE_NRF5_ESB flag for verbose debug prints related + * to the NRF5_ESB driver. Requires DEBUG to be enabled. + */ +//#define MY_DEBUG_VERBOSE_NRF5_ESB + +/** + * @def MY_NRF5_ESB_PA_LEVEL + * @brief Default NRF5 PA level. Override in sketch if needed. + */ +#ifndef MY_NRF5_ESB_PA_LEVEL +#define MY_NRF5_ESB_PA_LEVEL NRF5_PA_MAX +#endif + +/** + * @def MY_NRF5_ESB_CHANNEL + * @brief RF channel for the sensor net, 0-125. + * Frequence: 2400 Mhz - 2525 Mhz Channels: 126 + * http://www.mysensors.org/radio/nRF24L01Plus.pdf + * 0 => 2400 Mhz (RF24 channel 1) + * 1 => 2401 Mhz (RF24 channel 2) + * 76 => 2476 Mhz (RF24 channel 77) + * 83 => 2483 Mhz (RF24 channel 84) + * 124 => 2524 Mhz (RF24 channel 125) + * 125 => 2525 Mhz (RF24 channel 126) + * In some countries there might be limitations, in Germany for example only the + * range 2400,0 - 2483,5 Mhz is allowed + * http://www.bundesnetzagentur.de/SharedDocs/Downloads/DE/Sachgebiete/Telekommunikation/Unternehmen_Institutionen/Frequenzen/Allgemeinzuteilungen/2013_10_WLAN_2,4GHz_pdf.pdf + */ +#ifndef MY_NRF5_ESB_CHANNEL +#define MY_NRF5_ESB_CHANNEL 76 +#endif + +/** + * @def MY_NRF5_ESB_MODE + * @brief NRF5 mode (NRF5_250KBPS for 250kbs, NRF5_1MBPS for 1Mbps or NRF5_2MBPS + * for 2Mbps). + */ +#ifndef MY_NRF5_ESB_MODE +#define MY_NRF5_ESB_MODE NRF5_250KBPS +#endif + +/** + * @def MY_NRF5_ESB_BASE_RADIO_ID + * @brief NRF5 radio network identifier. + * + * This acts as base value for sensor nodeId addresses. Change this (or channel) + * if you have more than one sensor network. + */ +#ifndef MY_NRF5_ESB_BASE_RADIO_ID +#define MY_NRF5_ESB_BASE_RADIO_ID 0x00, 0xFC, 0xE1, 0xA8, 0xA8 +#endif + +/** + * @def MY_NRF5_ESB_ADDR_WIDTH + * @brief NRF5 address width. + * + * This defines the width of the complete address. + */ +#ifndef MY_NRF5_ESB_ADDR_WIDTH +#define MY_NRF5_ESB_ADDR_WIDTH 5 +#endif + +/** + * @def MY_NRF5_ESB_RX_BUFFER_SIZE + * @brief Declare the amount of incoming messages that can be buffered at driver + * level. + */ +#ifndef MY_NRF5_ESB_RX_BUFFER_SIZE +#define MY_NRF5_ESB_RX_BUFFER_SIZE (20) +#endif + +/** + * @def MY_NRF5_ESB_REVERSE_ACK + * @brief Switch to SI24R1 or faked NRF24L01+ compatible ACK mode. + */ +//#define MY_NRF5_ESB_REVERSE_ACK + /********************************** * RFM69 Driver Defaults ***********************************/ @@ -1213,6 +1308,10 @@ #define MY_RF24_ENABLE_ENCRYPTION #define MY_RX_MESSAGE_BUFFER_FEATURE #define MY_RX_MESSAGE_BUFFER_SIZE +// NRF5_ESB +#define MY_NRF5_ESB_ENABLE_ENCRYPTION +#define MY_DEBUG_VERBOSE_NRF5_ESB +#define MY_NRF5_ESB_REVERSE_ACK // RFM69 #define MY_IS_RFM69HW #define MY_RFM69_NEW_DRIVER diff --git a/MySensors.h b/MySensors.h index f3ddfea..6c7d3ad 100644 --- a/MySensors.h +++ b/MySensors.h @@ -66,7 +66,7 @@ #define MY_DEBUG_VERBOSE_OTA_UPDATE //!< MY_DEBUG_VERBOSE_OTA_UPDATE #endif -#if defined(MY_DEBUG) || defined(MY_DEBUG_VERBOSE_CORE) || defined(MY_DEBUG_VERBOSE_TRANSPORT) || defined(MY_DEBUG_VERBOSE_SIGNING) || defined(MY_DEBUG_VERBOSE_OTA_UPDATE) || defined(MY_DEBUG_VERBOSE_RF24) || defined(MY_DEBUG_VERBOSE_RFM69) || defined(MY_DEBUG_VERBOSE_RFM95) +#if defined(MY_DEBUG) || defined(MY_DEBUG_VERBOSE_CORE) || defined(MY_DEBUG_VERBOSE_TRANSPORT) || defined(MY_DEBUG_VERBOSE_SIGNING) || defined(MY_DEBUG_VERBOSE_OTA_UPDATE) || defined(MY_DEBUG_VERBOSE_RF24) || defined(MY_DEBUG_VERBOSE_NRF5_ESB) || defined(MY_DEBUG_VERBOSE_RFM69) || defined(MY_DEBUG_VERBOSE_RFM95) #define DEBUG_OUTPUT_ENABLED //!< DEBUG_OUTPUT_ENABLED #define DEBUG_OUTPUT(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug #else @@ -78,7 +78,7 @@ // Enable sensor network "feature" if one of the transport types was enabled -#if defined(MY_RADIO_RF24) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485) +#if defined(MY_RADIO_RF24) || defined(MY_RADIO_NRF5_ESB) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485) #define MY_SENSOR_NETWORK #endif @@ -93,6 +93,10 @@ #include "hal/architecture/MyHwSAMD.cpp" #elif defined(ARDUINO_ARCH_STM32F1) #include "hal/architecture/MyHwSTM32F1.cpp" +#elif defined(ARDUINO_ARCH_NRF5) +#include "drivers/NVM/VirtualPage.cpp" +#include "drivers/NVM/NVRAM.cpp" +#include "hal/architecture/MyHwNRF5.cpp" #elif defined(__linux__) #include "hal/architecture/MyHwLinuxGeneric.cpp" #endif @@ -247,7 +251,12 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs #if defined(MY_RADIO_RF24) #define __RF24CNT 1 //!< __RF24CNT #else -#define __RF24CNT 0 //!< __RF24CNT +#define __RF24CNT 0 //!< __RF24CNT +#endif +#if defined(MY_RADIO_NRF5_ESB) +#define __NRF5ESBCNT 1 //!< __NRF5ESBCNT +#else +#define __NRF5ESBCNT 0 //!< __NRF5ESBCNT #endif #if defined(MY_RADIO_RFM69) #define __RFM69CNT 1 //!< __RFM69CNT @@ -265,12 +274,12 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs #define __RS485CNT 0 //!< __RS485CNT #endif -#if (__RF24CNT + __RFM69CNT + __RFM95CNT + __RS485CNT > 1) +#if (__RF24CNT + __NRF5ESBCNT + __RFM69CNT + __RFM95CNT + __RS485CNT > 1) #error Only one forward link driver can be activated #endif // TRANSPORT INCLUDES -#if defined(MY_RADIO_RF24) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485) +#if defined(MY_RADIO_RF24) || defined(MY_RADIO_NRF5_ESB) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485) #include "hal/transport/MyTransportHAL.h" #include "core/MyTransport.h" @@ -292,7 +301,7 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs // RAM ROUTING TABLE #if defined(MY_RAM_ROUTING_TABLE_FEATURE) && defined(MY_REPEATER_FEATURE) // activate feature based on architecture -#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || defined(__linux__) +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_NRF5) || defined(__linux__) #define MY_RAM_ROUTING_TABLE_ENABLED #elif defined(ARDUINO_ARCH_AVR) #if defined(__avr_atmega1280__) || defined(__avr_atmega1284__) || defined(__avr_atmega2560__) @@ -331,6 +340,16 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs #endif #include "drivers/RF24/RF24.cpp" #include "hal/transport/MyTransportRF24.cpp" +#elif defined(MY_RADIO_NRF5_ESB) +#if !defined(ARDUINO_ARCH_NRF5) +#error No support for nRF5 radio on this platform +#endif +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) +#include "drivers/AES/AES.cpp" +#endif +#include "drivers/NRF5/Radio.cpp" +#include "drivers/NRF5/Radio_ESB.cpp" +#include "hal/transport/MyTransportNRF5_ESB.cpp" #elif defined(MY_RS485) #if !defined(MY_RS485_HWSERIAL) #if defined(__linux__) @@ -399,6 +418,8 @@ MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs #if !defined(MY_CORE_ONLY) #if defined(ARDUINO_ARCH_ESP8266) #include "hal/architecture/MyMainESP8266.cpp" +#elif defined(ARDUINO_ARCH_NRF5) +#include "hal/architecture/MyMainNRF5.cpp" #elif defined(__linux__) #include "hal/architecture/MyMainLinux.cpp" #elif defined(ARDUINO_ARCH_STM32F1) diff --git a/core/MyCapabilities.h b/core/MyCapabilities.h index f84b92b..fe19bfe 100644 --- a/core/MyCapabilities.h +++ b/core/MyCapabilities.h @@ -34,7 +34,7 @@ #endif -#if defined(MY_RADIO_NRF24) +#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_NRF5_ESB) #define MY_CAP_RADIO "N" #elif defined(MY_RADIO_RFM69) #if !defined(MY_RFM69_NEW_DRIVER) @@ -64,6 +64,8 @@ #if defined(ARDUINO_ARCH_SAMD) #define MY_CAP_ARCH "S" +#elif defined(ARDUINO_ARCH_NRF5) +#define MY_CAP_ARCH "N" #elif defined(ARDUINO_ARCH_ESP8266) #define MY_CAP_ARCH "E" #elif defined(ARDUINO_ARCH_AVR) diff --git a/core/MyTransport.h b/core/MyTransport.h index 0c39e05..8871b74 100644 --- a/core/MyTransport.h +++ b/core/MyTransport.h @@ -215,6 +215,9 @@ #if defined(MY_RS485) #error Receive message buffering not supported for RS485! #endif +#if defined(MY_RADIO_NRF5_ESB) +#error Receive message buffering not supported for NRF5 radio! Please define MY_NRF5_RX_BUFFER_SIZE +#endif #elif !defined(MY_RX_MESSAGE_BUFFER_FEATURE) && defined(MY_RX_MESSAGE_BUFFER_SIZE) #error Receive message buffering requires message buffering feature enabled! #endif diff --git a/drivers/NRF5/Flash.cpp b/drivers/NRF5/Flash.cpp new file mode 100644 index 0000000..9b603ed --- /dev/null +++ b/drivers/NRF5/Flash.cpp @@ -0,0 +1,148 @@ +/* + Flash.cpp - Flash library + Original Copyright (c) 2017 Frank Holtz. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "drivers/NVM/Flash.h" + +FlashClass Flash; + +uint32_t FlashClass::page_size() const +{ + return (size_t)NRF_FICR->CODEPAGESIZE; +} + +uint8_t FlashClass::page_size_bits() const +{ +#if defined(NRF51) + return 10; +#elif defined(NRF52) + return 12; +#endif +} + +uint32_t FlashClass::page_count() const +{ + return (uint32_t)NRF_FICR->CODESIZE; +} + +uint32_t FlashClass::specified_erase_cycles() const +{ + return FLASH_ERASE_CYCLES; +} + +uint32_t *FlashClass::page_address(size_t page) +{ + return (uint32_t *)(page << page_size_bits()); +} + +void FlashClass::erase(uint32_t *address, size_t size) +{ + size_t end_address = (size_t)address + size; + + // align address + address = + (uint32_t *)((size_t)address & (size_t)((size_t)(~0) - FLASH_PAGE_SIZE)); + + // Wrong parameters? + if ((size_t)address >= end_address) { + return; + } + + // get old nvm controller state + uint32_t old_config = NRF_NVMC->CONFIG; + + // Enable erasing flash + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos; + + // Erase page(s) + while ((size_t)address < end_address) { + wait_for_ready(); + // Erase one 1k/4k page + NRF_NVMC->ERASEPAGE = (size_t)(address); + address = (uint32_t *)((size_t)address + FLASH_PAGE_SIZE); + } + + // Disable erasing + wait_for_ready(); + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos; + + // Restore old state + wait_for_ready(); + NRF_NVMC->CONFIG = old_config; + + // Go back if controller is ready + wait_for_ready(); +} + +void FlashClass::erase_all() +{ + // Enable erasing flash + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); + + // Erase Flash and UICR + NRF_NVMC->ERASEALL = 1; + wait_for_ready(); + + // Disable erasing + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); +} + +void FlashClass::write(uint32_t *address, uint32_t value) +{ + // Compare word + if (*address != value) { + // Enable write + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); + // Write word + *address = value; + // Disable write + wait_for_ready(); + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); + } +} + +void FlashClass::write_block(uint32_t *dst_address, uint32_t *src_address, + uint16_t word_count) +{ + // Enable write + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); + + while (word_count > 0) { + if (*dst_address != *src_address) { + *dst_address = *src_address; + } + word_count--; + dst_address++; + src_address++; + } + + // Disable write + wait_for_ready(); + NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos; + wait_for_ready(); +} + +void FlashClass::wait_for_ready() +{ + while (NRF_NVMC->READY == NVMC_READY_READY_Busy) { + }; +} diff --git a/drivers/NRF5/Radio.cpp b/drivers/NRF5/Radio.cpp new file mode 100644 index 0000000..b3d7327 --- /dev/null +++ b/drivers/NRF5/Radio.cpp @@ -0,0 +1,88 @@ +#include "drivers/NRF5/Radio.h" + +int16_t NRF5_getTxPowerPercent(void) +{ + // NRF5_PA_MAX = 100% NRF5_PA_MIN=0% + int16_t dbm = NRF5_getTxPowerLevel(); + int16_t dbm_diff = ((int8_t)NRF5_PA_MAX-(int8_t)NRF5_PA_MIN); + int16_t dbm_min = (int8_t)NRF5_PA_MIN; + return ((dbm-dbm_min)*100)/dbm_diff; +} + +int16_t NRF5_getTxPowerLevel(void) +{ + return (int8_t)NRF_RADIO->TXPOWER; +} + +bool NRF5_setTxPowerPercent(const uint8_t powerPercent) +{ + /* Current mapping: + * NRF51/NRF52822: + * 0.. 2% -> -40dBm (0%) + * 3..56% -> -16dBm (54%) + * 57..65% -> -12dBm (63%) + * 66..72% -> -8dBm (72%) + * 75..84% -> -4dBm (81%) + * 85..95% -> 0dBm (90%) + * NRF51 96..100%-> 4dBm (100%) + * NRF52 96..99% -> 3dBm (97%) + * NRF52 100% -> 4dBm (100%) + */ + + // Calculate dbm level + int16_t dbm_diff = ((int8_t)NRF5_PA_MAX-(int8_t)NRF5_PA_MIN); + int16_t dbm_min = (int8_t)NRF5_PA_MIN; + int8_t dbm = ((dbm_diff * powerPercent)/100)+dbm_min; + if (dbm >= (int8_t)NRF5_PA_MAX) { + NRF_RADIO->TXPOWER = NRF5_PA_MAX; + return true; + } + if (dbm > 1) { +#ifdef RADIO_TXPOWER_TXPOWER_Pos2dBm + // NRF52840 + NRF_RADIO->TXPOWER = dbm; +#elif defined(NRF51) + // nRF51x22 + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos4dBm; +#else + // NRF52822 + if (dbm > 3) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos4dBm; + } else { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Pos3dBm; + } +#endif + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg4dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm; + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg4dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_0dBm; + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg8dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg4dBm; + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg12dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg8dBm; + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg16dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg12dBm; + return true; + } + if (dbm > (int8_t)RADIO_TXPOWER_TXPOWER_Neg20dBm) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg16dBm; + return true; + } + if (dbm > (int8_t)NRF5_PA_MIN) { + NRF_RADIO->TXPOWER = RADIO_TXPOWER_TXPOWER_Neg16dBm; + return true; + } + + NRF_RADIO->TXPOWER = NRF5_PA_MIN; + return true; +} diff --git a/drivers/NRF5/Radio.h b/drivers/NRF5/Radio.h new file mode 100644 index 0000000..cfb4d28 --- /dev/null +++ b/drivers/NRF5/Radio.h @@ -0,0 +1,82 @@ + +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Frank Holtz + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef __NRF_RADIO_H__ +#define __NRF_RADIO_H__ + +#if !defined(ARDUINO) +#error "NRF5 Radio is not supported for this platform." +#endif + +#if defined(NRF5_SOFTDEVICE) +#warning "NRF5 SoftDevice cannot be used with NRF5 Radio." +#endif + +#include +#include +#include +#include + +// Timer to use +#define NRF5_RADIO_TIMER NRF_TIMER2 +#define NRF5_RADIO_TIMER_IRQ_HANDLER TIMER2_IRQHandler +#define NRF5_RADIO_TIMER_IRQN TIMER2_IRQn + +// debug +#if defined(MY_DEBUG_VERBOSE_NRF5_ESB) +#define NRF5_RADIO_DEBUG(x, ...) debug(x, ##__VA_ARGS__) +#else +#define NRF5_RADIO_DEBUG(x, ...) +#endif + +// tx power +typedef enum { +#ifdef NRF51 + NRF5_PA_MIN = RADIO_TXPOWER_TXPOWER_Neg30dBm, // Deprecated +#else + NRF5_PA_MIN = RADIO_TXPOWER_TXPOWER_Neg40dBm, +#endif + NRF5_PA_LOW = RADIO_TXPOWER_TXPOWER_Neg16dBm, + NRF5_PA_HIGH = RADIO_TXPOWER_TXPOWER_0dBm, +#ifdef RADIO_TXPOWER_TXPOWER_Pos9dBm + // nRF52840 + NRF5_PA_MAX = RADIO_TXPOWER_TXPOWER_Pos9dBm, +#else + // nRF51x22/nRF52822 + NRF5_PA_MAX = RADIO_TXPOWER_TXPOWER_Pos4dBm, +#endif +} nrf5_txpower_e; + +// Radio mode (Data rate) +typedef enum { + NRF5_1MBPS = RADIO_MODE_MODE_Nrf_1Mbit, + NRF5_2MBPS = RADIO_MODE_MODE_Nrf_2Mbit, + NRF5_250KBPS = RADIO_MODE_MODE_Nrf_250Kbit, // Deprecated!!! + NRF5_BLE_1MBPS = RADIO_MODE_MODE_Ble_1Mbit, +} nrf5_mode_e; + +int16_t NRF5_getTxPowerPercent(void); +int16_t NRF5_getTxPowerLevel(void); +bool NRF5_setTxPowerPercent(const uint8_t powerPercent); + + +#endif // __NRF_RADIO_H__ diff --git a/drivers/NRF5/Radio_ESB.cpp b/drivers/NRF5/Radio_ESB.cpp new file mode 100644 index 0000000..06af368 --- /dev/null +++ b/drivers/NRF5/Radio_ESB.cpp @@ -0,0 +1,678 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Frank Holtz + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "Radio.h" +#include "Radio_ESB.h" +#include "hal/architecture/MyHwNRF5.h" +#include "drivers/CircularBuffer/CircularBuffer.h" +#include + +// internal functions +static uint8_t reverse_byte(uint8_t address); + +// RX Buffer +static NRF5_ESB_Packet rx_circular_buffer_buffer[MY_NRF5_ESB_RX_BUFFER_SIZE]; +// Poiter to rx circular buffer +static NRF5_ESB_Packet *rx_buffer; +// Circular buffer +static CircularBuffer +rx_circular_buffer(rx_circular_buffer_buffer, MY_NRF5_ESB_RX_BUFFER_SIZE); +// Dedect duplicate packages for every pipe available +static volatile uint32_t package_ids[8]; + +// ACK Buffer +static NRF5_ESB_Packet ack_buffer; + +// TX Buffer +static NRF5_ESB_Packet tx_buffer; +// remaining TX retries +static volatile int8_t tx_retries; +// PID number for ACK +static volatile int8_t ack_pid; +// Flag for ack received +static volatile bool ack_received; +// Flag for end TX event +static volatile bool events_end_tx; +// Flag if radio is disabled +static volatile bool radio_disabled; +// Last RSSI sample provided by NRF5_ESB_readMessage +static volatile int16_t rssi_rx; +// Last RSSI sample by last package +static volatile int16_t rssi_tx; +// Buffer node address +static uint8_t node_address = 0; +// TX power level +static int8_t tx_power_level = (MY_NRF5_ESB_PA_LEVEL << RADIO_TXPOWER_TXPOWER_Pos); + +// Initialize radio unit +static bool NRF5_ESB_initialize() +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:INIT:ESB\n")); + + // Power on radio unit + NRF_RADIO->POWER = 1; + + // Enable radio interrupt + NVIC_SetPriority(RADIO_IRQn, 1); + NVIC_ClearPendingIRQ(RADIO_IRQn); + NVIC_EnableIRQ(RADIO_IRQn); + + // Enable timer interrupt + NVIC_SetPriority(NRF5_RADIO_TIMER_IRQN, 2); + NVIC_ClearPendingIRQ(NRF5_RADIO_TIMER_IRQN); + NVIC_EnableIRQ(NRF5_RADIO_TIMER_IRQN); + + // Clear events + NRF_RADIO->EVENTS_END = 0; + NRF_RADIO->EVENTS_READY = 0; + NRF_RADIO->EVENTS_BCMATCH = 0; + + // Disable all interrupts + NRF_RADIO->INTENCLR = (uint32_t)~0; + + // Select interrupt events + NRF_RADIO->INTENSET = RADIO_INTENSET_END_Msk | RADIO_INTENSET_BCMATCH_Msk; + + // Configure radio parameters: tx power + NRF_RADIO->TXPOWER = tx_power_level; + + // Configure radio parameters: radio channel + NRF_RADIO->FREQUENCY = MY_NRF5_ESB_CHANNEL; + + // Configure radio parameters: data rate + NRF_RADIO->MODE = MY_NRF5_ESB_MODE; + + // Configure radio parameters: CRC16 + NRF_RADIO->CRCCNF = (RADIO_CRCCNF_LEN_Two << RADIO_CRCCNF_LEN_Pos); + NRF_RADIO->CRCINIT = 0xFFFFUL; + NRF_RADIO->CRCPOLY = 0x11021UL; + + // Radio address config + uint8_t address[MY_NRF5_ESB_ADDR_WIDTH] = {MY_NRF5_ESB_BASE_RADIO_ID}; + + // Configure addresses + NRF_RADIO->PREFIX0 = (NRF5_ESB_NODE_ADDR_MSK | reverse_byte(node_address) << + (NRF5_ESB_NODE_ADDR << 5)); + NRF_RADIO->BASE0 = reverse_byte(address[1]) << 24 | + reverse_byte(address[2]) << 16 | + reverse_byte(address[3]) << 8 | reverse_byte(address[4]); + NRF_RADIO->BASE1 = reverse_byte(address[1]) << 24 | + reverse_byte(address[2]) << 16 | + reverse_byte(address[3]) << 8 | reverse_byte(address[4]); + NRF_RADIO->PREFIX1 = NRF5_ESB_TX_ADDR_MSK; // Broadcast and send address + + // Enable listening on Node and BC address + NRF_RADIO->RXADDRESSES = (1 << NRF5_ESB_NODE_ADDR) | (1 << NRF5_ESB_BC_ADDR); + + // Packet configuration for NRF24 compatibility + NRF_RADIO->PCNF0 = (6 << RADIO_PCNF0_LFLEN_Pos) | // 6 Bits length field + (0 << RADIO_PCNF0_S0LEN_Pos) | // No S0 +#ifdef RADIO_PCNF0_S1INCL_Pos + (1 << RADIO_PCNF0_S1INCL_Pos) | // Force include S1 in RAM +#endif + (3 << RADIO_PCNF0_S1LEN_Pos); // 3 Bits S1 (NOACK and PID) + + // Packet configuration + NRF_RADIO->PCNF1 = + (MAX_MESSAGE_LENGTH << RADIO_PCNF1_MAXLEN_Pos) | // maximum length + (0 << RADIO_PCNF1_STATLEN_Pos) | // minimum message length + ((MY_NRF5_ESB_ADDR_WIDTH - 1) << RADIO_PCNF1_BALEN_Pos) | // Set base address length + (RADIO_PCNF1_ENDIAN_Big << RADIO_PCNF1_ENDIAN_Pos) | // Big endian + (RADIO_PCNF1_WHITEEN_Disabled << RADIO_PCNF1_WHITEEN_Pos); // Disable whitening + + // HINT: Fast ramp up can enabled here. Needs more code on other lines + // Fast ramp up isn't supported by NRF24 and NRF51 series. + + // Set bitcounter to trigger interrupt after ACK bit + NRF_RADIO->BCC = NRF5_ESB_BITCOUNTER; + +#ifdef NRF51 + // Enable timer + NRF5_RADIO_TIMER->POWER = 1; +#endif + // Stop timer, if running + NRF5_RADIO_TIMER->TASKS_STOP = 1; + // Prepare timer running at 1 MHz/1us + NRF5_RADIO_TIMER->PRESCALER = 4; + // Timer mode + NRF5_RADIO_TIMER->MODE = TIMER_MODE_MODE_Timer; + // in 16 Bit mode + NRF5_RADIO_TIMER->BITMODE = TIMER_BITMODE_BITMODE_16Bit << TIMER_BITMODE_BITMODE_Pos; + // Stop timer when CC0 reached + NRF5_RADIO_TIMER->SHORTS = + TIMER_SHORTS_COMPARE1_CLEAR_Msk | TIMER_SHORTS_COMPARE1_STOP_Msk; + // Reset timer + NRF5_RADIO_TIMER->TASKS_CLEAR = 1; + // Reset compare events + NRF5_RADIO_TIMER->EVENTS_COMPARE[0] = 0; + NRF5_RADIO_TIMER->EVENTS_COMPARE[1] = 0; + // Enable interrupt + NRF5_RADIO_TIMER->INTENSET = TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos; + NRF5_RADIO_TIMER->INTENSET = TIMER_INTENSET_COMPARE1_Enabled << TIMER_INTENSET_COMPARE1_Pos; + + /** Configure PPI (Programmable peripheral interconnect) */ + // Start timer on END event + NRF_PPI->CH[NRF5_ESB_PPI_TIMER_START].EEP = (uint32_t)&NRF_RADIO->EVENTS_END; + NRF_PPI->CH[NRF5_ESB_PPI_TIMER_START].TEP = (uint32_t)&NRF5_RADIO_TIMER->TASKS_START; + // Disable Radio after CC[0] + NRF_PPI->CH[NRF5_ESB_PPI_TIMER_RADIO_DISABLE].EEP = (uint32_t)&NRF5_RADIO_TIMER->EVENTS_COMPARE[0]; + NRF_PPI->CH[NRF5_ESB_PPI_TIMER_RADIO_DISABLE].TEP = (uint32_t)&NRF_RADIO->TASKS_DISABLE; + + // Set internal variables + radio_disabled = true; + + return true; +} + +static void NRF5_ESB_powerDown() +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:PD\n")); + + // Disable inerrupt + NVIC_DisableIRQ(RADIO_IRQn); + NVIC_DisableIRQ(NRF5_RADIO_TIMER_IRQN); + + // Clear PPI + NRF_PPI->CHENCLR = NRF5_ESB_PPI_BITS; + + // Save power level + tx_power_level = NRF_RADIO->TXPOWER; + + // Power off readio unit + NRF_RADIO->POWER = 0; + + // Shutdown timer + NRF5_RADIO_TIMER->TASKS_SHUTDOWN = 1; +#ifdef NRF51 + // Power off timer + NRF5_RADIO_TIMER->POWER = 0; +#endif +} + +static void NRF5_ESB_powerUp() +{ + // Not needed +} + +static void NRF5_ESB_sleep() +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:SLP\n")); + NRF5_ESB_powerDown(); +} + +static void NRF5_ESB_standBy() +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:SBY\n")); + NRF5_ESB_initialize(); +} + +static bool NRF5_ESB_sanityCheck() +{ + // always true + return true; +} + +static void NRF5_ESB_setNodeAddress(const uint8_t address) +{ + node_address = address; + NRF_RADIO->PREFIX0 = (NRF_RADIO->PREFIX0 & NRF5_ESB_NODE_ADDR_MSK) | + reverse_byte(node_address) << (NRF5_ESB_NODE_ADDR << 5); +} + +static uint8_t NRF5_ESB_getNodeID() +{ + return reverse_byte((NRF_RADIO->PREFIX0 & NRF5_ESB_NODE_ADDR_MSK) >> (NRF5_ESB_NODE_ADDR << 5)); +} + +static void NRF5_ESB_startListening() +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:STL\n")); + + // Check if radio is initialized + if (NRF_RADIO->POWER == 0) { + NRF5_ESB_initialize(); + } + + // Enable Ready interrupt + NRF_RADIO->INTENSET = RADIO_INTENSET_READY_Msk; + + // Enable RX when ready, Enable RX after disabling task + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_RX; + + // Switch to RX + if (NRF_RADIO->STATE == RADIO_STATE_STATE_Disabled) { + NRF_RADIO->TASKS_RXEN = 1; + } else { + NRF_RADIO->TASKS_DISABLE = 1; + } + + radio_disabled = false; +} + +static bool NRF5_ESB_isDataAvailable() +{ + return rx_circular_buffer.available() > 0; +} + +static uint8_t NRF5_ESB_readMessage(void *data) +{ + // get content from rx buffer + NRF5_ESB_Packet *buffer = rx_circular_buffer.getBack(); + // Nothing to read? + if (buffer == NULL) { + return 0; + } + + // copy content + memcpy(data, buffer->data, buffer->len); + uint8_t ret = buffer->len; + rssi_rx = 0-buffer->rssi; + + // Debug message +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + NRF5_RADIO_DEBUG(PSTR("NRF5:RX:LEN=%d,NOACK=%d,PID=%d,RSSI=%d,RX=%d\n"), + buffer->len, buffer->noack, buffer->pid, rssi_rx, buffer->rxmatch); +#endif + + // release buffer + rx_circular_buffer.popBack(); + + // Check if radio was disabled by buffer end + if (radio_disabled == true) { + // Wait until disabling radio is finised + while (NRF_RADIO->EVENTS_DISABLED == 0) + ; + // Enable radio + NRF5_ESB_startListening(); + } + + return ret; +} + +void NRF5_ESB_endtx(); +void NRF5_ESB_starttx() +{ + if (tx_retries > 0) { + // Prevent radio to write into TX memory while receiving + if (NRF_RADIO->PACKETPTR != (uint32_t)&tx_buffer) { + // Disable shorts + NRF_RADIO->SHORTS = 0; + // Disable radio + NRF_RADIO->TASKS_DISABLE = 1; + } + + // Mark TX as unfinised + events_end_tx = false; + + // Configure TX address to address at index NRF5_ESB_TX_ADDR + NRF_RADIO->TXADDRESS = NRF5_ESB_TX_ADDR; + + // Enable TX when ready, Enable TX after disabling task + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_TX; + + // reset timer + NRF_RESET_EVENT(NRF5_RADIO_TIMER->EVENTS_COMPARE[0]); + NRF_RESET_EVENT(NRF5_RADIO_TIMER->EVENTS_COMPARE[1]); + NRF5_RADIO_TIMER->TASKS_STOP = 1; + NRF5_RADIO_TIMER->TASKS_CLEAR = 1; + // Set retransmit time + NRF5_RADIO_TIMER->CC[1] = NRF5_ESB_ARD - NRF5_ESB_RAMP_UP_TIME; + // Set radio disable time to ACK_WAIT time + NRF5_RADIO_TIMER->CC[0] = NRF5_ESB_ACK_WAIT; + + // Set PPI + NRF_PPI->CHENSET = NRF5_ESB_PPI_BITS; + + // Disable Ready interrupt + NRF_RADIO->INTENCLR = RADIO_INTENSET_READY_Msk; + + // Set buffer + NRF_RADIO->PACKETPTR = (uint32_t)&tx_buffer; + + // Switch to TX + if (NRF_RADIO->STATE == RADIO_STATE_STATE_Disabled) { + NRF_RADIO->TASKS_TXEN = 1; + } else { + NRF_RADIO->TASKS_DISABLE = 1; + } + } else { + // finised TX + NRF5_ESB_endtx(); + } + tx_retries--; +} + +void NRF5_ESB_endtx() +{ + // Clear PPI + NRF_PPI->CHENCLR = NRF5_ESB_PPI_BITS; + // Enable Ready interrupt + NRF_RADIO->INTENSET = RADIO_INTENSET_READY_Msk; + // Stop Timer + NRF5_RADIO_TIMER->TASKS_STOP = 1; + // Shutdown timer + NRF5_RADIO_TIMER->TASKS_SHUTDOWN = 1; + // Mark TX as end + events_end_tx = true; + // Debug output +#ifdef NRF5_ESB_DEBUG_INT_TX_END + NRF5_RADIO_DEBUG(PSTR("NRF5:INT:ENDTX\n")); +#endif +} + +static bool NRF5_ESB_sendMessage(uint8_t recipient, const void *buf, uint8_t len, const bool noACK) +{ + NRF5_RADIO_DEBUG(PSTR("NRF5:SND:TO=%d,LEN=%d,PID=%d,NOACK=%d\n"), recipient, len, tx_buffer.pid, + tx_buffer.noack); // send message + // Check if radio is initialized + if (NRF_RADIO->POWER == 0) { + NRF5_ESB_initialize(); + } + + // check length and truncate data + if (len > MAX_MESSAGE_LENGTH) { + len = MAX_MESSAGE_LENGTH; + } + + // copy data to tx_buffer + memcpy(&tx_buffer.data[0], buf, len); + + // build metadata + tx_buffer.len = len; +#ifndef MY_NRF5_ESB_REVERSE_ACK + tx_buffer.noack = noACK || recipient==BROADCAST_ADDRESS; +#else + // reverse the noack bit + tx_buffer.noack = !(noACK || recipient==BROADCAST_ADDRESS); +#endif + tx_buffer.pid++; + + // Calculate number of retries + tx_retries = ((recipient == BROADCAST_ADDRESS)?(NRF5_ESB_BC):(NRF5_ESB_ARC)); + int8_t tx_retries_start = tx_retries; + + // This line disables sending a broadcast packet multiple times + // ack_received = (recipient == BROADCAST_ADDRESS); + // This line force to send an boradcast packet multiple times like NRF24 + ack_received = tx_buffer.noack; + + // configure TX address + NRF_RADIO->PREFIX1 = (NRF_RADIO->PREFIX1 & NRF5_ESB_TX_ADDR_MSK) | + (reverse_byte(recipient) << (NRF5_ESB_TX_ADDR - 4)); + + // Enable listening on Node, BC and TX address + NRF_RADIO->RXADDRESSES = (1 << NRF5_ESB_NODE_ADDR) | (1 << NRF5_ESB_BC_ADDR) | + (1 << NRF5_ESB_TX_ADDR); + + // Set RSSI to invalid + rssi_tx = INVALID_RSSI; + + NRF5_ESB_starttx(); + + // Wait for end of transmission +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + uint32_t wakeups = 0; +#endif + while (events_end_tx == false) { + // Power off CPU until next interrupt + hwSleep(); + // hwWaitForInterrupt(); +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + wakeups++; +#endif + } + + // Enable listening on Node and BC address + NRF_RADIO->RXADDRESSES = (1 << NRF5_ESB_NODE_ADDR) | (1 << NRF5_ESB_BC_ADDR); + + // Calculate RSSI + if (rssi_tx == INVALID_RSSI) { + // calculate pseudo-RSSI based on retransmission counter (ARC) + // min -104dBm at 250kBps + // Arbitrary definition: ARC 0 == -29, ARC 15 = -104 + rssi_tx = (-29 - (8 * (tx_retries_start - tx_retries))); + } + +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + NRF5_RADIO_DEBUG(PSTR("NRF5:SND:END=%d,ACK=%d,RTRY=%d,RSSI=%d,WAKE=%d\n"), + events_end_tx, ack_received, tx_retries_start - tx_retries, rssi_tx, wakeups); +#endif + + return ack_received; +}; + +static int16_t NRF5_ESB_getSendingRSSI() +{ + return rssi_tx; +} + +static int16_t NRF5_ESB_getReceivingRSSI() +{ + return rssi_rx; +} + +/* + * Internal helper functions + */ + +// Reverse a byte for address +static uint8_t reverse_byte(uint8_t address) +{ +#if __CORTEX_M >= (0x01U) + return __REV(__RBIT(address)); +#else + address = ((address * 0x0802LU & 0x22110LU) | (address * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16; +#endif + return address; +} + +// Calculate time to transmit an byte in us as bit shift -> 2^X +static inline uint8_t NRF5_ESB_byte_time() +{ + if ((MY_NRF5_ESB_MODE == NRF5_1MBPS) or + (MY_NRF5_ESB_MODE == NRF5_BLE_1MBPS)) { + return (3); + } else if (MY_NRF5_ESB_MODE == NRF5_2MBPS) { + return (2); + } else if (MY_NRF5_ESB_MODE == NRF5_250KBPS) { + return (5); + } +} + +extern "C" { + /** Radio Interrupt handler */ + void RADIO_IRQHandler() + { + /** Bitcounter event is used to switch between RX/TX + * In RX mode, when an ACK required packet is received, switch to TX, + * elsewhere start RX again. + * In TX mode switch always to RX. + */ + if (NRF_RADIO->EVENTS_BCMATCH == 1) { + NRF_RESET_EVENT(NRF_RADIO->EVENTS_BCMATCH); + + // Disable bitcounter + NRF_RADIO->TASKS_BCSTOP = 1; + + // In RX mode -> prepare ACK or RX + if (NRF_RADIO->STATE == RADIO_STATE_STATE_Rx) { + // ACK only for node address and unset noack bit +#ifndef MY_NRF5_ESB_REVERSE_ACK + if ((NRF_RADIO->RXMATCH != NRF5_ESB_NODE_ADDR) || (rx_buffer->noack)) { +#else + if ((NRF_RADIO->RXMATCH != NRF5_ESB_NODE_ADDR) || (!rx_buffer->noack)) { +#endif + // Handle incoming ACK packet + if (NRF_RADIO->RXMATCH == NRF5_ESB_TX_ADDR) { + /** Calculate time to switch radio off + * This is an ACK packet, the radio is disabled by Timer + * event after CC[0], calculate the time switching of the + * radio. + */ + // Read current timer value + NRF5_RADIO_TIMER->TASKS_CAPTURE[0] = 1; + + // Set Timer compare register 0 to end of packet (len+CRC) + NRF5_RADIO_TIMER->CC[0] += ((rx_buffer->len + 3) << NRF5_ESB_byte_time()); +#if __CORTEX_M == 0x04 + // read back to clear cache + (void)NRF5_RADIO_TIMER->CC[0]; +#endif + } + // No ACK -> Start RX after END + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_RX; + } else { + // Send ACK after END, an empty packet is provided in READY event + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_RX_TX; + // HINT: Fast ramp must be disabled for every device with unknown + // support (payload). Needs more code on other lines + } + } else { + // Current mode is TX: + // After TX the Radio has to be always in RX mode to + // receive ACK or start implicit listen mode after send. + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_TX_RX; + // HINT: Fast ramp up can enabled here. Needs more code on other lines + } + } + + /** Ready event is generated before RX starts + * An free rx buffer is allocated or radio is disabled on failures + */ + if (NRF_RADIO->EVENTS_READY == 1) { + NRF_RESET_EVENT(NRF_RADIO->EVENTS_READY); + // Fetch a new buffer + rx_buffer = rx_circular_buffer.getFront(); + + // Is buffer valid? + if (rx_buffer == NULL) { + // No buffer -> disable listening + radio_disabled = true; + NRF_RADIO->SHORTS = 0; + NRF_RADIO->EVENTS_DISABLED = 0; + NRF_RADIO->TASKS_DISABLE = 1; + return; + } + + // Configure DMA target address + NRF_RADIO->PACKETPTR = (uint32_t)rx_buffer; + + /* Don't care about if next packet RX or ACK, + * prepare current rx_buffer to send an ACK */ + + // Set outgoing address to node address for ACK packages + NRF_RADIO->TXADDRESS = NRF5_ESB_NODE_ADDR; + + // HINT: Fast ramp up: Add a fast ramp up signaling payload here. Needs more + // code on other lines + } + + /** This event is generated after TX or RX finised + */ + if (NRF_RADIO->EVENTS_END == 1) { + NRF_RESET_EVENT(NRF_RADIO->EVENTS_END); + + // Enable ACK bitcounter for next packet + NRF_RADIO->BCC = NRF5_ESB_BITCOUNTER; + + // End of RX packet + if ((NRF_RADIO->STATE == RADIO_STATE_STATE_Rx) or + (NRF_RADIO->STATE == RADIO_STATE_STATE_RxIdle) or + (NRF_RADIO->STATE == RADIO_STATE_STATE_RxDisable) or + (NRF_RADIO->STATE == RADIO_STATE_STATE_TxRu)) { + if (NRF_RADIO->CRCSTATUS) { + // Ensure no ACK package is recieved + if (NRF_RADIO->RXMATCH != NRF5_ESB_TX_ADDR) { + // calculate a package id + uint32_t pkgid = rx_buffer->pid << 16 | NRF_RADIO->RXCRC; + if (pkgid != package_ids[NRF_RADIO->RXMATCH]) { + // correct package -> store id to dedect duplicates + package_ids[NRF_RADIO->RXMATCH] = pkgid; + rx_buffer->rssi = ack_buffer.data[0] = NRF_RADIO->RSSISAMPLE; +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + // Store debug data + rx_buffer->rxmatch = NRF_RADIO->RXMATCH; +#endif + // Push data to buffer + rx_circular_buffer.pushFront(rx_buffer); + + // Prepare ACK package + ack_buffer.pid++; + ack_buffer.len=1; // data[0] is set some lines before +#ifndef MY_NRF5_ESB_REVERSE_ACK + ack_buffer.noack = 1; +#else + ack_buffer.noack = 0; +#endif + + // Set pointer to ACK packet + NRF_RADIO->PACKETPTR = (uint32_t)&ack_buffer; + } + } else { + // ACK package received, ducplicates are accepted + + // rssi value in ACK included? + if (rx_buffer->len == 1) { + rssi_tx = 0-rx_buffer->data[0]; + } + // notify TX process + ack_received = true; + // End TX + NRF5_ESB_endtx(); + } + } else { + /** Invalid CRC -> Switch back to RX, Stop sending ACK */ + + // Enable RX when ready, Enable RX after disabling task + NRF_RADIO->SHORTS = NRF5_ESB_SHORTS_RX; + + // Start disabling radio -> switch to rx by shorts + NRF_RADIO->TASKS_DISABLE = 1; + } + } else { + // TX end + } + } + } + + /** Timer Interrupt Handler + * This timer is used to handle TX retransmit timing + * + */ + void NRF5_RADIO_TIMER_IRQ_HANDLER() + { + if (NRF5_RADIO_TIMER->EVENTS_COMPARE[0] == 1) { + NRF_RESET_EVENT(NRF5_RADIO_TIMER->EVENTS_COMPARE[0]); + } + + if (NRF5_RADIO_TIMER->EVENTS_COMPARE[1] == 1) { + NRF5_RADIO_TIMER->TASKS_STOP = 1; + NRF_RESET_EVENT(NRF5_RADIO_TIMER->EVENTS_COMPARE[1]); + if (ack_received == false) { + // missing ACK, start TX again + NRF5_ESB_starttx(); + } else { + // finised TX + NRF5_ESB_endtx(); + } + } + } +} // extern "C" diff --git a/drivers/NRF5/Radio_ESB.h b/drivers/NRF5/Radio_ESB.h new file mode 100644 index 0000000..87a8bb5 --- /dev/null +++ b/drivers/NRF5/Radio_ESB.h @@ -0,0 +1,157 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Frank Holtz + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef __NRF5_ESB_H__ +#define __NRF5_ESB_H__ + +#include "Radio.h" +#include + +// Check maximum messae length +#if MAX_MESSAGE_LENGTH > (32) +#error "Unsupported message length. (MAX_MESSAGE_LENGTH)" +#endif + +// check rx buffer size +#if MY_NRF5_ESB_RX_BUFFER_SIZE < (4) +#error "MY_NRF5_ESB_RX_BUFFER_SIZE must be greater than 3." +#endif + +/** Wait for start of an ACK packet in us + * Calculation: ramp up time + packet header (57 Bit): round to 9 Byte + * If you don't receive ACK packages, you have to increase this value. + * My measured (Aurduino Uno+NRF24L01P) minimal timeouts: + * 250kbit 411us -> 182 us to ACK start + * 1MBit 205us -> 147 us + * 2MBit 173us -> 143 us + */ +#define NRF5_ESB_ACK_WAIT \ + ((NRF5_ESB_RAMP_UP_TIME << 1) + (9 << NRF5_ESB_byte_time())) + +// auto retry delay in us, don't set this value < 1500us@250kbit +#define NRF5_ESB_ARD (1500) + +// auto retry count +#define NRF5_ESB_ARC (15) + +// How often broadcast messages are send +#define NRF5_ESB_BC (1) + +// Node address index +#define NRF5_ESB_NODE_ADDR (0) +#define NRF5_ESB_NODE_ADDR_MSK (0xffffff00UL) + +// TX address index +#define NRF5_ESB_TX_ADDR (4) +#define NRF5_ESB_TX_ADDR_MSK (0xffffff00UL) + +// BC address index +#define NRF5_ESB_BC_ADDR (7) +#define NRF5_ESB_BC_ADDR_MSK (0xffffffffUL) + +// Shorts for RX mode +#define NRF5_ESB_SHORTS_RX \ + (RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_START_Msk | \ + RADIO_SHORTS_DISABLED_RXEN_Msk | RADIO_SHORTS_ADDRESS_BCSTART_Msk | \ + RADIO_SHORTS_ADDRESS_RSSISTART_Msk | RADIO_SHORTS_DISABLED_RSSISTOP_Msk) + +// Shorts for TX mode +#define NRF5_ESB_SHORTS_TX \ + (RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_END_START_Msk | \ + RADIO_SHORTS_DISABLED_TXEN_Msk | RADIO_SHORTS_ADDRESS_BCSTART_Msk) + +// Shorts to switch from RX to TX +#define NRF5_ESB_SHORTS_RX_TX \ + (RADIO_SHORTS_END_DISABLE_Msk | RADIO_SHORTS_DISABLED_TXEN_Msk | \ + RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_ADDRESS_BCSTART_Msk) + +// Shorts to switch from TX to RX +#define NRF5_ESB_SHORTS_TX_RX \ + (RADIO_SHORTS_END_DISABLE_Msk | RADIO_SHORTS_DISABLED_RXEN_Msk | \ + RADIO_SHORTS_READY_START_Msk | RADIO_SHORTS_ADDRESS_BCSTART_Msk | \ + RADIO_SHORTS_ADDRESS_RSSISTART_Msk | RADIO_SHORTS_DISABLED_RSSISTOP_Msk) + +// PPI Channels +#define NRF5_ESB_PPI_TIMER_START 14 +#define NRF5_ESB_PPI_TIMER_RADIO_DISABLE 15 +#define NRF5_ESB_PPI_BITS \ + ((1 << NRF5_ESB_PPI_TIMER_START) | \ + (1 << NRF5_ESB_PPI_TIMER_RADIO_DISABLE)) + +/** Bitcounter for Packet Control Field length + * 6 Bits address length + 3 Bits S1 (NOACK + PID) + */ +#define NRF5_ESB_BITCOUNTER (9) + +/** ramp up time + * Time to activate radio TX or RX mode + */ +#define NRF5_ESB_RAMP_UP_TIME (130) + + +static bool NRF5_ESB_initialize(); +static void NRF5_ESB_powerDown(); +static void NRF5_ESB_powerUp(); +static void NRF5_ESB_sleep(); +static void NRF5_ESB_standBy(); +static bool NRF5_ESB_sanityCheck(); + +static void NRF5_ESB_setNodeAddress(const uint8_t address); +static uint8_t NRF5_ESB_getNodeID(); + +static void NRF5_ESB_startListening(); +static bool NRF5_ESB_isDataAvailable(); +static uint8_t NRF5_ESB_readMessage(void *data); + +static bool NRF5_ESB_sendMessage(uint8_t recipient, const void *buf, uint8_t len, const bool noACK); + +static int16_t NRF5_ESB_getSendingRSSI(); +static int16_t NRF5_ESB_getReceivingRSSI(); + +// Calculate time to transmit an byte in us as bit shift -> 2^X +static inline uint8_t NRF5_ESB_byte_time(); + +/** Structure of radio rackets + */ +typedef struct nrf5_radio_packet_s { + // structure written by radio unit + struct { + uint8_t len; + union { + uint8_t s1; + struct { + uint8_t noack : 1; + uint8_t pid : 2; + }; + }; + uint8_t data[MAX_MESSAGE_LENGTH]; + int8_t rssi; + /** Debug data structure */ +#ifdef MY_DEBUG_VERBOSE_NRF5_ESB + uint32_t rxmatch; +#endif + } +#ifndef DOXYGEN + __attribute__((packed)); +#endif +} NRF5_ESB_Packet; + +#endif // __NRF5_H__ diff --git a/drivers/NRF5/nrf5_wiring_constants.h b/drivers/NRF5/nrf5_wiring_constants.h new file mode 100644 index 0000000..0e07885 --- /dev/null +++ b/drivers/NRF5/nrf5_wiring_constants.h @@ -0,0 +1,42 @@ +/* + * nRF5 output modes + */ +// Standard 0, Standard 1 +#ifndef OUTPUT_S0S1 +#define OUTPUT_S0S1 (0x10) +#endif + +// High Drive 0, Standard 1 +#ifndef OUTPUT_H0S1 +#define OUTPUT_H0S1 (0x11) +#endif + +// Standard 0, High Drive 1 +#ifndef OUTPUT_S0H1 +#define OUTPUT_S0H1 (0x12) +#endif + +// High Drive both +#ifndef OUTPUT_H0H1 +#define OUTPUT_H0H1 (0x13) +#endif + +// Disconnected 0, Standard 1 +#ifndef OUTPUT_D0S1 +#define OUTPUT_D0S1 (0x14) +#endif + +// Disconnected 0, High Drive 1 +#ifndef OUTPUT_D0H1 +#define OUTPUT_D0H1 (0x15) +#endif + +// Standard 0, Disconnected 1 +#ifndef OUTPUT_S0D1 +#define OUTPUT_S0D1 (0x16) +#endif + +// High Drive 0, Disconnected 1 +#ifndef OUTPUT_H0D1 +#define OUTPUT_H0D1 (0x17) +#endif diff --git a/drivers/NRF5/nrf5_wiring_digital.c b/drivers/NRF5/nrf5_wiring_digital.c new file mode 100644 index 0000000..9285d13 --- /dev/null +++ b/drivers/NRF5/nrf5_wiring_digital.c @@ -0,0 +1,96 @@ +/* + Copyright (c) 2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2017 Frank Holtz All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "nrf.h" + +#include "Arduino.h" +#include "nrf5_wiring_constants.h" + +#ifdef __cplusplus +extern "C" { +#endif +void nrf5_pinMode(uint32_t ulPin, uint32_t ulMode) +{ + if (ulPin >= PINS_COUNT) { + return; + } + + ulPin = g_ADigitalPinMap[ulPin]; + + // Set pin mode according to chapter '22.6.3 I/O Pin Configuration' + switch (ulMode) { + case INPUT: + // Set pin to input mode + NRF_GPIO->PIN_CNF[ulPin] = + ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | + ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) | + ((uint32_t)GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | + ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + break; + + case INPUT_PULLUP: + // Set pin to input mode with pull-up resistor enabled + NRF_GPIO->PIN_CNF[ulPin] = + ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | + ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) | + ((uint32_t)GPIO_PIN_CNF_PULL_Pullup << GPIO_PIN_CNF_PULL_Pos) | + ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + break; + + case INPUT_PULLDOWN: + // Set pin to input mode with pull-down resistor enabled + NRF_GPIO->PIN_CNF[ulPin] = + ((uint32_t)GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos) | + ((uint32_t)GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) | + ((uint32_t)GPIO_PIN_CNF_PULL_Pulldown << GPIO_PIN_CNF_PULL_Pos) | + ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + break; + + case OUTPUT: + // Set pin to output mode + NRF_GPIO->PIN_CNF[ulPin] = + ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) | + ((uint32_t)GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) | + ((uint32_t)GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | + ((uint32_t)GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + break; + + default: + // calculate nRF specific output modes + if ((ulMode >= OUTPUT_S0S1) && (ulMode <= OUTPUT_H0D1)) { + // Set pin to given output mode + NRF_GPIO->PIN_CNF[ulPin] = + ((uint32_t)GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos) | + ((uint32_t)GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) | + ((uint32_t)GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | + ((uint32_t)(ulMode - OUTPUT_S0S1) << GPIO_PIN_CNF_DRIVE_Pos) | + ((uint32_t)GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos); + } + break; + } +} + +#ifdef __cplusplus +} +#endif diff --git a/drivers/NRF5/wdt.h b/drivers/NRF5/wdt.h new file mode 100644 index 0000000..e0c492e --- /dev/null +++ b/drivers/NRF5/wdt.h @@ -0,0 +1,185 @@ +/* Copyright (c) 2002, 2004 Marek Michalkiewicz + Copyright (c) 2005, 2006, 2007 Eric B. Weddington + Copyright (c) 2016 Frank Holtz + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of the copyright holders nor the names of + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. */ + +/* $Id$ */ + +/* + avr/wdt.h - macros for AVR watchdog timer + */ + +#ifndef _NRF5_WDT_H_ +#define _NRF5_WDT_H_ + +#include +#include + +/** \file */ +/** \defgroup avr_watchdog : Watchdog timer handling + \code #include \endcode + + This header file declares the interface to some inline macros + handling the watchdog timer like present in many AVR devices. + In order to prevent the watchdog timer configuration from being + accidentally altered by a crashing application, a special timed + sequence is required in order to change it. The macros within + this header file handle the required sequence automatically + before changing any value. Interrupts will be disabled during + the manipulation. +*/ + +/** + \ingroup avr_watchdog + Reset the watchdog timer. When the watchdog timer is enabled, + a call to this instruction is required before the timer expires, + otherwise a watchdog-initiated device reset will occur. +*/ + +#define wdt_reset() NRF_WDT->RR[0] = WDT_RR_RR_Reload + +/** + \ingroup avr_watchdog + Enable the watchdog timer, configuring it for expiry after + \c timeout (ms). + + The WDT is running in sleep mode. + + See also the symbolic constants \c WDTO_15MS et al. +*/ +#define wdt_enable(timeout) \ + NRF_WDT->CONFIG = NRF_WDT->CONFIG = (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos) | ( WDT_CONFIG_SLEEP_Run << WDT_CONFIG_SLEEP_Pos); \ + NRF_WDT->CRV = (32768*timeout)/1000; \ + NRF_WDT->RREN |= WDT_RREN_RR0_Msk; \ + NRF_WDT->TASKS_START = 1 + +/** + \ingroup avr_watchdog + Disable the watchdog timer. On nRF5 the timer cannot disabled. + The period is set to 36h of CPU run time. The WDT is stopped in sleep mode. +*/ +#define wdt_disable() \ + NRF_WDT->CONFIG = NRF_WDT->CONFIG = (WDT_CONFIG_HALT_Pause << WDT_CONFIG_HALT_Pos) | ( WDT_CONFIG_SLEEP_Pause << WDT_CONFIG_SLEEP_Pos); \ + NRF_WDT->CRV = 4294967295 + +/** + \ingroup avr_watchdog + Symbolic constants for the watchdog timeout. + + Possible timeout values are: 15 ms, 30 ms, 60 ms, 120 ms, 250 ms, + 500 ms, 1 s, 2 s, 4s, 8s. (Not all devices allow 4 s or 8 s.) + Symbolic constants are formed by the prefix + \c WDTO_, followed by the time. + + Example that would select a watchdog timer expiry of approximately + 500 ms: + \code + wdt_enable(WDTO_500MS); + \endcode +*/ +#define WDTO_15MS 15 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_30MS 30 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_60MS 60 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_120MS 120 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_250MS 250 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_500MS 500 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_1S 1000 + +/** \ingroup avr_watchdog + See \c WDTO_15MS */ +#define WDTO_2S 2000 + +/** \ingroup avr_watchdog + See \c WDTO_15MS + Note: This is only available on the + ATtiny2313, + ATtiny24, ATtiny44, ATtiny84, ATtiny84A, + ATtiny25, ATtiny45, ATtiny85, + ATtiny261, ATtiny461, ATtiny861, + ATmega48, ATmega88, ATmega168, + ATmega48P, ATmega88P, ATmega168P, ATmega328P, + ATmega164P, ATmega324P, ATmega644P, ATmega644, + ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561, + ATmega8HVA, ATmega16HVA, ATmega32HVB, + ATmega406, ATmega1284P, + AT90PWM1, AT90PWM2, AT90PWM2B, AT90PWM3, AT90PWM3B, AT90PWM216, AT90PWM316, + AT90PWM81, AT90PWM161, + AT90USB82, AT90USB162, + AT90USB646, AT90USB647, AT90USB1286, AT90USB1287, + ATtiny48, ATtiny88, + nRF51822, nRF52832 + */ +#define WDTO_4S 4000 + +/** \ingroup avr_watchdog + See \c WDTO_15MS + Note: This is only available on the + ATtiny2313, + ATtiny24, ATtiny44, ATtiny84, ATtiny84A, + ATtiny25, ATtiny45, ATtiny85, + ATtiny261, ATtiny461, ATtiny861, + ATmega48, ATmega48A, ATmega48PA, ATmega88, ATmega168, + ATmega48P, ATmega88P, ATmega168P, ATmega328P, + ATmega164P, ATmega324P, ATmega644P, ATmega644, + ATmega640, ATmega1280, ATmega1281, ATmega2560, ATmega2561, + ATmega8HVA, ATmega16HVA, ATmega32HVB, + ATmega406, ATmega1284P, + ATmega2564RFR2, ATmega256RFR2, ATmega1284RFR2, ATmega128RFR2, ATmega644RFR2, ATmega64RFR2 + AT90PWM1, AT90PWM2, AT90PWM2B, AT90PWM3, AT90PWM3B, AT90PWM216, AT90PWM316, + AT90PWM81, AT90PWM161, + AT90USB82, AT90USB162, + AT90USB646, AT90USB647, AT90USB1286, AT90USB1287, + ATtiny48, ATtiny88, + ATxmega16a4u, ATxmega32a4u, + ATxmega16c4, ATxmega32c4, + ATxmega128c3, ATxmega192c3, ATxmega256c3, + nRF51822, nRF52832 + */ +#define WDTO_8S 8000 + +#endif /* _NRF5_WDT_H_ */ diff --git a/drivers/NVM/Flash.h b/drivers/NVM/Flash.h new file mode 100644 index 0000000..4d53aef --- /dev/null +++ b/drivers/NVM/Flash.h @@ -0,0 +1,180 @@ +/* + * Flash.h - Flash library + * Original Copyright (c) 2017 Frank Holtz. All right reserved. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +/** + * @file Flash.h + * @brief Flash abstraction layer + * + * @ingroup NVM + * @details Nonvolatile Memory Class + * @{ + */ + +#pragma once + +#include +#include // for size_t + +#ifdef __RFduino__ +#include +#else +#include +#endif + +/* + * Define characteristics of Flash + * + * @def FLASH_ERASE_CYCLES + * @brief Specified number of erase cycles + * + * @def FLASH_PAGE_SIZE + * @brief Used/supported Flash page size + * + * @def FLASH_ERASE_PAGE_TIME + * @brief Time in ms to delete a page + * + * @def FLASH_WRITES_PER_WORD + * @brief How often a dataword (32 bit) can be written + * + * @def FLASH_WRITES_PER_PAGE + * @brief How many writes are allowed into a page + * + * @def FLASH_SUPPORTS_RANDOM_WRITE + * @brief Set this if it is allowed to write to a page in random order. + */ + +#if defined(NRF51) +#define FLASH_ERASE_CYCLES 20000 +#define FLASH_PAGE_SIZE 1024 +#define FLASH_ERASE_PAGE_TIME 23 +#define FLASH_SUPPORTS_RANDOM_WRITE true +#define FLASH_WRITES_PER_WORD 2 +#define FLASH_WRITES_PER_PAGE 512 +#elif defined(NRF52) +#define FLASH_ERASE_CYCLES 10000 +#define FLASH_PAGE_SIZE 4096 +#define FLASH_ERASE_PAGE_TIME 90 +#define FLASH_SUPPORTS_RANDOM_WRITE true +#define FLASH_WRITES_PER_WORD 32 +#define FLASH_WRITES_PER_PAGE 181 +#elif defined(NRF52840) +#define FLASH_ERASE_CYCLES 10000 +#define FLASH_PAGE_SIZE 4096 +#define FLASH_ERASE_PAGE_TIME 90 +#define FLASH_SUPPORTS_RANDOM_WRITE true +#define FLASH_WRITES_PER_WORD 2 +#define FLASH_WRITES_PER_PAGE 403 +#else +#define FLASH_ERASE_CYCLES 10000 +#define FLASH_PAGE_SIZE 4096 +#define FLASH_ERASE_PAGE_TIME 100 +//#define FLASH_SUPPORTS_RANDOM_WRITE true +#define FLASH_WRITES_PER_WORD 1 +#warning "Unknown platform. Please check the code." +#endif + +/** + * @class FlashClass + * @brief This class provides low-level access to internal Flash memory. + */ +class FlashClass +{ +public: + //---------------------------------------------------------------------------- + /** Constructor */ + FlashClass() {}; + //---------------------------------------------------------------------------- + /** Initialize Flash */ + void begin() {}; + /** Deinitialize Flash */ + void end() {}; + //---------------------------------------------------------------------------- + /* + * Physical flash geometry + */ + //---------------------------------------------------------------------------- + /** Page size in bytes + * @return Number of bytes + */ + uint32_t page_size() const; + //---------------------------------------------------------------------------- + /** Page address width in bits. Page size is 2^x + * @return Number of bits + */ + uint8_t page_size_bits() const; + //---------------------------------------------------------------------------- + /** Number of managed flash pages + * @return Number of pages + */ + uint32_t page_count() const; + //---------------------------------------------------------------------------- + /** Number of page erase cycles + * @return Number of page erase cycles + */ + uint32_t specified_erase_cycles() const; + //---------------------------------------------------------------------------- + /** Get a address of a page + * @param[in] page Page number, starting at 0 + * @return address of given page + */ + uint32_t *page_address(size_t page); + //---------------------------------------------------------------------------- + /* + * Accessing flash memory + */ + //---------------------------------------------------------------------------- + /** Erase a page of given size. Size must be page_size aligned! + * Take care about RADIO, WDT and Interrupt timing! + * @param[in] *address Pointer to page + * @param[in] size number of page aligned bytes to erase + */ + void erase(uint32_t *address, size_t size); + //---------------------------------------------------------------------------- + /** Erase the complete MCU. This can brick your device! + */ + void erase_all(); + //---------------------------------------------------------------------------- + /** write a aligned 32 bit word to flash. + * @param[in] *address 32 bit aligned pointer to destination word + * @param[in] value Data word to write + */ + void write(uint32_t *address, uint32_t value); + //---------------------------------------------------------------------------- + /** write a aligned block to flash. + * @param[in] *dst_address 32 bit aligned pointer to destination + * @param[in] *src_address 32 bit aligned pointer to source + * @param[in] word_count Number of words to write + */ + void write_block(uint32_t *dst_address, uint32_t *src_address, + uint16_t word_count); + +private: + // Wait until flash is ready + void wait_for_ready(); +}; + +extern FlashClass Flash; + +/** Load Hardwarespecific files */ +#ifdef NRF5 +#include "drivers/NRF5/Flash.cpp" +#else +#error "Unsupported platform." +#endif + +/** @} */ diff --git a/drivers/NVM/NVRAM.cpp b/drivers/NVM/NVRAM.cpp new file mode 100644 index 0000000..9117ba6 --- /dev/null +++ b/drivers/NVM/NVRAM.cpp @@ -0,0 +1,342 @@ +/* + NVRAM.cpp - Byte wise storage for Virtual Pages. + Original Copyright (c) 2017 Frank Holtz. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "NVRAM.h" + +// VirtualPage magic +#define NVRAM_MAGIC (0x7710fdb9) +// Number of emulated cells +#define NVRAM_LENGTH 3072 +// Log configuration: Address index in bits +#define NVRAM_ADDR_POS 20 +// Log configuration: Mask for comparsion (4k space) +#define NVRAM_ADDR_MASK 0xfff00000 +// Log configuration: Bit position of used address bitmap +#define NVRAM_BITMAP_POS 8 +// Log configuration: used address bitmap calulation +#define NVRAM_BITMAP_ADDR_SHIFT 8 +// Log configuration: Mask for bitmap extraction +#define NVRAM_BITMAP_MASK 0x000fff00 +#define ADDR2BIT(index) \ + ((1 << (index >> NVRAM_BITMAP_ADDR_SHIFT)) << NVRAM_BITMAP_POS) + +NVRAMClass NVRAM; + +uint16_t NVRAMClass::length() const +{ + return (NVRAM_LENGTH); +} + +void NVRAMClass::read_block(uint8_t *dst, uint16_t idx, uint16_t n) +{ + uint32_t *vpage; + uint16_t log_start, log_end; + + // find correct page + vpage = get_page(); + + // copy 0xff to dst when no page is available + if (vpage == (uint32_t *)~0) { + for (uint32_t i = 0; i < n; i++) { + ((uint8_t *)dst)[i] = 0xff; + } + return; + } + + // calculate actual log position + log_end = get_log_position(vpage); + if (log_end == 0) { + log_start = 1; + } else { + log_start = vpage[0] + 1; + } + /* + Serial.print("\r\nread_block idx="); + Serial.print(idx); + Serial.print(" n="); + Serial.print(n); + Serial.print("("); */ + while (n > 0) { + // Read cell + *dst = get_byte_from_page(vpage, log_start, log_end, idx); + // Serial.print(*dst, HEX); + // calculate next address + n--; + dst++; + idx++; + } + // Serial.println(")"); +} + +uint8_t NVRAMClass::read(const uint16_t idx) +{ + uint8_t ret; + read_block(&ret, idx, 1); + return ret; +} + +bool NVRAMClass::write_block(uint8_t *src, uint16_t idx, uint16_t n) +{ + uint32_t *vpage; + uint32_t bitmap; + uint16_t log_start, log_end; + + // find correct page + vpage = get_page(); + + // return on invalid page + if (vpage == (uint32_t *)~0) { + return false; + } + + // calculate actual log position + log_start = vpage[0] + 1; + log_end = get_log_position(vpage); + if (log_end > log_start) { + bitmap = vpage[log_end - 1] & NVRAM_BITMAP_MASK; + } else { + bitmap = 0; + } + + while (n > 0) { + // Read cell + uint8_t old_value = get_byte_from_page(vpage, log_start, log_end, idx); + uint8_t new_value = *src; + + // Have to write into log? + if (new_value != old_value) { + + // need to calculate a new page? + if (log_end >= VirtualPage.length()) { + vpage = switch_page(vpage, &log_start, &log_end); + if (vpage == (uint32_t *)~0) { + // do nothing if no page is available + return false; + } + bitmap = 0; + } + + // Add Entry into log + Flash.write(&vpage[log_end], (idx << NVRAM_ADDR_POS) | bitmap | + ADDR2BIT(idx) | (uint32_t)new_value); + log_end++; + } + + // calculate next address + n--; + src++; + idx++; + } + return true; +} + +bool NVRAMClass::write(uint16_t idx, uint8_t value) +{ + return (write_block(&value, idx, 1)); +} + +int NVRAMClass::write_prepare(uint16_t number) +{ + // find correct page + uint32_t *vpage = get_page(); + // Want to write to much or into an invalid page? + if ((vpage == (uint32_t *)~0) || (number > length())) { + return -1; + } + + // calculate actual log position + uint16_t log_end = get_log_position(vpage); + + // Calculate number of free bytes in log + int free_bytes = ((VirtualPage.length() - 1) - log_end); + + // switch page when + if (free_bytes < number) { + uint16_t log_start = vpage[0] + 1; + vpage = switch_page(vpage, &log_start, &log_end); + if (vpage == (uint32_t *)~0) { + // do nothing if no page is available + return -1; + } + log_end = get_log_position(vpage); + free_bytes = ((VirtualPage.length() - 1) - log_end); + } + return free_bytes; +} + +void NVRAMClass::clean_up(uint16_t write_preserve) +{ + VirtualPage.clean_up(); + if (write_preserve > 0) { + write_prepare(write_preserve); + } +} + +uint32_t *NVRAMClass::switch_page(uint32_t *old_vpage, uint16_t *log_start, + uint16_t *log_end) +{ + // Mark old page as in release + VirtualPage.release_prepare(old_vpage); + + // Get a blank page + uint32_t *new_vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length()); + if (new_vpage == (uint32_t *)~0) { + // failed + return new_vpage; + } + + // Store four bytes for map creation + uint32_t value; + + // Length of new map + uint16_t map_length = 0; + + // Build map +#ifdef FLASH_SUPPORTS_RANDOM_WRITE + // Copy current values + for (uint16_t i = 0; i < (NVRAM_LENGTH >> 2); i++) { + read_block((uint8_t *)&value, i << 2, 4); + if (value != (uint32_t)~0) { + // Value found + map_length = i + 1; + Flash.write(&new_vpage[i + 1], value); + } + } + // Store map length + Flash.write(new_vpage, map_length); +#else + // find map length + for (uint16_t i = (NVRAM_LENGTH >> 2); i > 0; i--) { + read_block((uint8_t *)&value, i << 2, 4); + if (value != (uint32_t)~0) { + // Value found + map_length = i; + break; + } + } + map_length++; + + // Store map length + Flash.write(new_vpage, map_length); + + // Copy current values + for (uint16_t i = 0; i <= map_length; i++) { + read_block((uint8_t *)&value, i << 2, 4); + if (value != (uint32_t)~0) { + // Value found + map_length = i; + Flash.write(&new_vpage[i + 1], value); + } + } +#endif + + // Release old page + VirtualPage.release(old_vpage); + + // Set log position + *log_start = map_length + 1; + *log_end = *log_start; + + return new_vpage; +} + +uint32_t *NVRAMClass::get_page() +{ + uint32_t *vpage = VirtualPage.get(NVRAM_MAGIC); + // Invalid page? + if (vpage == (uint32_t *)~0) { + // Allocate a new page + vpage = VirtualPage.allocate(NVRAM_MAGIC, VirtualPage.length()); + // Set map length to 0 + Flash.write(&vpage[0], 0x0); + } + return vpage; +} + +uint16_t NVRAMClass::get_log_position(uint32_t *vpage) +{ + uint16_t position_min = vpage[0] + 1; + uint16_t position_max = VirtualPage.length(); + + // Return if page is not filled + if ((vpage[0] == (uint32_t)~0) || (position_min >= position_max)) { + return 0; + } + + // loop until postition_min != position_max-1 + while (position_min != position_max - 1) { + // Calculate middle between min and max + uint16_t mid = position_min + ((position_max - position_min) >> 1); + // Set max or min to current position + if (vpage[mid] == (uint32_t)~0) { + position_max = mid; + } else { + position_min = mid; + } + } + + return position_max; +} + +uint8_t NVRAMClass::get_byte_from_page(uint32_t *vpage, uint16_t log_start, + uint16_t log_end, uint16_t idx) +{ + // mask matching a bit signaling wich address range is in log + uint32_t address_mask = ADDR2BIT(idx); + // mask matching the index address + uint32_t address_match = idx << NVRAM_ADDR_POS; + + // Check the log backwards + while (log_end > log_start) { + log_end--; + uint32_t value = vpage[log_end]; + // end here if address map bit is not set + if ((value & address_mask) == 0) { + break; + } + // check address match -> update found -> return + if ((value & NVRAM_ADDR_MASK) == address_match) { + return (uint8_t)value; + } + } + + // Calculate address in the eeprom map at the beginning of a vpage + uint16_t map_address = (idx >> 2); + map_address++; // jump over log offset field + + // look at map if calculated addess before log start position + if (map_address < log_start) { + switch (idx % 4) { + case 3: + return (uint8_t)(vpage[map_address] >> 24); + break; + case 2: + return (uint8_t)(vpage[map_address] >> 16); + break; + case 1: + return (uint8_t)(vpage[map_address] >> 8); + break; + default: + return (uint8_t)(vpage[map_address]); + break; + } + } + + // empty cell + return 0xff; +} diff --git a/drivers/NVM/NVRAM.h b/drivers/NVM/NVRAM.h new file mode 100644 index 0000000..08c7552 --- /dev/null +++ b/drivers/NVM/NVRAM.h @@ -0,0 +1,113 @@ +/* + * NVRAM.h - Byte-wise storage for Virtual Pages. + * Original Copyright (c) 2017 Frank Holtz. All right reserved. + * + * This library 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 2.1 of the License, or (at your option) any later version. + * + * This library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file NVRAM.h + * @brief Byte-wise storage for Virtual Pages. + * + * @defgroup NVM Nonvolatile Memory + * @ingroup internals + * @details Nonvolatile Memory Class + * @{ + */ +#pragma once + +#include "Flash.h" +#include "VirtualPage.h" +#include + +/** + * @class NVRAMClass + * @brief Nonvolatile Memory + */ +class NVRAMClass +{ +public: + //---------------------------------------------------------------------------- + /** Constructor. */ + NVRAMClass() {}; + //---------------------------------------------------------------------------- + /** Initialize Class */ + void begin() {}; + //---------------------------------------------------------------------------- + /** Deinitialize Class */ + void end() {}; + //---------------------------------------------------------------------------- + /** NVM available space in bytes + * @return Number of bytes + */ + uint16_t length() const; + //---------------------------------------------------------------------------- + /** Read a block of bytes + * @param[in] *dst Write bytes to given pointer + * @param[in] idx First NVM address to read + * @param[in] n Number of bytes to read + */ + void read_block(uint8_t *dst, uint16_t idx, uint16_t n); + //---------------------------------------------------------------------------- + /** Read a byte + * @param[in] idx NVM Address to read + * @return Byte from given address + */ + uint8_t read(const uint16_t idx); + //---------------------------------------------------------------------------- + /** Write a block + * @param[in] *src Read bytes from given pointer + * @param[in] idx First NVM address to write + * @param[in] n Number of bytes to write + * @return success + */ + bool write_block(uint8_t *src, uint16_t idx, uint16_t n); + //---------------------------------------------------------------------------- + /** Write a byte + * @param[in] idx NVM Address to write + * @param[in] value Byte to write + * @return success + */ + bool write(const uint16_t idx, uint8_t value); + //---------------------------------------------------------------------------- + /** Preserve the number of bytes in NVM log for time critical writes. Runtime + * up to 5000ms! + * @param[in] number Bytes to preserve, can 0 to find out free log space + * @return Bytes available for writing + */ + int write_prepare(uint16_t number); + //---------------------------------------------------------------------------- + /** lear log if full and prepare released pages for faster reallocation. + * Runtime up to 5000ms! + * @param[in] write_preserve Byte to preserve + */ + void clean_up(uint16_t write_preserve); + +private: + // Return a virtual page + uint32_t *get_page(); + // Get actual log position + uint16_t get_log_position(uint32_t *vpage); + // Read a byte from page + uint8_t get_byte_from_page(uint32_t *vpage, uint16_t log_start, + uint16_t log_end, uint16_t idx); + // switch a page + uint32_t *switch_page(uint32_t *old_vpage, uint16_t *log_start, + uint16_t *log_end); +}; + +/** Variable to access the NVRAMClass */ +extern NVRAMClass NVRAM; + +/** @} */ diff --git a/drivers/NVM/README.md b/drivers/NVM/README.md new file mode 100644 index 0000000..259b93f --- /dev/null +++ b/drivers/NVM/README.md @@ -0,0 +1,51 @@ +This code is based on (arduino-NVM)[https://github.com/d00616/arduino-NVM]. If you change code, please create a Pull Request for arduino-NVM to keep code synchronized. + +# arduino-NVM + +[![Build Status](https://travis-ci.org/d00616/arduino-NVM.svg?branch=master)](https://travis-ci.org/d00616/arduino-NVM) + +This library allows the usage of internal Flash memory. To enhance the limited erase cycles a VirtualPage layer is available. On top of VirtualPage, there is an NVRAM class to allow a lot of writes by using a log-based storage. + +For Arduino compatibility, a subset of avr/eeprom.h functionality and a complete port of EEPROM.h is provided. + +Accessing bytes via NVRAM or EEPROM is faster than an AVR controller until the internal log is full. At this point, a new page must build. This process takes up to 3400ms (nRF51) or 1300ms (nRF52) depending on your hardware and the highest written address. + +To find out more about timing, please run "test_all" example. + +_This code is not compatible with any SoftDevice. You have to use the [radio notification](https://devzone.nordicsemi.com/tutorials/14/radio-notification/) and VirtualPage.clean_up()/NVRAM.write_prepare(NUMBER) to ensure that writes are only used in a time without radio activation._ + +## Flash.h + +This class is the hardware abstraction to the Flash controller. Please look into Flash.h for a more detailed description. + +Please read the documentation of your microcontroller to find out limitations about writing into flash. You can use the FLASH_... defines in your code to take care about quirks. + +## VirtualPage.h + +This class provides manages Flash pages. This helps you to reach more erase cycles and handle page faults. The underlying Flash page needs to hold some metadata so a VirtualPage is some bytes smaller than 4k. The size can differ between different hardware. + +If you need to allocate VirtualPages in performance critical situations, call VirtualPage.clean_up(), when you have a time slot of more than 100ms. + +For VirtualPages the last 16k(nRF51) or 32k(nRF52) are used. This allows the same number of erase cycles on both platforms. + +## NVRAM.h + +This class provides a 3072 bytes large memory. You can access this memory in a random order without needing to take care of the underlying flash architecture. This class is stateless, this means there is nothing cached in RAM. With every access, the data structure is parsed. This saves RAM and avoids conflicts when you have more than one instance of NVRAM class in your code. + +To reach a maximum of write cycles and performance, place all your data at the beginning of the memory. This allows a maximum of write cycles. + +When you only use the first 8 Bytes of the NVRAM, you have 5,100,000 write cycles per byte. If you use all 3072 bytes, you have only 3,300 write cycles per byte. + +For your own calculation of write cycles, you can calculate the sum of available writes with: (VirtualPage.length()-4-HIGHEST_ADDRESS/4)*40,000 + +Reading or writing the NVRAM is fast until the internal log is full. On nRF51 you can calculate with 1.2ms and on nRF52 with 0,5ms. If the log is full a new VirtualPage is allocated and written. This procedure can take a time of 3400ms (nRF51) or 1300ms (nRF52). + +If you use this code in performance critical situations. Use NVRAM.write_prepare(NUMBER) before to guarantee a fast write for the given number of bytes. + +## EEPROM.h and avr_eeprom.h + +Both libraries are for Arduino compatibility. Instead of avr/eeprom.h, you have to include avr_eeprom.h. This file maps a limited set of functions to NVRAM. + +The EEPROM.h is fully compatible with the AVR version without committing changes. + +If you use one of both files, please keep in mind that writing a single byte is fast until the log becomes full. In that case, a single write operation can take up to 3400ms (nRF51) or 1300ms (nRF52). diff --git a/drivers/NVM/VirtualPage.cpp b/drivers/NVM/VirtualPage.cpp new file mode 100644 index 0000000..ed6abde --- /dev/null +++ b/drivers/NVM/VirtualPage.cpp @@ -0,0 +1,342 @@ +/* + VirtualPage.cpp - Flash page management + Original Copyright (c) 2017 Frank Holtz. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "VirtualPage.h" + +VirtualPageClass VirtualPage; + +#ifndef VNM_VIRTUAL_PAGE_SIZE_BITS +#define VNM_VIRTUAL_PAGE_SIZE_BITS 12 +#elif VNM_VIRTUAL_PAGE_SIZE_BITS < 12 +#error "VNM_VIRTUAL_PAGE_SIZE_BITS must be >= 12" +#endif + +// check page size +#ifndef VNM_VIRTUAL_PAGE_COUNT +#if FLASH_ERASE_CYCLES >= 20000 +// use 16k of flash memory +#define VNM_VIRTUAL_PAGE_COUNT 4 +#else +// use 32k of flash memory +#define VNM_VIRTUAL_PAGE_COUNT 8 +#endif +#endif + +/* + * How many virtual pages are skipped from top of flash + */ +#ifndef VNM_VIRTUAL_PAGE_SKIP_FROM_TOP +#define VNM_VIRTUAL_PAGE_SKIP_FROM_TOP 0 +#endif + +/* + * Calculate things around VNM_VIRTUAL_PAGE_SIZE + */ +#define VNM_VIRTUAL_PAGE_SIZE (1 << (VNM_VIRTUAL_PAGE_SIZE_BITS)) +#define VNM_VIRTUAL_PAGE_ADDRESS_MASK (~(VNM_VIRTUAL_PAGE_SIZE - 1)) +#define VNM_VIRTUAL_PAGE_ALIGN(address) \ + { address = (uint32_t *)((uint32_t)address & VNM_VIRTUAL_PAGE_ADDRESS_MASK); } + +/* + * Defines the position of status words in a page. + * Offsets are defined in words! + */ +#ifdef FLASH_SUPPORTS_RANDOM_WRITE +// use first 8 byte for magic, erase counter and status +#define OFFSET_MAGIC 0 +#define OFFSET_ERASE_COUNTER 1 +#if FLASH_WRITES_PER_WORD > 2 +// use first 12 bytes for magic, erase counter and status +#define MASK_ERASE_COUNTER 0x00FFFFFF +#define OFFSET_STATUS_RELEASE_PREPARE 1 +#define OFFSET_STATUS_RELEASE_END 1 +#define METADATA_SIZE 8 +#define OFFSET_DATA 2 +#elif FLASH_WRITES_PER_WORD == 2 +#define MASK_ERASE_COUNTER 0x00FFFFFF +#define OFFSET_STATUS_RELEASE_PREPARE 2 +#define OFFSET_STATUS_RELEASE_END 2 +#define METADATA_SIZE 12 +#define OFFSET_DATA 3 +#else +// use first 12 bytes for erase counter, and magic +#define OFFSET_MAGIC 1 +#define OFFSET_COUNTER 0 +#define MASK_ERASE_COUNTER 0x00FFFFFF +#define OFFSET_STATUS_RELEASE_PREPARE VNM_VIRTUAL_PAGE_SIZE - 8 +#define OFFSET_STATUS_RELEASE_END VNM_VIRTUAL_PAGE_SIZE - 4 +#define METADATA_SIZE 16 +#define OFFSET_DATA 4 +#endif + +#define BIT_STATUS_RELEASE_PREPARE (1 << 30) +#define BIT_STATUS_RELEASE_END (1 << 31) + +#define VNM_VIRTUAL_PAGE_DATA_SIZE (VNM_VIRTUAL_PAGE_SIZE - METADATA_SIZE) +#else +// use first 8 byte for magic and erase counter and last 8 byte for page release +#define OFFSET_MAGIC 1 +#define OFFSET_ERASE_COUNTER 0 +#define OFFSET_DATA 2 +#define OFFSET_STATUS_RELEASE_PREPARE \ + ((VNM_VIRTUAL_PAGE_SIZE - 8) / sizeof(uint32_t)) +#define OFFSET_STATUS_RELEASE_END \ + ((VNM_VIRTUAL_PAGE_SIZE - 4) / sizeof(uint32_t)) + +#define MASK_ERASE_COUNTER 0xFFFFFFFF + +#define BIT_STATUS_RELEASE_PREPARE 1 +#define BIT_STATUS_RELEASE_END 1 + +#define VNM_VIRTUAL_PAGE_DATA_SIZE (VNM_VIRTUAL_PAGE_SIZE - 16) + +#endif + +uint16_t VirtualPageClass::size() const +{ + return (VNM_VIRTUAL_PAGE_DATA_SIZE); +} + +uint16_t VirtualPageClass::length() const +{ + return (VNM_VIRTUAL_PAGE_DATA_SIZE / 4); +} + +uint16_t VirtualPageClass::page_count() const +{ + return (VNM_VIRTUAL_PAGE_COUNT - 1); +} + +uint32_t VirtualPageClass::wear_level() +{ + uint32_t max_erase_cycles = 0; + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t erase_cycles = get_page_erase_cycles(get_page_address(i)); + if (erase_cycles > max_erase_cycles) { + max_erase_cycles = erase_cycles; + } + } + return (uint32_t)((((uint64_t)max_erase_cycles * 10000)) / + Flash.specified_erase_cycles()); +} + +uint32_t *VirtualPageClass::get(uint32_t magic) +{ + + // Give back a page prepared for release and not closed + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t *page = get_page_address(i); + if ( + // correct magic is set + (page[OFFSET_MAGIC] == magic) && + // page is in release_prepare mode + ((page[OFFSET_STATUS_RELEASE_PREPARE] & BIT_STATUS_RELEASE_PREPARE) == + 0) && + // page is not released + ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) { + // Return page in release process with priority + return &page[OFFSET_DATA]; + } + } + + // check if a unreleased page is available + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t *page = get_page_address(i); + if ( + // correct magic is set + (page[OFFSET_MAGIC] == magic) && + // page is not released + ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0)) { + // return page in normal operation + return &page[OFFSET_DATA]; + } + } + + return (uint32_t *)(~0); +} + +uint32_t *VirtualPageClass::allocate(uint32_t magic) +{ + uint32_t *return_page = (uint32_t *)(~0); + uint32_t max_erase_cycles = (uint32_t)~0; + + // Avoid duplicate allocation of pages, look for the less used page + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t *page = get_page_address(i); + + // Delete duplicated pages + if ( + // same magic + (page[OFFSET_MAGIC] == magic) && + // Not in release_end state + ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) && + // Not in release_prepare state + (!release_started(page))) { + // clear the page + build_page(page, (uint32_t)~0); + } + + uint32_t erase_cycles = get_page_erase_cycles(page); + // When the page has less erase cycles and is not marked as failed + if ((erase_cycles < max_erase_cycles) && (page[OFFSET_MAGIC] > 0) && + ( + // magic is empty + (page[OFFSET_MAGIC] == (uint32_t)~0) || + // marked as released + ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) == + 0))) { + max_erase_cycles = erase_cycles; + return_page = page; + } + } + + // return if no page was found + if (return_page == (uint32_t *)~0) { + return return_page; + } + + build_page(return_page, magic); + return &return_page[OFFSET_DATA]; +} + +uint32_t *VirtualPageClass::allocate(uint32_t magic, uint32_t max_writes) +{ + // max_writes is not implemented yet -> page is erased with every allocate + (void)max_writes; + return allocate(magic); +} + +void VirtualPageClass::release_prepare(uint32_t *address) +{ + // move pointer to beginning of the page + VNM_VIRTUAL_PAGE_ALIGN(address); + + // Nothing to do at a empty page + if (address[OFFSET_MAGIC] == (uint32_t)~0) { + return; + } + + if (release_started(address) == false) { + // Clear bit BIT_PAGE_RELEASED + Flash.write(&address[OFFSET_STATUS_RELEASE_PREPARE], + address[OFFSET_STATUS_RELEASE_PREPARE] & + ~BIT_STATUS_RELEASE_PREPARE); + } + return; +} + +void VirtualPageClass::release(uint32_t *address) +{ + // move pointer to beginning of the page + VNM_VIRTUAL_PAGE_ALIGN(address); + + // Nothing to do at a empty page + if (address[OFFSET_MAGIC] == (uint32_t)~0) { + return; + } + + // Check if status bit already cleared + if ((address[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) > 0) { + // Clear bit BIT_PAGE_RELEASED + Flash.write(&address[OFFSET_STATUS_RELEASE_END], + address[OFFSET_STATUS_RELEASE_END] & ~BIT_STATUS_RELEASE_END); + } + return; +} + +bool VirtualPageClass::release_started(uint32_t *address) +{ + // move pointer to beginning of the page + VNM_VIRTUAL_PAGE_ALIGN(address); + + return (address[OFFSET_STATUS_RELEASE_PREPARE] & + BIT_STATUS_RELEASE_PREPARE) == 0; +} + +void VirtualPageClass::fail(uint32_t *address) +{ + // move pointer to beginning of the page + VNM_VIRTUAL_PAGE_ALIGN(address); + + build_page(address, 0x00000000); + return; +} + +void VirtualPageClass::clean_up() +{ + // No page found -> try to give back a page prepared for release + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t *page = get_page_address(i); + if ((page[OFFSET_STATUS_RELEASE_END] & BIT_STATUS_RELEASE_END) == 0) { + build_page(get_page_address(i), ~0); + return; // a maximum of a page is cleaned -> return + } + } +} + +void VirtualPageClass::format() +{ + for (int i = 1; i <= VNM_VIRTUAL_PAGE_COUNT; i++) { + uint32_t *address = get_page_address(i); + build_page(address, (uint32_t)~0); + } +} + +uint32_t *VirtualPageClass::get_page_address(uint16_t page) +{ + return (uint32_t *)((Flash.page_count() << Flash.page_size_bits()) - + ((page + VNM_VIRTUAL_PAGE_SKIP_FROM_TOP) + << VNM_VIRTUAL_PAGE_SIZE_BITS)); +} + +void VirtualPageClass::build_page(uint32_t *address, uint32_t magic) +{ + // move pointer to beginning of the page + VNM_VIRTUAL_PAGE_ALIGN(address); + // get erase counter + uint32_t erase_counter = get_page_erase_cycles(address); + + // Check if a magic is set + if (address[OFFSET_MAGIC] != (uint32_t)~0) { + Flash.erase(address, VNM_VIRTUAL_PAGE_SIZE); + } else { + // check if page is empty + for (int i = OFFSET_DATA; i < (VNM_VIRTUAL_PAGE_SIZE / 4); i++) { + if (address[i] != (uint32_t)~0) { + Flash.erase(address, VNM_VIRTUAL_PAGE_SIZE); + break; + } + } + } + + // write a new page + Flash.write(&address[OFFSET_MAGIC], magic); + if (address[OFFSET_ERASE_COUNTER] == (uint32_t)~0) { + Flash.write(&address[OFFSET_ERASE_COUNTER], + erase_counter | ~MASK_ERASE_COUNTER); + } +} + +uint32_t VirtualPageClass::get_page_erase_cycles(uint32_t *address) +{ + // Return number of cycles + return ((uint32_t)address[OFFSET_ERASE_COUNTER] & + (uint32_t)MASK_ERASE_COUNTER) + + 1; +} diff --git a/drivers/NVM/VirtualPage.h b/drivers/NVM/VirtualPage.h new file mode 100644 index 0000000..de8fe72 --- /dev/null +++ b/drivers/NVM/VirtualPage.h @@ -0,0 +1,139 @@ +/* + VirtualPage.h - Flash page management + Original Copyright (c) 2017 Frank Holtz. All right reserved. + + This library 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 2.1 of the License, or (at your option) any later version. + + This library 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 library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file VirtualPage.h + * @brief Virtual page management on top of Flash + * The managed pages are organized into VirtualPage.size() sized pages. + * Some Bytes of a Flash page are reserved for a magic number and page management. + * + * @ingroup NVM + * @details Nonvolatile Memory Class + * @{ + */ +#pragma once + +#include "Flash.h" +#include + +/** + * @class VirtualPageClass + * @brief Virtual page management on top of Flash + */ +class VirtualPageClass +{ +public: + //---------------------------------------------------------------------------- + /** Constructor */ + VirtualPageClass() {}; + //---------------------------------------------------------------------------- + /** Initialize Virtual Pages */ + void begin() {}; + //---------------------------------------------------------------------------- + /** Deinitilize Virtual Pages */ + void end() {}; + //---------------------------------------------------------------------------- + /** Reports usable page size in bytes + * @return Number of bytes + */ + uint16_t size() const; + //---------------------------------------------------------------------------- + /** Reports usable page size in 32 Bit words + * @return Number of words + */ + uint16_t length() const; + //---------------------------------------------------------------------------- + /** Reports the maximum number of allocatable pages + * @return Number of pages + */ + uint16_t page_count() const; + //---------------------------------------------------------------------------- + /** Calculates the rate of wear in percent*100. + * Values greater than 10000 indicates exceeding the chip specification. + * This value is only valid for controllers, never erased completely. + * @return calculated level + */ + uint32_t wear_level(); + //---------------------------------------------------------------------------- + /** Search for a page by given, unique magic number. + * Returns a pointer to (uint32_t *)~0 if there is no page. Don't write + * to this address. + * @param[in] magic A magic number to identify the page. + * @return Pointer to a page + */ + uint32_t *get(uint32_t magic); + //---------------------------------------------------------------------------- + /** Returns an address to a blank page or (uint32_t *)~0 if no space + * available. Take care about RADIO, WDT and Interrupt timing! Calculate with + * 0-100ms until a page is available. + * @param[in] magic A magic number to identify the page. + * @return Pointer to a page + */ + uint32_t *allocate(uint32_t magic); + //---------------------------------------------------------------------------- + /** Returns an address to a blank page or (uint32_t *)~0 if no space + * available. Take care about RADIO, WDT and Interrupt timing! Calculate with + * 0-100ms until a page is available. + * This function allows using a page multiple times without erasing on some platforms. + * @param[in] magic A magic number to identify the page. + * @param[in] max_writes The number of planned write operations in page lifecycle. + * @return Pointer to a page + */ + uint32_t *allocate(uint32_t magic, uint32_t max_writes); + //---------------------------------------------------------------------------- + /** Start releasing a page. You have to allocate a new one and then release the + * old page. + * @param[in] *address A pointer to the page to release + */ + void release_prepare(uint32_t *address); + //---------------------------------------------------------------------------- + /** End releasing a page. + * @param[in] *address A pointer to the page to release + */ + void release(uint32_t *address); + //---------------------------------------------------------------------------- + /** Returns true if page is in release_prepare state + * @param[in] *address A pointer to a page + * @return Release state + */ + bool release_started(uint32_t *address); + //---------------------------------------------------------------------------- + /** Mark a page as a defect page. + */ + void fail(uint32_t *address); + //---------------------------------------------------------------------------- + /** Prepare released pages for faster reallocation. Plan with 0-100ms. + */ + void clean_up(); + //---------------------------------------------------------------------------- + /** Release all pages + */ + void format(); + +private: + // convert page number 1..max_pages into an address + uint32_t *get_page_address(uint16_t page); + // build a page + void build_page(uint32_t *address, uint32_t magic); + // return number of erase cycles + uint32_t get_page_erase_cycles(uint32_t *address); +}; + +extern VirtualPageClass VirtualPage; + +/** @} */ diff --git a/hal/architecture/MyHwNRF5.cpp b/hal/architecture/MyHwNRF5.cpp new file mode 100644 index 0000000..b1f71b9 --- /dev/null +++ b/hal/architecture/MyHwNRF5.cpp @@ -0,0 +1,436 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "MyHwNRF5.h" +#include + +#define INVALID_INTERRUPT_NUM (0xFFu) + +volatile uint8_t _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu. +volatile uint8_t _wakeUp1Interrupt = + INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback. +volatile uint8_t _wakeUp2Interrupt = + INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback. + +void wakeUp1() // place to send the interrupts +{ + _wokeUpByInterrupt = _wakeUp1Interrupt; +} +void wakeUp2() // place to send the second interrupts +{ + _wokeUpByInterrupt = _wakeUp2Interrupt; +} + +void hwReadConfigBlock(void *buf, void *adr, size_t length) +{ + uint8_t *dst = static_cast(buf); + const int offs = reinterpret_cast(adr); + (void)NVRAM.read_block(dst, offs, length); +} + +void hwWriteConfigBlock(void *buf, void *adr, size_t length) +{ + uint8_t *src = static_cast(buf); + const int offs = reinterpret_cast(adr); + (void)NVRAM.write_block(src, offs, length); +} + +uint8_t hwReadConfig(int adr) +{ + return NVRAM.read(adr); +} + +void hwWriteConfig(int adr, uint8_t value) +{ + (void)NVRAM.write(adr, value); +} + +bool hwInit() +{ + +#ifdef MY_DISABLED_SERIAL + // Disable UART, when not configured +#ifdef NRF51 + NRF_UART0->POWER = 0; +#endif +#else + // Confiure UART + MY_SERIALDEVICE.begin(MY_BAUD_RATE); +#if defined(MY_GATEWAY_SERIAL) + while (!MY_SERIALDEVICE) { + } +#endif +#endif + + return true; +} + +void hwRandomNumberInit() +{ + // Start HWRNG +#ifdef NRF51 + NRF_RNG->POWER = 1; +#endif + // Enable "more random" numbers + NRF_RNG->CONFIG = RNG_CONFIG_DERCEN_Enabled << RNG_CONFIG_DERCEN_Pos; + NRF_RNG->TASKS_START = 1; + NRF_RNG->EVENTS_VALRDY = 0; + + uint32_t seed = 0; + for (uint8_t i = 0; i < 4; i++) { + // Wait for an random number + while (NRF_RNG->EVENTS_VALRDY == 0) { + yield(); + } + seed = (seed << 8) | (uint32_t)NRF_RNG->VALUE; + NRF_RNG->EVENTS_VALRDY = 0; + } + randomSeed(seed); + + // Stop HWRNG + NRF_RNG->TASKS_STOP = 1; +#ifdef NRF51 + NRF_RNG->POWER = 0; +#endif +} + +void hwWatchdogReset() +{ + NRF_WDT->RR[0] = WDT_RR_RR_Reload; +} + +void hwReboot() +{ + NVIC_SystemReset(); + while (true) + ; +} + +static volatile bool nrf5_rtc_event_triggered; +static volatile bool nrf5_pwr_hfclk; + +void hwSleepPrepare(unsigned long ms) +{ + // Enable low power sleep mode + NRF_POWER->TASKS_LOWPWR = 1; + + if (ms > 0) { + // Configure RTC +#ifdef NRF51 + MY_HW_RTC->POWER = 1; +#endif + // Reset RTC + MY_HW_RTC->TASKS_CLEAR = 1; + + // Calculate sleep time + // 8 Hz -> max 582.542 hours sleep. + MY_HW_RTC->PRESCALER = 4095; + // Set compare register to 1/125ms + 2 to garantee event triggering + MY_HW_RTC->CC[0] = (ms / 125) + 2; + + MY_HW_RTC->INTENSET = RTC_INTENSET_COMPARE0_Msk; + MY_HW_RTC->EVTENSET = RTC_EVTENSET_COMPARE0_Msk; + MY_HW_RTC->EVENTS_COMPARE[0] = 0; + nrf5_rtc_event_triggered = false; + MY_HW_RTC->TASKS_START = 1; + NVIC_SetPriority(MY_HW_RTC_IRQN, 15); + NVIC_ClearPendingIRQ(MY_HW_RTC_IRQN); + NVIC_EnableIRQ(MY_HW_RTC_IRQN); + } else { + NRF_RTC1->TASKS_STOP = 1; + } + + // Stop HFCLK + nrf5_pwr_hfclk = NRF_CLOCK->EVENTS_HFCLKSTARTED; + NRF_CLOCK->TASKS_HFCLKSTOP = 1; + + // Idle serial device +#ifndef MY_DISABLED_SERIAL + NRF_UART0->TASKS_STOPRX = 1; + NRF_UART0->TASKS_STOPTX = 1; + NRF_UART0->TASKS_SUSPEND = 1; +#endif + + // Clear NVRAM log, if needed and a time frame of 4 seconds available + if (ms > 40000) { + // preserve some bytes for writing to NVRAM + NVRAM.clean_up(32); + } +} + +void hwSleepEnd(unsigned long ms) +{ + // Start HFCLK + if (nrf5_pwr_hfclk) { + NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; + NRF_CLOCK->TASKS_HFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) + ; + // Enable low latency sleep mode + NRF_POWER->TASKS_CONSTLAT = 1; + } + + if (ms > 0) { + // Stop RTC +#ifdef NRF51 + MY_HW_RTC->POWER = 0; +#endif + MY_HW_RTC->INTENCLR = RTC_INTENSET_COMPARE0_Msk; + MY_HW_RTC->EVTENCLR = RTC_EVTENSET_COMPARE0_Msk; + MY_HW_RTC->TASKS_STOP = 1; + NVIC_DisableIRQ(MY_HW_RTC_IRQN); + } else { + // Start Arduino RTC for millis() + NRF_RTC1->TASKS_START = 1; + } + + // Start serial device +#ifndef MY_DISABLED_SERIAL + NRF_UART0->TASKS_STARTRX = 1; + NRF_UART0->TASKS_STARTTX = 1; +#endif +} + +// Halt CPU until next interrupt event +inline void hwWaitForInterrupt() +{ + __DSB(); + __WFI(); +} + +// Sleep in System ON mode +inline void hwSleep() +{ + __WFE(); + __SEV(); + __WFE(); +} + +int8_t hwSleep(unsigned long ms) +{ + hwSleepPrepare(ms); + while (nrf5_rtc_event_triggered == false) { + hwSleep(); + } + hwSleepEnd(ms); + return MY_WAKE_UP_BY_TIMER; +} + +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + return hwSleep(interrupt, mode, INVALID_INTERRUPT_NUM, 0u, ms); +} + +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, + uint8_t mode2, unsigned long ms) +{ + // Disable interrupts until going to sleep, otherwise interrupts occurring + // between attachInterrupt() + // and sleep might cause the MCU to not wakeup from sleep as interrupt has + // already be handled! + MY_CRITICAL_SECTION { + // Fix cppcheck "(style) Variable '__savePriMask' is assigned a value that is never used." + (void)__savePriMask; + // attach interrupts + _wakeUp1Interrupt = interrupt1; + _wakeUp2Interrupt = interrupt2; + + if (interrupt1 != INVALID_INTERRUPT_NUM) + { + attachInterrupt(interrupt1, wakeUp1, mode1); + } + if (interrupt2 != INVALID_INTERRUPT_NUM) + { + attachInterrupt(interrupt2, wakeUp2, mode2); + } + + // Reset attrubute + _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; + } + + // Prepare Timer and Hardware + hwSleepPrepare(ms); + + // Sleep until timeout or interrupt + while ((nrf5_rtc_event_triggered == false) and + (_wokeUpByInterrupt == INVALID_INTERRUPT_NUM)) { + hwSleep(); + } + + // Assure any interrupts attached, will get detached when they did not occur. + if (interrupt1 != INVALID_INTERRUPT_NUM) { + detachInterrupt(interrupt1); + } + if (interrupt2 != INVALID_INTERRUPT_NUM) { + detachInterrupt(interrupt2); + } + + // Wake up Hardware + hwSleepEnd(ms); + + // Return what woke the mcu. + int8_t ret = + MY_WAKE_UP_BY_TIMER; // default: no interrupt triggered, timer wake up + if (_wokeUpByInterrupt != INVALID_INTERRUPT_NUM) { + ret = static_cast(_wokeUpByInterrupt); + } + // Clear woke-up-by-interrupt flag, so next sleeps won't return immediately. + _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; + + return ret; +} + +extern "C" { + // RTC interrupt handler + void MY_HW_RTC_IRQ_HANDLER(void) + { + if (MY_HW_RTC->EVENTS_COMPARE[0] > 0) { + nrf5_rtc_event_triggered = true; + + NRF_RESET_EVENT(MY_HW_RTC->EVENTS_COMPARE[0]); + } + } +} + +bool hwUniqueID(unique_id_t *uniqueID) +{ + uint32_t *buffer = (uint32_t *)uniqueID; + buffer[0] = NRF_FICR->DEVICEID[0]; + buffer[1] = NRF_FICR->DEVICEID[1]; + buffer[2] = NRF_FICR->DEVICEADDR[0]; + buffer[3] = NRF_FICR->DEVICEADDR[1]; + return true; +} + +uint16_t hwCPUVoltage() +{ + // VDD is prescaled 1/3 and compared with the internal 1.2V reference +#if defined(NRF_ADC) + // NRF51: + // Sampling is done with lowest resolution to minimize the time + // 20uS@260uA + + // Concurrent ressource: disable + uint32_t lpcomp_enabled = NRF_LPCOMP->ENABLE; + NRF_LPCOMP->ENABLE = 0; + + // Enable and configure ADC + NRF_ADC->ENABLE = 1; + NRF_ADC->CONFIG = (ADC_CONFIG_EXTREFSEL_None << ADC_CONFIG_EXTREFSEL_Pos) | + (ADC_CONFIG_PSEL_Disabled << ADC_CONFIG_PSEL_Pos) | + (ADC_CONFIG_REFSEL_VBG << ADC_CONFIG_REFSEL_Pos) | + (ADC_CONFIG_INPSEL_SupplyOneThirdPrescaling << ADC_CONFIG_INPSEL_Pos) | + (ADC_CONFIG_RES_8bit << ADC_CONFIG_RES_Pos); + NRF_ADC->EVENTS_END = 0; + NRF_ADC->TASKS_START = 1; + while(!NRF_ADC->EVENTS_END); + NRF_ADC->EVENTS_END = 0; + int32_t sample = (int32_t)NRF_ADC->RESULT; + NRF_ADC->TASKS_STOP = 1; + NRF_ADC->ENABLE = 0; + + // Restore LPCOMP state + NRF_LPCOMP->ENABLE = lpcomp_enabled; + + return (sample*3600)/255; + +#elif defined(NRF_SAADC) + // NRF52: + // Sampling time 3uS@700uA + int32_t sample; + NRF_SAADC->ENABLE = SAADC_ENABLE_ENABLE_Enabled << SAADC_ENABLE_ENABLE_Pos; + NRF_SAADC->RESOLUTION = SAADC_RESOLUTION_VAL_8bit << SAADC_RESOLUTION_VAL_Pos; + NRF_SAADC->CH[0].PSELP = SAADC_CH_PSELP_PSELP_VDD << SAADC_CH_PSELP_PSELP_Pos; + NRF_SAADC->CH[0].CONFIG = (SAADC_CH_CONFIG_BURST_Disabled << SAADC_CH_CONFIG_BURST_Pos) | + (SAADC_CH_CONFIG_MODE_SE << SAADC_CH_CONFIG_MODE_Pos) | + (SAADC_CH_CONFIG_TACQ_3us << SAADC_CH_CONFIG_TACQ_Pos) | + (SAADC_CH_CONFIG_REFSEL_Internal << SAADC_CH_CONFIG_REFSEL_Pos) | + (SAADC_CH_CONFIG_GAIN_Gain1_6 << SAADC_CH_CONFIG_GAIN_Pos) | + (SAADC_CH_CONFIG_RESN_Bypass << SAADC_CH_CONFIG_RESN_Pos) | + (SAADC_CH_CONFIG_RESP_Bypass << SAADC_CH_CONFIG_RESP_Pos); + NRF_SAADC->OVERSAMPLE = SAADC_OVERSAMPLE_OVERSAMPLE_Bypass << SAADC_OVERSAMPLE_OVERSAMPLE_Pos; + NRF_SAADC->SAMPLERATE = SAADC_SAMPLERATE_MODE_Task << SAADC_SAMPLERATE_MODE_Pos; + NRF_SAADC->RESULT.MAXCNT = 1; + NRF_SAADC->RESULT.PTR = (uint32_t)&sample; + + NRF_SAADC->EVENTS_STARTED = 0; + NRF_SAADC->TASKS_START = 1; + while (!NRF_SAADC->EVENTS_STARTED); + NRF_SAADC->EVENTS_STARTED = 0; + + NRF_SAADC->EVENTS_END = 0; + NRF_SAADC->TASKS_SAMPLE = 1; + while (!NRF_SAADC->EVENTS_END); + NRF_SAADC->EVENTS_END = 0; + + NRF_SAADC->EVENTS_STOPPED = 0; + NRF_SAADC->TASKS_STOP = 1; + while (!NRF_SAADC->EVENTS_STOPPED); + NRF_SAADC->EVENTS_STOPPED = 1; + + NRF_SAADC->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos); + + return (sample*3600)/255; +#else + // unknown MCU + return 0; +#endif +} + +uint16_t hwCPUFrequency() +{ + return F_CPU / 100000UL; +} + +uint16_t hwFreeMem() +{ + // TODO: Not supported! + return FUNCTION_NOT_SUPPORTED; +} + +void hwDebugPrint(const char *fmt, ...) +{ + char fmtBuffer[MY_SERIAL_OUTPUT_SIZE]; +#ifdef MY_GATEWAY_FEATURE + // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) + snprintf(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;%lu "), + C_INTERNAL, I_LOG_MESSAGE, hwMillis()); + MY_SERIALDEVICE.print(fmtBuffer); +#else + // prepend timestamp + MY_SERIALDEVICE.print(hwMillis()); + MY_SERIALDEVICE.print(F(" ")); +#endif + va_list args; + va_start (args, fmt ); +#ifdef MY_GATEWAY_FEATURE + // Truncate message if this is gateway node + vsnprintf(fmtBuffer, sizeof(fmtBuffer), fmt, args); + fmtBuffer[sizeof(fmtBuffer) - 2] = '\n'; + fmtBuffer[sizeof(fmtBuffer) - 1] = '\0'; +#else + vsnprintf(fmtBuffer, sizeof(fmtBuffer), fmt, args); +#endif + va_end (args); + MY_SERIALDEVICE.print(fmtBuffer); + MY_SERIALDEVICE.flush(); +} diff --git a/hal/architecture/MyHwNRF5.h b/hal/architecture/MyHwNRF5.h new file mode 100644 index 0000000..f798847 --- /dev/null +++ b/hal/architecture/MyHwNRF5.h @@ -0,0 +1,120 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ +#ifndef MyHwNRF5_h +#define MyHwNRF5_h + +#include "MyHw.h" + +#ifdef __cplusplus +#include +#endif + +// Define NRF5_SOFTDEVICE when SoftDevice found +#if defined(S110) || defined(S130) || defined(S132) +#ifndef NRF5_SOFTDEVICE +#define NRF5_SOFTDEVICE +#endif +#endif + +#include "drivers/NRF5/nrf5_wiring_digital.c" +#include "drivers/NRF5/wdt.h" +#include "drivers/NVM/NVRAM.h" +#include "drivers/NVM/VirtualPage.h" +#include +#include + +#define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__) + +// Define these as macros to save valuable space +#define hwDigitalWrite(__pin, __value) digitalWrite(__pin, __value) +#define hwDigitalRead(__pin) digitalRead(__pin) +#define hwPinMode(__pin, __value) nrf5_pinMode(__pin, __value) +#define hwMillis() millis() + +bool hwInit(); +void hwWatchdogReset(); +void hwReboot(); +void hwReadConfigBlock(void *buf, void *adr, size_t length); +void hwWriteConfigBlock(void *buf, void *adr, size_t length); +void hwWriteConfig(int adr, uint8_t value); +uint8_t hwReadConfig(int adr); +void hwRandomNumberInit(); + +#ifndef MY_SERIALDEVICE +#define MY_SERIALDEVICE Serial +#endif + +/** + * Disable all interrupts. + * Helper function for MY_CRITICAL_SECTION. + */ +static __inline__ uint8_t __disableIntsRetVal(void) +{ + __disable_irq(); + return 1; +} + +/** + * Restore priority mask register. + * Helper function for MY_CRITICAL_SECTION. + */ +static __inline__ void __priMaskRestore(const uint32_t *priMask) +{ + __set_PRIMASK(*priMask); +} + +/** + * Reset events and read back on nRF52 + * http://infocenter.nordicsemi.com/pdf/nRF52_Series_Migration_v1.0.pdf + */ +#if __CORTEX_M == 0x04 +#define NRF_RESET_EVENT(event) \ + event = 0; \ + (void)event +#else +#define NRF_RESET_EVENT(event) event = 0 +#endif +/** + * RTC to use for hwSleep + * RTC1 is used by arduino-nRF5 + * RTC2 is used on nRF52 + * RTC0 is used on nRF51. This conflicts with SoftDevice! + */ +#ifdef NRF_RTC2 +#define MY_HW_RTC NRF_RTC2 +#define MY_HW_RTC_IRQ_HANDLER RTC2_IRQHandler +#define MY_HW_RTC_IRQN RTC2_IRQn +#else +#define MY_HW_RTC NRF_RTC0 +#define MY_HW_RTC_IRQ_HANDLER RTC0_IRQHandler +#define MY_HW_RTC_IRQN RTC0_IRQn +#endif + +#ifndef DOXYGEN +#define MY_CRITICAL_SECTION \ + for (uint32_t __savePriMask \ + __attribute__((__cleanup__(__priMaskRestore))) = __get_PRIMASK(), \ + __ToDo = __disableIntsRetVal(); \ + __ToDo; __ToDo = 0) +#endif /* DOXYGEN */ + +#endif // #ifdef ARDUINO_ARCH_NRF5 diff --git a/hal/architecture/MyMainNRF5.cpp b/hal/architecture/MyMainNRF5.cpp new file mode 100644 index 0000000..aa5cd46 --- /dev/null +++ b/hal/architecture/MyMainNRF5.cpp @@ -0,0 +1,69 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2017 Sensnology AB + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +// Initialize library and handle sketch functions like we want to + +extern "C" void __libc_init_array(void); + +// Weak empty variant initialization function. +// May be redefined by variant files. +void initVariant() __attribute__((weak)); +void initVariant() {} + +int main(void) +{ + // Suspend UART + NRF_UART0->TASKS_STOPRX = 1; + NRF_UART0->TASKS_STOPTX = 1; + NRF_UART0->TASKS_SUSPEND = 1; + + // Clock is manged by sleep modes. Radio depends on HFCLK. + // Force to start HFCLK + NRF_CLOCK->EVENTS_HFCLKSTARTED = 0; + NRF_CLOCK->TASKS_HFCLKSTART = 1; + while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0) + ; + + // Enable low latency sleep mode + NRF_POWER->TASKS_CONSTLAT = 1; + + // Enable cache on >= NRF52 +#ifndef NRF51 + NRF_NVMC->ICACHECNF = NVMC_ICACHECNF_CACHEEN_Msk; +#endif + + init(); + initVariant(); // arduino-nRF5 specific + delay(1); + + _begin(); // Startup MySensors library + + for (;;) { + _process(); // Process incoming data + if (loop) { + loop(); // Call sketch loop + } + if (serialEventRun) { + serialEventRun(); + } + } + return 0; +} diff --git a/hal/transport/MyTransportHAL.h b/hal/transport/MyTransportHAL.h index ce0c58d..f613f3c 100644 --- a/hal/transport/MyTransportHAL.h +++ b/hal/transport/MyTransportHAL.h @@ -26,6 +26,9 @@ #define INVALID_LEVEL ((int16_t)-256) //!< INVALID_LEVEL #if defined(MY_RX_MESSAGE_BUFFER_FEATURE) +#if defined(MY_RADIO_NRF5_ESB) +#error Receive message buffering not supported for NRF5 radio! Please define MY_NRF5_RX_BUFFER_SIZE +#endif #if defined(MY_RADIO_RFM69) #error Receive message buffering not supported for RFM69! #endif diff --git a/hal/transport/MyTransportNRF5_ESB.cpp b/hal/transport/MyTransportNRF5_ESB.cpp new file mode 100644 index 0000000..e50fb62 --- /dev/null +++ b/hal/transport/MyTransportNRF5_ESB.cpp @@ -0,0 +1,158 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of + * the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2017 Frank Holtz + * Full contributor list: + * https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "MyTransportHAL.h" +#include "drivers/NRF5/Radio.h" +#include "drivers/NRF5/Radio_ESB.h" + +#include "drivers/CircularBuffer/CircularBuffer.h" + +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) +#include "drivers/AES/AES.h" +#endif + +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) +AES _aes; +uint8_t _dataenc[32] = {0}; +uint8_t _psk[16]; +#endif + +bool transportInit(void) +{ +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) + hwReadConfigBlock((void *)_psk, (void *)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); + // set up AES-key + _aes.set_key(_psk, 16); + // Make sure it is purged from memory when set + memset(_psk, 0, 16); +#endif + + return NRF5_ESB_initialize(); +} + +void transportSetAddress(const uint8_t address) +{ + NRF5_ESB_setNodeAddress(address); + NRF5_ESB_startListening(); +} + +uint8_t transportGetAddress(void) +{ + return NRF5_ESB_getNodeID(); +} + +bool transportSend(const uint8_t to, const void *data, const uint8_t len, const bool noACK) +{ +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) + // copy input data because it is read-only + (void)memcpy(_dataenc, data, len); + // has to be adjusted, WIP! + _aes.set_IV(0); + const uint8_t finalLength = len > 16 ? 32 : 16; + // encrypt data + _aes.cbc_encrypt(_dataenc, _dataenc, finalLength / 16); + return NRF5_ESB_sendMessage(to, _dataenc, finalLength, noACK); +#else + return NRF5_ESB_sendMessage(to, data, len, noACK); +#endif +} + +bool transportAvailable(void) +{ + return NRF5_ESB_isDataAvailable(); +} + +bool transportSanityCheck(void) +{ + return NRF5_ESB_sanityCheck(); +} + +uint8_t transportReceive(void *data) +{ + uint8_t len = 0; + len = NRF5_ESB_readMessage(data); +#if defined(MY_NRF5_ESB_ENABLE_ENCRYPTION) + // has to be adjusted, WIP! + _aes.set_IV(0); + // decrypt data + if (_aes.cbc_decrypt((uint8_t *)(data), (uint8_t *)(data), + len > 16 ? 2 : 1) != AES_SUCCESS) { + len = 0; + } +#endif + return len; +} + +void transportPowerDown(void) +{ + NRF5_ESB_powerDown(); +} + +void transportPowerUp(void) +{ + NRF5_ESB_powerUp(); +} + +void transportSleep(void) +{ + NRF5_ESB_sleep(); +} + +void transportStandBy(void) +{ + NRF5_ESB_standBy(); +} + +int16_t transportGetSendingRSSI(void) +{ + return NRF5_ESB_getSendingRSSI(); +} + +int16_t transportGetReceivingRSSI(void) +{ + return NRF5_ESB_getReceivingRSSI(); +} + +int16_t transportGetSendingSNR(void) +{ + return INVALID_SNR; +} + +int16_t transportGetReceivingSNR(void) +{ + return INVALID_SNR; +} + +int16_t transportGetTxPowerPercent(void) +{ + return NRF5_getTxPowerPercent(); +} + +int16_t transportGetTxPowerLevel(void) +{ + return static_cast(NRF5_getTxPowerLevel()); +} + +bool transportSetTxPowerPercent(const uint8_t powerPercent) +{ + return NRF5_setTxPowerPercent(powerPercent); +} diff --git a/keywords.txt b/keywords.txt index a3c0718..b20187d 100644 --- a/keywords.txt +++ b/keywords.txt @@ -108,6 +108,17 @@ MY_RF24_CHANNEL LITERAL1 MY_RF24_DATARATE LITERAL1 MY_RF24_BASE_RADIO_ID LITERAL1 +# NRF5 +MY_RADIO_NRF5_ESB LITERAL1 +MY_DEBUG_VERBOSE_NRF5_ESB LITERAL1 +MY_NRF5_ESB_ADDR_WIDTH LITERAL1 +MY_NRF5_ESB_BASE_RADIO_ID LITERAL1 +MY_NRF5_ESB_CHANNEL LITERAL1 +MY_NRF5_ESB_ENABLE_ENCRYPTION LITERAL1 +MY_NRF5_ESB_MODE LITERAL1 +MY_NRF5_ESB_PA_LEVEL LITERAL1 +MY_NRF5_ESB_RX_BUFFER_SIZE LITERAL1 + # RFM95 MY_RADIO_RFM95 LITERAL1 MY_DEBUG_VERBOSE_RFM95 LITERAL1 @@ -185,4 +196,4 @@ MY_IP_SUBNET_ADDRESS LITERAL1 MY_ESP8266_SSID LITERAL1 MY_ESP8266_PASSWORD LITERAL1 MY_ESP8266_HOSTNAME LITERAL1 -MY_ESP8266_SERIAL_MODE LITERAL1 \ No newline at end of file +MY_ESP8266_SERIAL_MODE LITERAL1 diff --git a/library.json b/library.json index ad7226d..18cd87e 100644 --- a/library.json +++ b/library.json @@ -1,7 +1,7 @@ { "name": "MySensors", "keywords": "framework, sensor, rf", - "description": "Home Automation Framework. Create your own wireless sensor mesh using NRF24L01+, RFM69 and RFM95 radios running on Arduino or ESP8266. Over-the-air updates and MySensors support in 16+ home automation controllers.", + "description": "Home Automation Framework. Create your own wireless sensor mesh using NRF24L01+, RFM69 and RFM95 radios running on Arduino, NRF51822, NRF52832, SAMD and ESP8266. Over-the-air updates and MySensors support in 16+ home automation controllers.", "repository": { "type": "git", @@ -21,4 +21,4 @@ "Doxyfile" ] } -} \ No newline at end of file +} diff --git a/library.properties b/library.properties index 6458fce..73d7e4b 100644 --- a/library.properties +++ b/library.properties @@ -3,7 +3,7 @@ version=2.2.0-beta author=The MySensors Team maintainer=The MySensors Team sentence=Home Automation Framework -paragraph=Create your own wireless sensor mesh using NRF24L01+, RFM69 and RFM95 radios running on Arduino, SAMD and ESP8266. Allows over-the-air updates of nodes. Supported by 20+ home automation controllers. +paragraph=Create your own wireless sensor mesh using NRF24L01+, RFM69 and RFM95 radios running on Arduino, NRF51822, NRF52832, SAMD and ESP8266. Allows over-the-air updates of nodes. Supported by 20+ home automation controllers. category=Communication url=https://www.mysensors.org architectures=*