Sie sind auf Seite 1von 19

EEPROM Parameter Management

A Data Dictionary Approach


Organizing non-volatile storage of Arduino
settings to conserve program space, time and data .
Presented by

Bernd Felsche
Innovative Reckoning
at The Perth Artifactory
28th of March, 2012

Arduino EEPROM Storage Features

Arduino (AVR) provides a little EEPROM


space to store e.g. operational parameters that
need to survive power cycling and resets.
Limited durability wears out in around 50,000
erase/write cycles.
EEPROM.read() and EEPROM.write() operate
on a byte at a time.
Need to checksum multi-byte data

Why this Way

Lots of operational parameters (over 40)

Factory defaults

Dynamic configuration menus

Data Dictionary

Data storage and management dependent on type

Data object names and (short) labels

Provides for retrieval of data by object name

Consistent data management methods

Facilitates multiple sets of operating parameters

Conserves RAM and FLASH space for code

Stored in EEPROM, survives uploading of code.

Data Types
/*
*
*
*
*
*

There are a few data types. Eight (8)


Each data element can contain up to 15 bytes (4 bit size)
The space consumed by a multi-byte element includes the
single-byte checksum stored along with the value.
The attributes are packed into one byte in the dictionary
type bits 4-7
size bits 0-3 */

#define
#define
#define
#define
#define
#define
#define
#define

et_BIT
et_IP
et_MAC
et_UINT
et_INT
et_CHR
et_BYT
et_STR

0
1
2
3
4
5
6
7

//
//
//
//
//
//
//
//

a bit-pattern in a byte
an IPAddress (IPv4) 4 bytes + chksum
MAC number 6 bytes + checksum
unsigned integer 16-bits + checksum
signed integer 16-bits + checksum
single character in a byte
a number in a byte
a character array (string) n-bytes + chksum

Data Objects
// Base
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define
#define

Page
EEbase
0
NetMAGIC 170
// magic value (binary 10101010)
SerialScale
1200
// baud scaling factor
l_EEbase
"SysBase"
// Base page
m_EEbase
'S'
// index page/menu ID
Version
EEbase+1
// 1 byte version number
l_Version
"VR"
// index label within page
ll_Version
"Version"
// long label
t_Version
et_BYT
// data type
p_Version
true
// block run-time config change
s_Version
1
// length in EEPROM
Revision
Version+1
// 1 byte revision number
l_Revision
"RV"
ll_Revision
"Revision"
t_Revision
et_BYT
p_Revision
true
s_Revision
1
Release
Revision+1
// 1 byte release number
l_Release
"RL"
ll_Release
"Release"
t_Release
et_BYT
p_Release
true
s_Release
1
SerialSpd
Release+1
// 1 byte serial speed / 1200 (0=>300)
l_SerialSpd
"BD"
ll_SerialSpd
"Ser.Baud"
t_SerialSpd et_BYT
p_SerialSpd false
s_SerialSpd 1

// Network basic page


#define Netbase
SerialSpd+1
// basic network conf
#define l_Netbase
"BaseNet"
#define m_Netbase
'B'
#define MyMAC
SerialSpd+1
// 6 bytes + checksum
#define l_MyMAC "MM"
#define ll_MyMAC
"My MAC"
#define t_MyMAC et_MAC
#define p_MyMAC true
// don't change in runtime firmware
#define s_MyMAC 7
#define MyIPdhcp MyMAC+7
// Y/N
#define l_MyIPdhcp
"ID"
#define ll_MyIPdhcp
"DHCP IP"
#define t_MyIPdhcp
et_CHR
#define p_MyIPdhcp
false
#define s_MyIPdhcp
1
#define MyIP
MyIPdhcp+1 // 4 bytes + checksum
#define l_MyIP "IM"
#define ll_MyIP "Def IP"
#define t_MyIP et_IP
#define p_MyIP false
#define s_MyIP 5
#define MyNetMask MyIP+5 // 1 byte as bitmap length in bits
#define l_MyNetMask
"NM"
#define ll_MyNetMask
"Subnet"
#define t_MyNetMask
et_BYT
#define p_MyNetMask
false
#define s_MyNetMask
1
#define MyGwIP
MyNetMask+1 // 4 bytes + checksum

