Moin zusammen
ich bin vor kurzem über ein Kommentar von @combie gestolpert (Kommentar), wonach pages beim EEPROM im Zusammenhang mit Wear Leveling irgendwie wichtig zu sein schein.
Für meinen Projekt verwende ich ein Arduino Nano Every mit dem ATmega4809. Im EEPROM möchte zwei Parametersätze abspeichern, für die ich jeweils ein struct angelegt habe. Die beiden sehen so aus:
// Lighting Data including FactorySettings
// Use structs for data stored in EEPROM
struct EESettings { // Will be changed seldom. One address only
int colorTMinLED; // lowest colortemperature of used LED
int colorTMaxLED; // hightest
byte lmCWLED; // lm/W of cold white LED
byte lmWWLED; // warm
int colorTMinHCL; // lowest colortemperature in HCL mode
int colorTMaxHCL; // hightest
int hclTsDelta; // Time Shift, Starting/Min Time, in min
int hclTeDelta; // End/Max Time
byte fadingDelay; // Fading HCL <-> Man
byte powerUpDelay; // Delay for LightOn after PowerON
byte powerDnDelay; // LightOff PowerOFF
EESettings() { // === FACTORY SETTINGS by constructor ===
colorTMinLED = 2700; // lowest colortemperature of used LED
colorTMaxLED = 6500; // hightest
lmCWLED = 100; // lm/W of cold white LED
lmWWLED = 90; // warm
colorTMinHCL = 2700; // lowest colortemperature in HCL mode
colorTMaxHCL = 4000; // hightest
hclTsDelta = -30; // Time Shift, Starting/Min Time, in min
hclTeDelta = 105; // End/Max Time
fadingDelay = 6; // Fading HCL <-> Man, , 500ms
powerUpDelay = 17; // Delay for LightOn after PowerON , 2500ms
powerDnDelay = 17; // LightOff PowerOFF, 2500ms
}
};
struct EERotEncoder { // Will be saved at any power-off
unsigned long counter = 0; // Counter to find latest saved data
byte rdBrHCLLast; // Absolute Position HCL - Bright
byte rdCtHCLLast; // - ColorT
byte rdBrManLast; // Absolute Position Man - Bright
byte rdCtManLast; // - ColorT
EERotEncoder () { // === FACTORY SETTINGS by constructor ===
counter = 0; // Default is no EEPROM savings yet
rdBrHCLLast = 128; // HCL: 50% brightness
rdCtHCLLast = 128; // colorT at noon = half of HCL range
rdBrManLast = 128; // Man: 50% brightness
rdCtManLast = 0; // lowest colorT
}
};
Für beide wird nur eine Variable angelegt:
// <General Paramter> are always as adr = 0. Assumed to change seldom (far below 100.000x). They
// can be updated at any time because only updated(!) values are actual stored.
// <Rotary Encoder> are stored in remaining EEPROM using wear leveling technique. Data is put
// into a ring buffer, so that ~3Mio writings are obtained in 256byte EEPROM.
// It is deemed as sufficient from user-perpective to store in EEPROM at when
// light is switched off. It will then properly initialised after loss of
// electrical power
EESettings eeSettings;
EERotEncoder eeRotEncoder; // Rotoray Encoder Data
const byte sizeEESettings = sizeof(eeSettings); // Size of one Dataset - GeneralPara
const byte sizeEERotEncoder = sizeof(EERotEncoder); // - RotEncoder
const unsigned int eeRotEncAdr0 = sizeEESettings; // Adr for storing encoder positions: Offset
unsigned int eeRotEncAdrActual = eeRotEncAdr0; // Actual
unsigned int eeRotEncBufActual = 1; // [1..eeRotEncBufSize]
const unsigned int eeRotEncBufSize = (EEPROM.length() - sizeEESettings) / sizeEERotEncoder;
Die Daten in eeSettings ändern sich nur selten. Wear Leveling ist nicht notwendig. Anders bei eeERotEncoder, weswegen es den Zähler eeERotEncoder.counter gibt. Er wird beim jedem Schreiben um eins erhöht und bei der Programminitialisierung wird der zuletzt abgespeicherte Wert eben über den höchsten Zählerstand gefunden.
Wie sich (hoffentlich) erahnen lässt, liegt eeSettings bei der Adresse Null und der allererste EIntrag von eeERotEncoder folgt unmittelbar danach.
Im weiteren wird gemäß der Größe von eeERotEncoder als auch dem EEPROM bei jedem Speichervorgang die Adresse beim nächsten Schreibvorgang erhöht, bis das "Ende" vom EEPROM erreicht wurde, und das Spiel beginnt wieder bei eeRotEncAdr0. Für das Schreiben verwende dich die Methode put() aus der EEPROM.h, also, im Fall von eeERotEncoder:
OpError ShutDownPowerStage() {
OpError opErr = OpError::None; // Assume as Default
// (1) Collect required inputs
// (none)
// (2) Recovery
// Copy latest Last-Values to rotary encoder related data struct
// EncoderData (now stored in eeRotEncoder)
eeRotEncoder.counter++; // Increase counter to mark latest entry
eeRotEncoder.rdBrHCLLast = rdBrHCLLast; // HCL - Brightness
eeRotEncoder.rdCtHCLLast = rdCtHCLLast; // - color temperature at noon
eeRotEncoder.rdBrManLast = rdBrManLast; // Man - Brightness
eeRotEncoder.rdCtManLast = rdCtManLast; // - color temperature
// Calculate next address for EEPROM write at shutoff (for rotary encoder), and write EEPROM
if (eeRotEncBufActual == eeRotEncBufSize) eeRotEncBufActual = 1;
else eeRotEncBufActual++;
eeRotEncAdrActual = eeRotEncAdr0 + (eeRotEncBufActual-1) * sizeEERotEncoder;
EEPROM.put(eeRotEncAdrActual, eeRotEncoder); // Store in EEPROM at "soft turn-off"
// (3) Actual values
// (none)
// (4) Send Command: CMD-Byte = Light Off, Power Off, Off Delay, lowest colorT [K], zero brightness
(void) SetPowerCMD(powerCMD, 0b00000000, colorTMinLED, 0, powerDnDelay);
if (!SendCMD(powerCMD)) { // Send CMD by default, but may be blocked
opErr = OpError::I2C; // I2C Error in case it was not successful
}
// (5) Manage OLED Display.
runDisplay(DisplayMode::Off);
return(opErr);
}
Soweit so gut, mein Code funktioniert. Nur, habe ich damit auch ein Wear Leveling implementiert? Wie sich im Code erkennen läßt, verwende ich nirgends die Page-Größe es eingesetzten ATmega4809 vom Arduino Board.
Der ATmega4809 hat ein 256Byte großes EEPROM, die page ist 64Byte groß.
Ich Datenblatt des ATmega4809 konnte ich folgendes finden:
9.3.1.2 EEPROM: The EEPROM is divided into a set of pages where one page consists of multiple bytes. The EEPROM has byte granularity on the erase/write. Within one page, only the bytes marked to be updated will be erased/written. The byte is marked by writing a new value to the page buffer for that address location.
Dort steht: The EEPROM has byte granularity on the erase/write - was mich glauben lässt, pages sind egal... Also mein Code schont in der Tat das EEPROM. Und ich kann auch eeSettings und eeERotEncoder auf der ersten page abspeichern, ohne das beim mehrfachen Schreiben von eeERotEncoder an verschieden Adressen der ersten Page die Daten von eeSettings irgendwie mitmanipuliert werden.
Oder hätte ich doch pages berücksichtigen müssen. Und falls ja, wie?