Tutorial: Using the Arduino’s Internal EEprom to Store Calibration Data & Screen Fonts

This post is the second in a series on adding displays to expand the capability of the Arduino data loggers described in our SENSORS paper earlier this year. As more of those DIY units go into the field, it’s become important that serial #s & calibration constants are embedded inside the files produced by each logger.  One can always hard-code that information, but with multiple sensors and screens for live output, space is getting tight:

This prompted me to look at the ATmega328p’s internal EEprom as a potential solution. The EEprom can only store one byte per location, so saving numbers larger than 255 requires you to slice them up and store them in consecutive memory locations. That takes two locations for a the “high byte” and a “low byte” of an int, and four memory locations for longs & floats. That’s a little clunky but it mirrors the way you read & write high-bit registers on I2C sensors, so there are lots of code examples out there to follow.  Piece by piece approaches also require memory pointers for retrieval and re-assembly of your variables.

A more elegant method is to roll them into a single ‘struct’  and store that with a generic function, but even with read_block & write_block I’d be constantly tweaking for each logger since they often have dramatically different sensors in tow. I wanted a more generic “cut & paste” method that would handle file headers and highly variable boiler plate info (like a contact emails) without having to update a bunch of pointers.  The real clincher was realizing that the S&H constants generated by my thermistor calibration had too many significant figures to store in an Arduino float variable anyway.

A char array provided the simplest route to the flexibility I was after, and I wrapped that into a little “EEprom loader” utility:

#include <EEPROM.h>
#define PADLENGTH 1024
char eepromString [PADLENGTH+1];      //+1 to leave room for null terminator

void setup(){
strcpy(eepromString," ");
strcat(eepromString, "\r\n");      // a carriage return in Excel
strcat(eepromString,"#198, The Cave Pearl Project");
strcat(eepromString, "\r\n");
strcat(eepromString,"Etime=(raw*iSec)/(86400)+\"1/1/1970\"");
// NOTE: \ is an escape which lets you put "special" characters into the string 
strcat(eepromString, "\r\n");
strcat(eepromString,"Tres=(SeriesOhms)/((((65536*3)-1)/Raw16bitRead)-1)");
strcat(eepromString, "\r\n");
strcat(eepromString,"1M/100k@A7(16bitP32@1.1v),A=,-0.0003613129530,B=,0.0003479768279,C =,-0.0000001938681482");
strcat(eepromString, "\r\n");                 
// following this pattern, simply add more lines of text as needed (to 1024 characters)

// This fills the unused portion of eepromString with blank spaces:
int len = strlen(eepromString);  // strlen does not count the null terminator
memset(&eepromString[len],' ',PADLENGTH-len);

// Now write the entire array into the EEprom, one byte at a time:
for (int i = 0; i < 1024; i++){ 
EEPROM.update(i, eepromString[i]); 
}
}

void loop() {
// nuthin here...
}
 

This is just a bare-bones example I whipped up, and it’s worth noting that strcat will overflow if you try to add more characters than eepromString can hold.  You can check your count at sites like lettercount.com  or try using snprintf() as an alternative without that problem. Since this is a ‘run-once’ utility, I haven’t bothered to optimize it further.  Bracketing calibration numbers with a comma on each side makes them directly usable when the CSV data file is loaded into Excel later because they end up in their own cell.  Don’t put the EEPROM.write code in the main loop, or could accidentally burn through the limited write cycles of your EEprom.

With the data stored in the EEprom, it only takes a few lines to transfer that information to the SD card when a new file gets created:

char charbuffer[0]=" ";  //a one character buffer
file.open(FileName, O_WRITE | O_APPEND);
for (int j = 0; j < 1024; j++) {
 charbuffer[0] = EEPROM.read(j);
 file.write(charbuffer[0]);
 }
file.close(); 

The spaces used to pad out the array so that it fills all 1024 bytes of the EEprom do create an extra blank line in the file, but that’s a pretty harmless trade-off for this level of code simplicity.

What else can we store in the EEprom?

In the post covering how to drive a Nokia5110 LCD using shiftout, I went into some detail on the way the fonts for that screen were created and displayed. Three simple cascading functions (originally from Julian Ilett) let you send any string of ASCII characters to the screen.

