I2C详解.docx
- 文档编号:3919397
- 上传时间:2022-11-26
- 格式:DOCX
- 页数:12
- 大小:76.85KB
I2C详解.docx
《I2C详解.docx》由会员分享,可在线阅读,更多相关《I2C详解.docx(12页珍藏版)》请在冰豆网上搜索。
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结束本次通信并释放总线。
从机收到P信号后也退出与主机之间的通信
注意:
①主机通过发送地址码与对应的从机建立了通信关系,而挂接在总线上的其它从机虽然同时也收到了地址码,但因为与其自身的地址不相符合,因此提前退出与主机的通信;②主机的一次发送通信,其发送的数据数量不受限制。
主机是通过P信号通知发送的结束,从机收到P信号后退出本次通信;③主机的每一次发送后都是通过从机的ACK信号了解从机的接收状况,如果应答错误则重发。
5、主机接收数据流程
(1)主机发送启动信号后,接着发送命令字节(其中R/W=1)
(2)对应的从机收到地址字节后,返回一个应答信号并向主机发送数据
(3)主机收到数据后向从机反馈一个应答信号
(4)从机收到应答信号后再向主机发送下一个数据
(5)当主机完成接收数据后,向从机发送一个“非应答信号(ACK=1)”,从机收到ASK=1的非应答信号后便停止发送
(6)主机发送非应答信号后,再发送一个停止信号,释放总线结束通信
注意:
主机所接收数据的数量是由主机自身决定,当发送“非应答信号/A”时从机便结束传送并释放总线(非应答信号的两个作用:
前一个数据接收成功,停止从机的再次发送)。
6、总线死锁原因分析
I2C总线写操作过程中,主机在产生启动信号后控制SCL产生8个时钟脉冲,然后拉低SCL信号为低电平,在这个时候,从机输出应答信号,将SDA信号拉为低电平。
如果这个时候主机异常复位,SCL就会被释放为高电平。
此时,如果从机没有复位,就会继续I2C的应答,将SDA一直拉为低电平,直到SCL变为低电平,才会结束应答信号。
而对于主机来说,复位后检测SCL和SDA信号,如果发现SDA信号为低电平,则会认为I2C总线被占用,会一直等待SCL和SDA信号变为高电平。
这样,主机等待从机释放SDA信号,而同时从机又在等待主机将SCL信号拉低以释放应答信号,两者相互等待,I2C总线进人一种死锁状态。
同样,当I2C进行读操作时,从机应答后输出数据,如果在这个时刻主机异常复位而此时从机输出的数据位正好为0,也会导致I2C总线进入死锁状态。
解决方案通常有如下几种:
(1)将从机的电源设计为可控,当发生总线死锁的时将从机复位
(2)可以在从机的程序中加入监测功能,如果总线长时间被拉低则释放对总线的控制
(3)在主机中增加I2C总线恢复程序。
每次主机复位后,如果检测到SDA被拉低,则控制SCL产生<=9个时钟脉冲(针对8位数据的情况),每发送一个时钟脉冲就检测SDA是否被释放,如果SDA已经被释放就再模拟产生一个停止信号,这样从机就可以完成被挂起的读写操作,从死锁状态中恢复过来。
这种方法有一定的局限性,因为大部分主机的I2C模块由内置的硬件电路来实现,软件并不能够直接控制SCL信号模拟产生需要时钟脉冲
7、处理器的I2C模块会在如下所述的情况产生中断信号
RX_UNDER 当处理器通过IC_DATA_CMD寄存器读取接收缓冲器为空时置位
RX_OVER 当接收缓冲器被填满,而且还有数据从外设发送过来时被置位;缓冲器被填满后接收的数据将会丢失
RX_FULL 当接收缓冲器达到或者超过IC_RX_TL寄存器中规定的阈值时被置位;当数据低于阈值时标志位将被自动清除
TX_OVER 当发送缓冲器被填满,而且处理器试图发送另外的命令写IC_DATA_CMD寄存器时被置位
TX_EMPTY 当发送缓冲器等于或者低于IC_TX_TL寄存器中规定的阈值时被置位;当数据高于阈值时标志位将被自动清除
RD_REQ 当i2c模块作为从机时并且另外的主机试图从本模块读取数据时被置位
TX_ABRT 当i2c模块无法完成处理器下达的命令时被置位,有如下几种原因:
*发送地址字节后没有从机应答
*地址识别成功后主机发送的数据从机没有应答
*当i2c模块只能作为从机时试图发送主机命令
*当模块的RESTART功能被关闭,而处理试图完成的功能必须要RESTART功能开启才能完成
*高速模块主机代码被应答
* STARTBYTE被应答
*模块仲裁失败
无论标志位什么时候被置位,发送缓冲器和接收缓冲器的内容都会被刷新
RX_DONE 当i2c模块作为从机发送数据时,如果主机没有应答则置位;这种情况发生在i2c模块发送最后一个字节数据时,表明传输结束
ACTIVITY 表明i2c模块正在活动,这个标志位将会一直保持直到用以下4种方式清除:
*关闭i2c
*读取IC_CLR_ACTIVITY寄存器
*读取IC_CLR_INTR寄存器
*系统重启
即使i2c模块是空闲的,这个标志仍然需要被置位直到被清除,因为这表明i2c总线上有数据正在传输
STOP_DET 表明i2c总线上产生了STOP信号,无论模块作为主机还是从机
START_DET 表明i2c总线上产生了START信号,无论模块作为主机还是从机
1、通信接口
i2c发送或者接收一次数据都以数据包 structi2c_msg 封装
[cpp]viewplaincopy
1.struct i2c_msg {
2. __u16 addr; // 从机地址
3. __u16 flags; // 标志
4.#define I2C_M_TEN 0x0010 // 十位地址标志
5.#define I2C_M_RD 0x0001 // 接收数据标志
6. __u16 len; // 数据长度
7. __u8 *buf; // 数据指针
8.};
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为I2C_M_RD;len为此次通信的数据字节数;buf为发送或接收数据的指针。
在设备驱动中我们通常调用i2c-core定义的接口 i2c_master_send和 i2c_master_recv来发送或接收一次数据。
[cpp]viewplaincopy
1.int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
2.{
3. int ret;
4. struct i2c_adapter *adap=client->adapter; // 获取adapter信息
5. struct i2c_msg msg; // 定义一个临时的数据包
6.
7. msg.addr = client->addr; // 将从机地址写入数据包
8. msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
9. msg.len = count; // 将此次发送的数据字节数写入数据包
10. msg.buf = (char *)buf; // 将发送数据指针写入数据包
11.
12. ret = i2c_transfer(adap, &msg, 1); // 调用平台接口发送数据
13.
14. /* If everything went ok (i.e. 1 msg transmitted), return #bytes
15. transmitted, else error code. */
16. return (ret == 1) ?
count :
ret; // 如果发送成功就返回字节数
17.}
18.EXPORT_SYMBOL(i2c_master_send);
i2c_master_send接口的三个参数:
client为此次与主机通信的从机,buf为发送的数据指针,count为发送数据的字节数。
[cpp]viewplaincopy
1.int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
2.{
3. struct i2c_adapter *adap=client->adapter; // 获取adapter信息
4. struct i2c_msg msg; // 定义一个临时的数据包
5. int ret;
6.
7. msg.addr = client->addr; // 将从机地址写入数据包
8. msg.flags = client->flags & I2C_M_TEN; // 将从机标志并入数据包
9. msg.flags |= I2C_M_RD; // 将此次通信的标志并入数据包
10. msg.len = count; // 将此次接收的数据字节数写入数据包
11. msg.buf = buf;
12.
13. ret = i2c_transfer(adap, &msg, 1); // 调用平台接口接收数据
14.
15. /* If everything went ok (i.e. 1 msg transmitted), return #bytes
16. transmitted, else error code. */
17. return (ret == 1) ?
count :
ret; // 如果接收成功就返回字节数
18.}
19.EXPORT_SYMBOL(i2c_master_recv);
i2c_master_recv 接口的三个参数:
client为此次与主机通信的从机,buf为接收的数据指针,count为接收数据的字节数。
我们看一下 i2c_transfer 接口的参数说明:
[cpp]viewplaincopy
1.int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
其中adap为此次主机与从机通信的适配器;msgs为通信的数据包,这里可以是单个或多个数据包;num用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。
通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通信,所以num为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将num设置为2。
接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。
比如读一个寄存器的接口可以按照如下方式封装:
[cpp]viewplaincopy
1.static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data)
2.{
3. int ret;
4.
5. struct i2c_msg msgs[] = {
6. {
7. .addr = client->addr,
8. .flags = 0,
9. .len = 1,
10. .buf = ®, // 寄存器地址
11. },
12. {
13. .addr = client->addr,
14. .flags = I2C_M_RD,
15. .len = 1,
16. .buf = data, // 寄存器的值
17. },
18. };
19.
20. ret = i2c_transfer(client->adapter, msgs, 2); // 这里 num = 2,通信成功 ret = 2
21. if (ret < 0)
22. tp_err("%s error:
%d\n", __func__, ret);
23.
24. return ret;
25.}
还可调用前面所述的接口封装:
[cpp]viewplaincopy
1.static unsigned char read_reg(struct i2c_client *client, unsigned char reg)
2.{
3. unsigned char buf;
4.
5. i2c_master_send(client, ®, 1); // 发送寄存器地址
6. i2c_master_recv(client, &buf, 1); // 接收寄存器的值
7.
8. return buf;
9.}
2、reset接口
最近因为平台的i2c总线经常发生死锁,用逻辑分析仪检测发现通常为SDA和SCL都被拉低,于是在i2c-core中加入了reset机制,总体思路如下:
(1)在i2c.driver和i2c.adapter的结构中加入reset接口,即每一个i2c设备都可以注册reset函数,每条i2c总线都有相应的reset接口
(2)当发生死锁时,首先根据i2c-timeout的信息获取当前通信的设备地址和总线编号,然后依次执行当前总线下所有i2c设备的reset函数,再尝试发送是否成功;如果总线仍然处于死锁状态则执行i2c.adapter的reset函数;如果总线还是处于死锁状态就重启机器;总共3层reset机制
(3)i2c.driver的reset函数一般操作设备的resetpin或者电源(需要根据硬件设计进行相应操作)
(4)i2c.adapter的reset函数首选进行SCL的模拟解锁方案,然后再是操作整个总线上设备的电源(需要根据硬件设计进行相应操作)
(5)重启是最后的一层机制,此时无法恢复设备的正常使用就只能重启了
因为i2c.adapter层的需要,在i2c-core中加入了遍历当前总线所有设备并执行设备reset函数的接口i2c_reset_device:
[cpp]viewplaincopy
1./**
2. * i2c_reset_device - reset I2C device when bus dead
3. * @adapter:
the adapter being reset
4. * @addr:
the device address
5. */
6.static int __i2c_reset_device(struct device *dev, void *addrp)
7.{
8. struct i2c_client *client = to_i2c_client(dev);
9. int addr = *(int *)addrp;
10.
11. if (client && client->driver && client->driver->reset)
12. return client->driver->reset();
13.
14. return 0;
15.}
16.
17.int i2c_reset_device(struct i2c_adapter *adapter, int addr)
18.{
19. return device_for_each_child(&adapter->dev, &addr, __i2c_reset_device);
20.}
21.EXPORT_SYMBOL(i2c_reset_device);
需要注意的是i2c.driver的reset函数返回值需要为0,不然device_for_each_child不会继续后面的遍历。
用GPIO模拟SCL解锁的参考代码如下:
[cpp]viewplaincopy
1.static int i2c_reset_adapter(void)
2.{
3. int counter = 0;
4.
5. gpio_request(I2C_BUS_DATA, "gpioxx");
6. gpio_request(I2C_BUS_CLK, "gpioxx");
7. /* try to recover I2C bus */
8. gpio_direction_input(I2C_BUS_DATA);
9.
10. if (!
__gpio_get_value(I2C_BUS_DATA)) {
11. while((!
__gpio_get_value(I2C_BUS_DATA)) && ++counter < 10)
12. {
13. udelay(5);
14. gpio_direction_output(I2C_BUS_CLK, 1);
15. udelay(5);
16. gpio_direction_output(I2C_BUS_CLK, 0);
17. }
18. i2c_err("try to recover i2c bus, retry times are %d\n",counter);
19. if (counter < 10) {
20. udelay(5);
21. gpio_direction_output(I2C_BUS_DATA, 0);
22. udelay(5);
23. gpio_direction_output(I2C_BUS_CLK, 1);
24. udelay(5);
25. gpio_direction_output(I2C_BUS_DATA, 1);
26. msleep(10);
27. } else {
28. i2c_err("try to recover i2c bus failed!
\n");
29. }
30. }
31.
32. gpio_free(I2C_BUS
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- I2C 详解