GDE专栏 | Android Things中的I2C
文| 谷歌开发技术专家 王玉成 (York Wang)
上一讲中,我们说到 Android Things 的 API,以及 Peripheral I/O 设备包含的 API 的类型。但是作为程序员的我们,怎么理解这些 API 呢?
我们就拿 I2C 的 API 来说吧。看看我们怎样在 Android Things 中添加一个 I2C 的设备?首先得知道,I2C 是做什么的?怎么用?
实际上,I2C 是同步的串行通信总线,一般用于控制信号,比如控制 LCD, Camera 等设备。另外,大部分传感器有 I2C 的接口。I2C 是依靠时钟信号来传递数据的,所以有主设备(产生时钟的信号)和从设备(接收时钟的信号)之分。I2C 的通信每一次操作都是由主设备的发起的。
既然 I2C 是依靠时钟传递的信号,那么在连线上就有时钟钱 (SCL) 和数据线 (SDA),然后为了电势与大地相同,自然少不了地线 (GND)。为了方便没有接触过 I2C 总线的同学们理解这三个名词,贴上名词的全称:
Shared clock signal (SCL)
Shared data line (SDA)
Common ground reference (GND)
单看上面的图,为啥有三个 I2C 设备连接在一起呢?这三者之间又是什么关系?
其中,写有 Master Device 的 I2C 设备,称为主设备,另外两个为从设备。从主设备引出的 SDA 和 SCL 线构成 I2C 的总线。一个 I2C 的主设备可以提供一条 I2C 的总线,一条总线上最后可以连接 127 个 I2C 的从设备。
等等,为啥是 127 个呢?主要是 I2C 的地址有 7 位和 10 位两种地址。也就意味着,对于 7 位的地址表达的数据最大可以到 2^7=128,减去一个主设备,就是 127 个从设备了。这里的 I2C 设备地址,就是上图的 Address: 0x3c 和 0x4c,I2C 的主设备是通过从设备的地址,来找到从设备的。请注意,I2C 的主设备,是没有设备地址这一说法的。
我们还需要了解 I2C 的一些硬件信息:
I2C 是半双工,可以有主 -> 从方向的数据,也可以有从 -> 主方向的数据,但是同一时刻,只能有一种传输方式。这点和 SPI 是有差别的,SPI 总线支持全双工模式。但它同时只能访问一个从设备,由片选信号 (CS) 来决定。这就很明显了,I2C 通常用于控制命令的传输。而 SPI 通常用数据的批量传输。
了解完 I2C 的基本硬件信息。我们来了解一下 I2C 的从设备操作方式。不多不多,就是三大步。
连接从设备
对从设备进行读操作
对从设备进行写操作
设备连接
先要检查我们的物联网设备上有没有 I2C 总线。这时需要补充一下,有可能你的开发板上有多个 I2C 的总线。这时候, I2C 的总线地址 (此处非 I2C 的设备地址) 是有多个的,要明确你的 I2C 设备是接在哪个 I2C 的总线上。这也可以理解,为什么得到的 I2C 总线的数据是用 List 类型进行存放的。
如果有总线,我们再查找当前的 I2C 总线上对应的 I2C 设备。
关键的接口是 manager.openI2CDeivce(..),这个函数有两个参数,DEVICE_NAME 是用户定义的一个字符串,表示设备的名称。I2C_ADDRESS,也是之前所说的 I2C 从设备上的地址,这个地址在当前的 I2C 总线上是唯一的。
读写操作
首先得把 I2C 的操作流程搬出来说了。
这张图翻译成中文就是这样子的:
这样就完成了向设备地址为 0x30、寄存器地址为 0x10 的设备上读或者写入 0x06 这个数据。
那怎么知道是读数据,还是写数据呢?实际上 I2C 是 7 位的地址位。但是一个字节是 8 位,其中有一位叫做读写位。如果那一位设为读,就是去读操作,如果设为写,就是写操作。实际上,在示波器上我们还能看到另外的一个 ACK 位,保证硬件上传输正常,ACK 位在软件上不需要处理。
那么,加上 I2C 的读写位之后, I2C 数据传输会是什么样的呢?
我们可以看到, I2C 数据传输的时序,从硬件上来说 SCL 是按周期发的时钟信号,当 SCL 是高电平时,SDA 产生一个下降沿,这时候开始数据传输。其中传输 I2C 的从设备地址共有 8 位,1-7 位是地址,第 8 位是读写位,0 表示写,1 表示读。然后硬件自动产生 ACK 位。接下来就是数据传输的整个过程,最后当数据传完后,SCL 为高电平,SDA 产生上升沿时,产生 STOP 操作。事实上,在 I2C 做读操作需要往 I2C 的设备写入随机值,再去读,不过这些操作在 I2C 相关的接口中已经为我们封装好了。
这么大篇幅介绍了 I2C 的原理,还有 I2C 的时序,操作流程。实际上,Android Things 已经帮我们把读写接口封装好了,我们只需要在理解的基础上,调用接口就行了。
可以看出,Android Things 已经给我们封装好了 I2C 的读写操作 ,我们直接用就可以了。
这里面还有个细节比较绕。之前提到, I2C 的设备地址可以是 7 位,也可以是 10 位,但是 I2C 设备的寄存器可以是 8 位,也可以是 16 位。这里面就涉及到 8 位的设备,以及 16 位设备的读写问题。
六大函数出场:
8 位地址读写操作- readRegByte() 和 writeRegByte()
16 位地址读写操作 - readRegWord() 和 writeRegWord()
批量读写操作- readRegBuffer() and writeRegBuffer()
其中 Byte 是针对 8 位的 I2C 设备,Word 是针对 16 位的设备。
读操作:用寄存器的地址做为参数。
写操作:两个参数,寄存器地址,和你要写入的值。
上面的代码中,把寄存器的第 6 位置 1。所以操作流程是
读出寄存器的值
将这个值的第6位置1 (value |= 0x40;)
然后把新的值写回寄存器
不过对于 16 位的地址操作还有一个大小端的问题。(什么是大小端?去 Google 吧 )现在的 API 是依照小端模式来读写的 16 位设备地址。
直接批量数据操作,可以最大读到 32 个连续的寄存器的数值。
那么,我们怎么使用接口进行批量操作呢?
传输原始数据
还是先来张图吧:
这种操作方法,不同于上面的读写寄存器。在 I2C 的操作中,属于 burst 操作方法。即一次性的读写多少字节,最后再停止。
跟一个例子:
这样传输能带来更高的传输效率,解决了 I2C 传输的核心问题,我们也解决了支持 I2C 的任何外设的读写问题。
后记
Android Things 的 SDK 中,Peripheral I/O 部分是包括三种总线的,UART, I2C, SPI。对于软件开发人员来说,有下面几点需要注意:
UART 开发,需要了解 UART 的波特率、流控等概念。
SPI 开发,需要了解 MISO, MOSI,CLK, GND, CS 这些连线的作用,还有 SPI 的操作模式等,SDK 中的接口,与上文的 I2C 的开发流程相似。
I2C开发,就不用接着说了吧
下一讲我们会展示一个完整的 Android Things 应用。
您如果有任何涉及到 Android Things 方面的想法,都欢迎大家在下方留言,我们会把好的建议转交给 Android Things 的产品部门。也许在某一天,你的建议就是 Andorid Things 的一部分。
推荐阅读:
GDE专栏 | 完美支持Android Things的开发板都在这里了
GDE专栏 | Android与Android Things,父子还是兄弟?
Android Things Developer Preview 2 发布