Implement memory wear leveling algorithm

This commit is contained in:
2024-09-20 21:30:31 +02:00
parent e2b5c5fd42
commit 10298a99c1
3 changed files with 162 additions and 50 deletions

View File

@@ -1,48 +1,147 @@
#include "common.h"
#include <util/atomic.h>
#include <stdio.h>
#define MEM_SIZE 1024
#define BLOCK_SIZE sizeof(mem_data_t)
#define MAX_BLOCKS (MEM_SIZE / BLOCK_SIZE)
#define SENTINEL_MASK BIT(8)
#define MEM_SIZE 1024
#define MEM_NUM_CELLS 2
#define MEM_CELL_SIZE ((int) sizeof(word))
#define MEM_BLOCK_SIZE (MEM_NUM_CELLS * MEM_CELL_SIZE + 1)
#define MEM_MAX_BLOCKS (MEM_SIZE / MEM_BLOCK_SIZE)
#define MEM_IS_DIRTY 0x80 // Dirty block marker
static byte ReadRaw(word addr);
static int WriteRaw(word addr, byte data);
static word GetBlockAddress(void);
// TODO: Automated testing /w multiple write cycles
// TODO: Accept structure containing variable data
int MEM_Read(mem_data_t *out)
static int GetUsedBlock(void);
static int GetFreeBlock(void);
static byte ReadRaw(int addr);
static void WriteRaw(int addr, byte data);
void MEM_Init(void)
{
// TODO
UNUSED(out);
UNUSED(ReadRaw);
UNUSED(GetBlockAddress);
return 0;
// Valid first sentinel?
if (ReadRaw(0) == 0xFF) {
Info("Initializing fresh memory...");
MEM_Free(); // Set dirty block markers
}
}
int MEM_Write(mem_data_t *in)
void MEM_Read(word *temp, word *dewp)
{
// TODO
UNUSED(in);
UNUSED(WriteRaw);
return 0;
int bl;
word addr;
byte msb[2], lsb[2];
bl = GetUsedBlock();
if (bl < 0) {
*temp = 0;
*dewp = 0;
return; // Empty
}
addr = bl * MEM_BLOCK_SIZE;
msb[0] = ReadRaw(addr + 1);
lsb[0] = ReadRaw(addr + 2);
msb[1] = ReadRaw(addr + MEM_CELL_SIZE + 1);
lsb[1] = ReadRaw(addr + MEM_CELL_SIZE + 2);
*temp = ((msb[0] & 0xFF) << 8) | (lsb[0] & 0xFF);
*dewp = ((msb[1] & 0xFF) << 8) | (lsb[1] & 0xFF);
}
void MEM_Write(word temp, word dewp)
{
int bl;
word addr;
byte msb[2], lsb[2];
bl = GetFreeBlock();
if (bl < 0) {
// Full
MEM_Free();
bl = 0;
}
addr = bl * MEM_BLOCK_SIZE;
msb[0] = (temp >> 8) & 0xff;
lsb[0] = temp & 0xff;
msb[1] = (dewp >> 8) & 0xff;
lsb[1] = dewp & 0xff;
WriteRaw(addr, MEM_IS_DIRTY);
WriteRaw(addr + 1, msb[0]);
WriteRaw(addr + 2, lsb[0]);
WriteRaw(addr + MEM_CELL_SIZE + 1, msb[1]);
WriteRaw(addr + MEM_CELL_SIZE + 2, lsb[1]);
}
void MEM_Free(void)
{
int bl;
// Clear all dirty block markers
for (bl = 0; bl < MEM_MAX_BLOCKS; bl++) {
WriteRaw(bl * MEM_BLOCK_SIZE, 0x0);
if ((bl % 64) == 0) WDT_Reset();
}
}
void MEM_Dump(void)
{
// byte rom[1024];
// Info("Dumping EEPROM memory:");
// for (int i = 0; i < 1024; i++) {
// rom[i] = ReadRaw(i);
// }
byte raw;
char buf[3*16+1];
Info("Dumping EEPROM memory:");
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 16; j++) {
raw = ReadRaw(i * 16 + j);
sprintf(buf + j * 3, "%02X ", raw);
}
WDT_Reset();
Info(buf);
}
}
static word GetBlockAddress(void)
static int GetUsedBlock(void)
{
return 0;
int bl;
bl = GetFreeBlock();
if (bl == 0)
return -1;
else if (bl < 0)
return MEM_MAX_BLOCKS;
else
return bl - 1;
}
static int WriteRaw(word addr, byte data)
static int GetFreeBlock(void)
{
int bl;
byte flag;
bool is_free;
for (bl = 0; bl < MEM_MAX_BLOCKS; bl++) {
// Check dirty flag for current block
flag = ReadRaw(bl * MEM_BLOCK_SIZE);
if (flag != MEM_IS_DIRTY) {
is_free = true;
break;
}
}
// Is there any space left?
return (is_free) ? bl : -1;
}
static void WriteRaw(int addr, byte data)
{
// The EEMWE bit determines whether setting EEWE to
// one causes the EEPROM to be written. When EEMWE
@@ -68,12 +167,12 @@ static int WriteRaw(word addr, byte data)
// Flag cleared during all the steps to avoid these
// problems.
// Wait until ready
while (EECR & BIT(EEWE));
// No interrupts during EEPROM write
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// Wait until ready
while (EECR & BIT(EEWE));
// The EEPROM Address Registers EEARH and
// EEARL specify the EEPROM address in the
// 1024bytes EEPROM space. The EEPROM data
@@ -89,31 +188,33 @@ static int WriteRaw(word addr, byte data)
EECR |= BIT(EEMWE);
EECR |= BIT(EEWE);
}
return 0;
}
static byte ReadRaw(word addr)
static byte ReadRaw(int addr)
{
// Wait until ready
while (EECR & BIT(EEWE));
EEAR = addr;
// The EEPROM Read Enable Signal EERE is the read
// strobe to the EEPROM. When the correct address
// is set up in the EEAR Register, the EERE bit
// must be written to a logic one to trigger the
// EEPROM read.
// Read from address
EECR |= BIT(EERE);
// The EEPROM read access takes one instruction,
// and the requested data is available immediately.
// When the EEPROM is read, the CPU is halted for
// four cycles before the next instruction is
// executed.
// No interrupts during EEPROM read
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
// Wait until ready
while (EECR & BIT(EEWE));
EEAR = addr;
// Read from address
EECR |= BIT(EERE);
}
return EEDR;
}

