User Tools

Site Tools


tutorials:cb1:development:access_at24c_eeprom_via_i2c

Access AT24C EEPROM via I2C

About this Article

Abstract

In this tutorial, an approach for accessing AT24C EEPROM via I2C bus on is presented. Cubieboard1 or Cubieboard2 expand out groups of I2C pins which could be connected to peripheral I2C slave devices, like RTC device(DS1307), EEPROM device(AT24C) etc. It's possible to connect serval slave devices on a single I2C interface, because I2C has the ability to communicate with different devices by various device addresses. For details, you may look into literatures describing I2C protocols.

Pictures below illustrate the DIY result of accessing four AT24C256s via I2C on Cubieboard1. In fact, all Cubieboard series expand out I2C pins, so the approach in this article will apply for all Cubieboards, the mechanism is same.

Configuration

In order to access peripheral I2C devices, Cubieboard's script.bin should enable I2C interfaces. Edit script.fex, make certain that the following lines exist there.

[twi1_para]
twi1_used = 1
twi1_scl = port:PB18<2><default><default><default>
twi1_sda = port:PB19<2><default><default><default>

This will configure Cubieboard's IO port PB18 & PB19 to TWI1-SCK & TWI1-SDA (I2C-1).

Connection

Cubieboard side will be considered as the master side of I2C communication, while the AT24C EEPROM devices will be considered as the slave ones. In most cases, 4 wires of connection between Cubieboard & devices are enough, they are:

  • Vcc: Cubieboard Vcc(5V) ←→ AT24C Vcc
  • Gnd: Cubieboard Gnd ←→ AT24C Gnd
  • SDA: Cubieboard PB19 ←→ AT24C SDA
  • SCL: Cubieboard PB18 ←→ AT24C SCL

The connection of AT24C slave devices is in parallel(like the circuits below, which is a sort of write-protection AT24C configuration circuits), but they should have various device address in order to be addressed by I2C master. The addressing problem will be discussed later in this tutorial.

Once connection is done, you can use the i2c-tools(apt-get install it) to detect them. If things go normal, you should get the similar result below.

AT24C Access

AT24C series EEPROM's address could be splited into two parts, higher bits and lower bits. The higher bits are fixed at 0x5, while lower bits could be hard coded by setting A2, A1, A0's power logic. As for AT24C256, only A1, A0 are valid for address.

Example Codes

This code snippet is an interactive demo for storing text strings and fetching them back to and from AT24C256 chips. Of course you can modified it to make it able to handle binary data. You maybe need install libi2c-dev package to compile & run.

/*
 * at24c256_i2c.c
 *
 * This script is a demo for AT24C256 EEPROM access via I2C on Cubieboard
 *
 * Author: soloforce, forum.cubietech.com
 * Date: 2013.11.18
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * PAWEL W. OLSZTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
 
// Cubieboard IO port PB18 & PB19 are configured to TWI1-SCK & TWI1-SDA (I2C-1)
#define DEFAULT_AT24C256_DEV  "/dev/i2c-1"
 
// ATmega AT24C series EEPROM slave devices are addressed from 0x50~0x57
// As for AT24C256, should be 0x50~0x53
#define DEFAULT_AT24C256_ADDRESS 0x50
 
// AT24C256 has 512 pages, 64 bytes each
#define PAGE_BYTES  64
 
typedef unsigned char uint8;
typedef unsigned short uint16;
 
/*
 * Open the i2c bus device
 */
int init_at24c256_i2c(const char* device)
{
    int fd=open(device, O_RDWR);
    if(fd<0){
        perror("Failed to open I2C device!");
        return -1;
    }
 
    ioctl(fd, I2C_TIMEOUT, 3);    // i2c timeout, 1 for 10ms ; if too small, i2c may lose response
    ioctl(fd, I2C_RETRIES, 3);    // i2c retry limit
 
    return fd;
}
 
/*
 * Page write (or partially page write), data_len should less than PAGE_BYTES
 *
 */
int at24c256_write(int fd, int device_addr, uint16 offset, const char* data, int data_len)
{
    struct i2c_rdwr_ioctl_data at24c256;    // structure pass to i2c driver
    struct i2c_msg i2cmsg;
 
    if(data_len>PAGE_BYTES){
        printf("*** Warning: Page write mode should not exceed %d bytes, otherwise the current page will be rolled over!\n",PAGE_BYTES);
    }
 
    at24c256.nmsgs = 1;    // message count in the structure
    at24c256.msgs = &i2cmsg;
    at24c256.msgs[0].len = 2 + data_len;    // the offset address word is 2 bytes long, plus the buf_len
    at24c256.msgs[0].addr = device_addr;    // i2c slave device address
    at24c256.msgs[0].flags = 0;    // 0 for write; 1 for read
    at24c256.msgs[0].buf = (uint8 *)malloc( at24c256.msgs[0].len );
    if(!at24c256.msgs[0].buf){
        printf("at24c256_write(): Failed to allocate memory for i2c message buffer!\n");
        return -1;
    }
 
    at24c256.msgs[0].buf[0] = (uint8)(offset >> 8);    // MSB first
    at24c256.msgs[0].buf[1] = (uint8)(offset);    // LSB
    strncpy( at24c256.msgs[0].buf+2, data, data_len); // copy user's data to the structure buffer
 
    if(ioctl(fd, I2C_RDWR, (unsigned long)&at24c256)<0 ){    // write ioctl data to the i2c bus
        printf("at24c256_write(): Failed to write data to i2c device!\n");
        free(at24c256.msgs[0].buf);
        return -1;
    }
 
    free(at24c256.msgs[0].buf);
    return 0;
}
 
