Addition of a utility class for cycling writes (#8)

I2C_eeprom
*  Added page sizes for larger memory sizes (still deriving from the ATxxCxxx datasheets)

I2C_eeprom_cyclic_store
* Add the cyclic store implementation.
* Add an example for cyclic use of EEPROM.
* Update keywords.txt for syntax highlighting the new class and it's members.
* Changed signature of begin() to match page size data type.
* Add getMetrics() to have an indication about lifetime
This commit is contained in:
Tomas Hübner
2021-01-04 08:33:22 +01:00
committed by GitHub
parent 5e12716b9c
commit 98235dfd82
4 changed files with 336 additions and 1 deletions
+11 -1
View File
@@ -57,11 +57,21 @@ I2C_eeprom::I2C_eeprom(const uint8_t deviceAddress, const unsigned int deviceSiz
this->_isAddressSizeTwoWords = false;
this->_pageSize = 16;
}
else
else if(deviceSize <= 8192 * 8)
{
this->_isAddressSizeTwoWords = true;
this->_pageSize = 32;
}
else if(deviceSize <= 65536 * 8)
{
this->_isAddressSizeTwoWords = true;
this->_pageSize = 64;
}
else
{
this->_isAddressSizeTwoWords = true;
this->_pageSize = 128;
}
}
#if defined (ESP8266) || defined(ESP32)
+262
View File
@@ -0,0 +1,262 @@
#pragma once
//
// FILE: I2C_eeprom_cyclic_access.h
// AUTHOR: Tomas Hübner
// VERSION: 1.0.0
// PURPOSE: Supplemental utility class for I2C_EEPROM library
//
#include <I2C_eeprom.h>
/**
* @brief This is a utility class for using an eeprom to store a simple
* data structure.
*
* The purpose of the utility is to extend the life of an eeprom memory by
* rotating the writes over different pages to reduce the number of writes
* to any individual page.
* It does this by partitioning the memory into slots (of continuous pages)
* big enough to contain the data structure. When data is written to a slot
* it is given a header with a
* version number.
* On initialization the slots are scanned for the one with the highest
* version number which is then used for subsequent reads.
* Whenever data is written the version number is incremented and the next
* slot in sequence is used (or the first slot if going past the end).
*
* Note that the data is stored in binary form which means it should not be
* expected that the eeprom can be moved between architectures. Data stored
* in an eeprom will also become invalid if the data structure is changed
* either manually or due to changed optimization settings.
*
* If the data structure has changed or if the eeprom contains other data it
* must first be formatted with a call to format().
*
* Finally, since the version number is a long word, this class will
* *derail and fail* to add new versions past 4294967295 writes.
*
* @tparam T the type of the data structure to store, should only contain
* **value** members and no constructor/destructor nor other
* methods/functions - e g a pure DTO.
*/
template <typename T>
class I2C_eeprom_cyclic_store
{
public:
/**
* @brief Initializes the instance
*
* This call searches the eeprom for the latest written version and
* sets the current slot accordingly.
*
* @param eeprom The instance of I2C_eeprom to use.
* @param pageSize The number of bytes in each write page.
* @param totalPages Specifies the total number of pages to use.
* Specifying a number that is less than the available pages will
* exclude the remaining pages from being used.
* @return True if initialization succeeds, false otherwise.
*/
bool begin(I2C_eeprom &eeprom, uint8_t pageSize, uint16_t totalPages)
{
_eeprom = &eeprom;
_pageSize = pageSize;
_totalPages = totalPages;
auto bufferSize = sizeof(_currentVersion) + sizeof(T);
_bufferPages = bufferSize / _pageSize + (bufferSize % _pageSize ? 1 : 0);
return (_bufferPages < _totalPages) && initialize();
};
/**
* @brief Formats the eeprom
*
* This must be done if the eeprom already contains data or if the
* structure of the data stored changes.
*
* Formatting is done by writing the max version number to each slot,
* thus it performs a write cycle to the first write page of each slot.
*/
void format()
{
// Reset the EEPROM by writing a ~0 into all pages
for (uint16_t slot = 0; slot < _totalPages; slot++)
{
_eeprom->writeBlock(slot * _pageSize, (uint8_t *)"\xff\xff\xff\xff", 4);
}
_isEmpty = true;
_currentSlot = 0;
_isInitialized = true;
}
/**
* @brief Read data from the eeprom into a buffer.
*
* The data is read from the current slot of the eeprom.
*
* @param buffer A reference to the buffer to read data into.
* @return True if data was read successfully, false otherwise.
*/
bool read(T &buffer) const { return read(&buffer); }
/**
* @brief Read data from the eeprom into a buffer.
*
* The data is read from the current slot of the eeprom.
*
* @param buffer A pointet to the buffer to read data into.
* @return True if data was read successfully, false otherwise.
*/
bool read(T *buffer) const
{
if (!_isInitialized)
return false;
if (_isEmpty)
return false;
return _eeprom->readBlock((_currentSlot * _bufferPages * _pageSize) + sizeof(_currentVersion), (uint8_t *)buffer, sizeof(T)) == sizeof(T);
}
/**
* @brief Write a buffer to the next slot in the eeprom.
*
* This updates the current slot of this instance.
*
* @param buffer A reference to the buffer to write data from.
* @return True if data was written successfully, false otherwise.
*/
bool write(T &buffer) { return write(&buffer); }
/**
* Write data of object to the next slot in the eeprom. This updates
* the current slot of the instance.
*
* @param buffer A pointer to the buffer to write data from.
* @return True if data was written successfully, false otherwise.
*/
bool write(T *buffer)
{
if (!_isInitialized)
{
return false;
}
if (_isEmpty)
{
_currentSlot = 0;
_currentVersion = 0;
}
else
{
_currentSlot++;
_currentVersion++;
// Wrap around to start if going past end of alotted region
uint16_t maxSlots = _totalPages / _bufferPages;
if (_currentSlot >= maxSlots)
_currentSlot = 0;
}
auto buffer_length = sizeof(_currentVersion) + sizeof(T);
uint8_t tmp[buffer_length];
memcpy(tmp, &_currentVersion, sizeof(_currentVersion));
memcpy(tmp + sizeof(_currentVersion), buffer, sizeof(T));
auto success = _eeprom->writeBlock(_currentSlot * _bufferPages * _pageSize, tmp, buffer_length) == 0;
if (success)
_isEmpty = false;
return success;
}
/**
* @brief Returns metrics for the eeprom usage.
*
* Dividing the returned values of \p writeCounter with \p slots yields the average number of
* writes to the individual write pages of the eeprom. This can be used to estimate the remaining
* number of possible writes.
*
* @param[out] slots The number of slots used to write the data buffer.
* @param[out] writeCounter The total number of write to the eeprom since the last format (or first use).
* @return True if the instance is initialized, false otherwise.
*/
bool getMetrics(uint16_t &slots, uint32_t &writeCounter)
{
if(!_isInitialized)
return false;
slots = _totalPages / _bufferPages;
writeCounter = _isEmpty ? 0 : _currentVersion+1;
return true;
}
private:
uint8_t _pageSize;
uint16_t _bufferPages;
uint16_t _totalPages;
uint16_t _currentSlot;
uint32_t _currentVersion;
bool _isInitialized = false;
bool _isEmpty = false;
I2C_eeprom *_eeprom;
bool initialize()
{
uint16_t startSlot, probeSlot, endSlot;
uint32_t current, probe;
auto slotSize = _pageSize * _bufferPages;
startSlot = 0;
endSlot = (_totalPages / _bufferPages) - 1; // Index of last slot
probeSlot = startSlot + ((endSlot - startSlot) / 2); // Midway between start and end
if(_eeprom->readBlock(0, (uint8_t *)&current, sizeof(current)) != sizeof(current))
{
return false;
}
if (current == ~0UL)
{
// Memory is blank
_isEmpty = true;
_currentSlot = 0;
_currentVersion = 0;
_isInitialized = true;
return true;
}
while (startSlot != probeSlot)
{
if(_eeprom->readBlock(probeSlot * slotSize, (uint8_t *)&probe, sizeof(current)) != sizeof(current))
{
return false;
}
if (probe == ~0UL || probe <= current)
{
// 1. Nothing has been written to the memory at Probe
// 2. The slots have the same timestamp, this shouldn't happen, treat as if Probe slot hasn't been written
// 3. Probe is older that Start, change End to slot before Probe
endSlot = probeSlot-1;
}
else
{
// 1. Probe is later than Start, change Start to Probe
startSlot = probeSlot;
current = probe;
}
probeSlot = startSlot + ((endSlot - startSlot + 1) / 2);
}
_currentSlot = startSlot;
_currentVersion = current;
_isEmpty = false;
_isInitialized = true;
return true;
}
};
@@ -0,0 +1,54 @@
//
// FILE: I2C_eeprom_cyclic_store.ino
// AUTHOR: Tomas Hübner
// VERSION: 1.0.0
// PURPOSE: Simple example of how to use cyclic storage.
//
#include <I2C_eeprom.h>
#include <I2C_eeprom_cyclic_store.h>
#define MEMORY_SIZE 0x2000 // Total capacity of the EEPROM
#define PAGE_SIZE 64 // Size of write page of device, use datasheet to find!
struct SampleData {
public:
uint32_t counter;
// Must use fixed length string, avoid using the String class.
char message[32];
};
SampleData data;
I2C_eeprom ee(0x50, MEMORY_SIZE);
I2C_eeprom_cyclic_store<SampleData> cs;
void setup()
{
Serial.begin(115200);
while(!Serial);
ee.begin();
cs.begin(ee, PAGE_SIZE, MEMORY_SIZE/PAGE_SIZE);
if(!cs.read(data))
{
// The eeprom is uninitialized
data.counter = 1;
sprintf(data.message, "Initialized to %x", data.counter);
cs.write(data);
}
}
void loop()
{
Serial.println(data.message);
data.counter++;
sprintf(data.message, "Written %x times", data.counter);
cs.write(data);
delay(10000);
}
+9
View File
@@ -2,8 +2,12 @@
# Datatypes (KEYWORD1)
I2C_eeprom KEYWORD1
I2C_eeprom_cyclic_store KEYWORD1
# Methods and Functions (KEYWORD2)
# Common
begin KEYWORD2
# I2C_eeprom
readByte KEYWORD2
writeByte KEYWORD2
setBlock KEYWORD2
@@ -11,5 +15,10 @@ readBlock KEYWORD2
writeBlock KEYWORD2
determineSize KEYWORD2
updateByte KEYWORD2
# I2C_eeprom_cyclic_store
format KEYWORD2
read KEYWORD2
write KEYWORD2
getMetrics KEYWORD2
# Constants (LITERAL1)