Skip to content

Latest commit

 

History

History
629 lines (401 loc) · 38.1 KB

File metadata and controls

629 lines (401 loc) · 38.1 KB

三、使用 GPIOs 处理输入和输出

在最后一章中,您为开发硬件接口安卓应用准备了您的开发电脑和 BBBAndroid 系统。现在您的开发环境已经设置好并准备就绪,您将开始探索您的第一个能够与连接到 BBB 的硬件直接通信的应用。

通用 - 目的输入输出 ( GPIO )是数字电子中最基础的接口之一。在本章的示例中,您将使用 GPIOs 从外部世界接收数字输入信号,并作为响应发送数字输出信号。虽然这只是一个小小的开始,但它是开发和理解复杂得多的硬件接口应用的第一步。GPIOs 可以用来实现复杂而强大的接口逻辑。我们将讨论 GPIO 接口的硬件和软件方面,并解释在安卓应用中调用 Java 方法如何与低级硬件接口代码接口。

在本章中,我们将涵盖以下主题:

  • 了解 GPIOs
  • 构建 GPIO 接口电路
  • 在您的应用中包含打包
  • 探索 GPIO 示例应用

了解 GPIOs

在最基本的层面上,两个硬件之间的通信需要在它们之间来回传输数据。在计算机系统中,这些数据被表示为通过将设备连接在一起的导线发送的电压电平。来回的电压模式和电平形成了设备之间用来传输数据的通信协议。

GPIO 是微控制器和微处理器提供的最基本的接口选项。BBB 处理器的一些引脚被分配为 GPIOs,用作输入(监控线路上的电压以接收数据)或输出(在线路上放置特定电压以发送数据)。BBB 有几十个可用的 GPIO 引脚,这使得 GPIO 成为安卓应用与外部世界交互的一种灵活简单的方式,而不需要花哨的设备驱动程序或额外的接口硬件。

GPIO 的螺母和螺栓

数字逻辑的工作原理是有两个离散的电压电平,分别代表 开/高状态和关/低状态。通过在这两种状态之间切换,数据的二进制位在设备之间传输。BBB 的高电平使用 3.3 V 电压,低电平使用 0 V 电压(接地)。这种电压方案被称为 3.3 V 逻辑电平,通常用于单板计算机,如 BeagleBoard 和树莓 Pi。许多微控制器(例如许多 Arduinos)使用 5 V 逻辑电平来代替。

类型

不要对任何 BBB 引脚施加超过 3.3 V 的电压!

对 BBB 的 GPIO 施加大于 3.3 V 的电压会烧毁 BBB 的处理器,因此在为 BBB 设计 GPIO 接口电路时,请务必确保仅在最高 3.3 V 的电压下工作。引脚 P9.3/4 提供 3.3 V 电源,引脚 P9.5/6 提供 5 V 电源。当您打算使用 3.3 V 引脚时,很容易意外地将试验板导线连接到提供 5 V 电源的引脚。为了避免这种错误,请尝试用一条胶带盖住 P9.5/6 引脚。这可以防止您不小心将线路板电线插入这些引脚。

BBB 的处理器有四个 GPIOs 库,每个库中有 32 个独立的 GPIOs。由于 P8/9 连接器上只有 92 个可用引脚,不可能让每个 GPIO 都能访问外部世界。事实上,BBB 的《系统参考手册》显示,即使所有其他被多路复用到 P8/9 的功能被禁用,也只能同时多路复用大约 65 个唯一的 GPIOs 到 P8/P9。还有一些其他的 gpio 在内部用于任务,如照明和闪烁 BBB 的指示灯,但你应该考虑自己只限于使用可通过 P8/P9 访问的 gpio,并且不与任何标准 BBB 功能冲突。

安卓下的 GPIO 访问方式

BBB 上有两种与 GPIOs 交互的基本方法:文件输入/输出内存映射。借助文件输入/输出,通过读写文件系统中的 GPIO 文件,您可以通过内核驱动程序传递 GPIO 请求。通过内存映射,您可以将 GPIO 控制电阻映射到内存中,然后读写这些映射的内存位置来直接操作控制电阻。由于这两种方法都是由 Linux 内核实现的,所以它们在安卓系统下和在 Linux 系统下一样好用。

文件 I/O 方法的优缺点

文件输入/输出方法可以由具有读取/写入 GPIO 设备文件的适当权限的任何进程执行。然而,像任何文件输入/输出操作一样,这可能相当慢。

内存映射方法的优缺点

内存映射方法允许您直接访问控制 GPIOs 的电阻。内存映射非常快(比文件 I/O 快 1000 倍左右!),但只有具有根权限的进程才能使用它。

由于您的应用无法在没有一些严重权限更改的情况下以 root 权限执行,因此您将无法使用内存映射来访问 GPIOs。这实际上限制了您只能对应用使用文件输入/输出。

