Writing and retrieving data from external eeprom

Hi,

I'm trying to use a minimalist set-up with an ATMega328P-PU loaded with a Lilypad bootloader to monitor and record Vcc using a DS1307 RTC and a CAT24C32 EEPROM.

My code is shown below, I am most interested in where I am going wrong when storing and displaying the data from the external EEPROM.

Any pointers would be appreciated.

/***************************************************
 * Tutorial found here - 
 *       http://www.gammon.com.au/forum/?id=12821
 ***************************************************/

#include <Wire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <extEEPROM.h>

#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/power.h>

#define nEEPROMs 1
#define PAGE_SIZE 32
#define nBYTES 4096

#define LOG_DUMP 7

extEEPROM eeprom(kbits_32, nEEPROMs, PAGE_SIZE);

// Utility Macros for ADC
#define adc_disable() (ADCSRA &= ~(1<<ADEN))    // disable ADC (before sleep)
#define adc_enable() (ADCSRA |= (1<<ADEN))      // re-enable ADC

// watchdog interrupt
ISR (WDT_vect)
{
  wdt_disable();  // disable watchdog
} // end of WDT_vect

float logData[3];
unsigned int writeAddress = 0;
unsigned int readAddress = 0;
boolean dumped = false;

void setup() {

  Serial.begin(9600);

  // initialise EEPROM
  eeprom.begin(twiClock400kHz);

  // slow clock down to 4 Mhz
  clock_prescale_set (clock_div_2);

  pinMode(LOG_DUMP, INPUT);
  // set pins to OUTPUT and LOW
  for (byte i = 0; i <= A5; i++)
  {
    if (i == 7)
    {
      continue;
    }
    pinMode(i, OUTPUT);
    digitalWrite(i, LOW);
  }

  adc_disable();          // disable ADC
  power_all_disable();    // turn off all modules

}

unsigned int counter;

