/********************************************************************************* * Copyright: (C) 2023 LingYun IoT System Studio * All rights reserved. * * Filename: at24c.c * Description: This file is AT24Cxx EEPROM code * * Version: 1.0.0(10/08/23) * Author: Guo Wenxue * ChangeLog: 1, Release initial version on "10/08/23 17:52:00" * * Pin connection: * AT24Cxx Raspberry Pi 40Pin * VCC <-----> #Pin1(3.3V) * SDA <-----> #Pin3(SDA, BCM GPIO2) * SCL <-----> #Pin5(SCL, BCM GPIO3) * GND <-----> GND * * /boot/config.txt: * dtoverlay=i2c1,pins_2_3 * ********************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*+----------------------+ *| EEPROM Information | *+----------------------+*/ /* AT24Cxx 7-bit I2C slave address */ #define AT24C_CHIPADDR 0x50 enum { AT24C01 = 1, AT24C02 = 2, AT24C04 = 4, AT24C08 = 8, AT24C16 = 16, AT24C32 = 32, AT24C64 = 64, AT24C128 = 128, AT24C256 = 256, AT24C512 = 512, } chipid_t; typedef struct i2c_s { char *dev; /* I2C master device, /dev/i2c-N */ int addr; /* 7-bits slave address */ int fd; /* File description */ } i2c_t; typedef struct eeprom_s { int chip; /* Chip ID */ uint32_t capacity; /* Chip size in bytes */ int pagesize; /* Page size in bytes */ } eeprom_t; #define EEP_INFO(_chip, _capacity, _pagesize) \ .chip = _chip, \ .capacity = _capacity, \ .pagesize = _pagesize, \ static eeprom_t at24c_ids[] = { { EEP_INFO(AT24C01, 128, 8) }, { EEP_INFO(AT24C02, 256, 8) }, { EEP_INFO(AT24C04, 512, 16) }, { EEP_INFO(AT24C08, 1024, 16) }, { EEP_INFO(AT24C16, 2048, 16) }, { EEP_INFO(AT24C32, 4096, 32) }, { EEP_INFO(AT24C64, 8192, 32) }, { EEP_INFO(AT24C128, 16384, 64) }, { EEP_INFO(AT24C256, 32768, 64) }, { EEP_INFO(AT24C512, 65536, 128) }, }; typedef struct at24c_s { i2c_t i2c; eeprom_t *eeprom; } at24c_t; int at24c_init(at24c_t *at24c, int chip); int at24c_read(at24c_t *at24c, int offset, uint8_t *buf, int size); int at24c_write(at24c_t *at24c, int offset, uint8_t *data, int len); int at24c_test(at24c_t *at24c); int i2c_init(i2c_t *i2c, char *i2cdev, int addr); int i2c_write(i2c_t *i2c, uint8_t *data, int len); int i2c_read(i2c_t *i2c, uint8_t *buf, int size); void i2c_term(i2c_t *i2c); static inline void msleep(unsigned long ms); void dump_buf(const char *prompt, char *buf, size_t len); static inline void banner(const char *progname) { printf("%s program Version v1.0.0\n", progname); printf("Copyright (C) 2023 LingYun IoT System Studio.\n"); } static void program_usage(const char *progname) { printf("Usage: %s [OPTION]...\n", progname); printf(" %s is AT24Cxx EEPROM test program. \n", progname); printf(" -c[chipid ] Specify EEPROM chipID: 1,2,4,8...512 \n"); printf(" -d[device ] Specify I2C device, such as /dev/i2c-1\n"); printf(" -h[help ] Display this help information\n"); printf(" -v[version ] Display the program version\n"); printf("\n"); banner(progname); return; } int main(int argc, char **argv) { char *progname=NULL; char *dev="/dev/i2c-1"; int chipid = AT24C256; /* default */ at24c_t at24c; int rv; struct option long_options[] = { {"chip", required_argument, NULL, 'c'}, {"device", required_argument, NULL, 'd'}, {"version", no_argument, NULL, 'v'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; progname = basename(argv[0]); /* Parser the command line parameters */ while ((rv = getopt_long(argc, argv, "c:d:vh", long_options, NULL)) != -1) { switch (rv) { case 'c': /* Set chip ID: 1,2,4,8...512 */ chipid = atoi(optarg); break; case 'd': /* Set I2C device path: /dev/i2c-1 */ dev = optarg; break; case 'v': /* Get software version */ banner(progname); return EXIT_SUCCESS; case 'h': /* Get help information */ program_usage(progname); return 0; default: break; } } if( at24c_init(&at24c, chipid) < 0 ) { printf("at24c initial failed!\n"); return 1; } if( i2c_init(&at24c.i2c, dev, AT24C_CHIPADDR) < 0 ) { printf("i2c initial failed!\n"); return 2; } if( at24c_test(&at24c) < 0 ) { return 3; } i2c_term(&at24c.i2c); return 0; } /*+----------------------+ *| EEPROM API functions | *+----------------------+*/ int at24c_init(at24c_t *at24c, int chip) { int i; if( !at24c ) return -1; for(i=0; ieeprom = &at24c_ids[i]; printf("Detect EEPROM AT24C%02d capacity %d bytes, pagesize %d bytes.\r\n", chip, at24c->eeprom->capacity, at24c->eeprom->pagesize); return 0; } } printf("EEPROM: Can not found EEPROM by chip ID[%d]\r\n", chip); return -2; } int at24c_test(at24c_t *at24c) { eeprom_t *eeprom; uint8_t buf[128]; int i; int addr = 0; if( !at24c ) return -1; eeprom = at24c->eeprom; /* Read data before write */ memset(buf, 0, sizeof(buf)); if( at24c_read(at24c, addr, buf, sizeof(buf)) < 0 ) { return -2; } dump_buf("<<pagesize; i++) { buf[i] = i; } /* write a page data from the address not page alignment */ if( at24c_write(at24c, addr+8, buf, eeprom->pagesize) < 0 ) { return -3; } /* Read data after write */ memset(buf, 0, sizeof(buf)); if( at24c_read(at24c, addr, buf, sizeof(buf)) < 0 ) { return -2; } dump_buf("<<eeprom; /* exceeding EEPROM size */ if( offset+len > eeprom->capacity ) return -1; if( len > eeprom->pagesize ) len = eeprom->pagesize; /* * A temporary write buffer must be used which contains both the address * and the data to be written, put the address in first based upon the * size of the address for the EEPROM. */ if( eeprom->chip >= AT24C16) { buf[0]=(uint8_t)(offset>>8); buf[1]=(uint8_t)(offset); addrlen = 2; } else { buf[0]=(uint8_t)(offset); addrlen = 1; } /* Put the data in the write buffer following the address */ memcpy(&buf[addrlen], data, len); /* Write a page of data at the specified address to the EEPROM. */ rv = i2c_write(&at24c->i2c, buf, len+addrlen); if( rv < 0 ) printf("%s() write data failed\n", __func__); /* Must give a delay here to wait page write finish */ msleep(5); return rv; } int at24c_write(at24c_t *at24c, int offset, uint8_t *data, int len) { int bytes; eeprom_t *eeprom; if(!at24c || offset<0 || !data || len<0) return -1; eeprom = at24c->eeprom; /* exceeding EEPROM size */ if( offset+len > eeprom->capacity ) return -1; /* The offset + write bytes shouldn't overflow that page, * or it will over write the start bytes of this page */ if( offset%eeprom->pagesize ) bytes = eeprom->pagesize - offset%eeprom->pagesize; else bytes = len>eeprom->pagesize? eeprom->pagesize : len; /* Write max one page at a time */ while(len > 0) { if( at24c_write_page(at24c, offset, data, bytes) ) { return -2; } len -= bytes; data += bytes; offset += bytes; bytes = len>eeprom->pagesize? eeprom->pagesize : len; } return 0; } /* Figure 12. Sequential Read */ int at24c_read(at24c_t *at24c, int offset, uint8_t *buf, int size) { struct i2c_rdwr_ioctl_data tr; eeprom_t *eeprom; uint8_t addr[2]; int addrlen; int bytes; int rv = 0; if(!at24c || offset<0 || !buf || size<=0) return -1; eeprom = at24c->eeprom; /* exceeding EEPROM size */ if( offset+size > eeprom->capacity ) return -1; memset(buf, 0, size); if( eeprom->chip >= AT24C16) { addr[0]=(uint8_t)(offset>>8); addr[1]=(uint8_t)(offset); addrlen = 2; } else { addr[0]=(uint8_t)(offset); addrlen = 1; } /* We can't call i2c_write() and i2c_read() because it will generate a * stop condition after send the offset address, it doesn't compatible * with EEPROM sequential read sequence; */ /* use I2C userspace API to send two message without stop condition */ tr.nmsgs = 2; tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs ); if ( !tr.msgs ) { printf("%s() msgs malloc failed!\n", __func__); return -2; } /* Create the I2C message for writing the EEPROM offset address */ tr.msgs[0].addr = at24c->i2c.addr; tr.msgs[0].flags = 0; //write tr.msgs[0].len = addrlen; tr.msgs[0].buf = addr; /* Create the I2C message for reading data from EEPROM */ memset(buf, 0, size); tr.msgs[1].addr = at24c->i2c.addr; tr.msgs[1].flags = I2C_M_RD; //read tr.msgs[1].len = size; tr.msgs[1].buf = buf; if( ioctl(at24c->i2c.fd, I2C_RDWR, &tr)<0 ) { printf("%s() ioctl failure: %s\n", __func__, strerror(errno)); rv = -4; } free( tr.msgs ); return rv; } /*+----------------------+ *| I2C API functions | *+----------------------+*/ int i2c_init(i2c_t *i2c, char *i2cdev, int addr) { if( !i2c || !i2cdev || !addr ) return -1; memset(i2c, 0, sizeof(*i2c)); i2c->addr = addr; i2c->dev = i2cdev; if( (i2c->fd=open(i2cdev, O_RDWR)) < 0) { printf("open i2c device %s failed: %s\n", i2cdev, strerror(errno)); return -1; } return 0; } void i2c_term(i2c_t *i2c) { if( !i2c ) return; if( i2c->fd > 0) close(i2c->fd); return ; } int i2c_write(i2c_t *i2c, uint8_t *data, int len) { struct i2c_rdwr_ioctl_data tr; int rv = 0; if( !data || len<= 0) { printf("%s() invalid input arguments!\n", __func__); return -1; } /* I2C device program API: ioctl() */ tr.nmsgs = 1; tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs ); if ( !tr.msgs ) { printf("%s() msgs malloc failed!\n", __func__); return -2; } tr.msgs[0].addr = i2c->addr; tr.msgs[0].flags = 0; //write tr.msgs[0].len = len; tr.msgs[0].buf = malloc(len); if( !tr.msgs[0].buf ) { printf("%s() msgs malloc failed!\n", __func__); rv = -3; goto cleanup; } memcpy(tr.msgs[0].buf, data, len); if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 ) { printf("%s() ioctl failure: %s\n", __func__, strerror(errno)); rv = -4; goto cleanup; } cleanup: if( tr.msgs[0].buf ) free(tr.msgs[0].buf); if( tr.msgs ) free(tr.msgs); return rv; } int i2c_read(i2c_t *i2c, uint8_t *buf, int size) { struct i2c_rdwr_ioctl_data tr; int rv = 0; if( !buf || size<= 0) { printf("%s() invalid input arguments!\n", __func__); return -1; } tr.nmsgs = 1; tr.msgs = malloc( sizeof(struct i2c_msg)*tr.nmsgs ); if ( !tr.msgs ) { printf("%s() msgs malloc failed!\n", __func__); return -2; } tr.msgs[0].addr = i2c->addr; tr.msgs[0].flags = I2C_M_RD; //read tr.msgs[0].len = size; tr.msgs[0].buf = buf; memset(buf, 0, size); if( ioctl(i2c->fd, I2C_RDWR, &tr)<0 ) { printf("%s() ioctl failure: %s\n", __func__, strerror(errno)); rv = -4; } free( tr.msgs ); return rv; } /*+----------------------+ *| Misc functions | *+----------------------+*/ static inline void msleep(unsigned long ms) { struct timespec cSleep; unsigned long ulTmp; cSleep.tv_sec = ms / 1000; if (cSleep.tv_sec == 0) { ulTmp = ms * 10000; cSleep.tv_nsec = ulTmp * 100; } else { cSleep.tv_nsec = 0; } nanosleep(&cSleep, 0); } #define LINELEN 81 #define CHARS_PER_LINE 16 static char *print_char = " " " " " !\"#$%&'()*+,-./" "0123456789:;<=>?" "@ABCDEFGHIJKLMNO" "PQRSTUVWXYZ[\\]^_" "`abcdefghijklmno" "pqrstuvwxyz{|}~ " " " " " " ???????????????" "????????????????" "????????????????" "????????????????" "????????????????" "????????????????"; void dump_buf(const char *prompt, char *buf, size_t len) { int rc; int idx; char prn[LINELEN]; char lit[CHARS_PER_LINE + 2]; char hc[4]; short line_done = 1; if( prompt ) printf("%s", prompt); rc = len; idx = 0; lit[CHARS_PER_LINE] = '\0'; while (rc > 0) { if (line_done) snprintf(prn, LINELEN, "%08X: ", idx); do { unsigned char c = buf[idx]; snprintf(hc, 4, "%02X ", c); strncat(prn, hc, LINELEN); lit[idx % CHARS_PER_LINE] = print_char[c]; } while (--rc > 0 && (++idx % CHARS_PER_LINE != 0)); line_done = (idx % CHARS_PER_LINE) == 0; if (line_done) { printf("%s %s\r\n", prn, lit); } } if (!line_done) { int ldx = idx % CHARS_PER_LINE; lit[ldx++] = print_char[(int)buf[idx]]; lit[ldx] = '\0'; while ((++idx % CHARS_PER_LINE) != 0) strncat(prn, " ", sizeof(prn)-strlen(prn)); printf("%s %s\r\n", prn, lit); } }