How to connect / use ATMEL 24C16 EEPROM chip?

hey,

i just registered heare and already got a question ^^
i took apart an old reciever and found an eeprom chip in it and i want to use it with my arduino duamilanove :slight_smile:

i found an datasheet of it:

im new in using external eeprom chips so i dont know how i have to connect it with the arduino in order to write to it / read from it. by the way the chip is old(7 years +) so i dont know if it still works without problems, in the reciever it did ^^

so my questions are:
-how to connect the chip
-how to program the arduino(first reading because i want to see what the reciever were saving to it ^^)

thanks

p.s. sorry for bad englisch, im from germany :smiley:

+5 to +VCC,
Gnd to Gnd.
SDA to SDA
SCL to SCL
(these are A4 & A5)
A0, A1, A2, WP to Gnd

Go read in the Learning Section, or the Playground, about I2C.
This is serial commands to/from the device with 1 clock line (SCL) and 1 data line (SDA), you send out an address/command, and then data out, or you get get data back.
There are libraries also, but read the data sheet to understand the address/command and data flow that will be going on.

The information and code on the playground are for large I2C EEPROMS (24C32 and larger). These have a protocol that has a control byte (device address) and two address bytes (for the EEPROM word address), so the EEPROM word address can be 16 bits long

The smaller EEPROMS have a protocol that use a control byte and only one address byte. For devices larger than 256 bytes (i.e. larger than 24C02), the upper address bits are stuffed into the lower bits of the control byte (the first byte in the transfer).

Here's a demonstration that I worked up for a colleague who was reading and writing some 24C04 devices. I created a few functions that I thought might be useful. The bottom line is that if you can read a single byte and you can write a single byte, you can expand to more elegant functions in any way that seems useful to you. The functions can be incorporated into a library if you are going to write different applications using them, but for testing I find that it's more convenient to have them in the main sketch.

[/begin disclaimer]
These few functions have been tested (somewhat) on 24C02 and 24C04 devices. As far as I know, they are "safe," to the hardware (assuming you are using 5 Volt devices) and they should work for all small I2C EEPROMS up to and including the 24C16 or equivalent.

Bottom line: IWFMYMMV (It Works For Me: Your Mileage May Vary)
---davekw7x
[/end disclaimer]

Regards,

Dave

/* 
  *  Use the I2C bus with small EEPROMs
  *  24C01, 20C02, 24C04, 24C08, 24C16
  *  Sketch:    I2C_EEPROM_Small.pde
  *  
  *  Derived from sketch for 24C64 devices posted on
  *     http://www.arduino.cc/playground/Code/I2CEEPROM
  *  From  hkhijhe   Date: 01/10/2010
  * 
  *  This one by davekw7x
  *  March, 2011
  *
  * For a single device, connect as follows:
  * EEPROM 4 (GND) to GND
  * EEPROM 8 (Vcc) to Vcc (5 Volts)
  * EEPROM 5 (SDA) to Arduino Analog Pin 4
  * EEPROM 6 (SCL) to Arduino Analog Pin 5
  * EEPROM 7 (WP)  to GND
  * EEPROM 1 (A0)  to GND
  * EEPROM 2 (A1)  to GND
  * EEPROM 3 (A2)  to GND
  */

#include <Wire.h>


// The seven-bit device address for EEPROMs
// I'll define it here rather than hard-code it inside all of the
// functions.
//
const byte DEVADDR = 0x50;

void setup()
{
    byte msg1[] = "Message 1.";   // data to write
    byte msg2[] = "Zaphod says yo";
    byte msg3[] = "Tttthat's all, folks!";
    byte msgf[16] = {
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
    };
    
    
    Wire.begin();
    Serial.begin(9600);

    //
    // Change #if 0 to #if 1 and it will erase the
    // EEPROM pages that we are going to write to:
    //
    #if 0
      eeprom_write_page(DEVADDR, 0x000, msgf, 16);
      eeprom_write_page(DEVADDR, 0x010, msgf, 16);
      eeprom_write_page(DEVADDR, 0x020, msgf, 16);
      eeprom_write_page(DEVADDR, 0x100, msgf, 16);
      eeprom_write_page(DEVADDR, 0x1f0, msgf, 16);
      Serial.println("After erasing pages starting at 0x000, 0x100, and 0x1f0:");
      eeprom_dump(DEVADDR, 0, 512);
    #endif

    //
    // Change #if 1 to #if 0 so that it won't write over the stuff next time
    //
    #if 1
    // Write some stuff to EEPROM 
    eeprom_write_page(DEVADDR, 0x000, msg1, sizeof(msg1));
    eeprom_write_page(DEVADDR, 0x100, msg2, sizeof(msg2));
    eeprom_write_page(DEVADDR, 0x1f0, msg3, 16);
    #endif

    Serial.println("Memory written");
}