void LcdWriteString(char *characters)
{
while(*characters) LcdWriteCharacter(*characters++);
} 
void LcdWriteCharacter(char character)
{
for(int i=0; i<5; i++){
LcdWriteData(pgm_read_byte(&ASCII[character - 0x20][i])); 
}
LcdWriteData(0x00);            //one row of spacer pixels between characters
} 
void LcdWriteData(byte dat)
{
digitalWrite(DCmodeSelect, HIGH);    // High for data 
digitalWrite(ChipEnable, LOW);  
shiftOut(DataIN, SerialCLK, MSBFIRST, dat);  // transmit serial data 
digitalWrite(ChipEnable, HIGH);
} 

I modified that code that slightly with reduced font-set arrays stored in PROGMEM and introduced a method for displaying larger numbers on screen by printing the upper and lower parts of each number in two separate passes.  PROGMEM requires pgm_read_byte to get the data out of the 2-D arrays for printing.

Now, with a little bit of juggling, a “loader” script can store that font data in the 328P’s internal EEprom by converting the two dimensional font array into a linear series of memory locations:

const byte ASCII[][5] PROGMEM =  
{
{0x00, 0x00, 0x00, 0x00, 0x00} // 20 
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 0x2d (dec 45) (-) in row 13 of source array
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
//...etc, a complete font is in the array, but I only store a 46 character "caps only"
// subset in the eeprom ranging from (-) at array row 13 to capital (Z) =array row 59
// this takes 235 bytes of EEprom memory storage (addresses 0-234)
} 

// move that sub-set of the font (13 to 59) from PROGMEM into the 328's internal EEprom
currentIntEEpromAddress=0;   // each letter is constructed from 5 byte-collumns of data
for(int i=13; i<59; i++){    // so each i value forces a 5-byte jump in EEprom address
for(int j=0; j<5; j++){      // while j value counts through each individual column
charbuffer=pgm_read_byte(&ASCII[i][j]);  // i&j used separately in the array
currentIntEEpromAddress=(((i-13)*5)+j);  // i&j combined for the EEprom address
EEPROM.update(currentIntEEpromAddress,charbuffer); //update to save unnecessary writes
}

Once the font has been transferred into the internal EEprom by the loader program, I only need to make two small changes to the original display functions so they pull that font from the EEprom. Note the calculation trick (character-0x2d) which uses the ASCII code for each character to calculate where the first of the five bytes is located for that character:

void LcdWriteCharacter(char character)
{
for(int j=0; j<5; j++){
LcdWriteData(EEPROM.read(((character -0x2d)*5)+j)); 
//we subtract 0x2d because the zeroth position in EE memory is (-) = ASCII(0x2d)
}
LcdWriteData(0x00);            
} 
// (character - 0x2d)*5 jumps 5 bytes at a time through the EEprom address space
// Add a fixed offset for other fonts stored at higher locations in the memory: 
// eg: LcdWriteData(EEPROM.read(235+((character -0x2d)*5)+j)); 
// the big#fonts are 11 byte-columns wide, so calc = (Offset+((character -0x2d)*11)+j)

This adds some delay, but because the Nokia 5110 is already dead-dog slow it’s not even noticeable.  A similar mod gets applied to the split print functions for the big number font, and moving both to the EEprom still leaves a very serviceable 500 characters for file header info.  The trick to stacking different kinds of information in the EEprom is figuring out what the resulting address offsets are so each reading function starts at the correct memory address. You skip over the memory locations holding the font data when transferring that file header text.

With fonts stored in EEprom, the remaining Nokia 5110 functions compile to just a little over 400 bytes of program storage and 10 bytes of dynamic (excluding EEPROM.h, which gets called by some of my other libraries even if the screen is not present)  That’s with three duplicatescopies of each LCD function because I’ve used a set for the standard size font, and two more for printing the large numbers in upper & lower rows.  With a bit more optimization I could get that down to about 200 bytes, which I suspect is probably the smallest memory footprint achievable for adding live data output to my loggers.

I’ve posted a ‘EEprom loader utility’ which demonstrates the dual Font/Text approach at our Project’s GitHub repository  so people can modify it to suit their own projects. On loggers with a number of DS18b20 sensors, I’ll use a similar approach to store the sensor bus addresses, which I usually store in two dimensional arrays very similar to those shown here for screen fonts.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s