Android Things 教程:为 Android Things 编写你的第一个驱动(SPI)

Android Things 是 Android 操作系统的轻量级版本,可安装在以物联网为重点的设备上。Android Things 包括“用户驱动程序”功能,使开发人员能够轻松控制各种外设(LED、开关、按钮、伺服电机等)。现在,让我们来看看为 Android Things 编写一个新的外设驱动程序需要什么。

在写这篇文章的时候,Google 并没有宣布一个硬件要求运行 Android Things 的要求,但是硬件文档声明他们正在关注模块(SoM)板上的系统。虽然唯一支持 Android Things 的开发板是 Raspberry Pi 3、Intel Edison 和 NXP Pico i.MX6UL,但似乎几乎所有的开发板都能够运行 Android Things。支持这些的开发板不提供许多传感器、LED和按钮,但是一旦 Android Things 设备启动并运行,您就可以轻松插入外围设备。

用户驱动

其中一件让我兴奋的事情就是用户驱动程序

如上图所示,开发人员可以直接使用 Android Framework API,并使用用户驱动程序构建应用程序。这些是访问和控制 Android Things 应用程序的外围硬件的一个薄层。这使我们能够专注于重要的事情:编写出色的应用程序。

您可以在 Github 上找到可用的用户驱动程序的列表。这些驱动程序的好处是,它们就像任何其他 Android 库一样,意味着你可以在项目的 build.gradle 依赖项中添加一行,驱动程序就能被导入。

如果您想使用没有 Android Things 驱动程序的外设,会发生什么情况? 你有2个选项:

  • 选择一个不同的外设,希望有一个驱动程序。
  • 去使用 DIY 的方式,自己写驱动程序。

如果您采取第二种选择,您可以通过共享驱动程序,让任何人(包括您)在未来的项目中重复使用该代码,从而回馈开源社区。 为此,您需要供应商提供的外设文档。

硬件 Hacking

首先你需要了解外设使用哪种通信协议。Android Things 支持以下类型的通信:

  • 通用输入/输出(GPIO):这是最简单的通信方式,您可以使用它从外设(输入)读取数据,并写入到外设(输出)2。 每个物理引脚代表一个输入或一个输出(您可以从您的应用程序配置每个引脚模式),它可以只有两个值:高和低(或1和0)。 GPIO 用于处理按钮,运动传感器等等。

  • 脉冲宽度调制(PWM):与 GPIO 类似,PWM 使用一个物理引脚,但仅为输出(意味着 Android Things 板发送数据,从不读取)。使用 PWM,您可以控制更复杂的设备,如伺服电机,可调光灯和其他可以采用更宽范围值的设备,而不仅仅是1或0。请参阅 PWM 文档以进一步阅读该接口。

  • 串行外设接口(SPI):如果您的外设稍微复杂一些,则可能需要使用 SPI,从 RGB LED 灯条到图形显示器等广泛的外设使用 SPI。SPI 允许主设备(在这种情况下为 Android Things 设备)与一个或多个从设备进行通信。它使用一个单向数据交换引脚,或两个双向交换引脚进行数据传输,一个引脚用于时钟,在多个从机的情况下,需要额外的芯片选择线。

  • 内部集成电路(I2C):I2C 基于两个引脚,因为它需要数据连接和一个时钟。与 SPI 相似,I2C 可以与一个或多个从机一起使用,但每个从机都有自己的地址,主机可以指定。在这种情况下,主机一次发送一个字节的数据,从机必须确认是否已经正确接收到数据。此确认内置于I2C。该协议用于一些传感器,LCD显示器等。

  • 通用异步收发器(UART):虽然 SPI 和 I2C 都是同步接口(因为它们都需要时钟线来同步),但 UART 是异步的。一个简单的 UART 实现需要两个引脚:一个数据输入和一个数据输出。与 SPI 和 I2C 不同,UART 不支持多个从机,数据被封装在数据帧中。数据帧包括起始位,5-9个数据位,可能是奇偶校验位和一个或两个结束位。 UART 用于 GPS 设备,XBee 无线电,一些打印机等。

请阅读外围设备供应商的文档,了解要使用的内容。如果使用上述方法之一,那么实现一个驱动程序应该是非常简单的。否则,您将必须实现与 GPIO 端口一起使用的任何协议。

调试驱动程序协议

我买了两块芯片上印有代码 WS2801 的 RGB LED 灯条。这是 WS2801 的数据表,我通过搜索 “WS2801 数据表”找到。在 PDF 中,功能列表提到芯片使用 PWM,但不要让这个骗过你。在第五页上有一个输入和输出引脚的列表,你可以看到芯片有一个时钟输入(CKI),一个数据输入(SDI),一个电源(VCC)和地(GND),也可以在 LED 灯条中找到。

调查使用哪个协议,这里的主要指标是所需的引脚数量。尽管电源和接地对于大多数外设是通用的,但是将时钟和数据输入作为单独引脚的需要表明,您必须使用 I2C 或 SPI(不是 PWM,因为它只使用一条线路)。

另一个指标是时钟速度:I2C 标准是 400kHz(但可以达到〜3MHz),而数据表中说 WS2801 接受 25MHz 的最大时钟频率。

第三个也是最后一个指标是数据格式:第 12 页显示芯片需要操作的唯一数据是 3 个字节(红色,绿色和蓝色值),它将为下一个芯片留下其余的数据(即带上的下一个 LED)。这里没有提到承认(acknowledgement)或双向沟通。列表中遗漏了 I2C 和 UART,所以你可以使用 SPI。

外围设备数据表还将指定重要的值,例如最小和最大电压,最大时钟速度以及数据是在前沿还是后沿时钟沿读取的。

