Reading and writing EEPROM via I2C with Linux

37,279

Solution 1

Alternatively, you could access it via the kernel at24.c driver, if you're able to compile and install a different kernel device tree for your Raspberry Pi.

The kernel device tree needs to specify the EEPROM's type and address, and which I²C bus it's connected to. I'm not sure about Raspberry Pi, but for the BeagleBone Black EEPROM it goes like this:

&i2c0 {
    eeprom: eeprom@50 {
        compatible = "at,24c32";
        reg = <0x50>;
    };
};

For your device you'd specify compatible = "at,24c256";

Ensure the kernel config specifies CONFIG_EEPROM_AT24=y (or =m).

Then you should be able to access the EEPROM memory from userspace at something like /sys/bus/i2c/devices/0-0050/eeprom or /sys/bus/i2c/drivers/at24/0-0050/eeprom.

Solution 2

maybe this here might help. http://www.richud.com/wiki/Rasberry_Pi_I2C_EEPROM_Program since it handles apparently the device you are trying to program and also explains some caveats of addressing 24c256

Solution 3

Craig McQueen's answer got me on the right track, but it is not easy to figure the whole thing out on your own.
Here is a AT24C256 device tree overlay that works for me on the Raspberry Pi:

/dts-v1/;
/plugin/;
/ {
  fragment@0 {
    target = <&i2c1>;
    overlay {
      pinctrl-names = "default";
      pinctrl-0 = <&i2c1_pins>;
      clock-frequency = <100000>;
      status = "okay";
      at24@50 {
        compatible = "atmel,24c256","at24";
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0x50>;
        pagesize = <64>;
        size = <32768>;
        address-width = <16>;
      };
    };
  };
};

Save it to "at24c256.dts", compile (might need to install the device tree compiler) it using:

dtc -O dtb -o at24c256.dtbo -b 0 -@ at24c256.dts

and save it in "/boot/overlays". Then activate the overlay by adding:

dtparam=i2c_arm=on    
dtoverlay=at24c256

to "/boot/config.txt" and reboot. You should now have a device file "/sys/class/i2c-dev/i2c-1/device/1-0050/eeprom" (if your I2C bus number is 1) which you can write to like a normal file.

Write to it using e.g.:

echo 'Hello World' | sudo tee /sys/class/i2c-dev/i2c-1/device/1-0050/eeprom

Read from it using e.g.:

sudo more /sys/class/i2c-dev/i2c-1/device/1-0050/eeprom

Not sure how you can get around the su-rights for accessing the device though. Adding the user to the i2c-group does not help...

Solution 4

Small and simple program to understand the easy management of an eeprom

/*
    Simple program to write / read the eeprom AT24C32.
    Developed and tested on the Raspberry pi3B jessie

    To create the executable use the following command:

        gcc -Wall -o thisprogram.exe thisprogram.c
*/

#include <stdio.h>
#include <sys/ioctl.h> // ioctl
#include <fcntl.h>     // open
#include <unistd.h>    // read/write usleep
#include <time.h>
#include <netinet/in.h> // htons
#include <linux/i2c-dev.h>

#pragma pack(1)

#define PAGESIZE 32
#define NPAGES  128
#define NBYTES (NPAGES*PAGESIZE)

#define ADDRESS 0x57  //  AT24C32's address on I2C bus 

typedef struct {
    ushort AW;
    char  buf[PAGESIZE+2];
}WRITE;

static WRITE AT = {0};

int main() {
  int fd;
  char bufIN[180] = {0};
  time_t clock=time(NULL);

  snprintf(AT.buf, PAGESIZE+1, "%s: my first attempt to write", ctime(&clock)); //  the buffer to write, cut to 32 bytes

  if ((fd = open("/dev/i2c-1", O_RDWR)) < 0) {  printf("Couldn't open device! %d\n", fd); return 1; }

  if (ioctl(fd, I2C_SLAVE, ADDRESS) < 0)     { printf("Couldn't find device on address!\n"); return 1; }

  AT.AW = htons(32);    //  I will write to start from byte 0 of page 1 ( 32nd byte of eeprom )

  if (write(fd, &AT, PAGESIZE+2) != (PAGESIZE+2)) { perror("Write error !");    return 1; }
  while (1) { char ap[4];  if (read(fd,&ap,1) != 1) usleep(500); else break; } //   wait on write's end 

  if (write(fd, &AT, 2) != 2) {  perror("Error in sending the reading address");    return 1;  }

  if (read(fd,bufIN,PAGESIZE) != PAGESIZE) { perror("reading error\n"); return 1;}
  printf ("\n%s\n", bufIN);

  close(fd);
  return 0;
}
Share:
37,279
Jacob Calvert
Author by

