1. 前言

AT24C02是IIC接口的EEPROM存储芯片,这颗芯片非常经典,百度搜索可以找到非常多的资料,大多都是51、STM32单片机的示例代码,大多采用模拟时序、裸机系统运行。当前文章介绍在Linux系统里如何编写AT24C02的驱动,并且在应用层完成驱动读写测试,将AT24C02的存储空间映射成文件,在应用层,用户可以直接将AT24C02当做一个普通文件的形式进行读写,偏移文件指针;在Linux内核里有一套标准的IIC子系统框架专门读写IIC接口设备,采用平台设备模型框架,编写驱动非常方便。

当前开发板采用友善之臂的Tiny4412,CPU是三星的EXYNOS4412,4412是三星的第一款四核处理器,主频是1.5GHZ,稳定频率是1.4GHZ。

2. 硬件原理图

当前的开发板上自带了一颗EEPROM存储芯片(具体型号是24AA025E48,代码与AT24C02一样的),原理图如下:

image-20220104085514044

image-20220104085817035

自带的内核里没有内置EEPROM的驱动:

image-20220104085843239

存储芯片的数据手册介绍:

image-20220104090012199

设备地址:

image-20220104090059967

写字节、页写时序:

image-20220104090126403

读数据时序:

image-20220104090200161

3. 示例代码

3.1 EEPROM驱动端代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
static struct work_struct work;
static struct i2c_client *eeprom_client;

#define MAX_SIZE 255  //EEPROM大小
#define EEPROM_PAGE  16 //页字节大小

static u8 eeprom_buff[255];
static int tiny4412_open(struct inode *inode, struct file *file)
{
    printk("tiny4412_open-->ok\n");
    return 0;
}

static ssize_t tiny4412_read(struct file *file, char __user *buf, size_t size, loff_t *seek)
{   
    unsigned long err;
    //判断位置是否超出范围
    if(*seek+size>MAX_SIZE)
    {
        size=MAX_SIZE-*seek;
    }
    //读取数据
    i2c_smbus_read_i2c_block_data(eeprom_client,*seek,size,eeprom_buff);
    err=copy_to_user(buf,eeprom_buff,size);
    if(err!=0)return -1;
    *seek+=size;
    return size;
}

static ssize_t tiny4412_write(struct file *file, const char __user *buf, size_t size, loff_t *seek)
{
    size_t write_ok_cnt=0;
    unsigned long err;
    err=copy_from_user(eeprom_buff,buf,size);
    if(err!=0)return -1;
    //判断位置是否超出范围
    if(*seek+size>MAX_SIZE)
    {
        size=MAX_SIZE-*seek;
    }

    int write_byte=0;
    u8 *write_p=eeprom_buff;
    while(1)
    {
        if(size>EEPROM_PAGE)
        {
            write_byte=EEPROM_PAGE;
            size-=EEPROM_PAGE;
        }
        else
        {
            write_byte=size;
        }

        //写数据
        i2c_smbus_write_i2c_block_data(eeprom_client,*seek,write_byte,write_p);
        *seek+=write_byte;
        write_p+=write_byte;
        write_ok_cnt+=write_byte;  //记录写成功的字节数
        //等待写完成
        msleep(10);
        if(write_byte==size)break; //写完毕
    }
    return write_ok_cnt;
}

/*
filp:待操作的设备文件file结构体指针
off:待操作的定位偏移值(可正可负)
whence:待操作的定位起始位置
返回:返回移位后的新文件读、写位置,并且新位置总为正值
定位起始位置
  SEEK_SET:0,表示文件开头
  SEEK_CUR:1,表示当前位置
  SEEK_END:2,表示文件尾
*/
static loff_t tiny4412_llseek(struct file *filp, loff_t offset, int whence)
{
    loff_t newpos = 0;
    switch(whence)
    {
        case SEEK_SET:
            newpos = offset;
            break;
        case SEEK_CUR:
            newpos = filp->f_pos + offset;
            break;
        case SEEK_END:
            if(MAX_SIZE+offset>=MAX_SIZE)
            {
                newpos=MAX_SIZE;
            }
            else
            {
                newpos = MAX_SIZE + offset;
            }
            break;
        default:
            return -EINVAL;//无效的参数
    }
    filp->f_pos = newpos;
    return newpos;
}

