如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78835639
I2C部分已经接近尾声了,接下来我们回过头来看一下剩下的一些小知识点。如 I2C 仲裁、Linux I2C 工具查看配置I2C设备、什么是漏极开路等等。
看了那么多东西,都记不牢的或者理解不透。只有亲自画出时序图,你才能明白的。
就画 MT9P031 写操作,设备写地址为0xBA,片内寄存器地址为0x09,发送数据 0000 0010 和 1000 0100
我只画了起始位、设备写地址为0xBA、ACK 这几部分,详细的看下图。
当你画的时候,你就会注意到I2C协议的一些知识了:
1、SCL 为低电平,SDA 可以进行修改;
2、SCL 为高电平,SDA 保持稳定不变;
3、SCL 为高电平时,如果 SDA 有变动则视为特殊状况:启动(启始,SDA 由高转为低)或停止(结束,SDA 由低转为高)。
上面三条定义刚好把SCL和SDA三条讯号线会发生的讯号全部都包括了。
4、除了Start(启始)和Stop(结束)二个讯号之外,所有的信号传输固定8 bits(1 字节)为一组,这一个字节可能是一个设备地址,寄存器地址或写入或读取从机的数据,数据首先传输最高有效位(MSB)。
5、发送端在每组(8位)信号发出后,需读取接收端所回应的一个 ACK 位(信号为低)或 NACK 位(信号为高)。 (注意:发送端不一定是master。例如:读取资料时,发送端为slave)。
6、设备地址读/写位,0 表示主设备向从设备写数据,1 表示主设备向从设备读数据。
以AT24C02存储器为例:
通过芯片手册,得到设备地址相关的内容:
1010A2A1A0R/W:设备地址不包含读写位,右移1位,高位补0 =》 01010A2A1A0(A2A1A0都接地)=01010000=》AT24C02在开发板上的设备地址为0x50
读设备地址 = 设备地址 << 1 | 读写位(1) = 0xA1
写设备地址 = 设备地址 << 1 | 读写位(0) = 0xA0
我们晓得 I2C 允许多个主设备和多个从设备。如果两个或两个以上的主设备同时向总线上发送启动信号并开始传送数据,这样就形成了冲突。要解决这种冲突,就要进行仲裁的判决,这就是 I2C 总线上的仲裁。
I2C总线上的仲裁分两部分:SCL线的同步和SDA线的仲裁。
所有主机在 SCL 线上产生它们自己的时钟来传输 I2C 总线上的报文。数据只在时钟的高电平周期有效。因此,需要一个确定的时钟进行逐位仲裁。
时钟同步通过 线“与”(Wired-AND)连接 I2C 接口到 SCL 线来执行。这就是说: SCL 线的高到低切换会使器件开始数它们的低电平周期,而且一旦器件的时钟变低电平,它会使 SCL 线保持这种状态直到到达时钟的高电平。但是,如果另一个时钟仍处于低电平周期,这个时钟的低到高切换不会改变 SCL 线的状态。因此,SCL 线被有最长低电平周期的器件保持低电平。此时,低电平周期短的器件会进入高电平的等待状态。
当所有有关的器件数完了它们的低电平周期后,时钟线被释放并变成高电平。之后,器件时钟和 SCL 线的状态没有差别。而且所有器件会开始数它们的高电平周期。首先完成高电平周期的器件会再次将 SCL 线拉低。
这样,产生的同步 SCL 时钟的低电平周期由低电平时钟周期最长的器件决定,而高电平周期由高电平时钟周期最短的器件决定。
主机只能在总线空闲的时侯启动传输。两个或多个主机可能在起始条件的最小持续时间(tHD;STA)内产生一个起始条件,结果在总线上产生一个规定的起始条件。
当 SCL 线是高电平时,仲裁在 SDA 线发生;这样,在其他主机发送低电平时,发送高电平的主机将断开它的数据输出级,因为总线上的电平与它自己的电平不相同。
仲裁可以持续多位。它的第一个阶段是比较地址位。如果每个主机都尝试寻址相同的器件,仲裁会继续比较数据位(如果是主机-发送器)或者比较响应位(如果是主机-接收器)。因为 I2C 总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。
由于 Hs 模式的主机有一个唯一的 8 位主机码,因此一般在第一个字节就可以结束仲裁。如果主机也结合了从机功能 而且在寻址阶段丢失仲裁,它很可能就是赢得仲裁的主机在寻址的器件。因此,丢失仲裁的主机必须立即切换到它的从机模式。
下图显示了两个主机的仲裁过程。当然,可能包含更多的内容(由连接到总线的主机数量决定)。此时,产生 DATA1 的主机的内部数据电平与 SDA 线的实际电平有一些差别,如果关断数据输出,这就意味着总线连接了一个高输出电平。这不会影响由赢得仲裁的主机初始化的数据传输。
由于 I2C 总线的控制只由地址或主机码以及竞争主机发送的数据决定,没有中央主机,总线也没有任何定制的优先权。
必须特别注意的是:在串行传输时,当重复起始条件或停止条件发送到 I2C 总线的时侯,仲裁过程仍在进行。如果可能产生这样的情况,有关的主机必须在帧格式相同位置发送这个重复起始条件或停止条件。也就是说,仲裁不能在下面的情况之间进行:
1、重复起始条件和数据位
2、停止条件和数据位
3、重复起始条件和停止条件
4、从机不被卷入仲裁过程
I2C 仲裁机制,理解了 线“与”(Wired-AND),就一目了然了。
或者再简单说,它遵循“低电平优先”的原则,即谁先发送低电平谁就会掌握对总线的控制权。
参看:i2c-tools -- 维基百科
i2c-tools软件包包含一组用于Linux的异构I2C工具:
总线探测工具,芯片翻转器,寄存器级SMBus访问助手,EEPROM解码脚本,EEPROM编程工具以及用于SMBus访问的python模块。 只要内核包含I2C支持,所有版本的Linux都受支持。
下载:i2c-tools 下载
修改 Makefile ,编译器改为交叉编译:
COMPILE_PREFIX := /home/tarena/esd1503/arm-2009q3/bin/
CC := $(COMPILE_PREFIX)arm-none-linux-gnueabi-gcc
AR := $(COMPILE_PREFIX)arm-none-linux-gnueabi-ar
生成 i2cdetect, i2cdump, i2cget, i2cset,i2ctransfer
使用 file 指令检查一下:
# file i2cdetect
i2cdetect: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.16, not stripped
使用 tftp 将 i2cdetect, i2cdump, i2cget, i2cset,i2ctransfer 拷贝到开发板中。
然后执行 i2cdetect,出现错误:
# ./i2cdetect -l
./i2cdetect: error while loading shared libraries: libi2c.so.0: cannot open shared object file:
No such file or directory
缺少共享库 libi2c.so.0,将i2c-tools-4.0/lib/libi2c.so.0.1.0 拷贝到开发板 lib 目录下,然后增加权限 chmod 777 libi2c.so.0.1.0,然后重命名为 libi2c.so.0。
PS:本来想拷贝libi2c.so.0的,但是它和libi2c.so.0.1.0是硬链接关系,拷贝出现错误。
# stat libi2c.so.0
文件:"libi2c.so.0" -> "libi2c.so.0.1.0"
大小:15 块:0 IO 块:4096 符号链接
设备:801h/2049d Inode:2128654 硬链接:1
权限:(0777/lrwxrwxrwx) Uid:( 0/ root) Gid:( 0/ root)
最近访问:2017-12-20 10:31:47.155123897 +0800
最近更改:2017-12-20 10:24:43.174893417 +0800
最近改动:2017-12-20 10:24:43.174893417 +0800
创建时间:-
然后直接用 libi2c.so.0.1.0 也不行,需要重命名为 libi2c.so.0
到此可以在开发板上执行 i2cdetect:
]# ./i2cdetect -l
i2c-0 i2c s3c2410-i2c I2C adapter
i2c-1 i2c s3c2410-i2c I2C adapter
i2c-2 i2c s3c2410-i2c I2C adapter
现在挨个来看一下这些指令都该如何使用。
参看:Configuring_Aptina_MT9P031_using_i2c-tools
因为我购买的 S5PV210 开发板没有 AT24C02 和 S3C2410 芯片,所以没法测试。为了测试,找出尘封了好久的 DM368 开发板,主要看MT9P031。
查看它的选项:
# i2cdetect
Error: No i2c-bus specified!
Usage: i2cdetect [-y] [-a] [-q|-r] I2CBUS [FIRST LAST]
i2cdetect -F I2CBUS
i2cdetect -l
I2CBUS is an integer or an I2C bus name
If provided, FIRST and LAST limit the probing range.
首先在你的主板的终端中运行 i2cdetect -l 命令:
# i2cdetect -l
i2c-1 i2c DaVinci I2C adapter I2C adapter
该命令将显示任何可用的I2C总线。而输出的结果告诉我们有一个 I2C 总线,其ID是 i2c-1
一旦获得了I2C总线名称,就可以通过运行 i2cdetect -y I2CBUS 得到总线上都挂载的所有设备的设备地址
其中,I2CBUS 是一个整数或I2C总线名称。
# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- UU -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- UU -- -- -- --
通过,参看:S5PV210开发 -- I2C 你知道多少?(一) 知道 MT9P031 的设备地址为 0x5d。
然后查看下图可以发现 I2C 总线上是有挂载 MT9P031 设备地址的。
如果不确定,那么来做个测试,关闭电源,去除摄像头,然后再次执行 i2cdetect -y 1
结果如下,可以得出 0x5d 就是sensor MT9P031 的设备地址。
# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- UU -- -- -- --
查看它的选项
# i2cdump
Error: No i2c-bus specified!
Usage: i2cdump [-f] [-y] [-r first-last] I2CBUS ADDRESS [MODE [BANK [BANKREG]]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (byte, default)
w (word)
W (word on even register addresses)
s (SMBus block)
i (I2C block)
c (consecutive byte)
Append p for SMBus PEC
如果想要导出I2C设备中的所有寄存器内容,运行指令 i2cdump -f -y I2CBUS ADDRESS
其中 I2CBUS 是一个整数或I2C总线名称,ADDRESS是一个整数(0x03 - 0x77)。
例如,我们查看 MT9P031的寄存器内容。
# i2cdump -f -y 1 0x5d
No size specified (using byte-data access)
0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef
00: 18 00 00 07 0a 00 00 1f 00 07 80 00 00 00 00 00 ?..??..?.??.....
10: 00 64 00 00 00 00 00 00 00 00 00 00 00 00 40 00 .d............@.
20: 00 00 00 00 00 00 00 00 00 04 10 00 00 00 00 00 .........??.....
30: 00 00 00 00 00 00 00 00 00 00 00 00 12 00 80 00 ............?.?.
40: 00 00 00 00 02 10 10 10 00 00 00 00 00 0e 10 00 ....????.....??.
50: 80 00 80 00 00 00 00 00 80 00 00 00 00 2d 41 23 ?.?.....?....-A#
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
70: 00 a7 a7 0c 06 56 6b 6b a5 ab a9 a7 a7 ff a9 a9 .????Vkk?????.??
80: 00 1f 00 1b 1d 00 18 1a 00 00 00 00 00 00 00 00 .?.??.??........
90: 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ?...............
a0: 00 00 00 00 00 00 00 00 00 00 00 07 0a ca 00 44 ...........???.D
b0: bc 48 be 48 b8 c2 e0 06 01 09 09 09 03 0b 09 08 ?H?H????????????
c0: 02 01 04 06 06 0e 05 0b d2 c4 1a 49 a4 46 b0 60 ???????????I?F?`
d0: 44 08 d0 34 ee 4d 27 92 8d 5e a0 0f 91 24 9c 95 D??4?M'??^???$??
e0: 78 6c 11 d1 b4 13 59 1e 7c ad a1 3e e4 a1 14 13 xl????Y?|??>????
f0: 00 00 00 00 00 00 00 00 00 00 a3 0a 56 71 00 18 ..........??Vq.?
查看它的选项
# i2cget
Usage: i2cget [-f] [-y] I2CBUS CHIP-ADDRESS [DATA-ADDRESS [MODE]]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
b (read byte data, default)
w (read word data)
c (write byte/read byte)
Append p for SMBus PEC
上面 i2cdump 是读取所有寄存器的内容,那么如果只是想要读取特定寄存器的内容呢。
运行指令 i2cget -f -y I2CBUS CHIP-ADDRESS MODE
其中 I2CBUS 是一个整数或 I2C 总线名称,ADDRESS 是一个整数(0x03 - 0x77)。
MODE是以下之一:
b(读取字节数据,默认)
w(读取字数据)
c(写入字节/读取字节)
还是以 MT9P031 为例,我们看它的时序图,片内寄存器地址是 0x09。那我们就看这个寄存器的内容好了。
由于MT9P031有16位寄存器,所以我们必须使用读取字数据(w)。
# i2cget -f -y 1 0x5d 0x09 w
0x9707
我们看到MT9P031的数据表,你会看到正确的芯片版本寄存器的值是0x0284。就算是由于大小端的原因得出的结果最多也会是 0x8402,但是我们得到的却是 0x9707 我就不晓得为什么了。
再举个例子:
# i2cget -f -y 1 0x5d 0x00 w
0x0118
想要查看 MT9P031 的芯片 ID。片内寄存器地址为 0x00,得出的值为 0x0118,大小端转换其结果应为 0x1801.
然后再查看,内核启动信息里的这段话。
mt9p031 1-005d: Detected a MT9P031 chip ID 1801
mt9p031 1-005d: mt9p031 1-005d decoder driver registered !!
vpfe-capture vpfe-capture: v4l2 sub device mt9p031 registered
vpfe_register_ccdc_device: DM365 ISIF
DM365 ISIF is registered with vpfe.
发现 MT9P031的chip ID 确实是 1801。
查看它的选项
# i2cset
Usage: i2cset [-f] [-y] [-m MASK] I2CBUS CHIP-ADDRESS DATA-ADDRESS [VALUE] [MODE]
I2CBUS is an integer or an I2C bus name
ADDRESS is an integer (0x03 - 0x77)
MODE is one of:
c (byte, no value)
b (byte data, default)
w (word data)
Append p for SMBus PEC
如果你想在特定的寄存器内容写入呢?运行指令 i2cset -f -y I2CBUS CHIP-ADDRESS VALUE MODE
其中 I2CBUS 是一个整数或 I2C 总线名称,ADDRESS 是一个整数(0x03 - 0x77),VALUE是要写入寄存器的值。
MODE是以下之一:
b(读取字节数据,默认)
w(读取字数据)
c(写入字节/读取字节)
我们将 MT9P031 片内寄存器 0x09, 以读取字数据(w),这次我们写入正确的值 0x0284。
# i2cset -f -y 1 0x5d 0x09 0x8402 w
再读取:
# i2cget -f -y 1 0x5d 0x09 w
0x8402
说明可以内容写入的,重启开发板发现数据没有被重置,依旧是 0x8402 。
再举个例子:
之前我的DM368开发板主频为270Mhz,CVDD电压为1.2V,而只有当电压为1.35v才能实现主频为432Mhz。
我用的电源管理芯片是TVP65023RSB,这个芯片该如何配置,使其生成1.35v呢?
查看芯片手册,如下这段话,得出TVP65023 的设备地址为 0x48
The TPS65023 has a 7-bit address: 1001000, other addresses are available upon contact with the factory
# i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- 08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- UU -- --
60: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
70: -- -- -- UU -- -- -- --
通过i2ctool修改寄存器: 主要有关的寄存器为05和06如下图:
i2cset -f -y 1 0x48 0x05 0x80
i2cset -f -y 1 0x48 0x06 0x16
ok!!! 有电压1.35v。
到这步又有问题了,通过I2Ctool可以获得1.35v,但是掉电后会恢复默认的1.2v。
所以最后还是更改硬件电路实现的。
查看 i2c-1总线:
/sys/devices/platform/i2c_davinci.1/i2c-1# ls
1-001b 1-0049 1-0073 name
1-0025 1-0050 delete_device new_device
1-002c 1-005d device subsystem
1-0039 1-0068 i2c-dev uevent
然后进入 1-005d 目录,查看 name:
/sys/devices/platform/i2c_davinci.1/i2c-1/1-005d# ls
driver modalias name subsystem uevent
/sys/devices/platform/i2c_davinci.1/i2c-1/1-005d# cat name
mt9p031
查看I2C设备:
/sys/bus/i2c/drivers# ls
PCA9543A dummy rtc-ds1307 tvp514x
aic3x I2C Codec mt9p031 ths7303
dev_driver pcf857x tps65023
我这个只有一个 i2c-1 总线,如果是多个总线的话,通过这种方法就可以知道从设备是在哪个总线上了。
(当然文件系统不一样,可能位置也不同,仅供参考)
我们知道 I2C 器件连接到总线输出级必须是集电极开路或漏极开路形式才能实现线“与”的逻辑功能。
那么问题来了,什么是漏极开路或者集电极开路呢?
之前简单介绍过三极管,参看:S5PV210开发 -- 驱动开发相关硬件简介
再参看:如何正确的理解漏极开路输出跟推挽输出? —— 知乎
再参看:集电极开路 -- 维基百科
要理解推挽输出,首先要理解好三极管(晶体管)的原理。下面这种三极管有三个端口,分别是基极(Base)、集电极(Collector)和发射极(Emitter)。下图是NPN型晶体管。
这种三极管是电流控制型元器件,注意关键词电流控制。意思就是说,只要基极B有输入(或输出)电流就可以对这个晶体管进行控制了。
下面请允许我换一下概念,把基极B视为控制端,集电极C视为输入端,发射极E视为输出端。这里输入输出是指电流流动的方向。
当控制端有电流输入的时候,就会有电流从输入端进入并从输出端流出。
而PNP管正好相反,当有电流从控制端流出时,就会有电流从输入端流到输出端。
那么推挽电路:
上面的三极管是N型三极管,下面的三极管是P型三极管,请留意控制端、输入端和输出端。
当Vin电压为V+时,上面的N型三极管控制端有电流输入,Q3导通,于是电流从上往下通过,提供电流给负载。
经过上面的N型三极管提供电流给负载(Rload),这就叫「推」。
当Vin电压为V-时,下面的三极管有电流流出,Q4导通,有电流从上往下流过。
经过下面的P型三极管提供电流给负载(Rload),这就叫「挽」。
以上,这就是推挽(push-pull)电路。
要理解开漏,可以先理解开集。
如图,开集(Open Collector)的意思,就是集电极C一端什么都不接,直接作为输出端口。
如果要用这种电路带一个负载,比如一个LED,必须接一个上拉电阻(Pull up 电阻),就像这样。
当Vin没有电流,Q5断开时,LED亮。
当Vin流入电流,Q5导通时,LED灭。
开漏电路,就是把上图中的三极管换成场效应管(MOSFET)。
N型场效应管各个端口的名称:
场效应管是电压控制型元器件,只要对栅极施加电压,DS就会导通。结型场效应管有一个特性就是它的输入阻抗非常大,这意味着:没有电流从控制电路流出,也没有电流进入控制电路。没有电流流入或流出,就不会烧坏控制电路。而双极型晶体管不同,是电流控制性元器件,如果使用开集电路,可能会烧坏控制电路。这大概就是我们总是听到开漏电路而很少听到开集电路的原因吧?因为开集电路被淘汰了。
那我们现在分析一下 I2C 的电气特性。
参看:Understanding the I 2C Bus
I2C在同一条线上使用一个漏极开路/集电极开路的输入缓冲器,从而允许一条数据线 用于双向数据流。
漏极开路指的是一种输出,它可以将总线拉低到一个电压(接地),或者“释放”总线,并通过一个上拉电阻来拉起它。 如果总线在由主机或从机释放,线上的上拉电阻(Rpu)负责拉总线电压直至电源轨。 由于没有设备可能会迫使一个高线,这意味着总线永远不会遇到一个通信问题,一个设备可能试图发送一个高,另一个发送一个低, 造成短路(电源导轨接地)。 I2C 要求如果多主环境中的主设备传输一个高,但看到的线路是低的(另一个设备拉下来),以停止通信,因为另一台设备正在使用该总线(这也就是仲裁机制,线“与”)。推挽接口不允许这种类型的自由,这是一个 I2C 的好处。
上图显示了 SDA / SCL 线上从设备或主设备内部结构的简化视图,由读取输入数据的缓冲器和下拉式FET来传输数据组成。 一个设备是只能将总线拉低(提供对地短路)或释放总线(高阻接地),并允许上拉电阻提高电压。 这是处理 I2C 时要实现的一个重要概念,因为没有设备可以保持高总线。这个属性是允许双向通信发生。
如前一节所述,漏极开路设置可能只会拉低总线或“释放”它,并让一个电阻拉高。
下图显示了拉低总线电流的流程。 想要传送一个逻辑低电平时将激活下拉FET(场效应管),从而提供短路接地,拉低线路。
当从设备或主设备希望传输一个逻辑高电平时,只能通过关闭下拉FET(场效应管)来释放总线。 这使得总线悬空,上拉电阻将把电压拉高到电压轨,这将被解释为一个高电平。 下图显示了通过上拉电阻的电流流动,拉高总线。
I2C 总结用了十多天的时间,写了三篇文章,可算是讲完了,不容易啊。
而且我觉得讲的相当细致了,结合了之前开发时需要的问题,I2C 的软硬件都有涉及到。
以后开发再遇到 I2C 通信,绝对的不用愁了。哈哈...
如需转载请注明出处:https://blog.csdn.net/qq_29350001/article/details/78835639