查看原文
其他

GDE专栏 | Android Things中的I2C

2017-03-15 GDE-York 谷歌开发者


文| 谷歌开发技术专家 王玉成 (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。所以操作流程是

  1. 读出寄存器的值

  2. 将这个值的第6位置1 (value |= 0x40;)

  3. 然后把新的值写回寄存器


不过对于 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专栏 | 物联网到底是什么?

GDE专栏 | 完美支持Android Things的开发板都在这里了

GDE专栏 | Android Things开发环境搭建

GDE专栏 | Android与Android Things,父子还是兄弟?

Android Things Developer Preview 2 发布




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存