/*********************************************************************************
|
* 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 <guowenxue@gmail.com>
|
* 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 <stdio.h>
|
#include <stdlib.h>
|
#include <string.h>
|
#include <stdint.h>
|
#include <string.h>
|
#include <unistd.h>
|
#include <libgen.h>
|
#include <getopt.h>
|
#include <time.h>
|
#include <errno.h>
|
#include <fcntl.h>
|
#include <sys/ioctl.h>
|
#include <sys/types.h>
|
#include <sys/stat.h>
|
#include <linux/types.h>
|
#include <linux/i2c.h>
|
#include <linux/i2c-dev.h>
|
|
/*+----------------------+
|
*| 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; i<sizeof(at24c_ids)/sizeof(at24c_ids[0]); i++)
|
{
|
if( at24c_ids[i].chip == chip )
|
{
|
at24c->eeprom = &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("<<<EEPROM read data:\n", (char *)buf, sizeof(buf));
|
|
/* fill a page data */
|
for(i=0; i<eeprom->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 read data:\n", (char *)buf, sizeof(buf));
|
|
return 0;
|
}
|
|
/* Figure 9. Page Write */
|
int at24c_write_page(at24c_t *at24c, int offset, uint8_t *data, int len)
|
{
|
uint8_t buf[256];
|
int bytes, rv;
|
int addrlen;
|
eeprom_t *eeprom;
|
|
if(!at24c || offset<0 || !data || !len)
|
return -1;
|
|
eeprom = at24c->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);
|
|
}
|
}
|