static int tiny4412_release(struct inode *inode, struct file *file)
{
    printk("tiny4412_release-->ok\n");
    return 0;
}

static struct file_operations fops=
{
    .open=tiny4412_open,
    .read=tiny4412_read,
    .write=tiny4412_write,
    .release=tiny4412_release,
    .llseek=tiny4412_llseek
};

/*
Linux内核管理驱动---设备号
设备号是一个unsigned int 的变量--32位。
设备号=主设备号+次设备号
*/
static struct miscdevice misc=
{
    .minor = MISC_DYNAMIC_MINOR,  /*次设备号填255表示自动分配     主设备号固定为10*/
    .name = "tiny4412_eeprom",  /*/dev目录下文件名称*/
    .fops = &fops, /*文件操作接口*/
};

static int tiny4412_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
    printk("probe调用成功:%#X\n",client->addr);
    eeprom_client=client;

    /*1. 杂项设备的注册函数*/
    misc_register(&misc);

    return 0;
}

static int tiny4412_remove(struct i2c_client *client)
{
    /*2. 杂项设备的注销函数*/
    misc_deregister(&misc);
    printk("remove调用成功.\n");
    return 0;
}

static struct i2c_device_id id_table[]=
{
    {"tiny4412_eeprom",0},
    {}
};

static struct i2c_driver drv=
{
    .probe=tiny4412_probe,
    .remove=tiny4412_remove,
    .driver=
    {
        .name="eeprom_iic"
    },
    .id_table=id_table
};

static int __init tiny4412_drv_init(void)
{   
    /*注册IIC驱动端*/
    i2c_add_driver(&drv);
    printk("IIC驱动端: 驱动安装成功\n");
    return 0;
}

static void __exit tiny4412_drv_cleanup(void)
{
    /*注销IIC驱动端*/
    i2c_del_driver(&drv);
    printk("IIC驱动端: 驱动卸载成功\n");
}

module_init(tiny4412_drv_init);    /*驱动入口--安装驱动的时候执行*/
module_exit(tiny4412_drv_cleanup); /*驱动出口--卸载驱动的时候执行*/

MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/

3.2 EEPROM设备端代码

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>

static struct i2c_client *i2c_dev=NULL;
static struct i2c_adapter *adap=NULL;
static struct i2c_board_info info=
{
    .type="tiny4412_eeprom",
    .addr=0x50, /*设备地址*/
};

static int __init tiny4412_drv_init(void)
{   
    /*根据总线编号获取是适配器*/
    adap=i2c_get_adapter(0);
    /*注册IIC设备端*/
    i2c_dev=i2c_new_device(adap,&info);
    printk("IIC设备端: 驱动安装成功\n");
    return 0;
}

static void __exit tiny4412_drv_cleanup(void)
{
    /*注销IIC设备*/
    i2c_unregister_device(i2c_dev);
    i2c_put_adapter(adap);
    printk("IIC设备端: 驱动卸载成功\n");
}

module_init(tiny4412_drv_init);    /*驱动入口--安装驱动的时候执行*/
module_exit(tiny4412_drv_cleanup); /*驱动出口--卸载驱动的时候执行*/

MODULE_LICENSE("GPL");  /*设置模块的许可证--GPL*/

3.3 应用层测试代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define EEPROM_DEV "/dev/tiny4412_eeprom"

int main(int argc,char **argv)
{
    /*1. 打开设备文件*/
    int fd=open(EEPROM_DEV,O_RDWR);
    if(fd<0)
    {
        printf("%s 设备驱动打开失败.\n",EEPROM_DEV);
        return 0;
    }
    /*3.读写数据*/
    unsigned char buff[255];
    int cnt;
    int i;
    for(i=0;i<255;i++)buff[i]=i;
    cnt=write(fd,buff,255);
    printf("write成功:%d Byte\n",cnt);

    //偏移文件指针
    lseek(fd,SEEK_SET,0);

    unsigned char buff_r[255];
    cnt=read(fd,buff_r,255);
    printf("read成功:%d Byte\n",cnt);
    for(i=0;i<cnt;i++)
    {
        printf("%d ",buff_r[i]);
    }
    printf("\n");
    return 0;
}