277 lines
5.9 KiB
C
277 lines
5.9 KiB
C
#include "common.h"
|
|
|
|
#include <util/atomic.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#define MEM_SIZE 4096
|
|
#define MEM_START 0x00
|
|
#define MEM_BLOCK_SIZE ((int) sizeof(mem_block_t))
|
|
#define MEM_MAX_BLOCKS (MEM_SIZE / MEM_BLOCK_SIZE)
|
|
|
|
static byte GetSentinel(void);
|
|
static int GetFreeBlock(void);
|
|
static int GetUsedBlock(void);
|
|
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);
|
|
|
|
// The memory manager divides the memory space into blocks
|
|
// with each block starting with a sentinel byte. This byte
|
|
// indicates if the block was already used in this cycle.
|
|
|
|
// /--- used
|
|
// 80 50 DD EE FF 60 AA BB CC
|
|
// 00 00 00 00 00 00 00 00 00
|
|
// \--- free
|
|
|
|
// The first block is always considered used and therefore
|
|
// its sentinel value can act as a reference for the "used"
|
|
// flag. This means a block is considered free, only if its
|
|
// sentinel value differs from the first block.
|
|
|
|
void MEM_Write(mem_block_t *in)
|
|
{
|
|
int head;
|
|
byte flag;
|
|
|
|
// As we add entries we check the flags for each
|
|
// block in sequence until we find a free one and
|
|
// go back one step.
|
|
|
|
head = GetFreeBlock();
|
|
flag = GetSentinel();
|
|
|
|
// When we have to write new data but there are no
|
|
// free entries, we change the sentinel bit value
|
|
// for the reference block and all other blocks are
|
|
// considered free again without erasing any flags.
|
|
|
|
// /--- used
|
|
// 00 50 DD EE FF 60 AA BB CC
|
|
// 80 20 FF CC 25 00 EE FF CC
|
|
// \--- free
|
|
|
|
if (head == 0) {
|
|
// Clear all "used" flags
|
|
flag = flag ? 0x00 : 0x80;
|
|
}
|
|
|
|
in->flag = flag;
|
|
WriteBlock(head, in);
|
|
}
|
|
|
|
bool MEM_Read(mem_block_t *out)
|
|
{
|
|
int head;
|
|
|
|
head = GetUsedBlock();
|
|
|
|
if (head < 0) { // Empty?
|
|
return false;
|
|
}
|
|
|
|
ReadBlock(head, out);
|
|
|
|
return true;
|
|
}
|
|
|
|
void MEM_Free(void)
|
|
{
|
|
int head;
|
|
|
|
for (head = 0; head < MEM_MAX_BLOCKS; head++) {
|
|
// Set sentinel values to unitialized
|
|
WriteRaw(head * MEM_BLOCK_SIZE, 0xFF);
|
|
|
|
if ((head % 64) == 0) {
|
|
WDT_Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MEM_Dump(void)
|
|
{
|
|
int n, m;
|
|
char buf[MEM_BLOCK_SIZE*3+1];
|
|
char *ptr = buf;
|
|
|
|
// Used for debugging purposes
|
|
for (n = 0, m = 0; n < MEM_SIZE; n++) {
|
|
ptr += sprintf(ptr, "%02X ", ReadRaw(n));
|
|
if (++m == MEM_BLOCK_SIZE || n == (MEM_SIZE - 1)) {
|
|
Print("%s\r\n", buf);
|
|
WDT_Reset();
|
|
ptr = buf;
|
|
m = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static byte GetSentinel(void)
|
|
{
|
|
return ReadRaw(MEM_START);
|
|
}
|
|
|
|
static int GetFreeBlock(void)
|
|
{
|
|
int head;
|
|
byte ref, flag;
|
|
|
|
if ((ref = GetSentinel()) == 0xFF) {
|
|
return 0; // Fresh memory
|
|
}
|
|
|
|
for (head = 0; head < MEM_MAX_BLOCKS; head++) {
|
|
flag = ReadRaw(head * MEM_BLOCK_SIZE);
|
|
|
|
if (flag != ref) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (head == MEM_MAX_BLOCKS) {
|
|
return 0; // Rollover
|
|
}
|
|
|
|
return head;
|
|
}
|
|
|
|
static int GetUsedBlock(void)
|
|
{
|
|
int next, last;
|
|
byte ref;
|
|
|
|
if ((ref = GetSentinel()) == 0xFF) {
|
|
return -1; // Fresh memory
|
|
}
|
|
|
|
if ((next = GetFreeBlock()) == 0) {
|
|
// Important edge case: Rollover or empty?
|
|
last = (MEM_MAX_BLOCKS - 1) * MEM_BLOCK_SIZE;
|
|
|
|
if (ReadRaw(last) == GetSentinel()) {
|
|
return MEM_MAX_BLOCKS - 1;
|
|
} else {
|
|
return -1; // None
|
|
}
|
|
}
|
|
|
|
return next - 1;
|
|
}
|
|
|
|
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 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 WriteRaw(int addr, byte data)
|
|
{
|
|
// The EEMPE bit determines whether setting EEPE to
|
|
// one causes the EEPROM to be written. When EEMPE
|
|
// is set, setting EEPE within four clock cycles
|
|
// will write data to the EEPROM at the selected
|
|
// address.
|
|
|
|
// If EEMPE is zero, setting EEPE will have no
|
|
// effect. When EEMPE has been written to one by
|
|
// software, hardware clears the bit to zero after
|
|
// four clock cycles.
|
|
|
|
// Caution: An interrupt between the last two steps
|
|
// will make the write cycle fail, since the EEPROM
|
|
// Master Write Enable will time-out.
|
|
|
|
// If an interrupt routine accessing the EEPROM is
|
|
// interrupting another EEPROM Access, the EEAR or
|
|
// EEDR register will be modified, causing the
|
|
// interrupted EEPROM Access to fail.
|
|
|
|
// It is recommended to have the Global Interrupt
|
|
// Flag cleared during all the steps to avoid these
|
|
// problems.
|
|
|
|
// When the write access time has elapsed, the EEPE
|
|
// bit is cleared by hardware. The user software can
|
|
// poll this bit and wait for a zero before writing
|
|
// the next byte. When EEPE has been set, the CPU is
|
|
// halted for two cycles before the next instruction
|
|
// is executed.
|
|
|
|
// No interrupts during EEPROM write
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
|
|
|
// Wait until ready
|
|
while (EECR & BIT(EEPE));
|
|
|
|
// The EEPROM Address Registers - EEARH and
|
|
// EEARL specify the EEPROM address in the
|
|
// 512/1K/2K/4Kbytes EEPROM space. The EEPROM
|
|
// data bytes are addressed linearly between 0
|
|
// and 511/1023/2047/4096. The initial value
|
|
// of EEAR is undefined. A proper value must be
|
|
// written before the EEPROM may be accessed.
|
|
|
|
EEAR = addr;
|
|
EEDR = data;
|
|
|
|
// Write to address
|
|
EECR |= BIT(EEMPE);
|
|
EECR |= BIT(EEPE);
|
|
}
|
|
}
|
|
|
|
static byte ReadRaw(int addr)
|
|
{
|
|
byte data;
|
|
|
|
// 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.
|
|
|
|
// 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(EEPE));
|
|
|
|
EEAR = addr;
|
|
|
|
// Read from address
|
|
EECR |= BIT(EERE);
|
|
data = EEDR;
|
|
}
|
|
|
|
return data;
|
|
}
|