最全的IIC介绍及其设备驱动编写
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最全的IIC介绍及其设备驱动编写相关的知识,希望对你有一定的参考价值。
参考资料:
1、IIC介绍
IIC是通信协议中的一种,为一主多从的结构,对于主从,所有的数据都是从主机这边发起,从机只能接受,不能主动引起数据传输,只有两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL),IIC有硬件IIC和软件IIC,这里简单解释,硬件IIC为硬件构成的IIC,一般只需要操作相关寄存器即可,对于软件IIC,可以由IO口来模拟IIC总线进行对IIC设备的通信。
对于多个IIC设备来说,每个连接到总线的器件都可以使用软件根据它的唯一地址来识别,当我们要使用一个IIC设备时,主机通过SDA线向各IIC设备寻址,等待地址对应的IIC设备的回应即ACK信号。
IIC是半双工的,即SDA总线是双向传输的。两条线在硬件上都使用了开极电路,且都接有上拉电阻(即空闲时,SDA和SCL都为高电平)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uCKvuXrc-1628932720637)(C:\\Users\\liang\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210812203900555.png)]
- 传输过程:
当主机A要发送数据时,A拉低SDA总线,稍后向SCL输送时钟信号,开始传输数据(在每个时钟周期中,当SCL为高电平时,从机从SDA线上采样(获得)数据;当SCL为低电平时,主机向SDA传送(更新)数据)。主机每传完8个比特位,就将SDA释放(SDA恢复到高电平),若从机正常接收完8个比特位,就将SDA线拉低,以表示向主机回送一个ACK信号,若没有接收到8个比特位,就不会拉低SDA线,主机就收不到ACK信号,所以主机会发送一个终止或开始(重传)信号给从机。当主机已经完成数据传输时,会先释放SCL线,然后再释放SDA线。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3XsSO80t-1628932720640)(https://i.loli.net/2021/08/12/ieORwNA9xzGIbyW.png)]
并非每传输8位数据之后,都会有ACK信号,有以下3种例外:
- 当从机不能响应从机地址时(例如它正忙于其他事而无法响应I²总线的操作,或者这个地址没有对应的从机),在第9个SCL周期内SDA线没有被拉低,即没有ACK信号。这时,主机发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
- 如果从机接收器在传输过程中不能接收更多的数据时,它也不会发出ACK信号。这样,主机就可以意识到这点,从而发出一个Р信号终止传输或者重新发出一个S信号开始新的传输.
- 主机接收器在接收到最后一个字节后,也不会发出ACK信号。于是,从机发送器释放SDA线,以允许主机发出Р信号结束传输
- 使用IIC协议的芯片介绍—AT24C02/04/08/16
AT24Cxx系列芯片是采用IIC协议的EEPROM芯片,其读写过程如下:
写过程:主机先向芯片发出设备地址,再发出写地址后就可以开始传输数据;
读过程:主机先向芯片发出设备地址,再发出读地址,然后需要再发出一次设备地址,才能读数据,是因为AT24Cxx容量各有不同,有1K,2K,4K,8K,16K等大小,对于4K芯片来说其架构为512*8bit,而512为2的9次方,因此只传输一次读地址是不够的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fUFMLsrP-1628932720642)(https://i.loli.net/2021/08/12/3aHObDrZy2GxMLC.png)]
2、IIC驱动框架
I²C驱动框架:
App:open()、 read() 、write() |
IIC设备驱动程序:drv_open()、 drv_read() 、drv_write() 知道数据含义 |
IIC总线驱动程序:1.识别设备; 2.提供读写函数 。 知道怎么收发数据 |
IIC硬件:如AT24C02/AT24C08 |
- 这三层通过总线设备驱动模型联系在一块,在内核中有很多虚拟的总线例如platfoem_bus_type,对于IIC来说是i2c_bus_type总线,在这条总线中有i2c_client和i2c_driver两条链表,当i2c_client加到链表中后,会先跟i2c_driver链表的每一项比较,如果能匹配的话就调用相对应的i2c_driver的probe函数;对于i2c_driver加到链表中后,一样会先跟i2c_client链表的每一项比较,如果能匹配的话就调用i2c_driver的probe函数。匹配是调用i2c_bus_type总线中的match函数进行比较,其中比较的是id_table,而在id_table中比较的是两者链表中的name,因此简单描述如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49sBhfqD-1628932720646)(C:\\Users\\liang\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210813194017090.png)]
- 左边注册一个设备:i2c_client
- 右边注册一个驱动:i2c_driver
- 比较他们的名字,如果相同,则调用probe函数
- probe函数里,可以注册字符设备驱动
在i2c_bus_type总线中,dev链表不仅有i2c_client还有i2c_adapter,后面再解释。
- 对于裸板程序来说,可以直接对寄存器进行映射使用,但在内核中有完善的IIC总线驱动程序,会帮我们去发出起始信号,识别IIC设备参考i2c-s3c2410.c,在入口函数中注册了平台总线,在probe函数中设置adap并注册添加了adap(IIC适配器:i2c_add_adapter>i2c_register_adapter),在设置adap中的algo(算法)设置s3c24xx_i2c_xfer函数,这个函数实现了识别IIC设备的功能(发S信号,设备地址,等待应答)
- IIC总线驱动程序在内核源码中drivers\\i2c\\busses中参考i2c-s3c2410.c,在入口函数中注册了平台总线,在probe函数中设置adap并注册添加了adap(IIC适配器:i2c_add_adapter>i2c_register_adapter),在设置adap中的algo(算法)设置s3c24xx_i2c_xfer函数,这个函数实现了识别IIC设备的功能(发S信号,设备地址,等待应答)
static const struct i2c_algorithm s3c24xx_i2c_algorithm =
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
;
static struct s3c24xx_i2c s3c24xx_i2c =
.lock = __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
.wait = __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
.tx_setup = 50,
.adap =
.name = "s3c2410-i2c",
.owner = THIS_MODULE,
.algo = &s3c24xx_i2c_algorithm,
.retries = 2,
.class = I2C_CLASS_HWMON,
,
;
...
static int __init i2c_adap_s3c_init(void)
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
return ret;
...
static int s3c24xx_i2c_probe(struct platform_device *pdev)
...
ret = s3c24xx_i2c_init(i2c);
if (ret != 0)
goto err_iomap;
/* find the IRQ for this unit (note, this relies on the init call to
* ensure no current IRQs pending
*/
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL)
dev_err(&pdev->dev, "cannot find IRQ\\n");
ret = -ENOENT;
goto err_iomap;
ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
pdev->name, i2c);
if (ret != 0)
dev_err(&pdev->dev, "cannot claim IRQ\\n");
goto err_iomap;
i2c->irq = res;
dev_dbg(&pdev->dev, "irq resource %p (%lu)\\n", res,
(unsigned long)res->start);
ret = i2c_add_adapter(&i2c->adap);
...
...
static int __init i2c_adap_s3c_init(void)
int ret;
ret = platform_driver_register(&s3c2410_i2c_driver);
if (ret == 0)
ret = platform_driver_register(&s3c2440_i2c_driver);
if (ret)
platform_driver_unregister(&s3c2410_i2c_driver);
return ret;
- 参考IIC设备驱动\\drivers\\i2c\\chips\\eeprom.c,在入口函数中i2c_add_driver添加driver设备,attach_adapter会加到适配器中去,在i2c_add_driver过程中总线为i2c_bus_type,会先把i2c_driver放入总线的dri链表,从adap链表中取出"适配器"并调用attach_adapter,而attach_adapter会调用i2c_probe,其中会使用master_xfer函数发S信号,发设备地址,如果能收到ACK信号,则会调用i2c_probe参数中的function函数,这其中我们可以注册字符设备驱动来完善IIC设备驱动程序,对于总线驱动程序中i2c_add_adapter函数会把adap(适配器)放入链表,一样会调用dri的attach_adapter然后调用master_xfer函数。
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int res;
...
if (driver->attach_adapter)
struct i2c_adapter *adapter;
list_for_each_entry(adapter, &adapters, list)
driver->attach_adapter(adapter);
mutex_unlock(&core_lists);
return 0;
static int eeprom_attach_adapter(struct i2c_adapter *adapter)
return i2c_probe(adapter, &addr_data, eeprom_detect);
static struct i2c_driver eeprom_driver =
.driver =
.name = "eeprom",
,
.id = I2C_DRIVERID_EEPROM,
.attach_adapter = eeprom_attach_adapter,
.detach_client = eeprom_detach_client,
;
...
static int __init eeprom_init(void)
return i2c_add_driver(&eeprom_driver);
i2c_probe函数过程如下:
- i2c_probe(adapter, &addr_data, eeprom_detect);
- i2c_probe_address // 发出S信号,发出设备地址(来自addr_data)
- i2c_smbus_xfer
- i2c_smbus_xfer_emulated
- i2c_transfer
- adap->algo->master_xfer // s3c24xx_i2c_xfer
- i2c_add_driver:
- 1.把i2c_driver放入链表;
- 2.从adap链表里取出适配器,调用drv的attch_adapter函数;
- 3.调用i2c_probe(adapter, &addr_data, function),master_xfer函数用adapter参数发信号,确定有无设备,有则调用function;
- 4.构造i2c_client结构体(.address .adapter(指向左边的adap) .driver(指向右边的dri) )
- i2c_add_adapter:
- 1.把adap适配器放入链表;
- 2.调用drv的attch_adapter函数;
- 3.同上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sU9Thfsj-1628932720648)(https://i.loli.net/2021/08/12/bEoTn5YNORfzluM.png)]
3、自己编写IIC驱动程序(Linux2.6)
## 3.1基本框架
以I2C设备(EEPROM芯片)AT24CXX为例,编写其驱动程序的基本步骤如下:
- 1 分配一个i2c_driver结构体
- 2 设置
- attach_adapter // 它直接调用 i2c_probe(adap, 设备地址, 发现这个设备后要调用的函数);
- detach_client // 卸载这个驱动后,如果之前发现能够支持的设备,则调用它来清理
- 3 注册:i2c_add_driver
参考设备m41t00.c中设备地址怎么写(由AT24CXX的数据手册查得其地址为0xA0),在normal_i2c参数中地址值只需要7位,若识别设备先加入打印语句测试:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
static unsigned short ignore[] = I2C_CLIENT_END ;
static unsigned short normal_addr[] = 0x50, I2C_CLIENT_END ; /* 地址值是7位,取0xA0的高7位 */
static struct i2c_client_address_data addr_data =
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
;
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
printk("at24cxx_detected.\\n");
return 0;
static int at24cxx_attach(struct i2c_adapter *adapter)
return i2c_probe(adapter, &addr_data, at24cxx_detect);
static int at24cxx_detach(struct i2c_client *client)
printk("at24cxx_detached.\\n");
return 0;
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver =
.driver =
.name = "at24cxx",
,
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
;
static int at24cxx_init(void)
i2c_add_driver(&at24cxx_driver);
return 0;
static void at24cxx_exit(void)
i2c_del_driver(&at24cxx_driver);
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
编译加载驱动,若出现以下打印语句,则表示框架正确!
# insmod at24cxx.ko
at24cxx_detected.
3.2 强制发现IIC设备
当设备还未发现后面才发现,我们可以强制发现设备,添加forces属性,参考i2c_probe函数,ANY_I2C_BUS为任何i2c总线,0x60是没有设备的地址,修改addr_data:
static unsigned short force_addr[] = ANY_I2C_BUS, 0x60, I2C_CLIENT_END;
static unsigned short * forces[] = force_addr, NULL;
static struct i2c_client_address_data addr_data =
.normal_i2c = ignore, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
.forces = forces, /* 强制认为存在这个设备 */
;
编译测试,得到一样的情况:
# insmod at24cxx.ko
at24cxx_detected.# rmmod at24cxx
3.3 添加client(设备)
上面在卸载IIC驱动的时候,没有打印出”at24cxx_detached.”。参考eeprom.c得知,需要发现设备后,在detect函数中分配/设置/注册client结构体,收发数据时会用到。
所以现在修改调用函数at24cxx_detect,并且在at24cxx_detach中卸载client,设备地址重新改回0x50:
static unsigned short ignore[] = I2C_CLIENT_END ;
static unsigned short normal_addr[] = 0x50, I2C_CLIENT_END ;
static struct i2c_driver at24cxx_driver; //声明一下
static struct i2c_client_address_data addr_data =
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
;
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
struct i2c_client *new_client;
printk("at24cxx_detected.\\n");
/* 构造一个i2c_client结构体: 以后收发数据时会用到它 */
new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
new_client->addr = address;
new_client->adapter = adapter;
new_client->driver = &at24cxx_driver;
strcpy(new_client->name, "at24cxx");
i2c_attach_client(new_client);
return 0;
static int at24cxx_detach(struct i2c_client *client)
printk("at24cxx_detached.\\n");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
编译测试,卸载驱动打印出信息
# insmod at24cxx.ko
at24cxx_detect
# rmmod at24cxx
at24cxx_detach
3.4 完善驱动程序
- 在调用函数at24cxx_detect()中注册字符设备,修改at24cxx_detect函数,在at24cxx_detach函数中卸载,并添加相应头文件:
#include <linux/fs.h>
static int major;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
return 0;
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
return 0;
static struct file_operations at24cxx_fops =
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
;
static struct class *cls;
struct i2c_client *at24cxx_client; //修改为全局变量
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
printk("at24cxx_detected.\\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx");
i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return 0;
static int at24cxx_detach(struct i2c_client *client)
printk("at24cxx_detached.\\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "at24cxx");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
- 利用i2c_transfer进行传输,i2c_transfer参数一位client中的适配器,以i2c_msg结构体传输,在i2c_msg结构体中含有数据传输三要素,其中addr为设备地址,完整代码如下:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
static unsigned short ignore[] = I2C_CLIENT_END ;
static unsigned short normal_addr[] = 0x50, I2C_CLIENT_END ; /* 地址值是7位 */
/* 改为0x60的话, 由于不存在设备地址为0x60的设备, 所以at24cxx_detect不被调用 */
//static unsigned short force_addr[] = ANY_I2C_BUS, 0x60, I2C_CLIENT_END;
//static unsigned short * forces[] = force_addr, NULL;
static struct i2c_client_address_data addr_data =
.normal_i2c = normal_addr, /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 强制认为存在这个设备 */
;
static struct i2c_driver at24cxx_driver;
static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;
static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 1)
return -EINVAL;
copy_from_user(&address, buf, 1);
/* 数据传输三要素: 源,目的,长度 */
/* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示写 */
/* 然后启动读操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 数据=1 byte */
msg[1].flags = I2C_M_RD; /* 表示读 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 2);/* 2代表2个数组项 */
if (ret == 2)
copy_to_user(buf, &data, 1);
return 1;
else
return -EIO;
static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
unsigned char val[2];
struct i2c_msg msg[1];
int ret;
/* address = buf[0]
* data = buf[1]
*/
if (size != 2)
return -EINVAL;
copy_from_user(val, buf, 2);
/* 数据传输三要素: 源,目的,长度 */
msg[0].addr = at24cxx_client->addr; /* 目的(从设备地址) */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+数据=2 byte */
msg[0].flags = 0; /* 表示写 */
ret = i2c_transfer(at24cxx_client->adapter, msg, 1);/* 1代表一个数组项 */
if (ret == 1) /* msg一项,返回1表示成功 */
return 2;
else
return -EIO;
static struct file_operations at24cxx_fops =
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
;
static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
printk("at24cxx_detect\\n");
/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx");
i2c_attach_client(at24cxx_client);
major = register_chrdev(0, "at24cxx", &at24cxx_fops);
cls = class_create(THIS_MODULE, "at24cxx");
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
return 0;
static int at24cxx_attach(struct i2c_adapter *adapter)
return i2c_probe(adapter, &addr_data, at24cxx_detect);
static int at24cxx_detach(struct i2c_client *client)
printk("at24cxx_detach\\n");
class_device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
unregister_chrdev(major, "at24cxx");
i2c_detach_client(client);
kfree(i2c_get_clientdata(client));
return 0;
/* 1. 分配一个i2c_driver结构体 */
/* 2. 设置i2c_driver结构体 */
static struct i2c_driver at24cxx_driver =
.driver =
.name = "at24cxx",
,
.attach_adapter = at24cxx_attach,
.detach_client = at24cxx_detach,
;
static int at24cxx_init(void)
i2c_add_driver(&at24cxx_driver);
return 0;
static void at24cxx_exit(void)
i2c_del_driver(&at24cxx_driver);
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");
3.5 测试程序
#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)
int fd;
unsigned char buf[2];
if ((argc != 3) && (argc != 4))
print_usage(argv[0]);
return -1;
fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
printf("cant open /dev/at24cxx\\n");
return -1;
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)
buf[0] = strtoul(argv[2], NULL, 0);
buf[1] = strtoul(argv[3], NULL, 0);
write(fd, buf, 2);
else
print_usage(argv[0]);
return -1;
return 0;
4、编写IIC驱动(Linux3.4.2)
Linux下IIC驱动编写,介绍IIC子系统框架的使用