mirror of
https://github.com/RobTillaart/I2C_EEPROM.git
synced 2026-07-27 20:06:07 +00:00
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:
+11
-1
@@ -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)
|
||||
|
||||
@@ -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 *)¤t, 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);
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user