void loop() {
  // every 64 seconds send a reading
  if ((++counter & 7) == 0)
  {
    if (digitalRead(LOG_DUMP) == LOW) {
      recordLog();
    }
    if ((digitalRead(LOG_DUMP) == HIGH) && (dumped == false)) {
      displayLog();
    }
  } // end of 64 seconds being up

  // clear various "reset" flags
  MCUSR = 0;
  // allow changes, disble reset
  WDTCSR = bit (WDCE) | bit (WDE);
  // set interrupt mode and an interval
  WDTCSR = bit (WDIE) | bit (WDP3) | bit (WDP0);  // set WDIE, and 8 seconds delay
  wdt_reset();    // pat the dog

  set_sleep_mode (SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_cpu();
  sleep_disable();
}

void recordLog()
{
  power_all_enable();
  adc_enable();
  float millivoltage = readVcc();

  logData[0] = hour();
  logData[1] = minute();
  logData[2] = second();
  logData[3] = millivoltage / 1000;

  for (int i = 0; i < 4; i++)
  {
    eeprom.write(writeAddress, logData[i]);
    writeAddress += 4;
  }
  dumped = false;

  adc_disable();
  power_all_disable();
}

void displayLog()
{
  adc_enable();
  power_all_enable();

  for (int k = 0; k < 1023; k++) {
    float dumpData[3];
    for (int i = 0; i < 4; i++)
    {
      dumpData[i] = eeprom.read(readAddress);
      Serial.print(dumpData[i]);
      Serial.print(" ");
      readAddress += 4;
    }
    Serial.println();
  }
  
  dumped = true;
  adc_disable();
  power_all_disable();
}

long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
  ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
  ADMUX = _BV(MUX3) | _BV(MUX2);
#else
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH
  uint8_t high = ADCH; // unlocks both

  long result = (high << 8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

I found out why I wasn't getting any sensible readings from the serial monitor.

This line:-

clock_prescale_set (clock_div_2);

meant that when I initialised the serial connection with Serial.begin(9600) the IDE monitor was effectively twice the speed of the ATMega328p.

I commented out the divide by two line and now I can make sense of the serial monitor (I think it may work if I leave the divide by two line in, and double the baud rate of the ATMega328p so that my code reads Serial.begin(19200); and I still use the 9600 baud rate in the IDE but have yet to test it).

I still have to get my head around writing data (like time and voltage) to the EEPROM then reading it back and even using that data else where.

nzcrog:
I found out why I wasn't getting any sensible readings from the serial monitor.

This line:-

clock_prescale_set (clock_div_2);

meant that when I initialised the serial connection with Serial.begin(9600) the IDE monitor was effectively twice the speed of the ATMega328p.

I commented out the divide by two line and now I can make sense of the serial monitor (I think it may work if I leave the divide by two line in, and double the baud rate of the ATMega328p so that my code reads Serial.begin(19200); and I still use the 9600 baud rate in the IDE but have yet to test it).

I still have to get my head around writing data (like time and voltage) to the EEPROM then reading it back and even using that data else where.

One thing you need to remember, EEPROMs have a 'write page size'. What this means is that whenever you issue a Write command, your supplied address cause the Chip to load your specified page into an internal temp write buffer. If you are using an Atmel 24c32, this buffer is 32 bytes long.

so, if you issue a write command to address 31 or 0x001f, and send 4 bytes (0x00, 0x12, 0x13,0x14). you would expect your EEProm to have the following content.

address  data
0x001f   0x00
0x0020  0x12
0x0021  0x13
0x0022  0x14

But, that is not what happens. Because of the 'page write buffer', what actually happens is this:
the EEPROM stores your 0x00 data into the temp buffer at position 0x1f, then it increment its internal pointer to the next available temp buffer position. Since the buffer is only 32 bytes long, that internal pointer is rounded to 0x00 because 0x20 would be the 33nd position. your following three bytes are stored starting at address 0x0000.
So, this is what happens:

address data
0x0000 0x12
0x0001 0x13
0x0002 0x14
..
0x001f 0x00

Not what you expected? You will need to read the Datasheet for your specific brand and model of EEPROM. Some of the Smaller 256B or 1024B eeproms use a 4byte or 8byte write page. some of the larger ones can use 64B or 256B write pages.

You will have to detect any writes across a page boundary. If a write overlaps a page boundary you will have to issue two write sequences:
0. It is the EEPROM ready? (is a prior write command still processing?, Check the ready status)

  1. issue a Write command upto the page boundary.
  2. wait for the write to complete. (either a fixed delay, or by checking ready status)
  3. issue another Write command, making sure your command will fit in the write buffer,
  4. every thing written? else jump to step 2

Chuck.

p.s. to check for "ready" use this code:

bool i2cReady(uint8_t chipAddress){
Wire.begintransmission(chipAddress);
return (Wire.endTransmission()==0);
}
float logData[3]; << Should be logData[4] to match use in recordLog()
void recordLog()
{
  power_all_enable();
  adc_enable();
  float millivoltage = readVcc();

  logData[0] = hour();
  logData[1] = minute();
  logData[2] = second();
  logData[3] = millivoltage / 1000;

  for (int i = 0; i < 4; i++)
  {
    eeprom.write(writeAddress, logData[i]);
    writeAddress += 4;
  }
  dumped = false;

  adc_disable();
  power_all_disable();
}

I'm not sure why you have the time variables as floats.

#include <extEEPROM.h>

Which library are you using? The constructor extEEPROM eeprom(kbits_32, nEEPROMs, PAGE_SIZE); looks like the one used in the library from Jack Christensen.

Jack Christensen's extEEROM library will handle the page boundary issues when you execute multi byte writes, but it will not automatically handle the float or integer data types and break them into bytes.

However, you are using the syntax for a single byte write but are not breaking down your floats into four bytes.

You also need to manage the eeprom addresses better as you keep incrementing the values and will run over the top. If you are using the Christensen library, it will not circle around and start overwriting the beginning.

If you chose to perform single byte read/writes (and I don't think there is a problem in your sketch with the longer time taken as compared to block writes) there is probably no advantage to using the extEEPROM library instead of using Wire.h. In both cases you will need to break the multi byte floats/integers into single bytes for writing and recompose them when reading.

Thank you both for your replies.

I can see now how I have gone wrong. I am currently reading "Beginning C for Arduino: Learn C programming for the Arduino" by Jack Purdum, which I am finding to be a great book.

I see that I need to learn a lot more about pointers structs and unions which will help me with this issue. So I will continue to practice at these until I have them firmly ingrained :slight_smile:

In trying to get my head around breaking integers into bytes I have the following code:-

int n = 0;
  byte buf[4];
  
  for (int i=0; i < (nBYTES); i+=2) {
    for (int k=0; k < 2; k++) {
      buf[0] = (byte) n;
      buf[1] = (byte) n >> 8;
      eeprom.write(i, buf[k]);
    }
     n++; 
  }

Can anyone explain why I am seeing all zeros in the addresses ranged from 0x0000 to 0x0400 please.

I would really like to crack this mental block I am having :confused:

      buf[1] = (byte) n >> 8;

What happens when you cast n to a byte? What value does it have then? What happens when you shift a byte 8 places to the right? What value does it have then?

Can anyone explain why I am seeing all zeros in the addresses ranged from 0x0000 to 0x0400 please.

Sure. That is what you are writing to the EEPROM.

Suppose n is 49 (0b0000000000110001). Casting that to a byte results in 49 (0b00110001). Shifting that one place results in 0b00011000). Shifting 7 more times, and you have 0b00000000.

Why not use the highByte()/lowByte() "functions" that know how to shift properly?

Thanks for the reply :slight_smile:

I never knew about those two functions!

After having a little play with them I came up with this segment of code which gives the desired result:-

  int n = 0;
  int addr = 0;
  byte buf[2];
  
  for (int i=0; i < nBYTES/2; i+=2) {
    for (int k=0; k < 2; k++) {
      buf[0] = highByte(n);
      buf[1] = lowByte(n);
      eeprom.write(addr, buf[k]);
      addr++;
    }
     n++; 
  }

Does this look like a good approach or is there a better (maybe easier to understand) method?

    for (int k=0; k < 2; k++) {
      buf[0] = highByte(n);
      buf[1] = lowByte(n);
      eeprom.write(addr, buf[k]);
      addr++;
    }

Study that for a while. Some of that code doesn't belong in the for loop.

It's taken me a long time to understand (I hope I'm right!).

Would this be better:-

 buf[0] = highByte(n);
buf[1] = lowByte(n);

for (int k=0; k < 2; k++) {
  eeprom.write(addr, buf[k]);
  addr++;
}

Would this be better

Yes.