diff --git a/src/common/memory.c b/src/common/memory.c index d8f76f9..371cec2 100644 --- a/src/common/memory.c +++ b/src/common/memory.c @@ -1,110 +1,112 @@ #include "common.h" #include -#include +#include #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_BLOCK_SIZE ((int) sizeof(mem_block_t)) #define MEM_MAX_BLOCKS (MEM_SIZE / MEM_BLOCK_SIZE) #define MEM_IS_DIRTY 0x80 // Dirty block marker -// TODO: Automated testing /w multiple write cycles -// TODO: Accept structure containing variable data - static int GetUsedBlock(void); static int GetFreeBlock(void); -static byte ReadRaw(int addr); +static void WriteBlock(int n, mem_block_t *in); +static void ReadBlock(int n, mem_block_t *out); static void WriteRaw(int addr, byte data); +static byte ReadRaw(int addr); + +// TODO: Update and clean up documentation. +// TODO: Invert sentinel value when memory is full +// and always set first block as "used". This allows +// us to reset the flags without erasing any memory. void MEM_Init(void) { - // Valid first sentinel? - if (ReadRaw(0) == 0xFF) { + byte flag; + + flag = ReadRaw(0); + + // We divide the memory space into blocks and + // provide each block with a "dirty" flag to + // indicate if the block is in use. + + // /--- used + // 80 50 DD EE FF 60 AA BB CC + // 00 00 00 00 00 00 00 00 00 + // \--- free + + // We initialize the memory first if we don't find + // a valid flag at the first sentinel position. This + // value can be either a 0x00 or 0x80. Empty memory + // on Atmel chips is set to 0xFF so we must not use + // that as a flag. + + if (flag != 0x00 && flag != MEM_IS_DIRTY) { Info("Initializing fresh memory..."); - MEM_Free(); // Set dirty block markers + MEM_Free(); // Reset all dirty flags } } -void MEM_Read(word *temp, word *dewp) +void MEM_Write(mem_block_t *in) { - int bl; - word addr; - byte msb[2], lsb[2]; + int n; - bl = GetUsedBlock(); + // As we add entries it's trivial to keep track of + // the last one: We check the flags for each block in + // sequence until we find a "free" one and go back + // one step. - if (bl < 0) { - *temp = 0; - *dewp = 0; - return; // Empty - } + // When we have to write new data but there are no + // "free" entries, we start writing from the first + // entry and only reset the flags (for now). - addr = bl * MEM_BLOCK_SIZE; + // EEPROM cells are written by the hardware in a two + // step operation: First, the cell is set to all ones, + // then the bits to be written (effectively only those + // that are 0) are actually written. - 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 + n = GetFreeBlock(); + if (n < 0) { // Full? MEM_Free(); - bl = 0; + n = 0; } - addr = bl * MEM_BLOCK_SIZE; + in->flag = MEM_IS_DIRTY; + WriteBlock(n, in); +} - msb[0] = (temp >> 8) & 0xff; - lsb[0] = temp & 0xff; - msb[1] = (dewp >> 8) & 0xff; - lsb[1] = dewp & 0xff; +int MEM_Read(mem_block_t *out) +{ + int n; - 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]); + // To get the current block we search for the next + // free one and go back by one. If we reach the end + // we return a negative value to indicate empty + // memory. + + n = GetUsedBlock(); + if (n < 0) { // Empty? + return -1; + } + + ReadBlock(n, out); + + return 0; } void MEM_Free(void) { - int bl; + int n; - // 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(); - } -} + // For now we clear all dirty block markers but it + // would be better to invert the sentinel value when + // we've reached the end (and set the first block to + // always be "used"). This means we don't have to + // erase any flags below. -void MEM_Dump(void) -{ - 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); + for (n = 0; n < MEM_MAX_BLOCKS; n++) { + WriteRaw(n * MEM_BLOCK_SIZE, 0x0); + if ((n % 64) == 0) WDT_Reset(); } } @@ -114,23 +116,51 @@ static int GetUsedBlock(void) bl = GetFreeBlock(); - if (bl == 0) + if (bl == 0) { return -1; - else if (bl < 0) + } else if (bl < 0) { return MEM_MAX_BLOCKS; - else + } else { return bl - 1; + } +} + +static void ReadBlock(int n, mem_block_t *out) +{ + int addr; + byte block[MEM_BLOCK_SIZE]; + + addr = n * MEM_BLOCK_SIZE; + for (int i = 0; i < MEM_BLOCK_SIZE; i++) { + block[i] = ReadRaw(addr + i); + } + + memcpy(out, block, sizeof(mem_block_t)); +} + +static void WriteBlock(int n, mem_block_t *in) +{ + int addr; + byte *raw; + + raw = (byte *) in; + + addr = n * MEM_BLOCK_SIZE; + for (int i = 0; i < MEM_BLOCK_SIZE; i++) { + WriteRaw(addr + i, raw[i]); + } } static int GetFreeBlock(void) { - int bl; + int n; 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); + for (n = 0; n < MEM_MAX_BLOCKS; n++) { + // Only read first member of struct + flag = ReadRaw(n * MEM_BLOCK_SIZE); + if (flag != MEM_IS_DIRTY) { is_free = true; break; @@ -138,7 +168,7 @@ static int GetFreeBlock(void) } // Is there any space left? - return (is_free) ? bl : -1; + return (is_free) ? n : -1; } static void WriteRaw(int addr, byte data) diff --git a/src/common/memory.h b/src/common/memory.h index 46db37a..350822c 100644 --- a/src/common/memory.h +++ b/src/common/memory.h @@ -1,13 +1,20 @@ #ifndef MAD_CORE_COMMON_MEMORY_H #define MAD_CORE_COMMON_MEMORY_H -#define M_TEMPERATURE 0 -#define M_DEWPOINT 1 +typedef struct mem_block_s mem_block_t; + +// Important: Must reset EEPROM memory when the size of +// mem_block_s is changed. Also the flag value must be +// kept at the first position of the struct. + +struct mem_block_s { + byte flag; + float temp, dewp; +} __attribute__((packed)); void MEM_Init(void); -void MEM_Write(word temp, word dewp); -void MEM_Read(word *temp, word *dewp); +void MEM_Write(mem_block_t *in); +int MEM_Read(mem_block_t *out); void MEM_Free(void); -void MEM_Dump(void); #endif // MAD_CORE_COMMON_MEMORY_H diff --git a/src/main.c b/src/main.c index e6c01df..349a4b1 100644 --- a/src/main.c +++ b/src/main.c @@ -32,6 +32,8 @@ static word dewp, dewp_target; static int Init(void) { + mem_block_t mem; + state = S_IDLE; // MOSFETS control things like the heating element @@ -73,12 +75,17 @@ static int Init(void) // 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(); + // mem.temp = 30.50f; + // mem.dewp = 15.25f; + // MEM_Write(&mem); + // MEM_Free(); + + if (MEM_Read(&mem) == 0) { + Info("Found persistent configuration in EEPROM!"); + Info("Setting targets TEMP='%.2f', DEWP='%.2f'.", + mem.temp, mem.dewp); + } // There is a possiblity to use interrupt signals // for I2C communication but only as one large