PacktHAL 为 GPIO 访问实现了内存映射和文件输入输出。如果您对这两种方法如何工作的低级细节感兴趣,请查看PacktHAL.tgz中的jni/gpio.c文件。

准备安卓系统使用 GPIO

第二章与安卓的接口中,您使用adb将两个预建文件从 PacktHAL 推送到您的安卓系统中。这两个文件BB-PACKTPUB-00A0.dtboinit.{ro.hardware}.rc配置你的安卓系统来启用特定的 GPIOs,并允许你的应用访问它们。

请记住,当我们谈论init.{ro.hardware}.rc文件时,我们指的是安卓文件系统根目录中的init.genericam33xx(flatteneddevice.tr文件。

BB-PACKTPUB-00A0.dtbo文件是一个设备树覆盖,它混合了 BBB 来支持本书中的所有示例。就 gpio 而言,这种覆盖将 P9.11 和 P9.13 引脚多路复用到 gpio 中。在PacktHAL.tgz文件中,叠加的源代码位于cape/BB-PACKTPUB-00A0.dts文件中。负责复用两个 GPIOs 的代码位于fragment@0内的bb_gpio_pins节点:

/* All GPIO pins are PULLUP, MODE7 */
bb_gpio_pins: pinmux_bb_gpio_pins {
    pinctrl-single,pins = <
        0x070 0x17  /* P9.11, gpio0_30, OUTPUT */
        0x074 0x37  /* P9.13, gpio0_31, INPUT */
    >;
};

bb_gpio_pins节点使用的十六进制值的详细信息超出了本书的范围。然而,一般的想法是,它们指定感兴趣的引脚、该引脚应被多路复用到哪种模式、关于上拉/下拉电阻的一些细节、它是输入还是输出引脚,以及是否应对信号进行任何偏斜调整。

什么是偏斜以及如何调整偏斜的细节超出了本书的范围。如果你想了解更多关于偏斜的知识,我们建议以维基百科关于这个主题的页面作为一个很好的起点(http://en.wikipedia.org/wiki/Clock_skew)。

启动时,该覆盖由init.{ro.hardware}.rc文件加载。内核然后知道哪些引脚被视为 GPIOs。加载覆盖后,init.{ro.hardware}.rc文件执行一些命令,通过导出来明确“解锁”这些 GPIO 文件,供应用使用。导出一个 GPIO 引脚会在/sys文件系统中创建一系列文件,这些文件可以读写以与该 GPIO 引脚交互。

通过导出 GPIO 引脚,然后通过chmod更改/sys文件系统中适当文件的权限,任何进程都可以读写 GPIOs。这正是init.{ro.hardware}.rc文件中的命令所做的,以允许安卓应用与 GPIOs 接口。init.{ro.hardware}.rc文件的以下部分执行导出和chmod操作:

# Export GPIOs 30 and 31 (P9.11 and P9.13)
write /sys/class/gpio/export 30
write /sys/class/gpio/export 31

# Make GPIO 30 an output
write /sys/class/gpio/gpio30/direction out
# Make GPIOs 30 and 31 writeable from the FS
chmod 777 /sys/class/gpio/gpio30/value
chmod 777 /sys/class/gpio/gpio31/value

每个 GPIO 都有一个特定的整数标识符,该标识符由 GPIO 所属的存储体及其在该存储体中的位置决定。在我们的示例中,与 P9.11 相混合的 GPIO 是存储体 0 中的第 30 个 GPIO,而 P9.13 是存储体 0 中的第 31 个 GPIO。这使得它们的整数标识符分别为 30 和 31。

GPIO 引脚 30 和 31 仅通过/sys文件系统可用,因为它们是通过init.{ro.hardware}.rc文件中的write命令显式导出的。除非也以相同的方式显式导出,否则其他 GPIO 引脚将无法通过文件系统获得。

这是一种非常不安全的允许 GPIO 访问的方式,因为它为我们可能不想直接访问的进程打开了 GPIOs。对于实验和原型制作,这不是问题。但是,您当然不应该在商业系统中这样做。除非你开发一个合适的、有特权的安卓管理器来处理 GPIO 资源,否则你必须允许所有进程访问 GPIO 文件,除非你将权限定制为只能由属于特定用户或组的应用使用。由于每个应用都被分配了自己的用户,所以在将应用的.apk文件安装到系统上后,您必须将 GPIOs chown 给适当的用户和组。

构建 GPIO 接口电路

在开始开发使用 GPIOs 进行通信的软件之前,您必须首先为 GPIOs 构建一个与之接口的硬件电路。在本章中,您将构建一个简单的电路,由一个 1k 欧姆的电阻、一个发光二极管和一个按钮开关组成。这些组件的零件号和供应商列在第 1 章安卓和 BeagleBone Black介绍中。在开始之前,在将任何东西连接到 BBB 的 P8/P9 连接器之前,请确保您拥有所有正确的部件,并从 BBB 上拔下所有电源(拔下电源和 USB 电缆)。

类型

不要拆你的电路!

本章中的 GPIO 电路是第 6 章创建完整接口解决方案中使用的更大电路的一部分。如果您按照下图中的位置构建电路(朝向试验板的顶部),您可以在构建本书中的其余电路时,简单地将 GPIO 组件和导线留在原位。这样,当你到达第六章时,它就已经建成并开始工作了。

构建电路

您将使用以下四个 BBB 引脚构建的电路接口:

  • P9.1(接地)
  • P9.3 (3.3 V)
  • P9.11 (GPIO)
  • P9.13 (GPIO)

P9.11 引脚配置为输出 GPIO,驱动 LED。P9.13 引脚被配置为输入 GPIO,其状态取决于施加到其上的输入电压。两个 GPIO 引脚都由BB-PACKTPUB-00A0.dtbo覆盖层配置,以使用内部上拉电阻。如果你不熟悉什么是上拉电阻,不用担心。出于这些示例的目的,这仅仅意味着如果没有任何东西连接到 GPIO 引脚,GPIO 的逻辑电平将不会在开和关之间“浮动”。相反,逻辑电平将被“上拉”到导通状态。

有兴趣进一步了解什么是上拉电阻及其工作原理吗?我们建议您查看这个关于上拉和下拉电阻的在线教程,网址为

试验板的两侧通常有两条垂直总线,几乎贯穿试验板的整个长度。这些总线用于为插入试验板的任何元件提供方便的电源和接地信号。

Constructing the circuit

完整的图形输入输出接口电路

现在我们可以开始构建我们的电路:

  1. 将 BBB 的接地(P9.1)和 3.3 V (P9.3)信号连接到试验板上的两条垂直总线。地面总线是朝向试验板中心的垂直总线。3.3 V 总线是朝向试验板边缘的垂直总线。

  2. 接下来,将发光二极管的阳极或正极引线连接到 P9.11。发光二极管具有极性,因此电流只会沿一个方向流过它们。电流从发光二极管较长的引线(阳极)流向较短的引线(阴极)。

  3. 如果发光二极管的引线被切割成相同的长度,并且你无法分辨哪根引线是哪根,请触摸发光二极管塑料外壳的边缘。外壳的边缘在阴极侧是平的,在阳极侧是圆的。只要阴极接地,阳极接 GPIO 引脚,LED 就会正常工作。

  4. You must limit the current drawn by the LED to ensure that you do not damage the GPIO pin, so place a 1K ohm resistor between the LED's cathode lead and the ground signal. Resistors do not have a polarity like LEDs do, so the direction that you connect it to the breadboard will not matter.

    如果您希望了解更多关于使用带发光二极管的限流电阻的信息,例如为任务选择合适的电阻,我们建议您阅读位于https://www.sparkfun.com/tutorials/219的 SparkFun 教程。

  5. 现在 LED 和电阻已经连接到 BBB,你必须连接按钮开关。不同的开关有不同数量的导线,但是我们建议您使用的开关总共有四根导线。这些引线形成两对,每对两个引线。每对导线中的两根导线总是相互电连接,但只有当按下按钮时,一对导线才会与另一对导线电连接。开关的两侧是光滑的,另外两侧各有两条凸出的引线。开关单侧的两条突出的引线属于不同的引线对。在开关的一侧选择两根引线,将一根引线连接到 P9.13,另一根引线连接到试验板的接地总线。

你的电路现在完成了。对照完整的 GPIO 接口电路图,仔细检查您的接线,确保一切连接正确。

检查你的线路

一旦你完成了 GPIO 电路的接线,你就应该对其进行测试,以确保其正常工作。幸运的是,您可以通过进入 BBB 并使用导出的 GPIO 引脚文件轻松做到这一点。我们将假设您正在使用adb进入安卓系统,但是使用 FTDI 访问控制台外壳将以完全相同的方式工作。

类型

如何使用 FTDI 电缆?

如果你从未使用过 FTDI 电缆与你的 BBB 通信,在www.elinux.orgwiki(由 BeagleBoard.org 工作人员维护)上有一个页面可以帮助你入门,这个页面就是http://elinux.org/Beagleboard:Terminal_Shells

在本书中,我们将只使用 USB 电缆和 ADB 外壳来访问 BBB。然而,学习如何使用 FTDI 来监控和排除您的 BBB 故障确实会派上用场。

将电源连接到您的 BBB,然后使用 USB 电缆将 BBB 连接到您的开发系统。炮击 BBB 后,使用以下步骤开始测试您的 GPIO 电路:

  1. 进入固定到 P9.11 的 GPIO 引脚目录(GPIO 引脚 30):

    root@beagleboneblack:/ # cd /sys/class/gpio/gpio30
  2. 使用echo命令,通过强制该 GPIO 的状态为 1 来打开 LED:

    root@beagleboneblack:/ # echo 1 > value
  3. 指示灯现在将亮起。使用echo命令,通过强制该 GPIO 的状态为 0 来关闭 LED:

    root@beagleboneblack:/ # echo 0 > value
  4. 指示灯现在将关闭。进入固定到 P9.13 的 GPIO 引脚目录(GPIO 引脚 31):

    root@beagleboneblack:/ # cd /sys/class/gpio/gpio31
  5. 使用cat命令检查按钮开关的当前状态。执行此命令时,确保您没有按下按钮:

    root@beagleboneBlack:/ # cat value
    1
  6. Now, execute the following cat command while holding down the button. You should type the entire command, press the button, and then hit the Enter key to enter the command while still holding the button down:

    root@beagleboneblack:/ # cat value
    0

    由于电路的接线方式,按钮值看起来相反。当按钮未按下时,P9.13 上的上拉电阻将把 GPIO 的值拉至1。当按下按钮时,P9.13 引脚连接到接地信号,并将 GPIO 变为0

如果您看到指示灯亮起和熄灭,并且按下和松开开关时返回了正确的值,则说明您已经正确连接了电路。如果指示灯没有亮起,请确保您没有意外更换指示灯的阳极和阴极引线。如果开关总是返回 0 值,请确保将开关上正确的一对导线连接到接地信号总线和 P9.13

在你的应用中包含打包

在深入研究使用 PacktHAL 与 GPIOs 接口之前,您必须了解如何在您的应用中包含 PacktHAL 支持。我们将引导您完成将打包代码添加到您的应用中,然后构建它的过程。PacktHAL 将与您的应用一起打包在.apk应用中,作为共享库。库的源代码存在于应用的项目目录中,但它是与应用的 Java 代码分开构建的。在应用可以将其包含在.apk应用中并使用它之前,您必须手动构建 PacktHAL 共享库。

我们在本书包含的每个示例应用项目中都包含了一个预构建版本的 PacktHAL 库,因此您可以立即开始构建和运行示例应用,而无需担心构建 PacktHAL 的细节。一旦您开始创建自己的定制应用,并为自己的硬件项目修改 PacktHAL,您将需要了解如何从源代码构建 PacktHAL。

理解 Java 原生接口

安卓应用是用 Java 写的,但是 PacktHAL 里面的功能是用 C 原生代码写的。本机代码是编译成本机二进制(如共享库或可执行文件),然后由 Android OS 直接执行的代码。本机代码是使用安卓 NDK 系统提供的编译器工具链构建的。本机二进制文件不像安卓应用的“一次构建,随处运行”字节码那样可移植,但它们可以以 Java 代码无法实现的方式用于低级接口。与 Java 字节码不同,Java 字节码可在任何具有适当虚拟机的平台上执行,本机代码是为一种特定的硬件架构(如 ARM、x86 或 PowerPC)编译的,并且只能在该架构上执行。

以本机代码实现的函数通过 Java 本机接口 ( JNI )从应用的 Java 代码中调用。JNI 是一种流行的接口机制,Java 应用使用它来与本机 C/C++ 代码交互。在其他特性中,JNI 被用来将 Java 数据类型翻译成 C 数据类型,反之亦然。

比如考虑 Java String类型。虽然 Java 有一个String实现,但是在 C 语言中没有等效的类型。在被 C 代码使用之前,字符串必须被适当地转换成兼容的类型。每个 Java 类型在 C 语言中都由一系列等价类型表示,如jintjstringjboolean,这些类型在安卓 NDK 提供的标准jni.h头文件中定义。

创建一个使用 PacktHAL 的新应用项目

以下步骤演示了如何创建包含 PacktHAL 的新自定义应用:

  1. 启动 Eclipse ADT,选择菜单选项文件,然后新建,然后安卓应用项目

  2. In the New Android Application dialog, enter myapp into the Application Name field. This will automatically populate the Project Name and Application Name fields. Change the Minimum Required SDK, Target SDK, and Compile With fields to API 19: Android 4.4. The theme field can be left alone or changed to whichever theme you would like for your app. When finished, click on the Next button.

    Creating a new app project that uses PacktHAL

    新的安卓应用屏幕

  3. 继续浏览连续的对话框屏幕,保留每个屏幕的默认设置,直到您单击最终屏幕上的完成按钮。

为您的新应用创建的默认活动的名称是MainActivity。创建新项目后,您的新myapp项目的文件夹结构将位于myapp ( $PROJECT)目录中,其目录结构类似于以下内容:

myapp
  |
  +----.settings/
  +----img/
  +----bin/
  +----gen/
  +----libs/
  +----res/
  +----src/
  +----...

首次创建应用后,将创建几个新文件夹来保存构建过程中创建的各种中间文件。一旦你创建了你的应用,你必须添加打包代码并编译它。

在窗户下搭建走廊

PacktHAL 必须被构建到一个库中,并包含在应用的项目代码库中,以便应用使用。假设您在c:\中解压缩了PacktHAL.tgz文件,您可以使用以下过程将打包代码复制到应用的项目目录($PROJECT)中:

  1. 打开文件浏览器窗口,浏览至$PROJECT目录。
  2. 打开第二个文件浏览器窗口,浏览至c:\PacktHAL
  3. 右键单击c:\PacktHAL目录中的jni目录,从上下文菜单中选择复制
  4. 右键单击$PROJECT目录窗口中任何方便的空白区域,然后从上下文菜单中选择粘贴

现在jni\目录已经存在于你的$PROJECT目录中,你可以使用安卓 NDK 构建一个打包器。假设您在c:\android-ndk安装了安卓 NDK,您可以使用以下过程构建 PacktHAL:

  1. 启动cmd.exe进入命令提示窗口。使用命令提示符,进入$PROJECT目录:

    c:\> cd $PROJECT\jni
  2. 使用安卓 NDK 构建打包库:

    c:\$PROJECT\jni> c:\android-ndk\ndk-build
    [armeabi] Compile thumb  : packtHAL <= jni_wrapper.c
    [armeabi] Compile thumb  : packtHAL <= gpio.c
    [armeabi] Compile thumb  : packtHAL <= fram.c
    [armeabi] Compile thumb  : packtHAL <= bmp183.c
    [armeabi] SharedLibrary  : libpacktHAL.so
    [armeabi] Install        : libpacktHAL.so => libs/armeabi/libpacktHAL.so

打包库现已构建,并以文件$PROJECT\libs\armeabi\libpacktHAL.so的形式出现在您的项目中。

在 Linux 下构建 PacktHAL

PacktHAL 必须被构建到一个库中,并包含在应用的项目代码库中,以便应用使用。假设您在$HOME目录中解压缩了PacktHAL.tgz文件,您可以使用以下命令将打包代码复制到应用的项目目录($PROJECT)中:

$ cd $PROJECT
$ cprf $HOME/PacktHAL/jni .

现在jni目录已经存在于你的$PROJECT目录中,你可以使用安卓 NDK 构建一个打包器。假设您在$HOME/android-ndk安装了安卓 NDK,您可以使用以下过程构建 PacktHAL:

  1. 转入$PROJECT/jni目录:

    $ cd $PROJECT/jni
  2. 使用安卓 NDK 构建打包库:

    $ ./$HOME/android-ndk/ndk-build
    [armeabi] Compile thumb  : packtHAL <= jni_wrapper.c
    [armeabi] Compile thumb  : packtHAL <= gpio.c
    [armeabi] Compile thumb  : packtHAL <= fram.c
    [armeabi] Compile thumb  : packtHAL <= bmp183.c
    [armeabi] SharedLibrary  : libpacktHAL.so
    [armeabi] Install        : libpacktHAL.so => libs/armeabi/libpacktHAL.so

PacktHAL 库现已构建,并以$PROJECT/libs/armeabi/libpacktHAL.so文件的形式出现在您的项目中。

探索 GPIO 示例应用

在这个部分,你将检查在 BBB 上执行 GPIO 接口的示例安卓应用。本应用的目的是演示如何使用 PacktHAL 从实际应用中执行 GPIO 读写过程。PacktHAL 提供了一组接口功能,您将使用这些功能在您的安卓应用中与 GPIOs 一起工作。这些函数允许您读取输入 gpio 的值并设置输出 gpio 的值。硬件接口的低级细节在 PacktHAL 中实现,因此您可以快速轻松地让您的应用与 GPIOs 交互。

在深入研究 GPIO 应用的代码之前,您必须将代码安装到您的开发系统中,并将应用安装到您的安卓系统中。该应用的源代码以及预编译的.apk包位于chapter3.tgz文件中,该文件可从该书的网站下载。

在 Windows 下安装应用和源码

一旦你下载了chapter3.tgz文件,你必须解压缩它。我们将假设您下载后已经将c:\的根目录chapter3.tgz复制到中,并将从那里解压缩。我们将您的工作区目录称为$WORKSPACE

我们将假设你的adb.exe二进制在你当前的路径中。如果不是,使用adb.exe二进制文件的完整路径调用adb:

  1. 打开文件资源管理器窗口并导航到该目录。
  2. 在文件浏览器中右键单击chapter3.tgz文件,选择提取此处

名为c:\gpio的目录现在已经存在,它包含了 GPIO 示例应用的所有文件。您必须将该项目导入您的 Eclipse ADT 工作区:

  1. 启动 Eclipse ADT。
  2. 打开文件菜单,选择导入
  3. 导入对话框中,展开安卓文件夹,突出显示现有安卓代码进入工作区。对话框底部的下一步按钮将激活。点击它继续。
  4. 导入项目对话框中,在根目录文本字段中键入c:\gpio。然后,点击刷新按钮。 gpio 项目将出现在要导入的项目列表中。
  5. 点击全选按钮,然后选择将项目复制到工作区的复选框。
  6. 点击完成按钮,将gpio应用项目导入您的工作区,并将c:\gpio目录复制到您的$WORKSPACE目录。

GPIO 应用的所有项目文件现在都位于那个gpio目录中。在$WORKSPACE\gpio\bin目录中提供了该应用的预建.apk包。您可以使用 adb将此.apk软件包直接安装到您的安卓系统上:

  1. 启动cmd.exe进入命令提示窗口。使用命令提示符,进入$WORKSPACE\gpio\bin目录:

    c:\> cd $WORKSPACE\gpio\bin
  2. 使用adb devices命令

    c:\$WORKSPACE\gpio\bin> adb devices
    List of devices attached
    BBBAndroid      device

    确认adb可以看到你的 BBB

  3. 通过adb :

    c:\$WORKSPACE\gpio\bin> adb install -d gpio.apk

    中的install命令将gpio.apk安装到您的安卓系统中

  4. 如果您已经安装过一次gpio.apk应用,现在收到INSTALL_FAILED_ALREADY_EXISTS的故障信息,请使用adb重新安装gpio.apk :

    c:\$WORKSPACE\gpio\bin> adb install -d -r gpio.apk

gpio.apk应用现已安装在你的安卓系统上,该应用的源代码现已安装在你的 Eclipse ADT 工作区中。

在 Linux 下安装应用和源码

一旦下载了chapter3.tgz文件,就必须解压并解压。我们将假设您在下载后已将chapter3.tgz复制到您的$HOME目录,并将从那里解压缩。我们将您的工作区目录称为$WORKSPACE

使用 Linux tar命令解压缩chapter3.tgz文件:

$ cd $HOME
$ tarxvf chapter3.tgz

名为gpio的目录现在存在于您的$HOME目录中,它包含 gpio 示例应用的所有文件。您必须将该项目导入您的 Eclipse ADT 工作区,如下所示:

  1. 启动 Eclipse ADT。
  2. 打开文件菜单,选择导入
  3. 导入对话框中,展开Android文件夹并突出显示将现有安卓代码导入工作区。对话框底部的下一步按钮将激活。点击它继续。
  4. 导入项目对话框中,在根目录文本字段中键入$HOME/gpio(用完整路径代替$HOME)。然后,点击刷新按钮。 gpio 项目将出现在要导入的项目列表中。
  5. 点击全选按钮,然后选择将项目复制到工作区的复选框。
  6. 点击完成按钮,将 gpio app 项目导入您的工作区,并将$HOME/gpio目录复制到您的$WORKSPACE目录。

该应用的所有项目文件现在都位于$WORKSPACE/gpio目录中。在gpio/bin目录中提供了 gpio 项目的预建.apk包。您可以使用adb将此.apk软件包直接安装到您的安卓系统上:

  1. 进入gpio项目的bin目录:

    $ cd $WORKSPACE/gpio/bin
  2. 使用adb devices命令

    $ adb devices
    List of devices attached
    BBBAndroid      device

    确认adb可以看到你的 BBB

  3. 通过adb :

    $ adb install -d gpio.apk

    中的install命令将gpio.apk安装到您的安卓系统中

  4. 如果您已经安装过一次gpio.apk应用,现在收到INSTALL_FAILED_ALREADY_EXISTS的故障信息,请使用adb重新安装gpio.apk :

    $ adb install -d -r gpio.apk

gpio.apk应用现在安装在你的安卓系统上,应用的源码现在安装在你的 Eclipse ADT 工作区。

应用的用户界面

在安卓系统上启动gpio应用,查看该应用的(UI)。如果您使用的是触摸屏 cape,只需轻触屏幕上的 gpio 应用图标即可启动该应用并与其 UI 进行交互。如果您正在使用 HDMI 进行视频,请将一个 USB 鼠标连接到 BBB 的 USB 端口,并使用鼠标单击 gpio 应用图标来启动该应用。

该应用使用一个非常简单的用户界面与 GPIOs 交互。就这么简单,app 唯一的活动就是默认MainActivity。用户界面仅由三个按钮和文本视图组成。

The app's user interface

GPIO 示例应用屏幕

轮询按钮状态 按钮检查按钮开关的当前状态,并更新按钮状态文本视图的值以报告该状态。在第一次按下轮询按钮状态按钮之前,开关状态将报告为未知开灯按钮如果还没有亮,会打开 LED,而关灯按钮会关闭 LED。

文本视图在res/layout/activity_main.xml中有一个与之相关联的标识,因此应用可以通过编程方式更新文本视图的值:

<TextViewandroid:text="@string/button_state"
  android:id="@+id/button_state" />

三个按钮中的每一个都定义了一个onClick()处理程序:

<Buttonandroid:text="@string/button_poll"
  android:onClick="onClickButtonPollStatus" />
<Buttonandroid:text="@string/button_lighton"
  android:onClick="onClickButtonLightOn" />
<Buttonandroid:text="@string/button_lightoff"
  android:onClick="onClickButtonLightOff" />

每个onClick()处理程序将触发其中一个 PacktHAL GPIO 功能来读取一个 GPIO 的状态或将一个新的状态写入一个 GPIO。

如果你需要复习各种安卓用户界面元素的细节,网上有几个资源可以帮助你。我们建议您从位于http://developer.android.com/guide/topics/ui/index.html的安卓开发者官方网站开始。

调用 PacktHAL 函数

PacktHAL 中的 GPIO 接口功能在四个 C 函数中实现:

  • openGPIO()
  • readGPIO()
  • writeGPIO()
  • closeGPIO()

这些功能的原型位于应用项目内的jni/PacktHAL.h头文件中:

extern int openGPIO(const int useMmap);
extern int readGPIO(const unsigned int header, const unsigned int pin);
extern int writeGPIO(const unsigned int header,
    const unsigned int pin, const unsigned int value);
extern void closeGPIO(void);

理想情况下,您可以将 PacktHAL 共享库加载到您的应用中,然后直接调用库函数来控制 GPIOs。示例应用实际上通过System.loadLibrary()调用加载了打包库,但是事情变得不那么简单了,因为这些 C 函数不能被直接调用。您必须指定调用时实际调用 C 函数的 Java 方法。

*MainActivity类用native关键字指定了四种方法来调用MainActivity.java中的 PacktHAL C 函数:

public class MainActivity extends Activity {
  private native boolean openGPIO();
private native void closeGPIO();
private native boolean readGPIO(int header, int pin);
private native void writeGPIO(int header, int pin, int val);

static {
System.loadLibrary("packtHAL");
}
  …
}

MainActivity中指定的这四个 Java 方法实际上并不是直接映射到 PacktHAL 中同名的 C 函数。注意MainActivity中的 GPIO 方法都是类范围内的private native。任何用native关键字定义的方法在被调用时都会试图调用本地的 JNI 包装函数。然而,被调用的 JNI 包装函数的命名遵循一些非常具体的规则,这些规则代表了它的 Java 端方法的范围。下图显示了这些 JNI 包装函数如何最终调用 PacktHAL 内部的 GPIO 接口函数:

Calling the PacktHAL functions

主活动方法和它们调用的打包硬件接口函数

名为name()MainActivity类中的每个native方法将使用 JNI 调用名为Java_com_packt_gpio_MainActivity_name()的 JNI 包装函数。这个包装函数的名称是通过用下划线替换应用的完全限定名中的每个.来确定的。函数名的Java_前缀告诉安卓,该函数是通过一个 Java 类中的方法调用的。这个 JNI 命名惯例有一些例外,但是这个一般规则将帮助你通过大多数情况。

类型

我需要了解 JNI 的一切才能制作自己的安卓界面项目吗?

不完全是。使用 JNI 可能会非常令人困惑,许多书籍和教程都致力于非常详细地描述它。现在,不要担心不知道关于 JNI 的一切。当你花了一些时间在安卓系统下尝试硬件接口时,你可以重新讨论这个话题,了解更多关于 JNI 如何工作的细节。在这本书里,我们将集中向你展示足够多的关于 JNI 的信息,让你开始学习。

例如,com.packtpub.gpio示例应用的MainActivity类中的 Java openGPIO()方法使用 JNI 调用包装器 C 函数Java_com_packtpub_gpio_MainActivity_openGPIO()。这有点令人困惑,但仍然非常容易管理。PacktHAL 在jni/packt_native_gpio.c文件中实现了这些 JNI 包装器 C 函数。查看这个源文件,可以看到 PacktHAL 中的Java_com_packtpub_gpio_MainActivity_openGPIO()函数调用 PacktHAL 中的openGPIO() C 函数的地方:

jboolean Java_com_packt_gpio_MainActivity_openGPIO(JNIEnv *env,
   jobject this)
{
  jboolean ret = JNI_TRUE;
  if ( openGPIO(0) == 0 ) {
    __android_log_print(ANDROID_LOG_DEBUG, PACKT_NATIVE_TAG,
          "GPIO Opened.");
  } else {
    __android_log_print(ANDROID_LOG_ERROR, PACKT_NATIVE_TAG,
          "openGPIO() failed!");
    ret = JNI_FALSE;
  }
  return ret;
}

为什么不干脆去掉单独的openGPIO() C 功能,把所有的硬件接口代码都放在Java_com_packt_gpio_MainActivity_openGPIO()里面呢?像 PacktHAL 中的openGPIO()这样的功能,一旦你让它们正常工作,通常不会改变,你可以在 Linux 和安卓下使用这些相同的功能。像Java_com_packt_gpio_MainActivity_openGPIO()这样的包装函数将根据它们如何以及在哪里从应用的 Java 代码中被调用来更改它们的名称和实现细节。最好隔离不会改变自身功能的功能。这避免了您在定制或重命名通过 JNI 调用的函数时意外破坏某些东西。

请记住,您的应用中的一个 Java 方法,如MainActivity类中的openGPIO(),会发出一个 JNI 调用,以调用一个名称冗长、混乱的函数,如Java_com_packt_gpio_MainActivity_openGPIO()。然后,JNI 包装函数将调用一个实际控制硬件的打包函数,例如openGPIO()。从应用开发者的角度来看,一旦梳理出 JNI 包装器函数的细节,几乎就像直接从 Java app 代码中调用控制硬件的 C 函数!

使用 PacktHAL GPIO 功能

现在您已经看到了如何从 Java 中调用 PacktHAL GPIO 函数,您将看到这些函数的功能以及如何使用它们。

openGPIO()功能初始化你的应用对 GPIOs 的访问。该功能为您提供了两种不同的 GPIO 接口方法,您可以使用openGPIO()功能的useMmap参数选择其中一种方法。这两种方法是文件输入/输出(通过将useMmap设置为 0)和内存映射(通过将useMmap设置为任意非零数字)。要从一种接口方法更改为另一种接口方法,您必须调用closeGPIO() 来关闭 PacktHAL 的 GPIO 部分,然后用不同的值再次调用openGPIO()``useMmap

进程必须作为root运行,以使用内存映射来直接访问 GPIO 控制电阻。由于应用不能以根用户身份运行,JNI 包装函数总是将0作为useMmap参数传递给openGPIO(),以强制使用文件输入/输出与 GPIOs 交互。因此,MainActivity类中的openGPIO()方法不接受任何论点。

示例应用从MainActivity类的onCreate()方法调用openGPIO()方法:

protected void onCreate(Bundle savedInstanceState) {
  ... //Existing statements    
  TextView tv = (TextView) findViewById(R.id.button_state);
tv.setText("Button State: UNKNOWN");

   if(openGPIO() == false) {
      Log.e("com.packt", "Unable to open GPIO.");
        finish();
   }
}

closeGPIO()方法的补充调用是由MainActivity类的onDestroy()方法发出的:

protected void onDestroy() {
   closeGPIO();
}

readGPIO()方法读取特定输入 GPIO 的状态。PacktHAL readGPIO()功能和MainActivity中的readGPIO()方法采用相同的两个参数。第一个参数是 BBB 上的连接器编号(8 或 9),第二个参数是该连接器上的引脚位置(1 至 42)。从PollStatus按钮的onClick()处理程序中调用readGPIO()方法:

public void onClickPollStatus(View view) {
   String status = readGPIO(9, 13) == true ? "ON" : "OFF";
TextView tv = (TextView) findViewById(R.id.button_state);
tv.setText("Button State: " + status);
}

onClickPollStatus()中,readGPIO()方法调用正在读取 GPIO 引脚 P9.13 的状态。这是您连接到按钮开关的 GPIO 引脚。如果在调用readGPIO()方法时按下开关,则返回true。否则,false被退回。

writeGPIO()方法用于设置输出 GPIO 的状态。PacktHAL writeGPIO()功能和MainActivity中的writeGPIO()方法都采用三个参数。第一个参数是 BBB 上的连接器编号(8 或 9),第二个参数是该连接器上的引脚位置(1 到 42),第三个参数是要设置的值(0 或 1)。从LightOnLightOff按钮的onClick处理程序中调用writeGPIO()方法:

public void onClickButtonLightOn(View view) {
   writeGPIO(9, 11, 1);
}

public void onClickButtonLightOff(View view) {
   writeGPIO(9, 11, 0);
}

在这两个onClick()处理程序中,正在设置的 GPIO 是 P9.11。这是您连接到 LED 的 GPIO 引脚。onClickButtonLightOn()方法将 GPIO 设置为 1,打开发光二极管。同样,onClickButtonLightOff()方法将 GPIO 设置为 0,关闭 LED。

类型

你准备好迎接挑战了吗?

既然您已经看到了 gpio 应用的所有部分,为什么不更改它以添加新的功能呢?对于挑战,尝试改变应用,只使用一个按钮来切换发光二极管的状态。如果发光二极管当前关闭,按下按钮将打开它,反之亦然。我们已经在chapter3_challenge.tgz文件中提供了一个可能的实现,该文件可以从该书的网站上下载。

总结

在本章中,我们向您介绍了 GPIOs 及其工作原理。您构建了一个使用 GPIOs 进行输入和输出的电路,然后对电路进行了一些基本测试,以确保电路构建正确,并且内核能够通过文件系统与电路交互。您还了解了 PacktHAL init.{ro.hardware}.rc文件和BB-PACKTPUB-00A0.dtbo设备树覆盖图中负责配置 GPIOs 并使其可供应用使用的部分。

我们向您展示了如何将 PacktHAL 添加到新创建的应用项目中,以及如何使用 Android NDK 构建 PacktHAL。然后,您学习了 JNI 如何通过 JNI 包装函数将 PacktHAL 集成到您的 Java 应用中,并探索了如何从应用中调用和使用 PacktHAL 的每个 GPIO 函数。

在下一章中,您将学习如何将 I2C 总线设备集成到您的应用中,并开始与比 GPIOs 的基本开/关逻辑复杂得多的硬件进行交互。*