I2C驱动

I2C总线

  • I2C(又称IIC)总线是由PHILIPS公司开发的串行总线,用于连接微控制器与外围设备,特点如下
    总线只有两条线:数据线(SDA),时钟线(SCL)
    每个连接到总线上的设备用唯一的地址来识别
    主/从关系,主设备是数据的传输的发起者并提供时钟信号。
    位速率:100kbit/s,400kbit/s,3.4Mbit/s。


  • 总线信号(3种)
    开始信号(S):SCL为高电平时,SDA由高变低电平
    结束信号(P):SCL为高电平时,SDA由低变高电平
    响应信号(ACK):接收器在收到8位数据后,在第9位


  • 位数据传输



    SDA上传输的数据必须在SCL为高电平期间保持稳定,SDA上数据的变化只能在SCL为低电平期间。

  • 几种I2C数据传输格式


Linux I2C体系结构

  • Linux的I2C体系结构分为3个组成部分
    • I2C核心ceng
      提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等
    • I2C适配器层
      对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部
      适配器i2c_adapter
      算法结构Algorithm。
    • I2C设备驱动层
      I2C设备驱动(也称为客户驱动)是对设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。
      使用了I2C总线-设备-驱动模型
      i2c_driver和i2c_client


4个数据结构的的关系

  • i2c_adapter与i2c_algorithm
    i2c_adapter对应于物理上的一个适配器(CPU上的I2C控制器)
    而i2c_algorithm对应一套通信方法
    一个I2C适配器需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期。
    缺少i2c_algorithm的i2c_adapter什么也做不了
    因此i2c_adapter中包含所使用的i2c_algorithm的指针。
  • i2c_adpater与i2c_client
    i2c_adpater与i2c_client的关系与I2C硬件体系中适配器和设备的关系一致。
    i2c_adapter对应于物理上的一个适配器(CPU上的I2C控制器)
    i2c_client代表连接到适配器中的设备,主要提供地址信息
    一个i2c_adpater也可以被多个i2c_client连接
    i2c_client必要依附在i2c_adpater上,才能使用。
    一个CPU可以有多个适配器硬件,对应对个i2c_adpater
  • i2c_driver与i2c_client
    i2c_client代表总线上的设备,主要提供地址信息
    i2c_driver代表对应设备的驱动程序
    i2c_driver与i2c_client依附在I2C总线上,适合device-bus-driver模型。

I2C设备驱动模型

  • I2C设备驱动层采用bus-dev-drv模型设计
    I2C总线定义如下图:



    I2C总线上的device可以包含两种数据(i2c_client和i2c_adapter)
    I2C总线提供了match、probe方法

  • I2C总线的match方法i2c_device_match()



  • I2C总线的match方法i2c_device_match()返回真,表示匹配成功,会调用I2C总线的probe函数i2c_device_probe():


    • 上面的函数是简化的示意代码:
      client表示匹配成功的dev
      driver表示匹配成功的drv
    • 函数的作用
      设置client->driver = driver
      调用driver->probe函数,参数是配置成功的client和id
  • I2C总线模型



    I2C总线上的device端有两种dev(client和adapter),而driver只有一种。
    向总线注册一个i2c_client的函数是i2c_new_device()
    向总线注册一个i2c_driver的函数是i2c_register_driver()

  • 分析i2c_new_device()函数简化流程如下:


    • 函数流程如下:
      分配一个client内存
      用参数adap、info初始化client
      注册client
  • 分析i2c_register_driver()函数简化流程如下:


    • 函数流程如下:
      • 注册driver
      • 对bus上的每个adapter调用__process_new_driver()函数
        bus上的dev出来client外,还有adapter,一个CPU上可以有多个adapter;
        所有CPU上的adapter都挂在bus的dev端。



i2c读写流程


驱动中的读、写方法,统一通过i2c_smbus_xxx族函数来进行读写

  • i2c_smbus函数族


EEPROOM

  • TINY4412的eeprom设备at24c08连接到CPU的适配器0上


  • 读写时序:



读写EEPROOM实战

at24_drv.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h>


static int major;
static struct class *class;
static struct i2c_client *at24_client;

/* 传入: buf[0] : addr               输出: buf[0] : data*/
static ssize_t at24_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
    unsigned char addr, data;
    
    copy_from_user(&addr, buf, 1);
    data = i2c_smbus_read_byte_data(at24_client, addr);
    copy_to_user(buf, &data, 1);
    return sizeof(unsigned char);
}

/* buf[0] : addr    buf[1] : data*/
static ssize_t at24_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
    unsigned char ker_buf[2];
    unsigned char addr, data;
    copy_from_user(ker_buf, buf, 2);
    addr = ker_buf[0];
    data = ker_buf[1];

    if (!i2c_smbus_write_byte_data(at24_client, addr, data))
        return 2*sizeof(unsigned char);
    else
        return -EIO;    
}

