Pass variable structure as argument for memory functions
This commit is contained in:
@@ -1,110 +1,112 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <util/atomic.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#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)
|
||||
|
||||
@@ -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
|
||||
|
||||
17
src/main.c
17
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
|
||||
|
||||
Reference in New Issue
Block a user