Reading and writing EEPROM via I2C with Linux
Asked Answered
H

5

8

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!

Hypesthesia answered 28/4, 2015 at 23:59 Comment(2)
Looks like you're writing two dummy bytes, or 0 and a two byte address? Example is different.Nanettenani
I succeeded in reading it using this: stackoverflow.com/posts/comments/121747352?noredirect=1Croquet
O
18

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.

Oatis answered 11/11, 2015 at 4:11 Comment(0)
S
5

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...

Sebaceous answered 20/3, 2020 at 20:54 Comment(1)
Regarding user permissions—you should be able to write a udev rule so that the EEPROM file is given certain permissions and group membership. See the following which describes doing so for LEDs; the concept is very similar. linux - How can I change the permissions in /sys to alter the state of a LED/light using udev? - Unix & Linux Stack ExchangeOatis
B
3

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

Brierwood answered 29/4, 2015 at 7:17 Comment(1)
Thanks for your input. This is almost what I'm looking for except that it uses SMBus commands and not raw control of the I2C device.Hypesthesia
V
2

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;
}
Vinyl answered 24/2, 2018 at 10:21 Comment(0)
H
0

My code:

enter code here

__s32 write_eeprom(__s32 fd,__u32 offset,__u32 len,__u8 *buf)
{
    __s32 ret;
    struct i2c_rdwr_ioctl_data msg_set;
    struct i2c_msg iomsgs;
    __u32 sended, sending;
    __u8 temp[ONE_PAGE + 1];

    if((offset + len) > BYTES_MAX || len == 0)
    {
        printf("write too long than BYTES_MAX\n");
        return -1;
    }
    sended = 0;
    iomsgs.addr = DEVICE_ADDR;
    iomsgs.flags = 0;   //write
    iomsgs.buf = temp;
    msg_set.msgs = &iomsgs;
    msg_set.nmsgs = 1;
    while(len > sended)
    {
        if(len - sended > ONE_PAGE)
            sending = ONE_PAGE;
        else
            sending = len - sended;
        iomsgs.len = sending + 1;
        temp[0] = offset + sended;
        memcpy(&temp[1], buf + sended, sending);
        //printf("sending:%d sended:%d len:%d offset:%d \n", sending, sended, len, offset);
        ret = ioctl(fd, I2C_RDWR, (unsigned long)&msg_set);
        if(ret < 0)
        {
            printf("Error dring I2C_RDWR ioctl with error code: %d\n", ret);
            return ret;
        }
        sended += sending;
        usleep(5000);
    }

    return sended;
}
Haye answered 20/9, 2017 at 10:41 Comment(5)
Could you please elaborate a bit more?Ingles
I have met the same problem. I found it would be successfully written when the length is less than 16 bytes. So I made a macro ONE_PAGE equal to 16 and made a loop to write the data 16 bytes by 16 bytes.I also found 5000us should be wait after each 16 bytes write.I tried 1000us but failed, only part of data is written.Haye
In fact, write failure has nothing to do with 16 bytes,it would be successfully written when the length is more or less than 16 bytes.Haye
Mainly depends on the writing process needs to be a waitHaye
EEPROM writes do take between 1 and 5mS. I generally wait 5000mS to be sure.Charente

© 2022 - 2024 — McMap. All rights reserved.