From c334a1f6262fd4ec5c95fbdb50977be1c8a9c6cd Mon Sep 17 00:00:00 2001 From: Frank Holtz Date: Tue, 7 Feb 2017 20:50:50 +0100 Subject: [PATCH] Initial commit --- .travis.yml | 38 ++ README.md | 49 ++- examples/flash_erase_all/flash_erase_all.ino | 57 +++ .../flash_erase_and_write.ino | 53 +++ examples/test_all/test_all.ino | 385 ++++++++++++++++++ .../virtual_page_format.ino | 15 + .../virtual_page_usage/virtual_page_usage.ino | 273 +++++++++++++ keywords.txt | 66 +++ library.properties | 9 + src/EEPROM.h | 145 +++++++ src/Flash.cpp | 135 ++++++ src/Flash.h | 121 ++++++ src/NVRAM.cpp | 323 +++++++++++++++ src/NVRAM.h | 64 +++ src/VirtualPage.cpp | 321 +++++++++++++++ src/VirtualPage.h | 94 +++++ src/avr_eeprom.h | 28 ++ 17 files changed, 2175 insertions(+), 1 deletion(-) create mode 100644 .travis.yml create mode 100644 examples/flash_erase_all/flash_erase_all.ino create mode 100644 examples/flash_erase_and_write/flash_erase_and_write.ino create mode 100644 examples/test_all/test_all.ino create mode 100644 examples/virtual_page_format/virtual_page_format.ino create mode 100644 examples/virtual_page_usage/virtual_page_usage.ino create mode 100644 keywords.txt create mode 100644 library.properties create mode 100644 src/EEPROM.h create mode 100644 src/Flash.cpp create mode 100644 src/Flash.h create mode 100644 src/NVRAM.cpp create mode 100644 src/NVRAM.h create mode 100644 src/VirtualPage.cpp create mode 100644 src/VirtualPage.h create mode 100644 src/avr_eeprom.h diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8fabf21 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,38 @@ +language: generic +addons: + apt: + packages: + - libc6:i386 + - libstdc++6:i386 +env: + global: + - IDE_VERSION=1.8.1 +before_install: + - /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16 + - sleep 3 + - export DISPLAY=:1.0 + - wget http://downloads.arduino.cc/arduino-$IDE_VERSION-linux64.tar.xz + - tar xf arduino-$IDE_VERSION-linux64.tar.xz + - mv arduino-$IDE_VERSION $HOME/arduino-ide + - export PATH=$PATH:$HOME/arduino-ide + - arduino --pref "boardsmanager.additional.urls=https://redbearlab.github.io/arduino/package_redbearlab_index.json,http://rfduino.com/package_rfduino166_index.json,https://redbearlab.github.io/arduino/package_redbearlab_index.json,https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json,https://d00616.github.io/arduino-nRF5/package_nRF5_boards_index.json" --save-prefs + - #arduino --install-boards arduino:sam >/dev/null + - #arduino --install-boards arduino:samd >/dev/null + - arduino --install-boards RFduino:RFduino >/dev/null + - arduino --install-boards RedBear:nRF51822 >/dev/null + - #arduino --install-boards sandeepmistry:nRF5 >/dev/null + - arduino --install-boards d00616:nRF5 >/dev/null + - buildExampleSketch() { arduino --verbose-build --verify --board $1 $PWD/examples/$2/$2.ino; } +install: + - mkdir -p $HOME/Arduino/libraries + - ln -s $PWD $HOME/Arduino/libraries/. +script: + - #buildExampleSketch "arduino:sam:arduino_due_x_dbg" test_all + - #buildExampleSketch "arduino:samd:arduino_zero_edbg" test_all + - buildExampleSketch "RFduino:RFduino:RFduino" test_all + - buildExampleSketch "RedBear:nRF51822:nRF51822" test_all + - buildExampleSketch "RedBear:nRF51822:nRF51822_NANO" test_all + - #buildExampleSketch "sandeepmistry:nRF5:Generic_nRF51822:chip=xxac" test_all + - #buildExampleSketch "sandeepmistry:nRF5:Generic_nRF52832" test_all + - buildExampleSketch "d00616:nRF5:Generic_nRF51822:chip=xxac" test_all + - buildExampleSketch "d00616:nRF5:Generic_nRF52832" test_all diff --git a/README.md b/README.md index f0aa1b8..924fe37 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ # arduino-NVM -Manage internal flash memory per page, VirutalPage or byte wise storage (EEPROM) + +[![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/examples/flash_erase_all/flash_erase_all.ino b/examples/flash_erase_all/flash_erase_all.ino new file mode 100644 index 0000000..cd22549 --- /dev/null +++ b/examples/flash_erase_all/flash_erase_all.ino @@ -0,0 +1,57 @@ +/* + * This skech erases all content of your controller. Use it only, when + * you are able to install all firemware parts via your programmer. + * + * There is no chance to to updates over the air or via serial port + * after erasing the Flash! + */ + +#include + +void setup() { + Serial.begin(9600); +} + +char num=0; +char yes=10; +void loop() { + if ((millis()%5000)<100) { + Serial.println(); + Serial.println("**************************************************"); + Serial.println("* This sketch deletes all Flash memory including *"); + Serial.println("* this sketch, bootloaders and SoftDevices!!! *"); + Serial.println("* ---------------------------------------------- *"); + Serial.println("* After erasing the Flash, you need to flash *"); + Serial.println("* a new Sketch with a programmer like J-Link, *"); + Serial.println("* ST-Link v2 or CMSIS-DAP. Other methods dosn't *"); + Serial.println("* work! *"); + Serial.println("* ---------------------------------------------- *"); + Serial.println("* If you are shure what you are doing, send a *"); + Serial.println("* 'Y' character to erase the nRF CPU completely. *"); + Serial.println("* ---------------------------------------------- *"); + Serial.println("* You may brick your device! *"); + Serial.println("**************************************************"); + num=0; + } else { + if (num++<49) { + Serial.print('.'); + } else { + Serial.println('.'); + num=0; + } + } + if (Serial.available() > 0) { + if (Serial.read() == 'Y') { + if (yes>0) { + Serial.print("\r\nYou may brick your device. Please give me "); + Serial.print(yes, DEC); + Serial.println(" additional 'Y' characters."); + yes--; + } else { + Serial.println("\r\nYou have been warned! Erase flash. Goodbye."); + Flash.erase_all(); + } + } + } + delay(50); +} diff --git a/examples/flash_erase_and_write/flash_erase_and_write.ino b/examples/flash_erase_and_write/flash_erase_and_write.ino new file mode 100644 index 0000000..872ab43 --- /dev/null +++ b/examples/flash_erase_and_write/flash_erase_and_write.ino @@ -0,0 +1,53 @@ +/* + * This skech do sime erase and write operations to the last available + * Flash page. + */ + +#include + +// Output function +void print_word(uint32_t *address); + +void setup() { + Serial.begin(9600); + delay(1500); + + // Print some flash data + Serial.print("Flash page size: "); + Serial.println(Flash.page_size()); + Serial.print("Number of flash pages: "); + Serial.println(Flash.page_count()); + Serial.print("Address of first page: 0x"); + Serial.println((size_t)Flash.page_address(0), HEX); + + // Find out address of the last available page + uint32_t *page = Flash.page_address(Flash.page_count() - 1); + print_word(page); + + // Erase the page + Serial.println("Erase page"); + Flash.erase(page, Flash.page_size()); + print_word(page); + + // Inform about write + Serial.println("Write 0x12345678"); + + // Write to flash, you can do more writes until writing is disabled + Flash.write(page, 0x12345678); + + // Print memory content + print_word(page); +} + +void loop() { + // Nothing to do here + yield(); +} + +// Print data +void print_word(uint32_t *address) { + Serial.print("Word at address 0x"); + Serial.print((size_t)address, HEX); + Serial.print("=0x"); + Serial.println(*address, HEX); +} diff --git a/examples/test_all/test_all.ino b/examples/test_all/test_all.ino new file mode 100644 index 0000000..bca6074 --- /dev/null +++ b/examples/test_all/test_all.ino @@ -0,0 +1,385 @@ +/* + * Test code for Flash, VirtualPage, NVM and EEPROM + * + * Do some tests and prints hopefully a lot of "OK" + * messages. + * + * This example code is in the public domain. + */ + +#include +#include +#include +#include +#include + +// Enable tests +#define TEST_FLASH +#define TEST_VIRTUALPAGE +#define TEST_NVRAM_TIMING +#define TEST_NVRAM_CONSISTENCY +#define TEST_EEPROM + +// Internal functions +void print_ok(); +void print_error(); +void print_compare(uint32_t word1, uint32_t word2); +void print_word(uint32_t *address); +void print_time(time_t time); +uint8_t addr2value(uint16_t idx); + +// Constants +#define MAGIC1 0x12345678 +#define MAGIC2 0x87654321 +#define MAGIC3 0x88888888 +#define EMPTY 0xffffffff + +void setup() { + // Initialize serial port + Serial.begin(9600); + // Wait some time + delay(1500); + + // Testdata + uint32_t *page, *old_page; + uint32_t testword = 0x12345678; + uint32_t testword_in; + time_t time_start, time_end, time_max; + uint16_t fill_size; + bool failed; + + // Clear Flash + VirtualPage.format(); + +/* + * Test Flash functionality + */ +#ifdef TEST_FLASH + Serial.println("\r\nFlash:\r\n======"); + // Print some flash data + Serial.print("Flash page size: "); + Serial.println(Flash.page_size()); + Serial.print("Number of flash pages: "); + Serial.println(Flash.page_count()); + Serial.print("Address of first page: 0x"); + Serial.println((size_t)Flash.page_address(0), HEX); + + // Find out address of a page + page = Flash.page_address(Flash.page_count() - 20); + print_word(page); + Serial.println(); + + // Erase the page + Serial.print("Erase page:"); + Flash.erase(page, Flash.page_size()); + print_compare(page[0], EMPTY); + + // Test write + Serial.print("Flash.write:"); + Flash.write(page, testword); + print_compare(page[0], testword); + + Serial.print("Flash.write_block:"); + Flash.write_block(&page[1], &testword, sizeof(testword)); + print_compare(page[1], testword); +#endif + +/* + * Test VirtualPage functionality + */ +#ifdef TEST_VIRTUALPAGE + Serial.println("\r\nVirtualPage:\r\n============"); + Serial.println("Format VirtualPage area."); + VirtualPage.format(); + + Serial.print("VirtualPage size:"); + Serial.println(VirtualPage.size()); + Serial.print("VirtualPage size:"); + Serial.println(VirtualPage.length()); + Serial.print("VirtualPage page_count:"); + Serial.println(VirtualPage.page_count()); + Serial.print("VirtualPage wear_level:"); + Serial.print(((float)VirtualPage.wear_level()) / 100); + Serial.println("%\r\n"); + + Serial.print("Get an invalid page:"); + page = VirtualPage.get(MAGIC3); + print_compare((size_t)page, EMPTY); + + Serial.print("Allocate a page:"); + page = VirtualPage.allocate(MAGIC1); + print_compare2((size_t)page, EMPTY); + + Serial.print("Page is not in 'Release' state: "); + print_compare(VirtualPage.release_started(page), 0); + + Serial.print("Get page again:"); + page = VirtualPage.get(MAGIC1); + print_compare2((size_t)page, EMPTY); + + Serial.print("Write to page:"); + Flash.write(&page[0], testword); + print_compare(page[0], testword); + + Serial.print("Prepare release a page:"); + VirtualPage.release_prepare(page); + print_compare(VirtualPage.release_started(page), 1); + old_page = page; + + Serial.println("Allocate a page:"); + page = VirtualPage.allocate(MAGIC1); + Serial.print(" - Valid page:"); + print_compare2((size_t)page, EMPTY); + Serial.print(" - Got a new page:"); + print_compare2((size_t)old_page, (size_t)page); + + Serial.println("Simulate aborted release:"); + page = VirtualPage.get(MAGIC1); + Serial.print(" - Got old page:"); + print_compare((size_t)old_page, (size_t)page); + Serial.print(" - Page in release:"); + print_compare(VirtualPage.release_started(page), 1); + + Serial.println("Allocate a page:"); + page = VirtualPage.allocate(MAGIC1); + Serial.print(" - Valid page:"); + print_compare2((size_t)page, EMPTY); + Serial.print(" - Got a new page:"); + print_compare2((size_t)old_page, (size_t)page); + + Serial.print("Release old page:"); + VirtualPage.release(old_page); + page = VirtualPage.get(MAGIC1); + print_compare2((size_t)old_page, (size_t)page); + + Serial.print("Write to page:"); + Flash.write(&page[0], testword); + print_compare(page[0], testword); + + Serial.print("Try to allocate duplicate pages:"); + failed = false; + // try to allocate more pages than available. This must work because the old + // page hast to deleted by allocate() + for (int i = 0; i <= (VirtualPage.page_count() + 1); i++) { + page = VirtualPage.allocate(MAGIC1); + // When no page is availabe or testdata found -> error + if (((size_t)page == EMPTY) || (page[0] == testword)) { + failed = true; + } + } + print_compare(failed, 0); + + Serial.print("Do clean_up(): "); + VirtualPage.release(old_page); + time_start = micros(); + VirtualPage.clean_up(); + time_end = micros(); + print_time(time_end - time_start); + // clear all empty pages + for (int i = 0; i < VirtualPage.page_count(); i++) { + VirtualPage.clean_up(); + } +#endif + +/* + * Test NVRAM functionality + */ +#if defined(TEST_NVRAM_CONSISTENCY) || defined(TEST_NVRAM_TIMING) + Serial.println("\r\nNVRAM:\r\n======="); + Serial.print("NVRAM size:"); + Serial.println(NVRAM.length()); + Serial.print("NVRAM log size:"); + Serial.println(NVRAM.write_prepare(0)); + Serial.println(); +#endif + +// Repeat this test with various length of used cells +#ifdef TEST_NVRAM_TIMING + fill_size = 1; + + while (fill_size < NVRAM.length() - 4) { + // Calculate addresses to fill + fill_size = fill_size * 2; + if (fill_size > NVRAM.length()) + fill_size = NVRAM.length(); + + // Fill + Serial.print("Fill NVRAM with "); + Serial.print(fill_size); + Serial.println(" bytes:"); + time_max = 0; + while (NVRAM.write_prepare(0) > 5) { + uint16_t addr = random(0, fill_size); + for (int i = 0; i < 4; i++) { + time_start = micros(); + NVRAM.write(addr, random(0, 256)); + time_end = micros(); + if ((time_end - time_start) > time_max) { + time_max = time_end - time_start; + } + addr++; + } + } + + Serial.print(" - Max write time until log is full: "); + print_time(time_max); + + // Read + time_max = 0; + for (int i = 0; i < NVRAM.length(); i++) { + time_start = micros(); + NVRAM.read(i); + time_end = micros(); + if ((time_end - time_start) > time_max) { + time_max = time_end - time_start; + } + } + Serial.print(" - Max read time when log is full: "); + print_time(time_max); + + // Force switch page + Serial.print(" - Log full write time: "); + time_start = micros(); + NVRAM.write_prepare(20); + time_end = micros(); + print_time(time_end - time_start); + } +#endif + +#ifdef TEST_NVRAM_CONSISTENCY + Serial.println("Check consistency: "); + + Serial.print(" - Write byte: "); + NVRAM.write(257, 0xcc); + print_compare(NVRAM.read(257), 0xcc); + + Serial.print(" - Write block: "); + NVRAM.write_block((uint8_t *)&testword, 257, sizeof(testword)); + NVRAM.read_block((uint8_t *)&testword_in, 257, sizeof(testword)); + print_compare(testword, testword_in); + + Serial.print(" - Until log is full: "); + // Clear all VirtualPages + VirtualPage.format(); + failed = false; + fill_size = 0; + // Fill until log is full + for (int i = 0; (i < NVRAM.length()) && (NVRAM.write_prepare(0) > 10); i++) { + uint8_t idxval = addr2value(i); + NVRAM.write(i, idxval); + if (NVRAM.read(i) != idxval) + failed = true; + fill_size = i; + } + print_compare(failed, 0); + + Serial.print(" - After clearing the log: "); + failed = false; + // Clear log + NVRAM.write_prepare(512); + // Check + for (int i = 0; i < fill_size; i++) { + uint8_t idxval = addr2value(i); + if (NVRAM.read(i) != idxval) + failed = true; + } + print_compare(failed, 0); +#endif + +#ifdef TEST_EEPROM + Serial.println("\r\nEEPROM:\r\n======="); + + Serial.print("eeprom_write_byte: "); + eeprom_write_byte(0, 0xaa); + print_compare(NVRAM.read(0), 0xaa); + + Serial.print("eeprom_read_byte: "); + print_compare(eeprom_read_byte(0), 0xaa); + + Serial.print("eeprom_write_block: "); + eeprom_write_block(&testword, 1, sizeof(testword)); + NVRAM.read_block((uint8_t *)&testword_in, 1, sizeof(testword)); + print_compare(testword, testword_in); + + Serial.print("eeprom_read_block: "); + eeprom_read_block(&testword_in, 1, sizeof(testword)); + print_compare(testword, testword_in); + + Serial.print("EEPROM.write: "); + EEPROM.write(5, 0x77); + print_compare(NVRAM.read(5), 0x77); + + Serial.print("EEPROM.read: "); + print_compare(EEPROM.read(5), 0x77); + + Serial.print("EEPROM[]++: "); + EEPROM[5]++; + print_compare(EEPROM[5], 0x78); + + Serial.print("EEPROM[]--: "); + EEPROM[5]--; + print_compare(EEPROM[5], 0x77); + +#endif + + Serial.println("\r\n***************\r\n* The END ;-) *\r\n***************"); +} + +void loop() { /** Empty loop. **/ +} + +void print_ok() { Serial.println(" OK"); } + +void print_error() { Serial.println(" ERROR"); } + +void print_compare(uint32_t word1, uint32_t word2) { + if (word1 == word2) { + Serial.println(" OK"); + } else { + Serial.print(" ERROR ("); + Serial.print(word1, HEX); + Serial.print(" != "); + Serial.print(word2, HEX); + Serial.println(")"); + } +} + +void print_compare2(uint32_t word1, uint32_t word2) { + if (word1 != word2) { + Serial.println(" OK"); + } else { + Serial.print(" ERROR ("); + Serial.print(word1, HEX); + Serial.print(" == "); + Serial.print(word2, HEX); + Serial.println(")"); + } +} + +void print_word(uint32_t *address) { + Serial.print("Word at address 0x"); + Serial.print((size_t)address, HEX); + Serial.print("=0x"); + Serial.println(*address, HEX); +} + +void fill_num(int num, char c) { + if (num < 100) { + Serial.print(c); + } + if (num < 10) { + Serial.print(c); + } +} + +void print_time(time_t time) { + int ms = time / 1000; + int modulo = time % 1000; + Serial.print(ms); + Serial.print('.'); + fill_num(modulo, '0'); + Serial.print(modulo); + Serial.println(" ms"); +} + +uint8_t addr2value(uint16_t idx) { return (uint8_t)(idx + (idx >> 8)); } diff --git a/examples/virtual_page_format/virtual_page_format.ino b/examples/virtual_page_format/virtual_page_format.ino new file mode 100644 index 0000000..556c199 --- /dev/null +++ b/examples/virtual_page_format/virtual_page_format.ino @@ -0,0 +1,15 @@ +#include +#include + +void setup() { + Serial.begin(9600); + delay(1500); + Serial.println("This erases all virtual pages. The wear level is preserved."); + VirtualPage.format(); + Serial.println("Virtual pages are reset."); + Serial.print("Actual Flash wear level: "); + Serial.print(((float)VirtualPage.wear_level()) / 100); + Serial.println("%"); +} + +void loop() { yield(); } diff --git a/examples/virtual_page_usage/virtual_page_usage.ino b/examples/virtual_page_usage/virtual_page_usage.ino new file mode 100644 index 0000000..5b46d13 --- /dev/null +++ b/examples/virtual_page_usage/virtual_page_usage.ino @@ -0,0 +1,273 @@ + +/* + * This skech erases demonstates the usage of VirtualPages. + * + * The VirtualPages are placed at the end of available Flash Memory. + * For nRF52 32k are used. The nRF51 use 16k of Flash Memory. + * + * You can put any type of 32 Bit aligned data into a VirtualPage. + * The size()/length() can differ at various platforms. At the moment + * a page has a minium size of 4080 bytes. + * + * For compatibility please don't use random writes or check if + * FLASH_SUPPORTS_RANDOM_WRITE is set after including . Fill + * a virtual page from beginning to the end to support a wide range of + * Flash Controllers. + * + * A page is addressed via a given MAGIC_COUNTER number. You can mange one or + * two differnt pages at a time on Controllers with 16k of reservation. + * + */ + +#include +#include + +// the MAGIC number to address your page +#define MAGIC_COUNTER 0xe7cba72b +#define MAGIC_TEST 0x5294aa9f + +// Functions +void print_word(uint32_t *address); +void print_time(time_t time_start, time_t time_end); +void print_page_data(uint32_t *address); +void print_address(uint32_t *address); +void print_counter(uint32_t counter); +void print_status(bool status); +void print_ok(); +void print_error(); + +// Variables +time_t time_start, time_end; + +/***************************************************/ + +void setup() { + Serial.begin(9600); + delay(1500); + + // Clear VirtualPages if needed + // VirtualPage.format(); + + // Variables + uint32_t *vpage, *new_vpage; + + // Print out some data about virtual pages + Serial.println("\r\n\r\nVirtual page " + "demonstration\r\n----------------------------------"); + Serial.print("Virtual page size in bytes: "); + Serial.println(VirtualPage.size()); + Serial.print("Virtual page number of words available: "); + Serial.println(VirtualPage.length()); + Serial.print("Maximum number of virtual pages: "); + Serial.println(VirtualPage.page_count()); + Serial.print("Wear level in percent: "); + Serial.println(((float)VirtualPage.wear_level()) / 100); + Serial.print("Used MAGIC_COUNTER word: 0x"); + Serial.println(MAGIC_COUNTER, HEX); + + // Try to allocate an old page, if it fails allocate a new page + time_start = micros(); + vpage = VirtualPage.get(MAGIC_COUNTER); + time_end = micros(); + if (vpage == (uint32_t *)~0) { + Serial.print("No page found with MAGIC_COUNTER 0x"); + Serial.print(MAGIC_COUNTER, HEX); + print_time(time_start, time_end); + Serial.print("Allocate a new page at "); + time_start = micros(); + // Allocate a new page + vpage = VirtualPage.allocate(MAGIC_COUNTER); + time_end = micros(); + } else { + Serial.print("Found an old page at "); + } + print_address(vpage); + print_time(time_start, time_end); + print_page_data(vpage); + print_counter(vpage[0]); + + Serial.println("\r\nUse pages in a loop. After 10 page releases we are using " + "clean_up() for faster allocation."); + for (int i = 0; i < 20; i++) { + Serial.print("Interation "); + Serial.println(i); + Serial.println("*************************"); + + // Print page data + print_page_data(vpage); + print_counter(vpage[0]); + + // Prepare releasing the old page + Serial.print("Prepare releasing the page "); + time_start = micros(); + VirtualPage.release_prepare(vpage); + time_end = micros(); + print_time(time_start, time_end); + + // Allocate a new page + Serial.print("Allocate a new page at "); + time_start = micros(); + new_vpage = VirtualPage.allocate(MAGIC_COUNTER); + time_end = micros(); + print_address(new_vpage); + print_time(time_start, time_end); + + // Print a message if allocating fails + if ((uint32_t)new_vpage == (uint32_t)~0) { + Serial.println("FAILED to allocate a new page. You can clear your " + "VirtualPages with VirtualPage.format();"); + } + + /* Write new counter to Flash. */ + // Write new counter value + Flash.write(&new_vpage[0], vpage[0] + 1); + + // Release the old page + Serial.print("Release the old page"); + time_start = micros(); + VirtualPage.release(vpage); + time_end = micros(); + print_time(time_start, time_end); + + // Swap page + vpage = new_vpage; + + // This code is for time measurement only. There is no need to call get() at + // this point + Serial.print("Time to get() the new page:"); + time_start = micros(); + vpage = VirtualPage.get(MAGIC_COUNTER); + time_end = micros(); + print_time(time_start, time_end); + + // Print page data + print_page_data(vpage); + print_counter(vpage[0]); + + // This code can called, in a none time critical moment + // Released pages are erased to allow faster allocating. + if (i > 9) { + Serial.print("Clean up released pages "); + time_start = micros(); + VirtualPage.clean_up(); + time_end = micros(); + print_time(time_start, time_end); + } + + Serial.println("\r\n"); + delay(1500); + } + + /* Do some testing. You don't need this in production code. */ + Serial.println("Test functionality"); + Serial.println("*************************"); + + Serial.print("Used MAGIC_TEST word: 0x"); + Serial.println(MAGIC_TEST, HEX); + + // Find a page + Serial.print("Try to get a page:"); + vpage = VirtualPage.get(MAGIC_TEST); + if (vpage == (uint32_t *)~0) { + Serial.println(" no page found. (OK on first run)"); + vpage = VirtualPage.allocate(MAGIC_TEST); + } else { + print_ok(); + } + + // Write test data + Serial.print("Write test:"); + Flash.write(&vpage[0], 0x12345678); + print_status(vpage[0] == 0x12345678); + + // Try to allocate a new page. Because the old page is not in release_sate, it + // must deleted + Serial.print("Alocate removes old pages:"); + new_vpage = VirtualPage.allocate(MAGIC_TEST); + print_status(vpage[0] != 0x12345678); + + // Allocate a new page, set it to release_prepare, allocate a new page and try + // to get the page -> old page is given + /* THIS IS BUGGY AT THE MOMENT + Serial.print("Test interrupted release process:"); + vpage = new_vpage; + // this writes 0x87654321 starting at the fourth byte of the page + Flash.write(&vpage[1], 0x87654321); + VirtualPage.release_prepare(vpage); + new_vpage = VirtualPage.allocate(MAGIC_TEST); + new_vpage = VirtualPage.get(MAGIC_TEST); + print_status( (size_t)vpage == (size_t)new_vpage); + */ + + // End + Serial.println("\r\nTHE END.\r\n\r\n"); +} + +void loop() { yield(); } + +/***************************************************/ + +// print a data word inluding address +void print_word(uint32_t *address) { + Serial.print("Word at address 0x"); + Serial.print((size_t)address, HEX); + Serial.print("=0x"); + Serial.println(*address, HEX); +} + +// print a time +void print_time(time_t time_start, time_t time_end) { + time_t micros = time_end - time_start; + + Serial.print(" "); + Serial.print(micros / 1000); + Serial.print("."); + int rest = (micros % 1000) / 10; + if (rest < 10) { + Serial.print("0"); + } + if (rest < 100) { + Serial.print("0"); + } + Serial.print(rest); + Serial.println("ms"); +} + +// dump page data +void print_page_data(uint32_t *address) { + Serial.println("---------- Page data ----------"); + Serial.print("Page address: 0x"); + Serial.println((size_t)address, HEX); + Serial.print("Page in release state: "); + Serial.println(VirtualPage.release_started(address)); + Serial.println("Data not equal to 0xffffffff:"); + for (int i = 0; i < VirtualPage.length(); i++) { + if (address[i] != ~(uint32_t)0) + print_word(&address[i]); + } + Serial.println("-------------------------------"); +} + +// Print a page address +void print_address(uint32_t *address) { + Serial.print("address 0x"); + Serial.print((size_t)address, HEX); +} + +// Print counter value +void print_counter(uint32_t counter) { + Serial.print("Data value is: "); + Serial.println(counter); +} + +void print_status(bool status) { + if (status) { + print_ok(); + } else { + print_error(); + } +} + +void print_ok() { Serial.println(" OK"); } + +void print_error() { Serial.println(" Error"); } diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..a2c86ca --- /dev/null +++ b/keywords.txt @@ -0,0 +1,66 @@ +####################################### +# Syntax Coloring Map For EEPROM +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +NVRAM KEYWORD1 +EEPROM KEYWORD1 +VirtualPage KEYWORD1 +Flash KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +# Flash/VirtualPage/EEPROM +length KEYWORD2 +page_count KEYWORD2 + +# Flash/NVRAM/EEPROM +write KEYWORD2 +write_block KEYWORD2 +clean_up KEYWORD2 + +# Flash +size KEYWORD2 +page_size KEYWORD2 +page_size_bits KEYWORD2 +page_count KEYWORD2 +specified_erase_cycles KEYWORD2 +page_address KEYWORD2 +erase KEYWORD2 +erase_all KEYWORD2 + +# VirtualPage +wear_level KEYWORD2 +get KEYWORD2 +allocate KEYWORD2 +release_prepare KEYWORD2 +release KEYWORD2 +release_started KEYWORD2 +fail KEYWORD2 +format KEYWORD2 + +# NVRAM +read_block KEYWORD2 +read KEYWORD2 +write_prepare KEYWORD2 + +# EEPROM +read KEYWORD2 +update KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +# Flash.h +FLASH_ERASE_CYCLES LITERAL1 +FLASH_PAGE_SIZE LITERAL1 +FLASH_ERASE_PAGE_TIME LITERAL1 +FLASH_SUPPORTS_RANDOM_WRITE LITERAL1 +FLASH_WRITES_PER_WORD LITERAL1 +FLASH_WRITES_PER_PAGE LITERAL1 diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..11a7146 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=NVM +version=0.9.0 +author=Frank Holtz +maintainer=Frank Holtz +sentence=Direct flash memory access, round robin virtual pages and EEPROM like memory. (Flash, VirtualPage, NVRAM) +paragraph=This package includes three Libraries (Flash, VirtualPage, NVRAM) and a EEPROM Emulation. Use avr_eeprom.h for a minimal AVR compatibility. Please look into README.md +category=Data Storage +url=https://github.com/d00616/arduino-NVM +architectures=nRF5,nRF52832,nRF51822 diff --git a/src/EEPROM.h b/src/EEPROM.h new file mode 100644 index 0000000..0c78c0b --- /dev/null +++ b/src/EEPROM.h @@ -0,0 +1,145 @@ +/* + EEPROM.h - EEPROM library + Original Copyright (c) 2006 David A. Mellis. All right reserved. + New version by Christopher Andrews 2015. + + 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 +*/ + +#ifndef EEPROM_h +#define EEPROM_h + +#include +#include "NVRAM.h" + +/*** + EERef class. + + This object references an EEPROM cell. + Its purpose is to mimic a typical byte of RAM, however its storage is the EEPROM. + This class has an overhead of two bytes, similar to storing a pointer to an EEPROM cell. +***/ + +struct EERef{ + + EERef( const int index ) + : index( index ) {} + + //Access/read members. + uint8_t operator*() const { return NVRAM.read( index ); } + operator uint8_t() const { return **this; } + + //Assignment/write members. + EERef &operator=( const EERef &ref ) { return *this = *ref; } + EERef &operator=( uint8_t in ) { return NVRAM.write( index, in ), *this; } + EERef &operator +=( uint8_t in ) { return *this = **this + in; } + EERef &operator -=( uint8_t in ) { return *this = **this - in; } + EERef &operator *=( uint8_t in ) { return *this = **this * in; } + EERef &operator /=( uint8_t in ) { return *this = **this / in; } + EERef &operator ^=( uint8_t in ) { return *this = **this ^ in; } + EERef &operator %=( uint8_t in ) { return *this = **this % in; } + EERef &operator &=( uint8_t in ) { return *this = **this & in; } + EERef &operator |=( uint8_t in ) { return *this = **this | in; } + EERef &operator <<=( uint8_t in ) { return *this = **this << in; } + EERef &operator >>=( uint8_t in ) { return *this = **this >> in; } + + EERef &update( uint8_t in ) { return in != *this ? *this = in : *this; } + + /** Prefix increment/decrement **/ + EERef& operator++() { return *this += 1; } + EERef& operator--() { return *this -= 1; } + + /** Postfix increment/decrement **/ + uint8_t operator++ (int){ + uint8_t ret = **this; + return ++(*this), ret; + } + + uint8_t operator-- (int){ + uint8_t ret = **this; + return --(*this), ret; + } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPtr class. + + This object is a bidirectional pointer to EEPROM cells represented by EERef objects. + Just like a normal pointer type, this can be dereferenced and repositioned using + increment/decrement operators. +***/ + +struct EEPtr{ + + EEPtr( const int index ) + : index( index ) {} + + operator int() const { return index; } + EEPtr &operator=( int in ) { return index = in, *this; } + + //Iterator functionality. + bool operator!=( const EEPtr &ptr ) { return index != ptr.index; } + EERef operator*() { return index; } + + /** Prefix & Postfix increment/decrement **/ + EEPtr& operator++() { return ++index, *this; } + EEPtr& operator--() { return --index, *this; } + EEPtr operator++ (int) { return index++; } + EEPtr operator-- (int) { return index--; } + + int index; //Index of current EEPROM cell. +}; + +/*** + EEPROMClass class. + + This object represents the entire EEPROM space. + It wraps the functionality of EEPtr and EERef into a basic interface. + This class is also 100% backwards compatible with earlier Arduino core releases. +***/ + +struct EEPROMClass{ + + //Basic user access methods. + EERef operator[]( const int idx ) { return idx; } + uint8_t read( int idx ) { return EERef( idx ); } + void write( int idx, uint8_t val ) { (EERef( idx )) = val; } + void update( int idx, uint8_t val ) { (EERef( idx )) = val; } + + //STL and C++11 iteration capability. + EEPtr begin() { return 0x00; } + EEPtr end() { return length(); } //Standards requires this to be the item after the last valid entry. The returned pointer is invalid. + uint16_t length() { return NVRAM.length(); } + + //Functionality to 'get' and 'put' objects to and from EEPROM. + template< typename T > T &get( int idx, T &t ){ + EEPtr e = idx; + uint8_t *ptr = (uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) *ptr++ = *e; + return t; + } + + template< typename T > const T &put( int idx, const T &t ){ + EEPtr e = idx; + const uint8_t *ptr = (const uint8_t*) &t; + for( int count = sizeof(T) ; count ; --count, ++e ) (*e).update( *ptr++ ); + return t; + } +}; + +extern EEPROMClass EEPROM; +#endif diff --git a/src/Flash.cpp b/src/Flash.cpp new file mode 100644 index 0000000..1613f2d --- /dev/null +++ b/src/Flash.cpp @@ -0,0 +1,135 @@ +/* + 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 "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/src/Flash.h b/src/Flash.h new file mode 100644 index 0000000..f6f6110 --- /dev/null +++ b/src/Flash.h @@ -0,0 +1,121 @@ +/* + 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 + */ + +#pragma once + +#include +#include // for size_t + +#ifdef __RFduino__ +#include +#else +#include +#endif + +/* + * Define characteristics of Flash + * + * FLASH_ERASE_CYCLES : Specified number of erase cycles + * FLASH_PAGE_SIZE : Used/supported Flash page size + * FLASH_ERASE_PAGE_TIME : Time in ms to delete a page + * FLASH_WRITES_PER_WORD : How often a dataword (32 bit) can written + * FLASH_WRITES_PER_PAGE : How many writes are allowed into a page + * FLASH_SUPPORTS_RANDOM_WRITE: 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 { +public: + FlashClass(){}; + void begin(){}; + void end(){}; + + /* + * Physical flash geometry + */ + + // Page size in bytes + uint32_t page_size() const; + + // Page size in bits + uint8_t page_size_bits() const; + + // Number of pages + uint32_t page_count() const; + + // Number of erase cycles + uint32_t specified_erase_cycles() const; + + // Get a address of a 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! + 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. Depends on write_enable! + void write(uint32_t *address, uint32_t value); + + // write a aligned 32 bit word to flash. Depends on write_enable! + 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; diff --git a/src/NVRAM.cpp b/src/NVRAM.cpp new file mode 100644 index 0000000..6b236fb --- /dev/null +++ b/src/NVRAM.cpp @@ -0,0 +1,323 @@ +/* + 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 + 1; + break; + } + } + + // 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/src/NVRAM.h b/src/NVRAM.h new file mode 100644 index 0000000..ac91d9b --- /dev/null +++ b/src/NVRAM.h @@ -0,0 +1,64 @@ +/* + 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 +*/ +#pragma once + +#include "Flash.h" +#include "VirtualPage.h" +#include + +class NVRAMClass { +public: + NVRAMClass(){}; + void begin(){}; + void end(){}; + + // Usable page size in bytes + uint16_t length() const; + + // Read a block + void read_block(uint8_t *dst, uint16_t idx, uint16_t n); + // Read a byte + uint8_t read(const uint16_t idx); + + // Write a block + bool write_block(uint8_t *src, uint16_t idx, uint16_t n); + // Write a byte + bool write(const uint16_t idx, uint8_t value); + + // prepare a time critical write of given bytes + int write_prepare(uint16_t number); + + // Clear log if full and prepare released pages for faster reallocation. + // Plan with 0-5000ms. + 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); +}; + +extern NVRAMClass NVRAM; diff --git a/src/VirtualPage.cpp b/src/VirtualPage.cpp new file mode 100644 index 0000000..5073328 --- /dev/null +++ b/src/VirtualPage.cpp @@ -0,0 +1,321 @@ +/* + 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/src/VirtualPage.h b/src/VirtualPage.h new file mode 100644 index 0000000..98d9e88 --- /dev/null +++ b/src/VirtualPage.h @@ -0,0 +1,94 @@ +/* + 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 +*/ + +/* + * Virtual page management: + * The managed pages are organized into VirtualPage.size() sized pages. + * The first and last 8 Bytes are reserved for magic number and page marking. + * + */ + +#pragma once + +#include "Flash.h" +#include + +class VirtualPageClass { +public: + VirtualPageClass(){}; + void begin(){}; + void end(){}; + + // Usable page size in bytes + uint16_t size() const; + + // Usable page size in data words + uint16_t length() const; + + // Maximum number of allocatable pages + uint16_t page_count() const; + + // Calculates rate of wear in percent*100. + // Values grater than 10000 indicates exeeding the chip specification + // this value is only valid for fresh controllers. + uint32_t wear_level(); + + // Search for a page by given, uniqe magic number. + // Returns a pointer to (uint32_t *)~0 if there is no page. Don't write to + // this address. + uint32_t *get(uint32_t magic); + + // Returns an address to an blank page or (uint32_t *)~0 if no space + // available. + // Take care about RADIO, WDT and Interrupt timing! Plan with 0-100ms + uint32_t *allocate(uint32_t magic); + + // Search for a blank page from top of flash to do max_writes write operations + // Take care about RADIO, WDT and Interrupt timing! Plan with 0-100ms + uint32_t *allocate(uint32_t magic, uint32_t max_writes); + + // Start releasing a page. You have to allocate a new one and than release the + // old page + void release_prepare(uint32_t *address); + + // Page is marked as available for next allocation + void release(uint32_t *address); + + // Returns true if page is in release_prepare state + bool release_started(uint32_t *address); + + // mark a page as defect + 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/src/avr_eeprom.h b/src/avr_eeprom.h new file mode 100644 index 0000000..8558415 --- /dev/null +++ b/src/avr_eeprom.h @@ -0,0 +1,28 @@ +/* + avr_eeprom.cpp - Emulate some avr/eeprom.h functionality. + 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 + +#define E2END (NVRAM.length() - 1) +#define eeprom_write_byte(idx, val) NVRAM.write(idx, val) +#define eeprom_read_byte(idx) NVRAM.read(idx) +#define eeprom_write_block(src, idx, n) \ + NVRAM.write_block((uint8_t *)src, (size_t)idx, n) +#define eeprom_read_block(dst, idx, n) \ + NVRAM.read_block((uint8_t *)dst, (size_t)idx, n)