View File

@@ -1,15 +1,13 @@
#ifndef MAD_CORE_COMMON_MEMORY_H
#define MAD_CORE_COMMON_MEMORY_H
typedef struct mem_data_s mem_data_t;
#define M_TEMPERATURE 0
#define M_DEWPOINT 1
struct mem_data_s {
byte sentinel; // Data marker bit
word value[2]; // Values to be written
};
int MEM_Read(mem_data_t *out);
int MEM_Write(mem_data_t *in);
void MEM_Init(void);
void MEM_Write(word temp, word dewp);
void MEM_Read(word *temp, word *dewp);
void MEM_Free(void);
void MEM_Dump(void);
#endif // MAD_CORE_COMMON_MEMORY_H

View File

@@ -7,6 +7,7 @@
#include <avr/interrupt.h>
// TODO: Config header for chip specifics like EEPROM size.
// TODO: Make sure wear leveling resets memory correctly.
// TODO: Implement primary state machine for update loop.
// TODO: Migrate to ATMega 1284P-PU for 2nd 16-bit timer.
// TODO: Keep persistent TEMP and DEWP targets in EEPROM.
@@ -26,8 +27,8 @@ enum state_e
};
static enum state_e state;
static float temp, temp_target;
static float dewp, dewp_target;
static word temp, temp_target;
static word dewp, dewp_target;
static int Init(void)
{
@@ -67,6 +68,18 @@ static int Init(void)
WDT_Enable();
WDT_SetTimeoutFlag(WDT2000); // 2 seconds
// Test persistent settings from EEPROM. There must
// be some sanity checking before these values are
// used.
MEM_Init();
MEM_Read(&temp_target, &dewp_target);
Info("Persistent memory contains [%d, %d]",
temp_target, dewp_target);
// MEM_Write(20, 60);
// MEM_Dump();
// There is a possiblity to use interrupt signals
// for I2C communication but only as one large
// branching routine for the whole I2C system.