AT24C32 EEPROM and arduino

I recently bought a small I2C demo board for the DS1307 RTC from Maxim and the AT24C32 EEPROM for Atmel, these boards are available everywhere and it contains a small battery for the RTC on the underside of the board. As I had not used I2C before, I decided to use tutorials to learn about the DS1307 but after I had mastered that I decided to see if I could work the EEPROM by myself. These EEPROMs are available in both 32K and 64K versions, and 8 chips can be used on the same bus at once, giving a total capacity of 64kbs if 8x 64K chips are present.

AT24C32 device addressing

Unlike the RTC which has a hardwired address, the AT24C32 has 3 pins which allow the address to be set by the user, to allow multiple AT24C32/64’s be installed on the I2C bus. As there are 3 address inputs (A0, A1 and A2), which can take 2 states, either a High or a Low, there are therefore 8 (2^3) different combinations of the state inputs, so 8 different addresses can be used. Looking at the datasheet it shows that these 3 bits are placed at the end of the 7-bit device address, just before the Read/Write bit. Examining the schematic provided by the seller, I see that all 3 inputs aDevice Addressingre tied low. And so the 7-bit address becomes 1010000 (Binary) or 0x50 (Hexadecimal). To read or write to the device I would take a 1 or 0 to the end of the 7-bit address, but gladly the arduino takes care of this.

Reading and writing data

Reading and WritingTo write to the AT24C32, requires only selecting the address and sending a byte. The address is in two bytes (not all 16 bits are used, see 12 bit register addresses) as there are 4096 possible addresses which is more than what one byte (255) can hold. Converting a numerical value to these two bytes is a little difficult and I’ll explain it in the next example. For now, we will just write to the first possible address.


#include <Wire.h>
#define address 0x50

int val = 100;
byte data;
void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Wire.beginTransmission(address);
 Wire.write(0x00);      //First Word Address
 Wire.write(0x00);      //Second Word Address

 Wire.write(0x41);      //Write an 'A'

 delay(10);

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Wire.beginTransmission(address);
 Wire.write(0x00);      //First Word Address
 Wire.write(0x00);      //Second Word Address
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(address, 1);
 delay(10);
 data = Wire.read();
 Serial.write(data);      //Read the data and print to Serial port
 Serial.println();
 delay(10);
}

void loop()
{
}

Sequential reading and semi-sequential writing

Both reading and writing to the EEPROM are sequential meaning that you can read bytes from the register without having to reset the address pointer, however writing is only sequential up to the limit of 32 bytes (called a page write), I discovered this to my peril when receiving illegal characters that I had apparently written from the following sketch. This writes the alphabet to the first 26 positions of the EEPROM. It took me about an hour to finally have the sense to check the datasheet for an answer!

#include <Wire.h>
#define address 0x50

const int val = 26;
byte data;

void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Serial.println("Writing to EEPROM:");
 Wire.beginTransmission(address);
 Wire.write(0x00);
 Wire.write(0x00);

 for(byte i=0; i<val; i++)      //Write 26 data bytes
 {
   Wire.write(i+65);    
   Serial.write(i+65);
   Serial.println();
 }
 delay(10);
 Serial.println();

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Serial.println("Reading from EEPROM:");
 Wire.beginTransmission(address);
 Wire.write(0x00);
 Wire.write(0x00);
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(address, val);
 delay(10);

 for(byte i=0; i<val; i++)      //Read 26 data bytes
 {
   data = Wire.read();
   Serial.write(data);
   Serial.println();
 }
 delay(10);
}

void loop()
{
}

Have a go at changing the value of ‘val’ at the beginning of the sketch, and you will see that you can only write 30 bytes into the chip (the other 2 bytes are addresses) without garbage (ÿ) being return when you read it.

12 bit register addresses

2 Byte addressesAs there are 4096 memory locations, there are 12-bit addresses and you can not simply 1 byte as an address, but must send two, with the address split over the 4 Least Significant Bits of the 1st word address and all 8-bits of the 2nd word address. To avoid complications, I started only reading/writing to the first possible address (0) and any following ones through sequential reads/writes. I then got to the problem of converting an int address to a 12-bit number.

We have an address, any number between 0 and 4095 inclusive. In binary this number is (0000) abcd efgh ijkl. To get the 1st word address, we bit shift the whole number right 8 bits, so it becomes 0000 abcd. Then, bit shifting the number back 8 bits it becomes (0000) abcd 0000 0000. If we then subtract this derived number (which is just the 1st word address with 8 0’s tacked on) from the original address ((0000) abcd efgh ijkl), we can get the lower byte of the address, (oooo oooo) efgh ijkl. To make the whole thing easier to understand, I’ve made up another example sketch to show the functionality of this.

