上一讲中,我们说到 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开发,就不用接着说了吧
【本文是51CTO专栏机构“谷歌开发者”的原创稿件,转载请联系原作者(微信公众号:Google_Developers)】