void loop()
{
    //
    // Read the first page in EEPROM memory, a byte at a time
    //
    Serial.println("eeprom_read_byte, starting at 0");
    for (int i = 0; i < 16; i++) {
        byte b = eeprom_read_byte(DEVADDR, i);
        Serial.print(b, HEX);
        Serial.print(' ');
    }
    Serial.println();
    
    //
    // Read the first page using the read_buffer function
    //
    Serial.println("eeprom_read_buffer, starting at 0");
    byte buffer[16];
    eeprom_read_buffer(DEVADDR, 0, buffer, sizeof(buffer));
    
    //
    //First print the hex bytes on this row
    //
    for (int i = 0; i < sizeof(buffer); i++) {
        char outbuf[6];
        sprintf(outbuf, "%02X ",buffer[i]);
        Serial.print(outbuf);
    }
    Serial.println();

    //
    // Now print the char if printable ASCII
    // otherwise print '.'
    //
    for (int i = 0; i < sizeof(buffer); i++) {
        if (isprint(buffer[i])) {
            Serial.print(buffer[i]);
        }
        else {
            Serial.print('.');
        }
    }
    Serial.println();
    
    // Now dump 512 bytes
    Serial.println("eeprom_dump(DEVADDR, 0, 512)");
    eeprom_dump(DEVADDR, 0, 512);
    Serial.println();

    delay(20000);

}

void eeprom_write_byte(byte deviceaddress, int eeaddress, byte data)
{
    // Three lsb of Device address byte are bits 8-10 of eeaddress
    byte devaddr = deviceaddress | ((eeaddress >> 8) & 0x07);
    byte addr    = eeaddress;
    Wire.beginTransmission(devaddr);
    Wire.send(int(addr));
    Wire.send(int(data));
    Wire.endTransmission();
    delay(10);
}

  // Pages are blocks of 16 bytes, starting at 0x000.
  // That is, pages start at 0x000, 0x010, 0x020, ...
  // For a device "page write", the last byte must be
  // on the same page as the first byte.
  //
  // No checking is done in this routine.
  //
  // TODO: Do some checking, or, better yet (maybe)
  // make length an int and do repeated device
  // page writes if necessary. (Then maybe rename to
  // eeprom_write_pages or some such thing.)
  //
void eeprom_write_page(byte deviceaddress, unsigned eeaddr,
                       const byte * data, byte length)
{
    // Three lsb of Device address byte are bits 8-10 of eeaddress
    byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
    byte addr    = eeaddr;
    Wire.beginTransmission(devaddr);
    Wire.send(int(addr));
    for (int i = 0; i < length; i++) {
        Wire.send(data[i]);
    }
    Wire.endTransmission();
    delay(10);
}

// TODO: Change to integer data type and return -1 if can't
// read.
//
int eeprom_read_byte(byte deviceaddress, unsigned eeaddr)
{
    byte rdata = -1;

    // Three lsb of Device address byte are bits 8-10 of eeaddress
    byte devaddr = deviceaddress | ((eeaddr >> 8) & 0x07);
    byte addr    = eeaddr;

    Wire.beginTransmission(devaddr);
    Wire.send(int(addr));
    Wire.endTransmission();
    Wire.requestFrom(int(devaddr), 1);
    if (Wire.available()) {
        rdata = Wire.receive();
    }
    return rdata;
}

//
// Returns number of bytes read from device
//
// Due to buffer size in the Wire library, don't read more than 30 bytes
// at a time!  No checking is done in this function.
//
// TODO: Change length to int and make it so that it does repeated
// EEPROM reads for length greater than 30.

int eeprom_read_buffer(byte deviceaddr, unsigned eeaddr,
                        byte * buffer, byte length)
{
    // Three lsb of Device address byte are bits 8-10 of eeaddress
    byte devaddr = deviceaddr | ((eeaddr >> 8) & 0x07);
    byte addr    = eeaddr;
    
    Wire.beginTransmission(devaddr);
    Wire.send(int(addr));
    Wire.endTransmission();

    Wire.requestFrom(devaddr, length);
    int i;
    for (i = 0; i < length && Wire.available(); i++) {
        buffer[i] = Wire.receive();
    }
    return i;
}

//
// The display is like hexdump -C.  It will always
// begin and end on a 16-byte boundary.
//

