学习笔记1-I2C架构篇
- 格式:docx
- 大小:49.16 KB
- 文档页数:7
I2C详解1、基本概念主机初始化发送,产⽣时钟信号和终⽌发送的器件从机被主机寻址的器件发送器发送数据到总线的器件接收器从总线接收数据的器件多主机同时有多于⼀个主机尝试控制总线但不破坏报⽂仲裁是⼀个在有多个主机同时尝试控制总线,但只允许其中⼀个控制总线并使报⽂不被破坏的过程同步两个或多个器件同步时钟信号的过程2、硬件结构每⼀个I2C总线器件内部的SDA、SCL引脚电路结构都是⼀样的,引脚的输出驱动与输⼊缓冲连在⼀起。
其中输出为漏极开路的场效应管、输⼊缓冲为⼀只⾼输⼊阻抗的同相器。
这种电路具有两个特点:(1)由于SDA、SCL 为漏极开路结构,借助于外部的上拉电阻实现了信号的“线与”逻辑;(2)引脚在输出信号的同时还将引脚上的电平进⾏检测,检测是否与刚才输出⼀致。
为“时钟同步”和“总线仲裁”提供硬件基础。
3、时钟同步如果从机希望主机降低传送速度可以通过将SCL主动拉低延长其低电平时间的⽅法来通知主机,当主机在准备下⼀次传送发现SCL的电平被拉低时就进⾏等待,直⾄从机完成操作并释放SCL线的控制控制权。
这样以来,主机实际上受到从机的时钟同步控制。
可见SCL 线上的低电平是由时钟低电平最长的器件决定;⾼电平的时间由⾼电平时间最短的器件决定。
这就是时钟同步,它解决了I2C总线的速度同步问题。
4、主机发送数据流程(1)主机在检测到总线为“空闲状态”(即SDA、SCL 线均为⾼电平)时,发送⼀个启动信号“S”,开始⼀次通信的开始(2)主机接着发送⼀个命令字节。
该字节由7 位的外围器件地址和1 位读写控制位R/W 组成(此时R/W=0)(3)相对应的从机收到命令字节后向主机回馈应答信号ACK(ACK=0)(4)主机收到从机的应答信号后开始发送第⼀个字节的数据(5)从机收到数据后返回⼀个应答信号ACK(6)主机收到应答信号后再发送下⼀个数据字节(7)当主机发送最后⼀个数据字节并收到从机的ACK 后,通过向从机发送⼀个停⽌信号P结束本次通信并释放总线。
I2C工作原理范文I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间进行通信。
它由Philips公司(现在是恩智浦半导体公司)于1982年推出,并已广泛应用于各种电子设备和嵌入式系统中。
I2C的工作原理如下:1.总线拓扑结构:I2C使用两根线进行通信,一根是串行数据线(SDA),另一根是串行时钟线(SCL)。
所有I2C设备都连接到同一条总线上,并且每个设备都有一个唯一的7位地址。
2. 主从模式:I2C通信分为主设备(Master)和从设备(Slave)。
主设备是发起通信的一方,负责控制总线上的通信。
从设备则是被动接收和响应来自主设备的命令或数据。
3. 起始信号和停止信号:I2C通信始于主设备发送一个起始信号(Start)和一个从设备地址。
起始信号告诉所有从设备,接下来的通信将是针对一些特定从设备的。
停止信号(Stop)则标志着一次通信的结束。
4.寄存器读写:主设备通过发送一个从设备地址和一个读/写位来指定是读取还是写入数据。
在写入模式下,主设备发送数据字节到从设备;在读取模式下,主设备请求从设备发送数据字节。
5.硬件应答:在每个字节的传输结束后,接收方(主设备或从设备)都会返回一个应答位。
如果接收方成功接收到了字节,则返回一个低电平的应答位;否则,返回一个高电平的非应答位。
6.时钟同步:I2C通信的时钟由主设备控制。
主设备在SCL线上产生时钟信号,而从设备则根据这个信号来同步自己的时钟。
总的来说,I2C通信是通过主设备发起的,它控制总线上的通信流程和时钟信号。
从设备根据主设备发送的命令或数据来执行相应的操作,并通过应答位来确认是否成功接收到数据。
这种通信协议适用于多个设备之间进行简单的数据交换和控制操作。
I2C的优点是可以同时连接多个设备,并且只需要两根线就能实现通信。
这大大减少了总线的复杂性和成本。
同时,I2C还具有可靠性高、速度适中、容错能力强等特点,使得它成为了很多电子设备中主要的串行通信协议之一总之,I2C是一种简单、灵活且可靠的串行通信协议。
内核中I2C体系结构1.总体框架 (2)1.1 I2C体系结构总体框图: (2)1.2 组成部分: (2)1.3 重要文件: (3)1.4 重要函数接口与结构体 (3)结构体: (3)函数: (3)各结构体的作用与它们之间的关系 (3)1.5 i2c子系统的初始化函数的执行先后顺序 (4)2.I2C核心 (4)2.1 i2c_init()函数 (4)2.1.1 i2c总线的注册 (5)2.1.2 i2c驱动注册 (6)2.1.3 i2c_init()函数总结 (6)3.总线驱动 (7)3.1 I2C platform_device初始化---smdk2440_machine_init() (7)3.1.1 s3c_i2c_set_platdata (8)3.1.2 i2c_register_board_info (9)3.1.3 platform_add_devices (10)3.2 I2C platform_drive适配器驱动初始化---i2c_adap_s3c_init() (10)3.3 platform_bus总线 (11)3.4总线设备和总线驱动初始化总结: (11)3.5 probe函数——adapter_device 和client_device的注册 (12)总结 (16)4,设备驱动 (17)4.1 i2c_dev_init() (17)4.1.1 register_chardev (17)4.1.2 i2c_add_drive 注册i2c_driver (i2cdev_driver) (18)总结i2c_dev_init() : (20)5,i2c子系统通信方法 (20)5.1 open设备/dev/i2c-0 (20)5.2 write (21)5.3 i2cdev_ioctl (27)5.2.1 I2C_SLA VE / I2C_SLA VE_FORCE (27)5.2.2 I2C_RDWR (27)6,I2C子系统总结: (29)附录: (30)说明:内核版本: 2.6.32 I2C设备: E2PROM (at24c02)/************************************************\跳过此内容不影响整体框架学习\************************************************/(标号)上下文相互提到1.总体框架1.1 I2C体系结构总体框图:从i2c驱动架构图中可以看出,linux内核对i2c架构抽象了一个叫核心层core的中间件,它分离了设备驱动device driver和硬件控制的实现细节(如操作i2c的寄存器),core层不但为上面的设备驱动提供封装后的内核注册函数,而且还为小面的硬件事件提供注册接口(也就是i2c 总线注册接口),可以说core层起到了承上启下的作用。
I2c是philips提出的外设总线.I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL.正因为这样,它方便了工程人员的布线.另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而I2C 是基于multi master机制.一同总线上可允许多个master.关于I2C协议的知识,这里不再赘述.可自行下载spec 阅读即可.二:I2C架构概述在linux中,I2C驱动架构如下所示:如上图所示,每一条I2C对应一个adapter.在kernel中,每一个adapter提供了一个描述的结构(struct i2c_adapter),也定义了adapter支持的操作(struct i2c_adapter).再通过i2c core层将i2c设备与i2c adapter关联起来.这个图只是提供了一个大概的框架.在下面的代码分析中,从下至上的来分析这个框架图.以下的代码分析是基于linux 2.6.26.分析的代码基本位于:linux-2.6.26.3/drivers/i2c/位置.I2C-mt6516.c三:adapter注册在kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.分别来看一下这两个函数的代码:int i2c_add_adapter(struct i2c_adapter *adapter){int id, res = 0;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh */res = idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id);mutex_unlock(&core_lock);if (res < 0) {if (res == -EAGAIN)goto retry;return res;}adapter->nr = id;return i2c_register_adapter(adapter);}在这里涉及到一个idr结构.idr结构本来是为了配合page cache中的radix tree而设计的.在这里我们只需要知道,它是一种高效的搜索树,且这个树预先存放了一些内存.避免在内存不够的时候出现问题.所在,在往idr中插入结构的时候,首先要调用idr_pre_get()为它预留足够的空闲内存,然后再调用idr_get_new_above()将结构插入idr中,该函数以参数的形式返回一个id.以后凭这个id就可以在idr中找到相对应的结构了.对这个数据结构操作不太理解的可以查阅本站<<linux文件系统之文件的读写>>中有关radix tree的分析.注意一下idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的参数的含义,它是将adapter结构插入到i2c_adapter_idr中,存放位置的id必须要大于或者等于__i2c_first_dynamic_bus_num,然后将对应的id号存放在adapter->nr中.调用i2c_register_adapter(adapter)对这个adapter进行进一步注册.看一下另外一人注册函数: i2c_add_numbered_adapter( ),如下所示:int i2c_add_numbered_adapter(struct i2c_adapter *adap){int id;int status;if (adap->nr & ~MAX_ID_MASK)return -EINVAL;retry:if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)return -ENOMEM;mutex_lock(&core_lock);/* "above" here means "above or equal to", sigh;* we need the "equal to" result to force the result*/status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);if (status == 0 && id != adap->nr) {status = -EBUSY;idr_remove(&i2c_adapter_idr, id);}mutex_unlock(&core_lock);if (status == -EAGAIN)goto retry;if (status == 0)status = i2c_register_adapter(adap);return status;}对比一下就知道差别了,在这里它已经指定好了adapter->nr了.如果分配的id不和指定的相等,便返回错误.过一步跟踪i2c_register_adapter().代码如下:static int i2c_register_adapter(struct i2c_adapter *adap){int res = 0, dummy;mutex_init(&adap->bus_lock);mutex_init(&adap->clist_lock);INIT_LIST_HEAD(&adap->clients);mutex_lock(&core_lock);/* Add the adapter to the driver core.* If the parent pointer is not set up,* we add this adapter to the host bus.*/if (adap->dev.parent == NULL) {adap->dev.parent = &platform_bus;pr_debug("I2C adapter driver [%s] forgot to specify ""physical device\n", adap->name);}sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);adap->dev.release = &i2c_adapter_dev_release;adap->dev.class = &i2c_adapter_class;res = device_register(&adap->dev);if (res)goto out_list;dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);/* create pre-declared device nodes for new-style drivers */if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);/* let legacy drivers scan this bus for matching devices */dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);out_unlock:mutex_unlock(&core_lock);return res;out_list:idr_remove(&i2c_adapter_idr, adap->nr);goto out_unlock;}首先对adapter和adapter中内嵌的struct device结构进行必须的初始化.之后将adapter内嵌的struct device注册. 在这里注意一下adapter->dev的初始化.它的类别为i2c_adapter_class,如果没有父结点,则将其父结点设为platform_bus.adapter->dev的名字为i2c + 总线号.测试一下:[eric@mochow i2c]$ cd /sys/class/i2c-adapter/[eric@mochow i2c-adapter]$ lsi2c-0可以看到,在我的PC上,有一个I2C adapter,看下详细信息:[eric@mochow i2c-adapter]$ tree.`-- i2c-0|-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0|-- name|-- subsystem -> ../../../class/i2c-adapter`-- uevent3 directories, 2 files可以看到,该adapter是一个PCI设备.继续往下看:之后,在注释中看到,有两种类型的driver,一种是new-style drivers,另外一种是legacy driversNew-style drivers是在2.6近版的kernel加入的.它们最主要的区别是在adapter和i2c driver的匹配上.3.1: new-style 形式的adapter注册对于第一种,也就是new-style drivers,将相关代码再次列出如下:if (adap->nr < __i2c_first_dynamic_bus_num)i2c_scan_static_board_info(adap);如果adap->nr 小于__i2c_first_dynamic_bus_num的话,就会进入到i2c_scan_static_board_info().结合我们之前分析的adapter的两种注册分式: i2c_add_adapter()所分得的总线号肯会不会小于__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()才有可能满足:(adap->nr < __i2c_first_dynamic_bus_num)mt6516_devs.c而且必须要调用i2c_register_board_info()将板子上的I2C设备信息预先注册时才会更改__i2c_first_dynamic_bus_num的值.在x86上只没有使用i2c_register_board_info()的.因此,x86平台上的分析可以忽略掉new-style driver的方式.不过,还是详细分析这种情况下.s首先看一下i2c_register_board_info(),如下:int __initi2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len){int status;mutex_lock(&__i2c_board_lock);/* dynamic bus numbers will be assigned after the last static one */if (busnum >= __i2c_first_dynamic_bus_num)__i2c_first_dynamic_bus_num = busnum + 1;for (status = 0; len; len--, info++) {struct i2c_devinfo *devinfo;devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);if (!devinfo) {pr_debug("i2c-core: can't register boardinfo!\n");status = -ENOMEM;break;}devinfo->busnum = busnum;devinfo->board_info = *info;list_add_tail(&devinfo->list, &__i2c_board_list);}mutex_unlock(&__i2c_board_lock);return status;}这个函数比较简单,struct i2c_board_info用来表示I2C设备的一些情况,比如所在的总线.名称,地址,中断号等.最后,这些信息会被存放到__i2c_board_list链表.跟踪i2c_scan_static_board_info():代码如下:static void i2c_scan_static_board_info(struct i2c_adapter *adapter){struct i2c_devinfo *devinfo;mutex_lock(&__i2c_board_lock);list_for_each_entry(devinfo, &__i2c_board_list, list) {if (devinfo->busnum == adapter->nr&& !i2c_new_device(adapter,&devinfo->board_info))printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",i2c_adapter_id(adapter),devinfo->board_info.addr);}mutex_unlock(&__i2c_board_lock);}该函数遍历挂在__i2c_board_list链表上面的i2c设备的信息,也就是我们在启动的时候指出的i2c设备的信息. 如果指定设备是位于adapter所在的I2C总线上,那么,就调用i2c_new_device().代码如下:struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info){struct i2c_client *client;int status;client = kzalloc(sizeof *client, GFP_KERNEL);if (!client)return NULL;client->adapter = adap;client->dev.platform_data = info->platform_data;device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);client->flags = info->flags & ~I2C_CLIENT_WAKE;client->addr = info->addr;client->irq = info->irq;strlcpy(client->name, info->type, sizeof(client->name));/* a new style driver may be bound to this device when we* return from this function, or any later moment (e.g. maybe* hotplugging will load the driver module). and the device* refcount model is the standard driver model one.*/status = i2c_attach_client(client);if (status < 0) {kfree(client);client = NULL;}return client;}我们又遇到了一个新的结构:struct i2c_client,不要被这个结构吓倒了,其实它就是一个嵌入struct device的I2C设备的封装.它和我们之前遇到的struct usb_device结构的作用是一样的.首先,在clinet里保存该设备的相关消息.特别的, client->adapter指向了它所在的adapter.特别的,clinet->name为info->name.也是指定好了的.一切初始化完成之后,便会调用i2c_attach_client( ).看这个函数的字面意思,是将clinet关联起来.到底怎么样关联呢?继续往下看:int i2c_attach_client(struct i2c_client *client){struct i2c_adapter *adapter = client->adapter;int res = 0;//初始化client内嵌的dev结构//父结点为所在的adapter,所在bus为i2c_bus_typeclient->dev.parent = &client->adapter->dev;client->dev.bus = &i2c_bus_type;//如果client已经指定了driver,将driver和内嵌的dev关联起来if (client->driver)client->dev.driver = &client->driver->driver;//指定了driver, 但不是newstyle的if (client->driver && !is_newstyle_driver(client->driver)) {client->dev.release = i2c_client_release;client->dev.uevent_suppress = 1;} elseclient->dev.release = i2c_client_dev_release;//clinet->dev的名称snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),"%d-%04x", i2c_adapter_id(adapter), client->addr);//将内嵌的dev注册res = device_register(&client->dev);if (res)goto out_err;//将clinet链到adapter->clients中mutex_lock(&adapter->clist_lock);list_add_tail(&client->list, &adapter->clients);mutex_unlock(&adapter->clist_lock);dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",client->name, client->dev.bus_id);//如果adapter->cleinet_reqister存在,就调用它if (adapter->client_register) {if (adapter->client_register(client)) {dev_dbg(&adapter->dev, "client_register ""failed for client [%s] at 0x%02x\n",client->name, client->addr);}}return 0;out_err:dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x ""(%d)\n", client->name, client->addr, res);return res;}参考上面添加的注释,应该很容易理解这段代码了,就不加详细分析了.这个函数的名字不是i2c_attach_client()么?怎么没看到它的关系过程呢?这是因为:在代码中设置了client->dev所在的bus为i2c_bus_type .因为只需要有bus为i2c_bus_type的driver 注册,就会产生probe了.这个过程在后面分析i2c driver的时候再来详细分析.3.2:legacy形式的adapter注册Legacy形式的adapter注册代码片段如下:dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,i2c_do_add_adapter);这段代码遍历挂在i2c_bus_type上的驱动,然后对每一个驱动和adapter调用i2c_do_add_adapter().代码如下:static int i2c_do_add_adapter(struct device_driver *d, void *data){struct i2c_driver *driver = to_i2c_driver(d);struct i2c_adapter *adap = data;if (driver->attach_adapter) {/* We ignore the return code; if it fails, too bad */driver->attach_adapter(adap);}return 0;}该函数很简单,就是调用driver的attach_adapter()接口.到此为止,adapter的注册已经分析完了.四:i2c driver注册在分析i2c driver的时候,有必要先分析一下i2c架构的初始化代码如下:static int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type);if (retval)return retval;retval = class_register(&i2c_adapter_class);if (retval)goto bus_err;retval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;return 0;class_err:class_unregister(&i2c_adapter_class);bus_err:bus_unregister(&i2c_bus_type);return retval;}subsys_initcall(i2c_init);很明显,i2c_init()会在系统初始化的时候被调用.在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.I2c_bus_type结构如下:static struct bus_type i2c_bus_type = {.name = "i2c",.dev_attrs = i2c_dev_attrs,.match = i2c_device_match,.uevent = i2c_device_uevent,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,.suspend = i2c_device_suspend,.resume = i2c_device_resume,};这个结构先放在这里吧,以后还会用到里面的信息的.从上面的初始化函数里也看到了,注册i2c driver的接口为i2c_add_driver().代码如下: static inline int i2c_add_driver(struct i2c_driver *driver){return i2c_register_driver(THIS_MODULE, driver);}继续跟踪:int i2c_register_driver(struct module *owner, struct i2c_driver *driver){int res;/* new style driver methods can't mix with legacy ones *///如果是一个newstyle的driver.但又定义了attach_adapter/detach_adapter.非法if (is_newstyle_driver(driver)) {if (driver->attach_adapter || driver->detach_adapter|| driver->detach_client) {printk(KERN_WARNING"i2c-core: driver [%s] is confused\n",driver->);return -EINVAL;}}/* add the driver to the list of i2c drivers in the driver core *///关联到i2c_bus_typesdriver->driver.owner = owner;driver->driver.bus = &i2c_bus_type;/* for new style drivers, when registration returns the driver core* will have called probe() for all matching-but-unbound devices.*///注册内嵌的driverres = driver_register(&driver->driver);if (res)return res;mutex_lock(&core_lock);pr_debug("i2c-core: driver [%s] registered\n", driver->);/* legacy drivers scan i2c busses directly *///遍历所有的adapter,对其都调用driver->attach_adapterif (driver->attach_adapter) {struct i2c_adapter *adapter;down(&i2c_adapter_class.sem);list_for_each_entry(adapter, &i2c_adapter_class.devices,dev.node) {driver->attach_adapter(adapter);}up(&i2c_adapter_class.sem);}mutex_unlock(&core_lock);return 0;}这里也有两种形式的区分,对于第一种,只需要将内嵌的driver注册就可以了,对于legacy的情况,对每一个adapter都调用driver->attach_adapter().现在,我们可以将adapter和i2c driver关联起来考虑一下了:1:如果是news style形式的,在注册adapter的时候,将它上面的i2c 设备转换成了struct client.struct client->dev->bus 又指定了和i2c driver同一个bus.因为,它们可以发生probe.2:如果是legacy形式,就直接找到对应的对象,调用driver->attach_adapter().五:i2c_bus_type的相关操作I2c_bus_type的操作主要存在于new-style形式的驱动中.接下来分析一下对应的probe过程:5.1:match过程分析Match对应的操作函数为i2c_device_match().代码如下static int i2c_device_match(struct device *dev, struct device_driver *drv){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(drv);/* make legacy i2c drivers bypass driver model probing entirely;* such drivers scan each i2c adapter/bus themselves.*/if (!is_newstyle_driver(driver))return 0;/* match on an id table if there is one */if (driver->id_table)return i2c_match_id(driver->id_table, client) != NULL;return 0;}如果该驱动不是一个new-style形式的.或者driver没有定义匹配的id_table.都会匹配失败.继续跟踪进i2c_match_id():static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,const struct i2c_client *client){while (id->name[0]) {if (strcmp(client->name, id->name) == 0)return id;id++;}return NULL;}由此可见.如果client的名字和driver->id_table[]中的名称匹配即为成功.5.2:probe过程分析Probe对应的函数为:i2c_device_probe()static int i2c_device_probe(struct device *dev){struct i2c_client *client = to_i2c_client(dev);struct i2c_driver *driver = to_i2c_driver(dev->driver);const struct i2c_device_id *id;int status;if (!driver->probe)return -ENODEV;client->driver = driver;dev_dbg(dev, "probe\n");if (driver->id_table)id = i2c_match_id(driver->id_table, client);elseid = NULL;status = driver->probe(client, id);if (status)client->driver = NULL;return status;}这个函数也很简单,就是将probe流程回溯到i2c driver的probe()六:其它的扩展分析完adapter和i2c driver的注册之后,好像整个架构也差不多了,其它,扩展的东西还有很多. 我们举一个legacy形式的例子,这个例子是在kernel中随便搜索出来的:在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函数为:static int __init ad7418_init(void){return i2c_add_driver(&ad7418_driver);}i2c_driver ad7418_driver结构如下:static struct i2c_driver ad7418_driver = {.driver = {.name = "ad7418",},.attach_adapter = ad7418_attach_adapter,.detach_client = ad7418_detach_client,};该结构中没有probe()函数,可以断定是一个legacy形式的驱动.这类驱动注册的时候,会调用driver的attach_adapter函数.在这里也就是ad7418_attach_adapter.这个函数代码如下:static int ad7418_attach_adapter(struct i2c_adapter *adapter){if (!(adapter->class & I2C_CLASS_HWMON))return 0;return i2c_probe(adapter, &addr_data, ad7418_detect);}在这里我们又遇到了一个i2c-core中的函数,i2c_probe().在分析这个函数之前,先来看下addr_data是什么?#define I2C_CLIENT_MODULE_PARM(var,desc) \static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; \static unsigned int var##_num; \module_param_array(var, short, &var##_num, 0); \MODULE_PARM_DESC(var,desc)#define I2C_CLIENT_MODULE_PARM_FORCE(name) \I2C_CLIENT_MODULE_PARM(force_##name, \"List of adapter,address pairs which are " \"unquestionably assumed to contain a `" \# name "' chip")#define I2C_CLIENT_INSMOD_COMMON \I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan " \"additionally"); \I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to " \"scan"); \static const struct i2c_client_address_data addr_data = { \.normal_i2c = normal_i2c, \.probe = probe, \.ignore = ignore, \.forces = forces, \}#define I2C_CLIENT_FORCE_TEXT \"List of adapter,address pairs to boldly assume to be present"由此可知道,addr_data中的三个成员都是模块参数.在加载模块的时候可以用参数的方式对其赋值.三个模块参数为别为probe,ignore,force.另外需要指出的是normal_i2c不能以模块参数的方式对其赋值,只能在驱动内部静态指定.从模块参数的模述看来, probe是指"List of adapter,address pairs to scan additionally"Ignore是指"List of adapter,address pairs not to scan "Force是指"List of adapter,address pairs to boldly assume to be present"事实上,它们里面的数据都是成对出现的.前面一部份表示所在的总线号,ANY_I2C_BUS表示任一总线.后一部份表示设备的地址.现在可以来跟踪i2c_probe()的代码了.如下:int i2c_probe(struct i2c_adapter *adapter,const struct i2c_client_address_data *address_data,int (*found_proc) (struct i2c_adapter *, int, int)){int i, err;int adap_id = i2c_adapter_id(adapter);/* Force entries are done first, and are not affected by ignoreentries *///先扫描force里面的信息,注意它是一个二级指针.ignore里的信息对它是无效的if (address_data->forces) {const unsigned short * const *forces = address_data->forces;int kind;for (kind = 0; forces[kind]; kind++) {for (i = 0; forces[kind][i] != I2C_CLIENT_END;i += 2) {if (forces[kind][i] == adap_id|| forces[kind][i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found force ""parameter for adapter %d, ""addr 0x%02x, kind %d\n",adap_id, forces[kind][i + 1],kind);err = i2c_probe_address(adapter,forces[kind][i + 1],kind, found_proc);if (err)return err;}}}}/* Stop here if we can't use SMBUS_QUICK *///如果adapter不支持quick.不能够遍历这个adapter上面的设备if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { if (address_data->probe[0] == I2C_CLIENT_END&& address_data->normal_i2c[0] == I2C_CLIENT_END) return 0;dev_warn(&adapter->dev, "SMBus Quick command not supported, ""can't probe for chips\n");return -1;}/* Probe entries are done second, and are not affected by ignore entries either *///遍历probe上面的信息.ignore上的信息也对它是没有影响的for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { if (address_data->probe[i] == adap_id|| address_data->probe[i] == ANY_I2C_BUS) {dev_dbg(&adapter->dev, "found probe parameter for ""adapter %d, addr 0x%02x\n", adap_id,address_data->probe[i + 1]);err = i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc);if (err)return err;}}/* Normal entries are done last, unless shadowed by an ignore entry */ //最后遍历normal_i2c上面的信息.它上面的信息不能在ignore中.for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { int j, ignore;ignore = 0;for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;j += 2) {if ((address_data->ignore[j] == adap_id ||address_data->ignore[j] == ANY_I2C_BUS)&& address_data->ignore[j + 1]== address_data->normal_i2c[i]) {dev_dbg(&adapter->dev, "found ignore ""parameter for adapter %d, ""addr 0x%02x\n", adap_id,address_data->ignore[j + 1]);ignore = 1;break;}}if (ignore)continue;dev_dbg(&adapter->dev, "found normal entry for adapter %d, ""addr 0x%02x\n", adap_id,address_data->normal_i2c[i]);err = i2c_probe_address(adapter, address_data->normal_i2c[i],-1, found_proc);if (err)return err;}return 0;}这段代码很简单,结合代码上面添加的注释应该很好理解.如果匹配成功,则会调用i2c_probe_address ().这个函数代码如下: static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,int (*found_proc) (struct i2c_adapter *, int, int)){int err;/* Make sure the address is valid *///地址小于0x03或者大于0x77都是不合法的if (addr < 0x03 || addr > 0x77) {dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",addr);return -EINVAL;}/* Skip if already in use *///adapter上已经有这个设备了if (i2c_check_addr(adapter, addr))return 0;/* Make sure there is something at this address, unless forced *///如果kind小于0.检查adapter上是否有这个设备if (kind < 0) {if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) < 0)return 0;/* prevent 24RF08 corruption */if ((addr & ~0x0f) == 0x50)i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL);}/* Finally call the custom detection function *///调用回调函数err = found_proc(adapter, addr, kind);/* -ENODEV can be returned if there is a chip at the given addressbut it isn't supported by this chip driver. We catch it here asthis isn't an error. */if (err == -ENODEV)err = 0;if (err)dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)\n",addr, err);return err;}首先,对传入的参数进行一系列的合法性检查.另外,如果该adapter上已经有了这个地址的设备了.也会返回失败.所有adapter 下面的设备都是以adapter->dev为父结点的.因此只需要遍历adapter->dev下面的子设备就可以得到当前地址是不是被占用了.如果kind < 0.还得要adapter检查该总线是否有这个地址的设备.方法是向这个地址发送一个Read的Quick请求.如果该地址有应答,则说明这个地址上有这个设备.另外还有一种情况是在24RF08设备的特例.如果adapter上确实有这个设备,就会调用驱动调用时的回调函数.在上面涉及到了IIC的传输方式,有疑问的可以参考intel ICH5手册的有关smbus部份.跟踪i2c_smbus_xfer().代码如下:s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){s32 res;flags &= I2C_M_TEN | I2C_CLIENT_PEC;if (adapter->algo->smbus_xfer) {mutex_lock(&adapter->bus_lock);res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,command,size,data);mutex_unlock(&adapter->bus_lock);} elseres = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data);return res;}如果adapter有smbus_xfer()函数,则直接调用它发送,否则,也就是在adapter不支持smbus协议的情况下,调用i2c_smbus_xfer_emulated()继续处理.跟进i2c_smbus_xfer_emulated().代码如下:static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,unsigned short flags,char read_write, u8 command, int size,union i2c_smbus_data * data){/* So we need to generate a series of msgs. In the case of writing, weneed to use only one message; when reading, we need two. We initializemost things with sane defaults, to keep the code below somewhatsimpler. *///写操作只会进行一次交互,而读操作,有时会有两次操作.//因为有时候读操作要先写command,再从总线上读数据//在这里为了代码的简洁.使用了两个缓存区,将两种情况统一起来.unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];//一般来说,读操作要交互两次.例外的情况我们在下面会接着分析int num = read_write == I2C_SMBUS_READ?2:1;//与设备交互的数据,一般在msg[0]存放写入设备的信息,在msb[1]里存放接收到的//信息.不过也有例外的//msg[2]的初始化,默认发送缓存区占一个字节,无接收缓存struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },{ addr, flags | I2C_M_RD, 0, msgbuf1 }};int i;u8 partial_pec = 0;//将要发送的信息copy到发送缓存区的第一字节msgbuf0[0] = command;switch(size) {//quick类型的,其它并不传输有效数据,只是将地址写到总线上,等待应答即可//所以将发送缓存区长度置为0 .再根据读/写操作,调整msg[0]的标志位//这类传输只需要一次总线交互case I2C_SMBUS_QUICK:msg[0].len = 0;/* Special case: The read/write field is used as data */msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;num = 1;break;case I2C_SMBUS_BYTE://BYTE类型指一次写和读只有一个字节.这种情况下,读和写都只会交互一次//这种类型的读有例外,它读取出来的数据不是放在msg[1]中的,而是存放在msg[0]if (read_write == I2C_SMBUS_READ) {/* Special case: only a read! */msg[0].flags = I2C_M_RD | flags;num = 1;}break;case I2C_SMBUS_BYTE_DATA://Byte_Data是指命令+数据的传输形式.在这种情况下,写只需要一次交互,读却要两次//第一次将command写到总线上,第二次要转换方向.要将设备地址和read标志写入总线.//应回答之后再进行read操作//写操作占两字节,分别是command+data.读操作的有效数据只有一个字节//交互次数用初始化值就可以了if (read_write == I2C_SMBUS_READ)msg[1].len = 1;else {msg[0].len = 2;msgbuf0[1] = data->byte;}break;case I2C_SMBUS_WORD_DATA://Word_Data是指命令+双字节的形式.这种情况跟Byte_Data的情况类似//两者相比只是交互的数据大小不同if (read_write == I2C_SMBUS_READ)msg[1].len = 2;else {msg[0].len=3;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;}break;case I2C_SMBUS_PROC_CALL://Proc_Call的方式与write 的Word_Data相似,只不过写完Word_Data之后,要等待它的应答//应该它需要交互两次,一次写一次读num = 2; /* Special case */read_write = I2C_SMBUS_READ;msg[0].len = 3;msg[1].len = 2;msgbuf0[1] = data->word & 0xff;msgbuf0[2] = data->word >> 8;break;case I2C_SMBUS_BLOCK_DATA://Block_Data:指command+N段数据的情况.//如果是读操作,它首先要写command到总线,然后再读N段数据.要写的command已经//放在msg[0]了.现在只需要将msg[1]的标志置I2C_M_RECV_LEN位,msg[1]有效长度为1字节.因为//adapter驱动会处理好的.现在现在还不知道要传多少段数据.//对于写的情况:msg[1]照例不需要.将要写的数据全部都放到msb[0]中.相应的也要更新//msg[0]中的缓存区长度if (read_write == I2C_SMBUS_READ) {msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */} else {//data->block[0]表示后面有多少段数据.总长度要加2是因为command+count+N段数据msg[0].len = data->block[0] + 2;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {dev_err(&adapter->dev, "smbus_access called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];}break;case I2C_SMBUS_BLOCK_PROC_CALL://Proc_Call:表示写完Block_Data之后,要等它的应答消息它和Block_Data相比,只是多了一部份应答而已num = 2; /* Another special case */read_write = I2C_SMBUS_READ;if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {dev_err(&adapter->dev, "%s called with invalid ""block proc call size (%d)\n", __func__,data->block[0]);return -1;}msg[0].len = data->block[0] + 2;for (i = 1; i < msg[0].len; i++)msgbuf0[i] = data->block[i-1];msg[1].flags |= I2C_M_RECV_LEN;msg[1].len = 1; /* block length will be added bythe underlying bus driver */break;case I2C_SMBUS_I2C_BLOCK_DATA://I2c Block_Data与Block_Data相似,只不过read的时候,数据长度是预先定义好了的.另外//与Block_Data相比,中间不需要传输Count字段.(Count表示数据段数目)if (read_write == I2C_SMBUS_READ) {msg[1].len = data->block[0];} else {msg[0].len = data->block[0] + 1;if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with ""invalid block write size (%d)\n",data->block[0]);return -1;}for (i = 1; i <= data->block[0]; i++)msgbuf0[i] = data->block[i];}break;default:dev_err(&adapter->dev, "smbus_access called with invalid size (%d)\n", size);return -1;}//如果启用了PEC.Quick和I2c Block_Data是不支持PEC的i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK&& size != I2C_SMBUS_I2C_BLOCK_DATA);if (i) {/* Compute PEC if first message is a write *///如果第一个操作是写操作if (!(msg[0].flags & I2C_M_RD)) {//如果只是写操作if (num == 1) /* Write only *///如果只有写操作,写缓存区要扩充一个字节,用来存放计算出来的PECi2c_smbus_add_pec(&msg[0]);else /* Write followed by read *///如果后面还有读操作,先计算前面写部份的PEC(注意这种情况下不需要//扩充写缓存区,因为不需要发送PEC.只会接收到PEC)partial_pec = i2c_smbus_msg_pec(0, &msg[0]);}/* Ask for PEC if last message is a read *///如果最后一次是读消息.还要接收到来自slave的PEC.所以接收缓存区要扩充一个字节if (msg[num-1].flags & I2C_M_RD)msg[num-1].len++;}if (i2c_transfer(adapter, msg, num) < 0)return -1;。
1 概述I2C总线以2根信号线(数据线SDA,时钟线SCL)实现双向同步数据传,并且可以连接到总线上的任何一个器件作为一个发送器或接收器。
执行数据传输时可以当作主机或从机。
发送器:传送中发送数据到总线的器件接收器:传送中从总线接收数据的器件主机:用来初始化发送、产生时钟信号和终止发送的器件,可是发送器或接收器从机:被主机寻址的器件,也可以作为发送器或接收器LPC1700有三个接口:I2C0/1/2。
I2C0为标准I2C总线接口(开漏引脚),该接口支持I2C规范中所叙述的功能,运行速度高达1MHz。
支持多主机操作,并允许挂接在I2C总线上运行器件在退出I2C总线功能时掉电;而I2C1和I2C2使用标准I/O引脚,专用于单主机I2C总线,不支持挂接在I2C总线上的运行器件在退出I2C总线功能时掉电,也不支持多主机I2C操作。
三个接口在标准模式下,总线数据传输的速度为0到100Kbit/s;高速模式下的为0到400Kbit/s;总线速率越高,总线上拉电阻要越小。
注意的是只有I2C0总线支持快速plus模式,速度可达1Mbit/s,可通过设置CPADCFG寄存器里的SDADRV0和SCLDRV0来实现。
2 总线特性:标准的I2C总线接口;可配置为主机、从机或主/从机;可编程时钟能够实现通用速率控制;主从之间双向数据传输;多主机总线;通信速率高达1MHZ(快速模式);支持监控模式;只能基于版内通信;3 传输协议(1)寻址字节主机产生起始信号后,发送的第一个字节为寻址字节。
前7位为从机地址,最低位决定报文方向:0表示主机写信息到从机,1表示主机读从机中的信息。
(2)传输格式主机产生起始信号后,发送一个寻址字节,收到应答后紧跟着的就是数据传输,数据传输一般由主机产生的停止位终止。
如果主机仍希望在总线上通讯,它可以产生重复起始信号和寻址另一个从机,而不是首先产生一个停止信号。
4 基本配置利用以下寄存器来配置I2C0/1/2接口:(1)电源:在寄存器PCONP中置位PCI2C0/1/2;(2)时钟:在寄存器PCLK_SEL0中选择PCLK_I2C0;在寄存器PCLK_SEL1中选择PCLK_I2C1/2(3)引脚:通过寄存器PINSEL使能I2C0引脚和选择I2C1/2引脚。
I2C总线——总结I2C总线是一种串行数据通信协议,用于连接集成电路之间进行通信。
它由Phillips公司于1982年首次提出,并在随后的几十年中得到广泛应用和发展。
I2C总线采用两根线(SDA和SCL)进行通信,具有简单、高效的特点,适用于较短距离的通信。
首先,I2C总线的架构包括两个主要组成部分:主设备和从设备。
主设备通常是微控制器或其他控制器,负责发起和控制通信。
从设备是主设备的外围设备,例如传感器、电池芯片、存储器等。
主设备通过发送信号来控制从设备,并接收从设备发送的响应信号。
在I2C通信中,数据以字节为单位传输,每个字节都包含8位。
通信的起始由主设备发起,并发送设备地址标识。
从设备必须匹配该地址才能进入通信状态。
在通信过程中,主设备发送读取或写入指令,然后发送或接收数据。
每个数据字节都由从设备发送确认信号,以确保数据的准确接收。
除了基本的读写操作,I2C总线还支持一些高级功能,例如时钟同步、主从模式切换和热插拔功能。
这些功能使得I2C总线适用于各种应用场景,包括电子设备、通信系统、工业控制和汽车电子等。
然而,尽管I2C总线具有许多优点,但也存在一些限制和挑战。
首先,I2C总线在传输速度方面不如其他通信协议(如SPI和CAN)。
其次,长距离传输可能受到电压下降、信号失真和干扰等因素的影响。
此外,I2C总线的主设备需要额外的控制逻辑和处理能力,这可能增加系统的复杂性和成本。
综上所述,I2C总线是一种非常常用和实用的串行通信协议。
它具有简单、高效的特点,适用于较短距离的设备间通信。
通过多主机配置和高级功能支持,I2C总线可以满足各种应用的需求。
然而,需要根据具体的应用场景和要求来选择合适的通信协议,以确保系统的性能和可靠性。
I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在芯片之间进行数据传输。
它由飞利浦半导体(现在的恩智浦半导体)于1982年开发,并广泛应用于各种电子设备中。
I2C具有简单、高效和可靠的特点,成为众多芯片和模块之间常用的通信接口之一。
本文将详细介绍I2C的基本工作原理。
一、总线架构I2C采用了主从结构的总线架构,其中主设备(Master)负责发起数据传输请求,而从设备(Slave)则在接收到请求后进行响应。
一个I2C总线上可以连接多个从设备,每个从设备都有一个唯一的地址。
主设备通过发送起始信号(Start)来启动通信,然后选择要与之通信的从设备地址,最后发送停止信号(Stop)结束通信。
二、物理层I2C使用双线制进行数据传输,包括数据线(SDA)和时钟线(SCL)。
数据线上的信号是双向的,用于传输数据。
时钟线则由主设备控制,用于同步数据传输。
三、起始和停止信号I2C通信以起始信号(Start)和停止信号(Stop)来标识通信的开始和结束。
起始信号由主设备产生,它表示将要发起一次新的通信。
停止信号同样由主设备产生,表示一次通信的结束。
四、数据传输格式I2C采用了基于字节的数据传输格式。
每个字节都由8位二进制数据组成,包括7位数据位和1位数据方向位。
数据方向位为0表示发送数据,为1表示接收数据。
在每个字节的传输过程中,都会先发送数据方向位,然后再发送数据位。
五、时钟同步I2C使用时钟同步机制来确保通信的准确性。
时钟线由主设备产生,并控制整个数据传输过程的时序。
在每个时钟周期中,数据线上的数据必须稳定,并且只有在时钟线为低电平时才能改变。
六、地址传输在I2C通信中,每个从设备都有一个唯一的7位地址。
主设备通过发送地址来选择要与之通信的从设备。
地址由8个位组成,最高位是固定的0或1,用于表示读(1)或写(0)操作。
其余的7位用于指定从设备的地址。
七、数据传输流程I2C通信的数据传输流程如下:1. 主设备发送起始信号(Start)。
I2C通信原理及程序详细讲解I2C(Inter-Integrated Circuit)是一种串行通信协议,常用于连接微控制器、传感器和其他外部设备。
I2C通信协议由荷兰飞利浦公司于1982年开发,它使用两根信号线(SDA和SCL)进行数据传输。
I2C通信协议采用主从结构,一个主设备(如微控制器)可以连接多个从设备(如传感器)。
主从设备之间通过SDA和SCL线进行数据传输。
SDA线是双向数据线,用于传输数据,SCL线是时钟线,用于同步数据传输。
I2C通信协议中,设备的地址是一个重要概念。
每个设备都有一个唯一的地址,通过该地址可以选择和通信特定的设备。
地址由7个位组成,其中最高位是固定的,并取决于设备是主设备还是从设备。
如果最高位为0,则表示该设备是主设备;如果最高位为1,则表示该设备是从设备。
通过以下步骤,让我们详细了解如何在I2C总线上进行通信。
1.初始化I2C总线:在程序开始时,需要初始化I2C总线。
这通常包括初始化SDA和SCL引脚,设置时钟频率等。
具体的初始化步骤取决于使用的硬件和软件环境。
2.发送开始信号:开始信号表示I2C数据传输的开始。
它由主设备发送,并且SDA线从高电平转为低电平时发出。
发送开始信号后,SDA线上的数据将被解释为地址数据。
3.发送设备地址:主设备发送一个包含设备地址和读/写位(R/W)的数据字节。
设备地址是唯一的,并且由主设备选择。
读/写位指示从设备是要读取数据还是写入数据。
4.等待从设备响应:主设备发送设备地址后,会等待从设备的响应。
从设备将响应一个应答位(ACK)来确认地址接收成功。
如果收到ACK位,则继续进行下一步,否则可能是设备未连接或通信错误。
5.发送数据:主设备发送数据给从设备。
数据可以是命令、配置或实际数据,具体取决于应用场景。
发送数据的方式是将每个数据字节传输到SDA线上,并在每个数据字节后发送一个ACK位。
6.接收数据:从设备将数据发送给主设备。
数据可以是传感器读数、存储器数据等。
学习笔记1: Linux设备驱动程序之I2C 基础架构篇I2C (Inter-Integrated Circuit)总线是一种由PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。
I2C 总线最主要的优点是其简单性和有效性。
由于接口直接在组件之上,因此I2C 总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。
I2C 总线概述I2C 总线是由数据线SDA 和时钟SCL 构成的串行总线,可发送和接收数据,每个器件都有一个惟一的地址识别。
I2C 规程运用主/从双向通讯。
器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。
主器件和从器件都可以工作于接收和发送状态。
总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。
SDA线上的数据状态仅在SCL 为低电平的期间才能改变,SCL 为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而 I2C 是基于multi master机制.一同总线上可允许多个master.I2C 总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为低电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC 在接收到8bit 数据后,向发送数据的IC 发出特定的低电平脉冲,表示已收到数据。
CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。
若未收到应答信号,由判断为受控单元出现故障。
Linux 的I2C 驱动架构Linux 中I2C 总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。
i2c通信的详细讲解I2C(全称Inter-IntegratedCircuit)是一种基于两线式总线系统,可以利用它让一台机器和另一台机器或者一个器件与另一个器件之间建立通信连接。
它由一个多晶片系统(Multi-Chip System)所创造出来,可以减少实现多晶片系统的内部连接的数量,从而减少印制电路板的体积。
它允许多达127个从设备以最低物理开销(Low Physical Overhead)与一个主设备连接,具有低成本和高可靠性,现已成为很多类型的微控制器(Microcontroller)和处理器(Processors)的标准总线,应用非常广泛。
1. I2C概述I2C从最初的Philips经过20多年的发展,已经成为一种横跨多个行业的解决方案。
它可以在不同的系统上提供高性能片上系统(System-on-a-Chip,SoC)间的通信和协调,支持多达127的电子设备连接。
主要由两条信号线,称为SDA(Data Line)和SCL(Clock Line)构成,它们分别连接电子设备的数据和时钟,每个设备还有一个物理上的地址,用于标识彼此来进行通信。
I2C总线具有速度快、低功耗、简单易用等优点,现在已经被应用在系统和高速处理器、传感器、可编程逻辑器件(PLD)、多媒体设备、电源管理、液晶/LED等多个行业中。
2. I2C通信原理I2C通信可以说是一种异步通信方式,它是一种时钟控制的异步通信方式,发送一个字节,都要经过下面几个步骤:(1)发送起始条件:S(Start):该条件由SDA和SCL电平组合构成,SDA在SCL电平为高时从高电平变低电平。
(2)发送设备地址:在I2C总线上,设备都会有自己的地址,每个设备可以收发多个字节,这样就可以确定设备的身份。
(3)发送控制位:也称命令位,它用于确定主设备发送的是某种命令,比如写入数据,读取数据,还是其它的控制信息。
(4)发送数据:数据由SDA高低电平传输,而SDA电平的变化必须在SCL的上升沿才能有效。
I2C总线入门1)最近学习51单片机,学到A/D,D/A转换的时候发现我板子上的转换芯片不是书上所讲的ADC0804和DAC0832而是PCF8591T,看了一下它的数据手册,发现它并不是书上所说的并行传输数据,是使用I2C 总线传输的。
搞了两天才搞懂,写出来给大家分享一下,不足之处请务必不吝指出。
以上是I2C总线的简单介绍。
就比如说AT24C02存储芯片,和PCF8591数模模数转换芯片都支持I2C端口。
(如下图)2)接下来看如何使用I2C总线进行通信以上是I2C总线通信的格式。
由上图可以看出进行通信需要以下几个步骤a.初始化I2C总线就是把SDA和SCL都变成高电平。
void init() //初始化{SDA=1;delay();SCL=1;delay();}delay()为延时函数void delay() //延时4-5个微秒{;;}b.发送起始信号就是保持SCL为高电平,而SDA从高电平降为低电平(这是I2C总线的规定,别问我为什么)void start()//起始信号{SDA=1;delay();SCL=1;delay();SDA=0;delay();}c.发送地址字(芯片的硬件地址)(8591的数据手册)前四位对同一种芯片来说是固定的,不同的芯片之间不同。
就像pcf8591是1001而at24c02是1010接下来三位A0,A1,A2是可编程的三个地址位,这里说说的编程并不是通过软件编程,而是把A0,A1,A2三个引脚接不同的电压来确定数值。
接VCC 表示1,接GND表示0。
为什么要有这三个呢?因为有可能你在I2C总线上“并联”了不止一个相同的元件(比如说接了三个8591),那你如何来分辨你要操作的是哪一个芯片呢,就是通过设置A0,A1,A2的数值,来区别。
可编程的地址一个有三位,也就是说最多可以接8个相同的芯片在同一个I2C总线上。
最后一位是读/写位,1为读,0为写。
@如何写数据写数据只需要按照时序图1.先将SCL置0(只有它为0的时候SDA才允许变化)2.改变SDA是数值(就是你当前要穿的一位是0还是1)3.把SCL置1(此时芯片就会读取总线上的数据)下面是代码#define uchar unsigned char#define uint unsigned intvoid write_byte(uchar date) //写一字节数据{uchar i,temp;temp=date;for(i=0;i<8;i++){temp=temp<<1; //左移一位移出的一位在CY中SCL=0; //只有在scl=0时sda能变化值delay();SDA=CY;delay();SCL=1;delay();}SCL=0;delay();SDA=1;delay();}发送地址的时候只需把地址传给该函数即可。
I2C总线入门1)最近学习51单片机,学到A/D,D/A转换的时候发现我板子上的转换芯片不是书上所讲的ADC0804和DAC0832而是PCF8591T,看了一下它的数据手册,发现它并不是书上所说的并行传输数据,是使用 I2C 总线传输的。
搞了两天才搞懂,写出来给大家分享一下,不足之处请务必不吝指出。
以上是I2C总线的简单介绍。
就比如说AT24C02存储芯片,和PCF8591数模模数转换芯片都支持I2C端口。
(如下图)2)接下来看如何使用I2C总线进行通信以上是I2C总线通信的格式。
由上图可以看出进行通信需要以下几个步骤a.初始化I2C总线就是把SDA和SCL都变成高电平。
void init() //初始化{S DA=1;d elay();S CL=1;d elay();}delay()为延时函数void delay() //延时4-5个微秒{;;}b.发送起始信号就是保持SCL为高电平,而SDA从高电平降为低电平(这是I2C总线的规定,别问我为什么)void start()//起始信号{SDA=1;delay();SCL=1;delay();SDA=0;delay();}c.发送地址字(芯片的硬件地址)(8591的数据手册)前四位对同一种芯片来说是固定的,不同的芯片之间不同。
就像pcf8591是1001而at24c02是1010接下来三位A0,A1,A2是可编程的三个地址位,这里说说的编程并不是通过软件编程,而是把A0,A1,A2三个引脚接不同的电压来确定数值。
接VCC表示1,接GND表示0。
为什么要有这三个呢?因为有可能你在I2C总线上“并联”了不止一个相同的元件(比如说接了三个8591),那你如何来分辨你要操作的是哪一个芯片呢,就是通过设置A0,A1,A2的数值,来区别。
可编程的地址一个有三位,也就是说最多可以接8个相同的芯片在同一个I2C总线上。
最后一位是读/写位,1为读,0为写。
@如何写数据写数据只需要按照时序图1.先将SCL置0(只有它为0的时候SDA才允许变化)2.改变SDA是数值(就是你当前要穿的一位是0还是1)3.把SCL置1(此时芯片就会读取总线上的数据)下面是代码#define uchar unsigned char#define uint unsigned intvoid write_byte(uchar date) //写一字节数据{uchar i,temp;temp=date;for(i=0;i<8;i++){temp=temp<<1; //左移一位移出的一位在CY中SCL=0; //只有在scl=0时sda能变化值delay();SDA=CY;delay();SCL=1;delay();}SCL=0;delay();SDA=1;delay();}发送地址的时候只需把地址传给该函数即可。
特点3线(SDA,SCL,GND),同步,半双工,多主机,短距离。
连接到相同总线的IC 数量只受到总线的最大电容400pF 限制,并可以通过中继器增加器件数量。
四种工作模式:Master Transmitter;Master Receiver;Slave Transmitter;Slave Receiver。
三种速度模式低速模式(标准模式,S):100kbps;快速模式(F):400kbps;高速模式(Hs):3.4Mbps。
总线时序各种状态:起始(S)/重启(Sr),地址(ADDR),读/写(R/W),响应(ACK),数据(DA TA),结束(P)。
起始/重启:SCL为高时,SDA由高到低结束:SCL为高时,SDA由低到高数据:位传输:SDA 线上的数据必须在时钟的高电平期间保持稳定,数据线的高或低电平状态只有在SCL 线的时钟信号是低电平时才能改变。
字节传输:MSB First,每个字节必须为8 位,每次传输可以发送的字节数量不受限制。
每个字节后必须跟一个响应位。
响应:响应位的产生原则是谁接收谁响应。
在响应的时钟脉冲期间,接收器必须进行响应。
将SDA 线拉低以产生ACK信号,表明接受输入;拉高表示NACK,表明拒绝输入。
主机在传输完最后一个字节后,可以不产生ACK信号而直接产生停止信号。
时钟同步与总线仲裁:当总线上存在多主机同时工作的时候,就需要进行时钟同步和总线仲裁。
同步的目的是让总线上工作的器件按照一致的时钟进行数据收发。
同步是在SCL上实现的,总线的SCL高电平时间长度由所有工作主机中高电平时间最短的一方决定,而低电平时间由低电平时间最长一方决定。
任何时间上只能有一个主机获得总线控制权,总线的仲裁是在SDA上实现的。
在其他主机发送低电平时,发送高电平的主机将因失去总线控制权而需要断开它的数据输出级,因为总线上的电平与它自己的发出的电平不相同。
失去总线控制权的主机需要立刻转入Slave模式,以防当前被寻址器件就是它。
ESP32学习笔记(四)之I2C总线上⼀篇帖⼦我们了解了⼀下ESP32运⾏多任务的操作以及现象,其实也就是⼀个实时操作系统。
那么,这篇帖⼦我们就结合“LM75a”温度传感器来学习⼀下ESP32的IIC总线吧。
⾸先我们通过esp32_technical_referance发现,esp32的I2C总线具有以下特性• ⽀持主机模式以及从机模式• ⽀持多主机多从机通信• ⽀持标准模式(100 kbit/s)• ⽀持快速模式(400 kbit/s)• ⽀持7-bit 以及10-bit 寻址• ⽀持关闭SCL 时钟实现连续数据传输• ⽀持可编程数字噪声滤波功能ESP32的I2C控制器可以⼯作于Master 模式或者Slave 模式,我们这⾥只以Master模式为例。
上图为ESP32 I2C Master模式的基本架构,包含了32X8 Bit的RAM,16 个命令寄存器(cmd0 ~ cmd15) 以及⼀个CMD_Controller,2个总线控制器,2个总线滤波器以及⼀个数据移位寄存器。
看起来挺复杂的样⼦,其实实际操作起来,只有命令寄存器跟我们直接接触得最多吧,个⼈感觉,嗯。
具体跟寄存器相关的就介绍到这⾥,感兴趣的朋友可以⾃⾏查看esp32_technical_referance,那么接下来就该谈怎么操作以及实验了。
⾸先,调⽤库函数,初始化I2C总线(类似于stm32那样)这个初始化结构体看起来也挺简单初始化模式:MasterIO⼝引脚:ESP32具有两个I2C,分别映射到的引脚为18 19 25 26。
我们这⾥选择I2C0,也就是I2C_NUM_0,对应18 19号引脚。
总线上拉使能:enable总线速度 :100K调⽤初始化i2c_param_config()函数把初始化好的结构体参数传回去,这⾥特别要注意的是第⼀个参数选择的是I2C0还是选择I2C1,前者对应I2C_NUM_0,后者对应I2C_NUM_0。
linux驱动学习i2c驱动架构davinc dm368 i2c驱动分析但是Linux的i2c驱动体系结构却有相当的复杂度,不管是叫linux i2c驱动还是单片机i2c驱动,其根本还是操作soc芯片内部的i2c模块(也叫i2c adapter)(读写i2c相关的寄存器)来产生start、stop还有ack信号而已。
linux设备驱动到底复杂在什么地方?假设soc芯片dm368有两个i2c adapter(368内部真正只有一个i2c模块):i2c_adapter1,i2c_adapter1;然后外部有三个i2c接口的设备i2c_device1,i2c_device2,i2c_device3。
现在要求在裸机下写出他们的驱动函数。
那么肯定要写出6个不同的驱动函数:i2c_adapter1_ReadWrite_i2c_device1();i2c_adapter1_ReadWrite_i2c_device2()i2c_adapter1_ReadWrite_i2c_device3()i2c_adapter2_ReadWrite_i2c_device1()i2c_adapter2_ReadWrite_i2c_device2()i2c_adapter2_ReadWrite_i2c_device3()设想一共有m个i2c adapter和n个外设i2c device,那么将需要m*n个驱动。
并且这m*n个驱动程序必要会有很大部分重复的代码,而且不利于驱动程序的移植。
如果采用adapter和device分离的思想来写这样的驱动会是怎样呢?图1这样分离之后,只需要m+n个驱动,而且Adapter和Device的几乎没有耦合性,增加一个Adapter或者device并不会影响其余的驱动。
这就是分离思想带来的好处。
除此之外,linux虽然是C写的,但是大量使用了面向对象的变成方法(可以理解为分层的思想),仅仅分离细想和分层思想的引入,就大大增加了linux设备驱动的复杂度。
学习笔记⼀:I2C协议学习和Verilog实现1//////////////////////////////////////////////////2//clk = 20 MHz ,⼀个周期50ns3//sck = 100 kHz (scl) ,⼀个周期 1000ns4//I2C在sck下降沿更新数据,上升沿读取(采样)数据5///////////////////////////////////////////////////6module demo_I2C #(parameter F100K = 9'd200)(clk,rstn,start_sig,word_addr,wr_data,rd_data,done_sig,scl,sda,sq_i);78input clk ;9input rstn ;1011input [1:0] start_sig ; //12input [7:0] word_addr ; //word address13input [7:0] wr_data ; //Data14output [7:0] rd_data ; //Data from EEPROM15output done_sig ;1617output scl ; //sda和scl其实是⽤来作为仿真信号添加在这⾥的,寄存器信号都⽤rscl和rsda表⽰了,最后⽤assign将rscl和rsda赋值给sda和scl,连到模块外部仿真⽤ 18inout sda ; //sda表⽰当前sda的in或out的值1920output [4:0] sq_i ;21/************************************22在这⾥,iic_func_module.v 的步骤i已经被引出来了。
读者要知道步骤i在⽆论是在设计上还是仿真上都有许多的好处。
23步骤i在仿真中可以充当“调试跟踪”的作⽤,因为只要模块的那个部分出问题,步骤i就会指向它。
I2C总线完全版——I2C总线的结构工作时序与模拟编程I2C(Inter-Integrated Circuit)是一种常用的串行通信接口,用于连接微控制器、传感器、存储器等各种模块。
它具有简单、低成本、高可靠性等优点,被广泛应用于各种领域。
本文将详细介绍I2C总线的结构、工作时序和模拟编程。
一、I2C总线的结构在I2C总线结构中,还包括两类设备:主设备(Microcontroller)和从设备(Slave Device)。
主设备一般是微控制器或者芯片,负责控制整个通信过程;从设备则是各种模块,如传感器、存储器等,被主设备控制和访问。
二、I2C总线的工作时序1. 开始条件(Start Condition):开始条件由主设备产生,即下降沿信号。
在开始条件下,SCL保持高电平,SDA从高电平跳变到低电平。
这个跳变信号标志着通信的开始。
2. 数据传输(Data Transfer):在数据传输过程中,主设备发出读/写命令、地址和数据等,从设备则进行应答。
-读操作:主设备发出读命令和从设备地址,之后由从设备产生应答信号。
主设备继续读取数据,每读取一个字节,都需要由主设备发出应答信号来确认继续传输。
-写操作:主设备发出写命令和从设备地址,之后由从设备产生应答信号。
主设备将数据发送给从设备,然后继续发送下一个数据。
3. 结束条件(Stop Condition):结束条件也由主设备产生,即上升沿信号。
在结束条件下,SDA从低电平跳变到高电平,表示数据传输结束。
三、I2C总线的模拟编程在控制器编程中,可以通过软件模拟I2C总线的通信过程,实现与从设备的数据交换。
1.初始化:初始化I2C总线,包括设置SCL和SDA为输出模式、将SCL和SDA线拉高等。
2.发送开始条件:发送开始条件的命令,即将SDA线从高电平跳变到低电平,并保持SCL线为高电平。
3.写操作:通过发送写命令和从设备地址,将需要写入的数据发送给从设备。
4.读操作:通过发送读命令和从设备地址,从从设备中读取数据。
I2C时序分析和基础知识总结I2C(Inter-Integrated Circuit)是一种串行通信协议,用于在集成电路之间传输数据。
它由Philips公司在1980年代初开发,并在现代的许多嵌入式系统中得到了广泛应用。
本文将对I2C的时序分析和基础知识进行总结。
一、I2C的基础知识1.主从结构:I2C通信有一个主设备和一个或多个从设备,主设备控制整个通信过程,从设备接收和响应主设备的命令。
2.总线:I2C使用双线制,包括一个双向的数据线(SDA)和一个时钟线(SCL)。
所有设备都通过这两条线连接在一起形成一个总线。
3.地址:每个从设备在总线上都有一个唯一的7位或10位地址,用于识别设备。
4. 传输速率:I2C的传输速率通常有标准模式(100Kbps)、快速模式(400Kbps)和高速模式(3.4Mbps)三种选择。
5. 触发方式:I2C通信可以通过主设备发出开始条件(start condition)和停止条件(stop condition)来触发。
二、I2C的时序分析I2C通信的时序分析主要涉及到以下几个关键的时刻:1. 开始条件(Start Condition):主设备拉低SDA线,然后拉低SCL线,在总线上发出一个开始信号。
2.地址传输:主设备发送从设备的地址,从设备通过检测总线上的地址匹配来判断自己是否被选中。
3.数据传输:在总线上的每个时钟周期内,数据(0或1)被传输。
4. 停止条件(Stop Condition):主设备释放SDA线,然后拉高SCL线,在总线上发出一个停止信号。
5. 确认位(ACK bit):在数据传输后,接收设备会发送一个ACK位,以确认接收到数据。
6. 重复启动条件(Repeated Start Condition):主设备可以在传输过程中发出一个重复启动信号,以重新寻址或不释放总线。
对于每个操作,如读取或写入数据,都需要经历上述的流程,主设备通过时钟线控制整个通信的时序。
I2C详细介绍及编程I2C(Inter-Integrated Circuit)是一种串行通信协议,常用于连接微控制器、传感器、存储器等设备,以实现数据通信。
本文将详细介绍I2C的原理、特点以及编程。
一、I2C的原理和特点I2C协议由飞利浦(Philips)公司于1982年开发,旨在简化数字电路上周边设备的通信。
I2C使用两条线(SCL和SDA)进行数据传输,其中SCL是时钟线,SDA是数据线。
这种双线式的通信使得I2C可以同时进行数据传输和电源供给,极大地简化了设备之间的连接。
在I2C通信中,主设备(通常是微控制器)发起通信,而从设备被动应答。
主设备通过在SCL线上产生时钟信号来控制通信节奏,并通过SDA 线实现数据传输。
数据的传输可以是单向的(主设备向从设备发送数据)或双向的(主设备与从设备之间的双向数据传输)。
I2C协议中的从设备通过一个唯一的地址来识别和寻址。
主设备可以选择与一个或多个从设备进行通信,只需发送相应的地址即可。
在开始通信前,主设备会发送一个开始信号,然后跟着从设备地址和读写位,然后才是数据或命令。
从设备在收到自己地址后会发出应答信号,主设备接收到应答信号后才会继续发送数据。
通信结束后,主设备会发送停止信号。
I2C的特点包括:1.双向通信:主设备和从设备之间可以进行双向的数据传输,减少通信线的需求和复杂度。
2.主-从结构:I2C通信中有一个主设备控制通信的发起和终止,而从设备被动应答。
3.多从结构:主设备可以与多个从设备进行通信,只需要发送不同的地址。
4.低速传输:I2C通信的时钟频率相对较低,一般在100kHz或400kHz。
二、I2C的编程实现在进行I2C编程之前,需要确保硬件上有I2C接口。
常见的I2C接口引脚包括SCL(时钟线)和SDA(数据线),同时需要进行相应的电源连接。
I2C编程的具体实现会有所差异,根据不同的硬件平台和编程语言而有所不同。
以下是一个基于Arduino平台的简单示例:```cpp#include <Wire.h>#define DEVICE_ADDRESS 0x50void setuWire.begin(;Serial.begin(9600);void loo//发送命令Wire.beginTransmission(DEVICE_ADDRESS);Wire.write(0x00); // 使用写入地址0x00Wire.write(0x01); // 写入数据0x01Wire.endTransmission(;delay(100);//读取数据Wire.requestFrom(DEVICE_ADDRESS, 1);if (Wire.available()int data = Wire.read(;Serial.print("Received: ");Serial.println(data);}delay(1000);```上述示例代码中,我们使用Wire库来实现I2C通信。
学习笔记1: Linux设备驱动程序之I2C 基础架构篇I2C (Inter-Integrated Circuit)总线是一种由PHILIPS 公司开发的两线式串行总线,用于连接微控制器及其外围设备。
I2C 总线最主要的优点是其简单性和有效性。
由于接口直接在组件之上,因此I2C 总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。
I2C 总线概述I2C 总线是由数据线SDA 和时钟SCL 构成的串行总线,可发送和接收数据,每个器件都有一个惟一的地址识别。
I2C 规程运用主/从双向通讯。
器件发送数据到总线上,则定义为发送器,器件接收数据则定义为接收器。
主器件和从器件都可以工作于接收和发送状态。
总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL)控制总线的传输方向,并产生起始和停止条件。
SDA线上的数据状态仅在SCL 为低电平的期间才能改变,SCL 为高电平的期间,SDA状态的改变被用来表示起始和停止条件。
另外,I2C是一种多主机控制总线.它和USB总线不同,USB是基于master-slave机制,任何设备的通信必须由主机发起才可以.而 I2C 是基于multi master机制.一同总线上可允许多个master.I2C 总线在传送数据过程中共有三种类型信号,它们分别是:开始信号、结束信号和应答信号。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为低电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的IC 在接收到8bit 数据后,向发送数据的IC 发出特定的低电平脉冲,表示已收到数据。
CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。
若未收到应答信号,由判断为受控单元出现故障。
Linux 的I2C 驱动架构Linux 中I2C 总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。
其中总线驱动的职责,是为系统中每个I2C 总线增加相应的读写方法。
但是总线驱动本身并不会进行任何的通讯,它只是存在那里,等待设备驱动调用其函数,参见图1。
设备驱动则是与挂在I2C 总线上的具体的设备通讯的驱动。
通过I2C 总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
在我们的Linux 驱动的i2c 文件夹下有algos,busses,chips 三个文件夹,另外还有i2c-core.c 和i2c- dev.c 两个文件。
其中i2c-core.c 文件实现了I2C core 框架,是Linux 内核用来维护和管理的I2C 的核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver 结构和I2C adapter 结构。
I2C core 提供接口函数,允许一个I2C adatper,I2C driver 和I2C client 初始化时在I2C core 中进行注册,以及退出时进行注销。
同时还提供了I2C 总线读写访问的一般接口,主要应用在I2C 设备驱动中。
Busses 文件夹下的i2c-mpc.c 文件实现了PowerPC 下I2C 总线适配器驱动,定义描述了具体的I2C 总线适配器的i2c_adapter 数据结构,实现比较底层的对I2C 总线访问的具体方法。
I2C adapter 构造一个对I2C core 层接口的数据结构,并通过接口函数向I2C core 注册一个控制器。
I2C adapter 主要实现对I2C 总线访问的算法,iic_xfer() 函数就是I2C adapter 底层对I2C 总线读写方法的实现。
同时I2C adpter 中还实现了对I2C 控制器中断的处理函数。
i2c-dev.c 文件中实现了I2C driver,提供了一个通用的I2C 设备的驱动程序,实现了字符类型设备的访问接口,实现了对用户应用层的接口,提供用户程序访问I2C 设备,包括实现open,release,read,write 以及最重要的ioctl 等标准文件操作的接口函数。
我们可以通过open 函数打开I2C 的设备文件,通过ioctl 函数设定要访问从设备的地址,然后就可以通过read 和write 函数完成对I2C 设备的读写操作。
为了更方便和有效地使用I2C 设备,我们可以为一个具体的I2C 设备开发特定的I2C 设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。
图1 I2C基础架构分析驱动程序代码一.先分析一下i2c架构的初始化代码如下:static int __init i2c_init(void){int retval;retval = bus_register(&i2c_bus_type);if (retval)return retval;retval = class_register(&i2c_adapter_class);if (retval)goto bus_err;retval = i2c_add_driver(&dummy_driver);if (retval)goto class_err;return 0;class_err:class_unregister(&i2c_adapter_class);bus_err:bus_unregister(&i2c_bus_type);return retval;}subsys_initcall(i2c_init);很明显,i2c_init()会在系统初始化的时候被调用.在i2c_init中,先注册了i2c_bus_type的bus,i2c_adapter_class的class.然后再调用i2c_add_driver()注册了一个i2c driver.二.I2C驱动程序。
下面分别介绍两种方式的实现。
分别称为Adapter(LEGACY)和Probe方式(new style)”。
(1)adapter方式(LEGACY)(参考2.6.27内核的pca953x.c设备驱动程序代码,原始代码采用Probe方式)●构建i2c_driverstatic struct i2c_driver pca953x_driver = {.driver = {.name= "pca953x", //名称},.id= ID_pca9555,//id号.attach_adapter= pca953x_attach_adapter, //调用适配器连接设备.detach_client= pca953x_detach_client,//让设备脱离适配器};●注册i2c_driverstatic int __init pca953x_init(void){return i2c_add_driver(&pca953x_driver);}module_init(pca953x_init);●attach_adapter动作执行i2c_add_driver(&pca953x_driver)后,如果内核中已经注册了i2c适配器,则顺序调用这些适配器来连接i2c设备。
此过程是通过调用i2c_driver中的attach_adapter方法完成的。
实现形式如下:static int pca953x_attach_adapter(struct i2c_adapter *adapter){return i2c_probe(adapter, &addr_data, pca953x_detect);/*adapter:适配器addr_data:地址信息pca953x_detect:探测到设备后调用的函数*/}地址信息addr_data是由下面代码指定的。
/* Addresses to scan */static unsigned short normal_i2c[] = {0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,I2C_CLIENT_END};I2C_CLIENT_INSMOD;注意:normal_i2c里的地址必须是你i2c芯片的地址。
否则将无法正确探测到设备。
而I2C_CLIENT_INSMOD是一个宏,它会利用normal_i2c构建addr_data。
●构建i2c_client,并注册字符设备驱动i2c_probe在探测到目标设备后,后调用pca953x_detect,并把当时的探测地址address作为参数传入。
static int pca953x_detect(struct i2c_adapter *adapter, int address, int kind){struct i2c_client *new_client;struct pca953x_chip *chip; //设备结构体int err = 0,result;dev_t pca953x_dev=MKDEV(pca953x_major,0);//构建设备号,根据具体情况设定,这里我只考虑了normal_i2c中只有一个地址匹配的情况。
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA| I2C_FUNC_SMBUS_WORD_DATA))//判定适配器能力goto exit;if (!(chip = kzalloc(sizeof(struct pca953x_chip), GFP_KERNEL))) {err = -ENOMEM;goto exit;}/****构建i2c-client****/chip->client=kzalloc(sizeof(struct i2c_client),GFP_KERNEL);new_client = chip->client;i2c_set_clientdata(new_client, chip);new_client->addr = address;new_client->adapter = adapter;new_client->driver = &pca953x_driver;new_client->flags = 0;strlcpy(new_client->name, "pca953x", I2C_NAME_SIZE);if ((err = i2c_attach_client(new_client)))//注册i2c_clientgoto exit_kfree;if (err)goto exit_detach;if(pca953x_major){result=register_chrdev_region(pca953x_dev,1,"pca953x");}else{result=alloc_chrdev_region(&pca953x_dev,0,1,"pca953x");pca953x_major=MAJOR(pca953x_dev);}if (result < 0) {printk(KERN_NOTICE "Unable to get pca953x region, error %d\n", result);return result;}pca953x_setup_cdev(chip,0); //注册字符设备,此处不详解return 0;exit_detach:i2c_detach_client(new_client);exit_kfree:kfree(chip);exit:return err;}●字符驱动的具体实现函数操作struct file_operations pca953x_fops = {.owner = THIS_MODULE,.ioctl= pca953x_ioctl,.open= pca953x_open,.read= pca953x_read,.write= pca953x_write,.release =pca953x_release,};字符设备驱动本身即是通过open()建立与文件之间的关系,read()函数实现用户读取I2C设备的数据,write()函数实现用户数据写入I2C设备.(2)Probe方式(new style)●构建i2c_driver和LEGACY方式一样,也需要构建i2c_driver,但是内容有所不同。