Jacob Calvert

Field Application Engineer for Wind River in the A&amp;D world. I love tech and music. Blending them is even better.

Updated on January 21, 2021

Comments

  • Jacob Calvert
    Jacob Calvert over 3 years

    I trying to read and write an Atmel 24C256 EEPROM with a Raspberry Pi B+ over I2C, but I'm having trouble getting it all to work right.

    Here is the code I have so far:

    #include <stdio.h>
    #include <stdlib.h>
    #include <linux/i2c-dev.h>
    #include <fcntl.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #include <errno.h>
    #include <linux/i2c.h>
    
    #define DEVICE_PATH "/dev/i2c-1"
    
    #define PAGE_SIZE 64
    
    #define DEVICE_ADDR 0x50 // 0b1010xxxx
    
    
    int file_desc;
    char buffer[PAGE_SIZE + 2]; // 64 bytes + 2 for the address
    
    void teardownI2C()
    {
        int result = close(file_desc);
    }
    
    void setupI2C()
    {
        file_desc = open(DEVICE_PATH, O_RDWR);
        if(file_desc < 0)
        {
        printf("%s\n", strerror(errno));
        exit(1);
        }
        if(ioctl(file_desc, I2C_SLAVE, DEVICE_ADDR) < 0)
        {
        printf("%s\n", strerror(errno));
        teardownI2C();
        exit(1);
    
        }
    }
    
    int write_to_device(char addr_hi, char addr_lo, char * buf, int len)
    {
         struct i2c_rdwr_ioctl_data msg_rdwr;
         struct i2c_msg i2cmsg;
         char my_buf[PAGE_SIZE + 2];
         if(len > PAGE_SIZE + 2)
         {
         printf("Can't write more than %d bytes at a time.\n", PAGE_SIZE);
         return -1;
         }
         int i;
         my_buf[0] = addr_hi;
         my_buf[1] = addr_lo;
    
         for(i= 0; i < len; i++)
         {
         my_buf[2+i] = buf[i];
         }
         msg_rdwr.msgs = &i2cmsg;
         msg_rdwr.nmsgs = 1;
         i2cmsg.addr  = DEVICE_ADDR;
         i2cmsg.flags = 0;
         i2cmsg.len   = 2+len;
         i2cmsg.buf   = my_buf;
    
        if(ioctl(file_desc,I2C_RDWR,&msg_rdwr)<0)
        {
        printf("write_to_device(): %s\n", strerror(errno));
        return -1;
        }
    
        return 0;
    
    }
    
    int read_from_device(char addr_hi, char addr_lo, char * buf, int len)
    {
        struct i2c_rdwr_ioctl_data msg_rdwr;
        struct i2c_msg             i2cmsg;
    
    
    
        if(write_to_device(addr_hi, addr_lo ,NULL,0)<0)
        {
        printf("read_from_device(): address reset did not work\n");
        return -1;
        }
    
        msg_rdwr.msgs = &i2cmsg;
        msg_rdwr.nmsgs = 1;
    
        i2cmsg.addr  = DEVICE_ADDR;
        i2cmsg.flags = I2C_M_RD;
        i2cmsg.len   = len;
        i2cmsg.buf   = buf;
    
        if(ioctl(file_desc,I2C_RDWR,&msg_rdwr)<0)
        {
        printf("read_from_device(): %s\n", strerror(errno));
        return -1;
        }
    
    
        return 0;
    }
    
    void fill_buffer(char *buf)
    {
        int i = 0;
        while(i < PAGE_SIZE && *buf)
        {
        buffer[i+2] = *buf++;
        }
        while(i++ < PAGE_SIZE-1)
        {
        buffer[i+2] = '*'; // fill the buffer with something
        }
    }
    
    
    int main()
    {
    
        setupI2C(); //setup
    
        fill_buffer("Here are some words.");
        write_to_device(0x01, 0x00, buffer, PAGE_SIZE);
        char newbuf[PAGE_SIZE];
    
        if(read_from_device(0x01, 0x00, newbuf, PAGE_SIZE)>0)
        {
        printf("%s\n", newbuf);
        }
    
    
        teardownI2C(); //cleanup
        return EXIT_SUCCESS;
    }
    

    Writing to the device like in the line write_to_device(0x01, 0x00, buffer, PAGE_SIZE); doesn't generate any errors but when I try to read from the device, I have to write a "dummy" byte according to the spec sheet and then try to read from the device but for some reason writing the dummy byte results in an error "Input/output error". I can't figure out how this works. I am using two resources to guide me, the Linux I2C-Dev documentation and an example from a similar EEPROM device. I'm sort of stuck here and don't know what to try. Any suggestions or pointers are greatly appreciated!