static struct file_operations at24_fops = {
    .owner = THIS_MODULE,
    .read  = at24_read,
    .write = at24_write,
};

static int __devinit at24_probe(struct i2c_client *client,
                  const struct i2c_device_id *id)
{
    printk("probe\n");
    at24_client = client;
    major = register_chrdev(0, "at24", &at24_fops);
    class = class_create(THIS_MODULE, "at24");
    device_create(class, NULL, MKDEV(major, 0), NULL, "at24"); 
    
    return 0;
}

static int __devexit at24_remove(struct i2c_client *client)
{
    device_destroy(class, MKDEV(major, 0));
    class_destroy(class);
    unregister_chrdev(major, "at24");
        
    return 0;
}

static const struct i2c_device_id at24_id_table[] = {
    { "at24c0x", 0 },{}
};
int (*detect)(struct i2c_client *, struct i2c_board_info *);
unsigned short tiny4412_address_list[] = I2C_ADDRS(0x50);
static struct i2c_driver at24_driver = {/*  分配/设置i2c_driver */
    .driver = {
        .name   = "at24c0x",
        .owner  = THIS_MODULE,
    },
    .probe      = at24_probe,
    .remove     = __devexit_p(at24_remove),
    .id_table   = at24_id_table,
    .address_list = ,
    .detect =,
};

static int at24_drv_init(void)
{
    i2c_add_driver(&at24_driver);   /*  注册i2c_driver */
    return 0;
}

static void at24_drv_exit(void)
{
    i2c_del_driver(&at24_driver);
}

module_init(at24_drv_init);
module_exit(at24_drv_exit);
MODULE_LICENSE("GPL");

at24_dev.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static struct i2c_board_info at24_info = {  
    I2C_BOARD_INFO("at24c0x", 0x50),
};

static struct i2c_client *at24_client;

static int at24_dev_init(void)
{
    struct i2c_adapter *i2c_adap;

    i2c_adap = i2c_get_adapter(0);
    at24_client = i2c_new_device(i2c_adap, &at24_info);
    i2c_put_adapter(i2c_adap);
    
    return 0;
}

static void at24_dev_exit(void)
{
    i2c_unregister_device(at24_client);
}


module_init(at24_dev_init);
module_exit(at24_dev_exit);
MODULE_LICENSE("GPL");

app_test.c


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


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
    printf("%s r addr\n", file);
    printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
    if ((argc != 3) && (argc != 4))
    {
        print_usage(argv[0]);
        return -1;
    }
    
    int fd;
    unsigned char buf[2];

    fd = open("/dev/at24", O_RDWR);
    if (strcmp(argv[1], "r") == 0)
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        read(fd, buf, 1);
        printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
    }
    else if ((strcmp(argv[1], "w") == 0) && (argc == 4))
    {
        buf[0] = strtoul(argv[2], NULL, 0);
        buf[1] = strtoul(argv[3], NULL, 0);
        if (write(fd, buf, 2) != 2)
            printf("write err, addr = 0x%02x, data = 0x%02x\n", buf[0], buf[1]);
    }
    return 0;
}

makefile

obj-m += at24_dev.o  at24_drv.o
all:
    make -C /home/sice/linux-3.5 M=`pwd` modules 
    arm-linux-gcc app_test.c -o app_test
install:
    make -C /home/sice/linux-3.5 M=`pwd` INSTALL_MOD_PATH=/opt/rootfs modules_install
    cp app_test /opt/rootfs
clean:
    make -C /home/sice/linux-3.5 M=`pwd` clean
    rm app_test
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 简介 I2C驱动由I2C核心,I2C总线驱动和I2C设备驱动组成.I2C核心是I2C总线驱动和I2C设备驱动的中间...
    傀儡世界阅读 4,845评论 0 1
  • 一、前言 I2C总线 是一种常用的总线协议,在设备中经常看到,比如 sensor、陀螺仪等都是使用 I2C总线。而...
    wipping的技术小栈阅读 8,606评论 0 3
  • 引言 单片机的IIC编程中,如果我们直接一点,只需要控制IIC硬件GPIO脚,然后根据IIC协议模拟各种电平时序实...
    开源519阅读 3,237评论 0 0
  • 一,前言 爽11选东东花费了我一周的业余时间,趁着周末又调整回了常规的学习状态。之前做的applepaper是虚拟...
    applecai阅读 5,326评论 0 1
  • I2C总线仅仅使用 SCL 、 SDA 两根信号线就实现了设备之间的数据交互。 由于各种SOC都有自己的I2C总线...
    gbmaotai阅读 9,322评论 0 3

友情链接更多精彩内容