void eeprom_dump(byte devaddr, unsigned addr, unsigned length)
{
    // Start with the beginning of 16-bit page that contains the first byte
    unsigned startaddr = addr & (~0x0f);

    // stopaddr is address of next page after the last byte
    unsigned stopaddr  = (addr + length + 0x0f) & (~0x0f);

    for (unsigned i = startaddr; i < stopaddr; i += 16) {
        byte buffer[16]; // Hold a page of EEPROM
        char outbuf[6];  //Room for three hex digits and ':' and ' ' and '\0'
        sprintf(outbuf, "%03x: ", i);
        Serial.print(outbuf);
        eeprom_read_buffer(devaddr, i, buffer, 16);
        for (int j = 0; j < 16; j++) {
            if (j == 8) {
                Serial.print(" ");
            }
            sprintf(outbuf, "%02x ", buffer[j]);
            Serial.print(outbuf);            
        }
        Serial.print(" |");
        for (int j = 0; j < 16; j++) {
            if (isprint(buffer[j])) {
                Serial.print(buffer[j]);
            }
            else {
                Serial.print('.');
            }
        }
        Serial.println("|");
    }
}

Thanks a lot for your info. I was going ape with libraries, intended for bigger eproms.

Added multi page write.
The pages must be on the same 256 bytes block!
Enjoy!

Alberto

_24c16.ino (8.75 KB)

Jack Christensen has written a library for small and large external eeproms which covers from 2k bits, to 2M bits.
It handles the pages and checks for writes beyond the end of the memory. It works very well.

bigjohnson:
Added multi page write.
The pages must be on the same 256 bytes block!
Enjoy!

Alberto

Thx for sharing the code. However, I am pretty new to AVR programming and I am getting partially jumbled data using your code. For example: When I write "ABCDEFGHIJKLMNOP", I get:

eeprom_read_byte:
FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
 
eeprom_read_buffer: HEX:
FFF62 FFAB2 3D2 FFDF2 FFFF2 FFFF2 FFA72 4F2 FFFD2 FFA72 FFE72 FFBD2 FFEC2 FFFC2 FFFE2 132 

eeprom_read_buffer: ASCII:
..=....O........

eeprom_dump
000: 31 33 32 20 00 20 00 48  49 4a 4c 4b 4d 4e 4f 50  |132 . .HIJLKMNOP|

Here is what the documentation says:

The AT24C01A/02/04/08A/16A provides 1024/2048/4096/8192/16384 bits of serial electrically erasable and programmable read-only memory (EEPROM) organized as 128/256/512/1024/2048 words of 8 bits each.

AT24C16A, 16K SERIAL EEPROM: Internally organized with 128 pages of 16 bytes each, the 16K requires an 11-bit data word address for random word addressing.

The 1K, 2K, 4K, 8K and 16K EEPROM devices all require an 8-bit device address word following a start condition to enable the chip for a read or write operation

The 16K does not use any device address bits but instead the 3 bits are used for memory page addressing. These page addressing bits on the 4K, 8K and 16K devices should be considered the most significant bits of the data word address which follows. The A0, A1 and A2 pins are no connect.

The 1K/2K EEPROM is capable of an 8-byte page write, and the 4K, 8K and 16K devices are capable of 16-byte page writes.

The device address for 24C16 is: 1 0 1 0 P1 P2 P3 R/W [High for read, low for write]

I am assuming P1, P2, P3 are page address bits. I don't know what 16 byte page means because if each page was 16 bytes long then we would need 10 bit page addressing, but we only got three. The documentation mentions that the EEPROM is specced for 16K (2048 x 8) addressing. Does that mean it has eight pages of 2048 byte? But then why is your write_page() function taking only a 16 byte chunk?

The documentation also mentions the 11 bit word address. 11 bit word size makes sense if each page is 2048 bytes long.

The write_pages() function is just a string generator that passes a 16 byte string to write_page(). write_page() adds a prefix of devaddr+char(eeaddr) to that string and writes it to the EEPROM.

write_page() is receiving eeaaddr in an unsigned variable. I dunno how Arduino compiler handles that. Doesn't it need to be an 11 bit binary number? I know unsigned int.. those are 16 bit long, but I have no idea what happens when you take just an unsigned.

You have used Wire.beginTransmission() to start the transmission, and according to documentation that takes seven bit device address as input. However, the device address of 24C16 is eight bit long including the R/W flag.

Theoretically, the prefix for the first word should be "1010000100000000000". But I can't find it anywhere. When I tried to print Serial.println(devaddr, BIN) and Serial.println(char(eeaddr)), I got proper value for for devaddrr (1010000) but a blank space for eeaddrr. Why the need to convert eaddrr to char?