void setup() 
{
  Serial.begin(9600);
  word address = 4096;
  byte BYTE_1 = address >> 8;
  byte BYTE_2 = address - (BYTE_1 << 8);

  Serial.print("Address is: ");
  Serial.println(address);
  Serial.print("1st word is: ");
  Serial.println(BYTE_1);
  Serial.print("2nd word is: ");
  Serial.println(BYTE_2);

}

void loop() 
{

}

I made functions to simplify sketches needing to use these:

byte highAddressByte(word address)
{
  byte BYTE_1;
  BYTE_1 = address >> 8;
  return BYTE_1;
}

byte lowAddressByte(word address)
{
  byte BYTE_1;
  byte BYTE_2;
  BYTE_1 = address >> 8;
  BYTE_2 = address - (BYTE_1 << 8);
  return BYTE_2;
}

These can be easily integrated into the first sketch:

#include <Wire.h>
#define AT24C32 0x50

byte data;
word a = 0;

byte highAddressByte(word address)
{
  byte BYTE_1;
  BYTE_1 = address >> 8;
  return BYTE_1;
}

byte lowAddressByte(word address)
{
  byte BYTE_1;
  byte BYTE_2;
  BYTE_1 = address >> 8;
  BYTE_2 = address - (BYTE_1 << 8);
  return BYTE_2;
}

void setup()
{ 
 Wire.begin();
 Serial.begin(9600);
 delay(1000); 

 //WRITE!!!!*******************************
 Wire.beginTransmission(AT24C32);

 Wire.write(highAddressByte(a));      //First Word Address
 Wire.write(lowAddressByte(a));      //Second Word Address

 Wire.write(0x41);      //Write an 'A'

 delay(10);

 Wire.endTransmission();
 delay(10);

 //READ!!!!*********************************
 Wire.beginTransmission(AT24C32);
 Wire.write(highAddressByte(a));      //First Word Address
 Wire.write(lowAddressByte(a));      //Second Word Address
 Wire.endTransmission();
 delay(10);

 Wire.requestFrom(AT24C32, 1);
 delay(10);
 data = Wire.read();
 Serial.write(data);      //Read the data and print to Serial port
 Serial.println();
 delay(10);
}

void loop()
{
}

You can also further integrate these functions into other functions eg.  AT24C32.Write/Read(address, location, data), but I’ll leave that to the reader to work out, it really shouldn’t be hard…

Advertisements

11 Comments

Filed under Arduino

11 responses to “AT24C32 EEPROM and arduino

  1. Very helpful, thank you!

  2. Pingback: Buffering sensor data to an AT24C32 eeprom on I2C | An Arduino based underwater sensor project

  3. Wire.beginTransmission(address);

    Wire.write(0x00); //First Word Address
    Wire.write(0x00); //Second Word Address

    Wire.write(0x41); //Write an ‘A’
    ; //Second Word Address

    Wire.write(0x41); //Write an ‘A’

    this part is form the top of your code. I am confused on “second word address” line, the value is (0x00) and it is just the same of prior line( the first word address)
    is this correct? if correct why are they same ? I am confused exactly here, can you explain ?
    btw. thank you for sharing, it is really helpful.

    • The number of available addresses is larger than one byte (0-255), so the address word stretches over 2 bytes to give a greater address range. Check out page 11 of the atmel datasheet for a more technical explanation. Hope this helps.

  4. and another;
    //READ!!!!*********************************
    Wire.beginTransmission(address);
    Wire.write(0x00); //First Word Address
    Wire.write(0x00); //Second Word Address
    ***********************************************************
    this part refers to read data from eprom(I think).
    but you used Wire.write command, is this correct? shouldn’t it be “Wire.read” . if your command is true rhat is the difference between reading and writing. how does the eprom chip understand your commend if it is read or write. and why are the values (0x00) are the same in both two lines?

  5. Namo Aton

    Thanks! Very helpfull

  6. Andrew

    Thank you very much!

  7. Andrew

    There is an easier way to calculate BYTE_2:
    byte BYTE_2 = address & 0xFF;

  8. Calade

    Very interesting and well explained !

  9. scuba_2

    Brilliant , well explained thank you very much.

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