Defining space for Defaults


and the Dictionary
#define EEend

CtrlEnd // End of the world.

// FACTORY Default Space


// Base address of EEPROM Factory default storage
#define FDefault
EEend
#define FDefEnd
FDefault+EEend-EEbase
// Data Dictionary
#define DictBase

FDefEnd // all above here can get zapped

#define EEtop

// After Last address

1024

Structure of the Dictionary


/* A small part of this is loaded at runtime
* from the data stored in EEPROM */
#define N_DICT_PAGES 10
#define S_DICT_HEADER 13
// size in EEPROM
struct DictPage {
byte mnu;
uint16_t data; // points to base of live data page
uint16_t dict; // points to entry in dict area
uint16_t label; // points to label in dict area
} dpages[N_DICT_PAGES];
#define N_DICT_ENTRIES 10
#define S_DICT_ENTRY 14 // size in EEPROM (6 w/o desc)
struct DictEntry {
char label[3];
uint16_t desc; // point to description in dict EEPROM
uint16_t data; // point to live data
uint16_t dict; // points to entry in dict EEPROM
byte type;
byte sz;
bool prot;
// protected from runtime edit
} entries[N_DICT_ENTRIES];

Loading the Dictionary Page List


void getDictPages() {
int i;
for ( i = 0 ; i < N_DICT_PAGES ; i++ ) // Erase previous ones
dpages[i].dict = 0;
// First, locate the pages
if ( EEPROM.read(DictBase) != NetMAGIC ) // bomb out if no dict
return;
uint16_t dsz = EEuint(DictBase+1);
uint16_t dtop = DictBase + dsz;
i = 0;
int ptr = DictBase + 4; // should point to first one
while ( i < N_DICT_PAGES && ptr < dtop ) {
if ( EEPROM.read(ptr) == 0 ) { // We have a header. I think.
dpages[i].mnu
= EEPROM.read(ptr+1);
dpages[i].label = ptr+2;
dpages[i].data = EEuint(ptr+10);
dpages[i].dict = ptr;
I++; ptr += S_DICT_HEADER;
}
// skip zero or more page entries.
while ( ptr < dtop ) {
if ( EEPROM.read(ptr) == 0 )
break;
ptr += S_DICT_ENTRY; // YES ... ENTRY size skips.
}
} // while enough pages and within bounds
}

Showing the Dictionary Page List


// Pages menu is "dynamic" based on data dictionary
if ( mnu == 1 && cmd == "." ) {
Print(PagesMenuHdr);
for ( int i = 0 ; i < N_DICT_PAGES ; i++ ) {
if ( dpages[i].dict == 0 )
break;
Serial.print((char)dpages[i].mnu);
serialRparen();serialspace();
Serial.print(EElabel(dpages[i].label,8));
serialnewline();
}
cmd = ""; ppc = true;
}

Loading a Page of Parameters


// Load Dictionary Entries
void getDictEntries(uint8_t page) {
/* Argument is the start of the page header
first entry is S_DICT_HEADER from there */
int i;
for ( i = 0 ; i < N_DICT_ENTRIES ; i++ )
entries[i].dict = 0; // a NULL dictionary address means no more
uint16_t dsz = EEuint(DictBase+1);
uint16_t dtop = DictBase + dsz;
i = 0;
int ptr = dpages[page].dict + S_DICT_HEADER;
while ( i < N_DICT_ENTRIES && ptr < dtop ) {
if ( EEPROM.read(ptr) == 0 ) break;
entries[i].label[0] = ( EEPROM.read(ptr) & 127 );
entries[i].label[1] = EEPROM.read(ptr+1);
entries[i].label[2] = 0;
entries[i].data
= EEuint(ptr+2);
entries[i].dict
= ptr;
byte sztyp = EEPROM.read(ptr+5);
entries[i].type
= sztyp & 15;
entries[i].sz
= sztyp >> 4;
entries[i].desc
= ptr+6; // pointer into EEPROM
i++; ptr += S_DICT_ENTRY; // next entry or menu header
}
}