/*
 * Sequential read from AT24C256, data_len should less than PAGE_BYTES
 */
int at24c256_read(int fd, int device_addr, uint16 offset, char* data, int data_len)
{
    struct i2c_rdwr_ioctl_data at24c256;    // structure pass to i2c driver
    struct i2c_msg i2cmsg[2]; // message count in the structure
    char dummy_write_buf[2];
 
    if(data_len>PAGE_BYTES){
        printf("*** Warning: Sequential read should not exceed %d bytes, otherwise the read data will be rolled over!\n",PAGE_BYTES);
    }
 
    // A dummy write operation should be done according to the AT24C256 i2c protocol
    at24c256.nmsgs = 2;    // message count in the structure
    at24c256.msgs = i2cmsg;
    at24c256.msgs[0].len = 2; // the offset address word is 2 bytes long
    at24c256.msgs[0].addr = device_addr;
    at24c256.msgs[0].flags = 0;    // 0 for write;
    at24c256.msgs[0].buf = dummy_write_buf;
    at24c256.msgs[0].buf[0] = (unsigned char)(offset >> 8);    // write address
    at24c256.msgs[0].buf[1] = (unsigned char)(offset);    // write address
 
    // read operation
    at24c256.msgs[1].len = data_len;
    at24c256.msgs[1].addr = device_addr;
    at24c256.msgs[1].flags = 1;    // 1 for read; 0 for write
    at24c256.msgs[1].buf = data;
 
    if (ioctl(fd, I2C_RDWR, (unsigned long)&at24c256) < 0) {
        printf("at24c256_read(): Failed to read data from EEPROM via i2c!\n");
        return -1;
    }
 
    return 0;
}
 
/*
 * Show help message
 */
void show_usage(char* argv[])
{
    printf("%s <-d device> <-a address> [-o offset] [-r length] [-w data] [-h]\n",argv[0]);
    printf("\t-d device: i2c device file, like /dev/i2c-1 by default\n");
    printf("\t-a address: i2c slave device base address, like 0x50 by default\n");
    printf("\t-o offset: offset from base address, like 0x00, 0x10 ...\n");
    printf("\t-r length: read length bytes from at24c256 eeprom\n");
    printf("\t-w data: write to at24c256 eeprom\n");
    printf("\t-h: show help message\n\n");
}
 
int main(int argc, char* argv[])
{
    char* device=DEFAULT_AT24C256_DEV;
    int slave_address=DEFAULT_AT24C256_ADDRESS;
    int offset=0x00; // default offset is 0
    int length=32; // read 32 bytes by default
    char* data=NULL;
    int rw_mode=1; // 0 for write, 1 for read;
 
    if(argc==1){
        show_usage(argv);
        return 0;
    }
 
    int i;
    while ((i = getopt(argc, argv, "d:a:o:r:w:n:h")) >= 0){
        switch(i){
        case 'd':
            device=optarg;
            break;
        case 'a':
            if (sscanf(optarg, "0x%x", &slave_address) != 1) {
            fprintf(stderr, "Cannot parse '%s' as i2c slave device address, example: '0x50'\n", optarg);
                return -1;
            }
            break;
        case 'o':
            if (sscanf(optarg, "%d", &offset) != 1) {
            fprintf(stderr, "Cannot parse '%s' as address offset, example: '32'\n", optarg);
                return -1;
            }
            break;
        case 'r':
            rw_mode=1;
            if (sscanf(optarg, "%d", &length) != 1) {
                fprintf(stderr, "Cannot parse '%s' as length, example: '17'\n", optarg);
                return -1;
            }
            break;
        case 'w':
            rw_mode=0;
            data=optarg;
            length=strlen(data);
            break;
        case 'h':
            show_usage(argv);
            return 0;
            break;
        }
    }
 
    int fd=init_at24c256_i2c(device);
    if(fd<0) return -1;
 
    printf("i2c device file:%s\nslave_address:0x%X\nR/W offset:0x%X\n",device, slave_address, offset);
 
    if(rw_mode==0){
        // test at24c256 write
        at24c256_write(fd, slave_address, offset, data, length);
        printf("try to write: %s (%d bytes)!\n",data,length);
        usleep(10000); // some delay is necessary for the write operation done
    }else if(rw_mode==1){
        // test at24c256 read
        char *buf=(char*)malloc(length+1);
        memset(buf,0,length+1);
        at24c256_read(fd, slave_address, offset, buf, length);
        usleep(10000); // necessary for the read operation done
 
        printf("read from at24c256 eeprom: %s (%d bytes)\n", buf, strlen(buf));
        free(buf);
    }
 
    close(fd);
}

tutorials/cb1/development/access_at24c_eeprom_via_i2c.txt · Last modified: 2013/12/23 14:50 (external edit)