mirror of
https://github.com/mysensors/MySensors.git
synced 2026-03-02 22:34:12 +01:00
* 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.
343 lines
8.2 KiB
C++
343 lines
8.2 KiB
C++
/*
|
|
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;
|
|
}
|