一旦你知道你必须使用哪种形式的通信(在上面的例子中是 SPI),你可以开始考虑连接和编码。

连接外围设备到开发者板

现在是时候找出主板上,哪些引脚需要用来与 Android 端的外设通话。最简单的方法是在 Android Things 开发者网站上找到开发板的引脚。引脚分配显示哪个引脚可用于每种通信形式。看一看 Raspberry Pi 3 的例子:

在这种情况下,我用针脚 2(或 4)作为电源(VCC),针脚 6(或 9,14 等)接地(GND),针脚 23 针对时钟(CKI),针脚 19 针对数据(SDI)。请注意,有两个类似的引脚:19 - MOSI,它代表主从输入,引脚 21 - MISO 或主从输出。在这种情况下,我必须使用 MOSI,因为主设备(Android Things设备)将发送数据,从设备(LED灯条)将读取数据。

需要注意的是,不同的电路板有不同的输出电压,不同的外设需要不同的电压来操作,所以要小心。外设可能无法工作,可能会烧坏,因此在将外设插入电路板之前,请务必检查规格,并根据需要使用电平转换器。例如,关于 Raspberry Pi 3 GPIO 的这篇文章,解释了引脚可以输出 0V(低电平)或 3.3V(高电平),同时它也有两个 5V 引脚。如果 GPIO 引脚配置为输入,那么使用高于 3.3V 的任何值都会损坏电路板。

软件 Hacking

在这一点上,你应该有一个外围设备插入到你的 Android Things 开发板,你知道你将要使用的协议。 所以,现在是编写代码的时候了。

现在,我们来看看如何使用 SPI 来实现外设驱动程序。几天前,我向 Github 上的驱动程序存储库提交了一个 PR,添加了 WS2801 的驱动程序。编写驱动程序时,以下是您需要包含的主要内容。

创建 Android Things 驱动

首先创建一个标准的 gradle 模块,并在 build.gradle 中添加:

apply plugin: 'com.android.library'

android {  
    compileSdkVersion 24
    buildToolsVersion '24.0.3'

    defaultConfig {
        minSdkVersion 24
        targetSdkVersion 24
        // Other default stuff...
    }
}

dependencies {  
    provided 'com.google.android.things:androidthings:0.1-devpreview'
}

请记住,这和其他 Android 库一样。 你可以使用 com.android.library 插件,为了编写 Android Things 你至少需要使用 API 24 的 SDK 版本。您还应该指定 com.google.android.things:android things 作为提供的依赖项。这意味着你需要它能够编译应用程序,但是一旦它被发布,你就不想在驱动程序中分发这个依赖。

接下来,你需要添加一行到你的 AndroidManifest.xml 中:

<manifest package="com.xrigau.driver.ws2801" xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<uses-library android:name="com.google.android.things"></uses-library> // THIS
    </application>
</manifest>  

添加此行是指示应用程序只能在 Android Things 设备上运行,而不是在手机,平板电脑等上运行的方式。

现在是驱动程序本身。创建一个新的类(我称之为 Ws2801.java)并编写驱动程序代码。 你需要做的第一件事是打开 SPI 端口,以便开始发送数据,然后关闭端口。 这里是主要部分:

package com.xrigau.driver.ws2801;

import com.google.android.things.pio.PeripheralManagerService;  
import com.google.android.things.pio.SpiDevice;  
import java.io.IOException;

public class Ws2801 implements AutoCloseable {  
  // ...
  private final SpiDevice device;

  public static Ws2801 create(String spiBusPort) throws IOException {
    PeripheralManagerService pioService = new PeripheralManagerService();
    try {
      return new Ws2801(pioService.openSpiDevice(spiBusPort));
    } catch (IOException e) {
      throw new IOException("Unable to open SPI device in bus port " + spiBusPort, e);
    }
  }

  Ws2801(SpiDevice device) throws IOException {
    this.device = device;
    device.setFrequency(1000000); // 1MHz clock frequency
    device.setMode(SpiDevice.MODE0); // Mode 0 seems to work best for WS2801
    device.setBitsPerWord(8);
  }

  public void write(int[] colors) throws IOException {
    // … some computation
    Int[] colorsToSend = new int[]{Color.RED, Color.WHITE, Color.parseColor(“#0FACE0”)}; // As many as LEDs in the strip
    device.write(colorsToSend, colorsToSend.length);
  }

  @Override
  public void close() throws IOException {
    device.close(); // IMPORTANT: If you don’t close it then the resource can’t be opened again.
  }
}

注意,首先你需要创建一个 PeripheralManagerService的实例。它用于获取开发板所具有的不同输入/输出(I/O)接口,以及开放总线或 GPIO 引脚的信息。在上面的示例代码中,我调用了 openSpiDevice(String spiBusName) 方法来打开 SPI 总线接口。一旦你有了一个 SpiDevice 的参考,你可以配置总线,并开始发送数据。

注意:代码已被简化,请参阅 Github上的完整源代码

这就是你所需要的。现在你可以开始使用驱动程序了。

到目前为止,我们已经看到了 Android Things 开发卡支持的不同类型的输入和输出,以及如何识别与外设进行通信所需的接口。我们已经介绍了针对每个不同接口使用的引脚,以及如何使用 Android Things SDK 实现驱动程序来控制外设。

在我的下一篇博客文章中,我们将介绍如何测试外设驱动程序,以提高您在使用或共享外设驱动程序时的信心。如果您想讨论这篇文章,或者对我的下一篇博客文章给我任何反馈意见,请在 TwitterGoogle+ 上与我联系。

最后虽然这并不重要的一点,我要感谢 Daniele Bonaldo,Luis Valle,特别感谢 Paul Blundell 对本文的评论。

原文链接:https://www.novoda.com/blog/writing-your-first-android-things-driver-p1/