Showing a Page of Parameters


//// ENTRIES
if ( mnu == 2 && cmd == "." ) {
// load entries for this page
serialnewline();
Serial.print(EElabel(dpages[pge].label,8));
Print(SettingsMenuHdr);
for ( int entry = 0 ; entry < N_DICT_ENTRIES ; entry++ ) {
if ( entries[entry].dict == 0 )
break; // no more!
Serial.write(entries[entry].label);
SerialRparen(); serialspace();
Serial.print(EElabel(entries[entry].desc,8));
serialspace();
// show values for dict entry
showEntry(entry,false);
serialnewline();
}
cmd = ""; ppc = true;
}

Retrieving Parameters from EEPROM


// Read nchar characters from lbl EEPROM address
String EElabel(uint16_t lbl, uint8_t nchar) {
String blast = "";
for ( int c = 0 ; c < nchar ; c++ )
blast += (char)EEPROM.read(lbl+c);
return blast;
}
// Read unsigned 16bit integer from EEPROM
uint16_t EEuint(int16_t base) {
byte b[] = {EEPROM.read(base),EEPROM.read(base+1)};
byte s = EEPROM.read(base+2);
byte sum = b[0] + b[1]+b[1];
if ( s != sum )
EEchecksum = base;
uint16_t v = (b[0]<<8) + b[1];
return(v);
}

Store Parameters
// Write unsigned 16-bit integer to EEPROM and a byte checksum
byte setEEuint(uint16_t *v_int, uint16_t base){
byte b[] = {255,255};
// memcpy does a "byte-swap"
memcpy((void *)b,(void *)v_int,2);
byte sum = b[1] + b[0]+b[0];
EEPROM.write(base+1,b[0]);
EEPROM.write(base, b[1]);
EEPROM.write(base+2, sum);
return sum;
}

Store Parameters in EEPROM


// Write many bytes and their checksum to EEPROM
byte setEEbytesCRC(uint8_t *bytes, uint16_t base, uint16_t nbytes){
byte sum = 0;
for ( uint16_t off = 0 ; off < nbytes ; off++ ) {
EEPROM.write(base+off, bytes[off]);
sum += bytes[off];
if ( off % 2 != 0 ) // Add every second byte twice
sum += bytes[off];
}
EEPROM.write(base+nbytes,sum);
return sum;
}
// Write an IP address to EEPROM with checksum
byte setEEIP(uint8_t *ip, uint16_t base){
return setEEbytesCRC(ip,base,4);
}
// Write a MAC number to EEPROM with checksum
byte setEEMAC(uint8_t *eemac, uint16_t base){
return setEEbytesCRC(eemac,base,6);
}

Handling Types
// Display value corresponding to dictionary value
void showEntry(uint8_t idx, bool factory) {
byte sz = entries[idx].sz - 1;
uint16_t ptr = entries[idx].data;
if (factory) // For factory settings shift base
ptr += FDefault;
// format according to type
switch (entries[idx].type) {
case et_BIT: // 0
// it's a bit-pattern
Serial.write("bits\t");
ee_byte = EEPROM.read(ptr);
printbits(ee_byte);
break;
case et_IP: // 1
Serial.write("IPAddr\t");
ee_ip = EEIPAddr(ptr);
Serial.print(ee_ip);
break;
case et_MAC: // 2
Serial.write("MAC\t");
EEMAC(ee_mac,ptr);
displayMAC(ee_mac);
break;

Back End

Perhaps better to implement as C++ classes and


methods
Probably uses more RAM

No-Sweat Exercises

In your own Arduino project, work out which settings could


usefully survive power cycling and resets.
Determine the types of data; bytes, integers, etc
Consider the possibility of saving and loading different
configurations using several sets of settings (preferences).
Build a header file with definitions of types and map out the
settings and their labels
Build a new sketch, including that header file, to display and
change those settings over USB-serial. Once you have that
working, make the changes to your project's sketch.
If you have a working human interface in your Arduino
project, consider how you can incorporate e.g. a SaveSettings
function to remember input control settings and how to
restore/select them when the Arduino restarts.

Das könnte Ihnen auch gefallen