Also, you said: "Three lsb of Device address byte are bits 8-10 of eeaddr"? You are right shifting eeaddr by eight bits and then keeping only last three bits. Then you are writing those three bits on last three empty bits on devaddrr using bitwise OR.. which you initiated as 1010000.

That is seven bit long... excluding the R/W flag. That could fit as a parameter in Wire.beginTransmission(). However, I have no idea who is handling the R/W flag here. I think eeaddr is your way of saying logical address that you formatted as page address + word address. Therefore eeaddr should be a 14 bit binary. However, you are taking bits 8,9, and 10 of eeaddr as page address through the previous bitwise operation instead of bits 11, 12, and 13.

I understand that the three MSBs of eeaddr will automatically point towards the page address... provided you took a 14 bit eeaddr. Wouldn't your current code limit page size to only 2^7 words?

TL:DR: Why doesn't anything make sense?

bigjohnson:
Added multi page write.
The pages must be on the same 256 bytes block!
Enjoy!

Alberto

I also noticed that Wire.write() is ONLY writing data to EEPROM when data is presented as a pointer. It fails to work if I replace data(i) with a byte or even char.. Wire.write() vehemently denies to send ANYTHING other than arrays.

Data is getting cleared after power cycle.

In the eeprom_read_buffer() function, you took the word address as an unsigned, saved it in a byte, then converted it to int before passing it to Wire library. What's the point of using unsigned if you are gonna convert it to int anyway? Isn't this gonna cause issues?

I noticed that the eeprom_read_buffer() call inside loop() is not working at all. There is no difference in output if I disable that call. But eeprom_dump() is working even though eeprom_dump() is using eeprom_read_buffer() to do its work. The only difference I see is that that four bit mask. Four bit mask on an 11 bit address?

Start with the beginning of 16-bit page that contains the first byte

Umm....

I would suggest you try the Christensen library referenced in post #5. It is available through the library manger.

cattledog:
I would suggest you try the Christensen library referenced in post #5. It is available through the library manger.

Library on top of library? That is what I hate the most. The more layers of obfuscation, the more meaningless complexities will arise.

As expected, Christensen's library did not work. Maybe I used it wrong, but that is why now I need to study Christensen's code to determine what went wrong... to do that first I need to understand Wire library first... to understand Wire I need to study I2c and EEPROM.

The 24C16 is 16kb (2Kx18). Christiansen's library goes only up to "kbits_2048"... even though the documentation claims to support even ST Micro M24M02 (2M bit). How?

The more layers of obfuscation, the more "How?"s will arise.

Library on top of library?

What library on top of library?

As expected, Christensen's library did not work.

Your expectations are wrong. That library is rock solid.

Why can't I use I2c directly?

You certainly can, but if you knew what you were doing, you wouldn't be bogged down in a seven year old thread.

The only reason to use an external eeprom library is to handle the page boundaries on the block writes. If you write one byte at a time, or if you limit the block writes to 4 bytes and start the writes at a register address evenly dividable by four you never need to worry about page boundaries.

Have you run the basic i2c scanner sketch to check if your device is seen on the bus with the wire library.

// I2C Scanner
// Written by Nick Gammon
// Date: 20th April 2011

#include <Wire.h>

void setup() {
  Serial.begin (115200);

  // Leonardo: wait for serial port to connect
  while (!Serial) 
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}

cattledog:
What library on top of library?

Your expectations are wrong. That library is rock solid.

You certainly can, but if you knew what you were doing, you wouldn't be bogged down in a seven year old thread.

The only reason to use an external eeprom library is to handle the page boundaries on the block writes. If you write one byte at a time, or if you limit the block writes to 4 bytes and start the writes at a register address evenly dividable by four you never need to worry about page boundaries.

Have you run the basic i2c scanner sketch to check if your device is seen on the bus with the wire library.

// I2C Scanner

// Written by Nick Gammon
// Date: 20th April 2011

#include <Wire.h>

void setup() {
  Serial.begin (115200);

// Leonardo: wait for serial port to connect
  while (!Serial)
    {
    }

Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
 
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}

Well, I already said that I am new in this area of programming... To me "basic" is what google shows me first.

What does it mean if that code you provided freezes at "I2C scanner. Scanning ..."? Bad EEPROM? That code did the trick. I can now figure out faulty wiring (nothign found on scan) as well as faulty EEPROM (freezing during scan).

Many thanks you for the assistance. Everything is in order now.

davekw7x's mistake was not checking the return value of wire.endTransmission(). I have no idea where that code was reading that partially garbled data from... but it was not from EEPROM.