Reading and writing EEPROM via I2C with Linux
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;
}
Jacob Calvert
Field Application Engineer for Wind River in the A&D world. I love tech and music. Blending them is even better.
Updated on January 21, 2021Comments
-
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!