As I've had occasion to note elsewhere on this forum elsewhere, that handy EEPROM on most or all AVR chips is seldom used.
I've been playing with LCD's lately (and thanks to everyone who has provided code!) and noticed that the "character generator ROM", which either is or could be declared constant is included in sketch after sketch. Sometimes it's accessed from PGM space, sometimes not. How about storing it in that otherwise unused EEPROM?
Okay, so here are some needs: how does the sketch know if the data is there? how does it find it? how do I remember what I burned where?
After going over a few options, I came up with a (very) simple directory structure, just a two-letter "file name" and a pointer to the base address of the data block. The "directory" starts at the top (high addresses) of the EEPROM and grows downwards, the data blocks are stored at the beginning of the EEPROM (low address) and grow upwards. To be consistent, I stored the names and addresses in little endian order.
Here's my code (the EMULATE def and ifdef's let me develop it in gcc before debugging on Arduino):
/* burn and/or verify eeprom
* using #include file provided.
*
* DdelV 20140320
*
* This version implements directory management
*
*
*/
//#define EMULATE
#ifndef EMULATE
#include <EEPROM.h>
#include <progmem.h>
#include <StreamPrint.h>
#else
#include <stdio.h>
#include <stdint.h>
#include "emulate.h"
#include "EEPROM.h"
#endif
/* the user-supplied include (eeprom data and parameters)
* must define
* uint8_t fileName[2] = { 'X','X' }; // use unique character pair for directory entry
* static const unsigned char Buff[] = { <data> };
*/
#include "ASCII5x7font.h"
//#include "EEPROM_spec.h"
uint16_t fileSize = sizeof(Buff);
#define E2PROMSIZE E2END+1
#ifdef EMULATE
#define readEEPROM(a) EEPROMread(a)
#define writeEEPROM(a,d) EEPROMwrite(a,d)
extern unsigned char EEPROMread(int );
extern void EEPROMwrite(int, unsigned char );
#else
#define readEEPROM(a) EEPROM.read(a)
#define writeEEPROM(a,d) EEPROM.write(a,d)
#endif
#ifndef EMULATE
char myGetch() {
while(Serial.available() == 0) {
; // wait here (busy)
}
return Serial.read();
}
#else
char myGetch() {
char c;
while(!isprint(c=getchar())) { ; }
return c;
}
#endif
void gobble() {
#ifndef EMULATE
while (Serial.available())Serial.read();
#endif
}
char Menu() {
gobble();
Serialprint("\nB)urn EEPROM\n");
Serialprint("D)ump EEPROM\n");
Serialprint("S)how Directory\n");
Serialprint("V)erify EEPROM\n");
Serialprint("W)ipe EEPROM\n");
Serialprint("Q)uit\n");
Serialprint("\nChoose: ");
return myGetch();
}
int yesno() {
// return true if user types 'y' or 'Y', false otherwise.
switch (myGetch()) {
case 'Y':
case 'y':
return 1;
default:
return 0;
}
}
void showParams(uint16_t address, uint16_t size) {
Serialprint("%d bytes to be written to EEPROM at address: %04x\n",size,address);
}
void verifyError(uint16_t address, uint8_t sb, uint8_t is) {
Serialprint("Verify error! Address: %04x should be %02x, is %02x\n",address, sb, is);
}
typedef struct {
uint16_t ep;
uint8_t nm[2];
uint16_t fp;
} eepdir ;
eepdir dir;
uint16_t isNM(uint16_t f) {
if (isupper(f >> 8) && isupper(f & 127))
return 1;
else
return 0;
}
uint16_t isEEaddr(uint16_t f) {
if (f <= E2END)
return 1;
else
return 0;
}
uint16_t fetch16(uint16_t eepp) {
uint16_t field;
// Little endian
field = (readEEPROM(eepp--)) << 8;
field += readEEPROM(eepp);
return field;
}
// for debug:
void dumpDir(uint16_t ep) {
printf("ep: %04x\nnm: %c%c (%02x%02x)\nfp: %04x\n\neep=%04x\n",dir.ep,dir.nm[0],dir.nm[1],dir.nm[0],dir.nm[1],dir.fp,ep);
}
uint16_t burnFile(uint16_t S, uint8_t *Name) {
Serialprint("Burning included file to EEPROM\nPlease wait...\n");
uint16_t eep = E2END;
uint16_t i,w;
dir.ep = 0;
// find the freepointer,
do {
dir.nm[0] = fetch16(eep) & 0xff;
dir.nm[1] = fetch16(eep) >> 8;
if (dir.nm[0] & dir.nm[1] == 0xff) break;
eep -= 2;
if ((dir.fp = fetch16(eep--)) == 0xffff)
break;
eep -= 2;
if ((dir.ep = fetch16(eep)) == 0xffff)
break;
eep -= 2;
} while (eep); // search entire EEPROM if needed.
// now sort out conditions
// dumpDir(eep);
if (E2END == eep) { // new directory, add name and freepointer
Serialprint("New Directory\n");
dir.nm[0] = Name[0];
dir.nm[1] = Name[1];
dir.fp = S + 1;
} else if (dir.ep == 0xffff) { // un-named block has freepointer
Serialprint("New Entry\n");
dir.ep = dir.fp;
dir.nm[0] = Name[0];
dir.nm[1] = Name[1];
dir.fp = dir.ep + S + 1;
}
// dumpDir(eep);
if (dir.fp < (eep-4)) {
Serialprint("Okay to burn\n");
Serialprint("Burning Directory entry\n");
if(dir.ep)eep++;
writeEEPROM(eep,dir.nm[1]);
writeEEPROM(--eep,dir.nm[0]);
writeEEPROM(--eep,(dir.fp >> 8));
writeEEPROM(--eep,(dir.fp & 0xff));
// ensure sentinal is present.
writeEEPROM(--eep,0xff);
writeEEPROM(--eep,0xff);
Serialprint("Burning block\n");
w = dir.ep;
for (i=0; i<S; i++) {
writeEEPROM(w++,Buff[i]);
}
Serialprint("Done!\n");
}
else Serialprint("Sorry! Not enough room.\n");
return dir.ep;
}
void verifyFile(uint16_t A, uint16_t S) {
int i, E=0;
uint8_t D;
Serialprint("Verifying include file versus EEPROM\n");
for (i=0 ; i<S ; i++) {
if (Buff[i] != (D = readEEPROM(A))) {
verifyError(A,Buff[i],D);
E++; // count errors
}
++A;
}
char c = (E==1)?'.':'s';
Serialprint("%d verify error%c.\n",E,c);
}
void dumpEEPROM() {
#define NCOLS 32
int i, addr;
uint8_t value;
// mostly pretty-printing...
Serialprint("\n ");
for (i=0; i<NCOLS; i++) {
Serialprint("%02x ",i);
}
Serialprint("\n---+");
for (i=0; i<NCOLS; i++) {
Serialprint("---");
}
for (addr=0; addr<=E2END; addr++) {
if((addr % NCOLS) == 0) {
Serialprint("\n%03x: ",addr);
}
else {
Serialprint(" ");
}
value = readEEPROM(addr); // there! did something.
Serialprint("%02x",value);
}
}
void wipeEEPROM() {
int i;
Serialprint("This will erase all %d bytes of EEPROM!\n",E2PROMSIZE);
Serialprint("ARE YOU SURE?");
if (yesno()) {
for (i=0; i<=E2END; i++) {
writeEEPROM(i,255);
}
Serialprint("Done.\n");
}
else Serialprint("Wipe aborted.\n");
}
void showDirectory() {
uint16_t eep;
uint16_t addr = 0;
char Name[3];
Name[2] = '\0';
for (eep = E2END ; eep ; eep -= 4) {
Name[1] = readEEPROM(eep);
Name[0] = readEEPROM(eep-1);
if (!isalpha(Name[0]) || !isalpha(Name[1])) break;
Serialprint("Block named %s at 0x%04x\n",Name,addr);
addr = fetch16(eep-2);
}
Serialprint("Free space @ 0x%04x\n",addr);
}
void setup() {
#ifndef EMULATE
Serial.begin(115200);
while(!Serial) ;
#endif
Serialprint("%d bytes to be written to EEPROM.\n",fileSize);
Serialprint("Total EEPROM size is %d\n",E2PROMSIZE);
}
void loop() {
char c;
uint16_t start;
c = Menu();
switch(c) {
case 'b':
case 'B': start = burnFile(fileSize, fileName);
break;
case 'v':
case 'V': verifyFile(start, fileSize);
break;
case 'd':
case 'D': dumpEEPROM();
break;
case 'w':
case 'W': wipeEEPROM();
break;
case 's':
case 'S': showDirectory();
break;
case 'q':
case 'Q':
#ifndef EMULATE
while(1) { ; }
#else
exit(0);
#endif
default: Serialprint("Bad Command! (%02x) Try again.\n",c);
}
}
I'm not claiming it's great (kinda flat-footed, which is often best), but it seems to work. Could use some polish.
The "directory" format is a bit wasteful of EEPROM space in that it costs 4 bytes for each block. With a bit more complexity 3 (or even 4) character names could be packed into the 32 bits along with the 10-bit (for 1K EEPROM) address pointers.
Now to get to work on LCD routines that find and use this data (and some benchmarks!).
Any questions or suggestions are welcome.