Android Things 是 2016 年 12 月份Google 推出的物联网操作系统。它是通过 Google 开发的物联网操作系统 Brillo 改进优化的。
Android Things使用和Android开发一样的工具,Android框架和Google APIs,这些都使得开发物联网变得更简单。
Android things的平台架构如下:
为嵌入式设备开发app比为手机或者电脑开发更加拉近开发者跟硬件外设和驱动的距离。此外,嵌入式设备对用户来说通常代表了一个单独的APP体验。接下来看看Android 开发和Android Things开发的不同。
Android Things继承了Android核心框架层并且通过支持包提供额外的APIs。这些APIs允许apps与新型的硬件之间交互,但是这些硬件并不存在于移动设备上。
Android Things平台仍然为单一应用程序使用。系统app不存在,并且你的app将会自动启动来给你的用户带来沉浸式体验。
外围I/O接口API
外围I/O接口API使你的应用程序与传感器和驱动器之间通过工业标准协议和接口进行交互,它支持GPIO,PWM,I2C,SPI,UART.
用户驱动API
用户驱动继承已经存在的Android框架服务,它允许app注入硬件事件到框架层来使得其他app可以通过Android标准API访问这些事假。
核心应用程序包
Android Things不包括一套标准的系统app以及content provider。开发者要避免使用通用intents(common intents)以及以下content provider API在你的APP中。
显示是可选的
Android Things使用与传统的Android app一样的UI 套件来支持图形用户界面。在图形模式下,应用程序的窗口将会全屏显示。Android Things不包括系统状态栏或者导航栏,使应用程序完全控制用户的视觉体验。
然而,Android Things并不要求一定有一个显示设备。在不存在图形显示的设备上,activities任然是你的Android Things APP的一个基础的组件。这是因为Android Things 框架层会传递所有的输入事件到获取了焦点的前台activity。你的app无法通过其他任何应用程序组件接收按键事件或者动作事件。
支持主界面
Android Things期望一个应用程序在它的manifest中暴露一个 主界面 作为主入口,这会使得系统在启动时自动启动这个入口。这个activity必须包含一个同时拥有 CATEGORY_DEFAULT 和 IOT_LAUNCHER的intent filter。
为了开发简单,这个主界面应该包含一个 CATEGORY_LAUNCHER intent filter,这样Android Studio在部署或调试的时候可以将它作为一个
默认的activity启动起来。
不支持运行时申请权限,因为嵌入式设备不能保证有一个界面来弹出运行时对话框。在你的app的manifest文件中申明权限。在manifest中申明的所有的正常的和敏感的权限在安装时都会授权。
在Android Things中不支持通知,所以避免在你的应用程序里调用 NotificationManager接口。
购买硬件
进行Android Things物联网开发,首先要准备一些硬件设备,Android Things目前支持四款硬件平台:
这些硬件可以在网上买到,当然仅仅有开发板还不行,还需要一些其他的配件,比如网线,HDMI线,电源线,SD卡,摄像头,显示屏等等,不过刚开始不用一次性全部买齐,等到开发时缺什么再去买什么,这样可以避免买错。笔者买的是树莓派3b(Raspberry Pi 3 b)的一个套餐(散热片,HDMI线,网线,16G SD卡,主板,风扇,外壳,电源线)300多。
烧写Android Things系统
首先下载各个开发板对应的Android Things系统镜像:
平台 | Android Things包 | 大小 | SHA-1 校验和 |
---|---|---|---|
Inter Edison | androidthings_edison_devpreview_2.zip | 249 MB (261384078bytes) |
0621e4a890c0cc25d6bb16aed87aee8d5ec819a7 |
Inter Joule | androidthings_joule_devpreview_2.zip | 267 MB (280434440 bytes) |
b11317de771a85a0bbee514c383a8974d87866b2 |
NXP | androidthings_imx6ul_pico_devpreview_2.zip | 239 MB (250738643 bytes) |
6571f00a785cd47f404722c8ae3ad63c821e5865 |
Raspberry Pi | androidthings_rpi3_devpreview_2.zip | 244 MB (250738643 bytes) |
b8790030c1bb889b65b0222e462f9911fc0f0a71 |
针对不同的开发板,有不同的烧写方法,具体如下:
先决条件
1. 下载或者更新最新版本的Android Studio。
2. 更新你的SDK tools到24及以上(更新SDK tools允许你去构建和测试Things的app)
3. 更新你的SDK到Android 7.0(API 24)或者更高(更新后的平台为Things app提供新的API)
4. 创建或者更新你的项目的目标为Android 7.0(API level 24)或者更高。
5. 按照创建Android项目一样创建Things项目。
添加依赖包
Android Things设备通过支持包暴露APIs,这些支持包不属于Android SDK的一部分。要在你的Android Things的APP中添加Things支持包:
1. 在你的APP级别的build.gradle文件的dependencies 代码块中添加一下依赖:
dependencies {
...
provided 'com.google.android.things:androidthings:0.2-devpreview'
}
2.在你的app的manifest文件中添加Android Things的共享库:
...
注意:添加 provided 依赖是为了确保构建工具在编译时不会复制共享库到apk中。添加
申明主界面
一个应用程序要想在一个嵌入式设备上运行,必须在它的manifest文件中申明一个activity来作为设备启动后程序的主入口。提供一个包含以下元素的intent filter:
致此,硬件环境和软件环境已经搭建好,可以使用Android Things中的API了。
Android Things提供外围I/O接口APIs 使用工业标准协议和接口来与传感器和驱动器进行交互。
协议名称 | 传输类型 | 线缆数 | 外设数量 | 传输速度 |
---|---|---|---|---|
I2C | 同步 | 2 | 最大127 | 速度较低 |
SPI | 同步 | 4+ | 无限 | 速度最高 |
UART | 异步 | 2或者4 | 1 | 速度中等 |
通用输入输出(GPIO)针脚提供了一个可编程的接口来读取一个二进制输入设备的状态(比如一个按钮开关)或者控制二进制输出设备的打开或者关闭状态(比如一个LED灯)。
你可以配置具有高或低状态的 GPIO针脚作为输入或者输出。如果作为一个输入,一个外部源决定它的状态,并且你的app可以读取当前值或者对状态的变化作出反应。如果作为一个输出,你的app可以配置针脚的状态。
注意:为避免损坏GPIO针脚,进行电线连接之前务必查看你的硬件的输入和输出的范围。
为了打开一个到GPIO端口的连接,你应该知道唯一的端口名。在开发的初期阶段,或者当连接一个app到一个新设备时,通过 PeripheralManagerService 使用 getGpioList(): 方法查看所有可用的端口名称是很有用的。
PeripheralManagerService manager = new PeripheralManagerService();
List<String> portList = manager.getGpioList();
if (portList.isEmpty()) {
Log.i(TAG, "No GPIO port available on this device.");
} else {
Log.i(TAG, "List of available ports: " + portList);
}
一旦你知道目标端口的名称,使用 PeripheralManagerService 来连接这个端口。当你完成与GPIO端口的交互后,你应该关闭连接来释放资源。另外,你不能打开一个新的连接到同样的端口直到现有的连接已经关闭。为了关闭连接,使用端口的 close() 方法。
public class HomeActivity extends Activity {
// GPIO Pin Name
private static final String GPIO_NAME = ...;
private Gpio mGpio;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the GPIO
try {
PeripheralManagerService manager = new PeripheralManagerService();
mGpio = manager.openGpio(GPIO_NAME);
} catch (IOException e) {
Log.w(TAG, "Unable to access GPIO", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mGpio != null) {
try {
mGpio.close();
mGpio = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close GPIO", e);
}
}
}
}
读取一个GPIO端口作为输入:
1. 使用 setDirection() 的 DIRECTION_IN 参数配置一个端口作为输入端口。
2. 通过使用ACTIVE_HIGH或者ACTIVE_LOW调用setActiveType() 方法配置返回为true时是高电压还是低电压信号。
3. 通过调用getValue()方法访问当前的状态。
下面的代码向你展示了如何将高电压信号与一个输入返回true值关联起来。
public void configureInput(Gpio gpio) throws IOException {
// Initialize the pin as an input
gpio.setDirection(Gpio.DIRECTION_IN);
// High voltage is considered active
gpio.setActiveType(Gpio.ACTIVE_HIGH);
...
// Read the active high pin state
if (gpio.getValue()) {
// Pin is HIGH
} else {
// Pin is LOW
}
}
一个被配置为输入的GPIO端口可以当它的状态在高和低之间变化时通知你的app。你只需要注册这些变更事件的监听器即可:
1. 关联一个GpioCallback到现有的端口连接。
2. 调用 setEdgeTriggerType()方法申明哪一种状态的改变会触发一个中断事件。边沿触发支持一下四种类型:
3.在onGpioEdge()方法中返回true时表示监听器将会继续接收端口的状态变化事件。
下面的代码在给定的输入端口上为所有的状态变化注册一个中断事件监听器:
public void configureInput(Gpio gpio) throws IOException {
// Initialize the pin as an input
gpio.setDirection(Gpio.DIRECTION_IN);
// Low voltage is considered active
gpio.setActiveType(Gpio.ACTIVE_LOW);
// Register for all state changes
gpio.setEdgeTriggerType(Gpio.EDGE_BOTH);
gpio.registerGpioCallback(mGpioCallback);
}
private GpioCallback mGpioCallback = new GpioCallback() {
@Override
public boolean onGpioEdge(Gpio gpio) {
// Read the active low pin state
if (mDevice.getValue()) {
// Pin is LOW
} else {
// Pin is HIGH
}
// Continue listening for more interrupts
return true;
}
@Override
public void onGpioError(Gpio gpio, int error) {
Log.w(TAG, gpio + ": Error event " + error);
}
};
4.当你的app不再需要监听传入的事件时,应该取消注册任何中断处理器。
public class HomeActivity extends Activity {
private Gpio mGpio;
...
@Override
protected void onStart() {
super.onStart();
// Begin listening for interrupt events
mGpio.registerGpioCallback(mGpioCallback);
}
@Override
protected void onStop() {
super.onStop();
// Interrupt events no longer necessary
mGpio.unregisterGpioCallback(mGpioCallback);
}
}
以编程方式控制GPIO端口的状态:
1. 使用setDirction()方法的DIRECTION_OUT_INITIALLY_HIGH 或者DIRECTION_OUT_INITIALLY_HIGH 模式来配置一个端口作为输出端口。这些方法确保在配置的同时也正确的设置端口的初始状态。
2. 通过使用ACTIVE_HIGH或者ACTIVE_LOW调用setActiveType() 方法配置返回为true时是高电压还是低电压信号。
3. 通过setValue()方法设置当前状态。
下面的代码为你展示如何设置一个输出端口的初始状态为高,然后通过使用setValue()方法切换它的状态为低。
public void configureOutput(Gpio gpio) throws IOException {
// Initialize the pin as a high output
gpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_HIGH);
// Low voltage is considered active
gpio.setActiveType(Gpio.ACTIVE_LOW);
...
// Toggle the value to be LOW
gpio.setValue(true);
}
PWM是用于使用数字输出引脚将比例控制信号应用于外部设备的常用方法。例如:伺服机使用输入PWM信号的脉冲宽度来确定它们的旋转角度。LCD显示屏根据PWM信号的平均值来调整它们的亮度。
PWM是一个根据给定的频率和占空比震荡的数字(即方波)信号:
为了打开一个到PWM端口的连接,你需要知道那个唯一的PWM名称。在开发的初始阶段,或者移植app到一个新硬件,使用PeripheralManagerService 的getPwmList()方法获取所有可用PWM的端口名称是很帮助的:
PeripheralManagerService manager = new PeripheralManagerService();
List<String> portList = manager.getPwmList();
if (portList.isEmpty()) {
Log.i(TAG, "No PWM port available on this device.");
} else {
Log.i(TAG, "List of available ports: " + portList);
}
一旦你知道了目标端口的名称,使用PeripheralManagerService 来连接到该端口。当你完成与PWM端口的交互后,要关闭连接来释放资源。另外,直到当前连接关闭之前你不能在此端口上重新打开一个连接。使用端口的close()方法来关闭连接。
public class HomeActivity extends Activity {
// PWM Name
private static final String PWM_NAME = ...;
private Pwm mPwm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the PWM port
try {
mPwm = mPeripheralManager.openPwm(PWM_NAME);
} catch (IOException e) {
Log.w(TAG, "Unable to access PWM", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mPwm != null) {
try {
mPwm.close();
mPwm = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close PWM", e);
}
}
}
}
在获取到连接之后,为PWM信号配置时序参数。你在第一次激活信号之前必须设置这些参数。调用setEnabled(true)来激活PWM信号,如果你需要暂时禁用信号,你可以调用setEnabled(false)。
下面的例子配置了PWM的频率为120HZ(周期为8.33ms),并且占空比为25%(每个周期的高电平时间是2.08ms):
public void initializePwm(Pwm pwm) throws IOException {
pwm.setPwmFrequencyHz(120);
pwm.setPwmDutyCycle(25);
// Enable the PWM signal
pwm.setEnabled(true);
}
I2C总线连接一些简单的设备,这些设备的数据负载较小。传感器和执行器用的就是I2C,还有其他的比如加速度计,温度计,LED显示器和电机驱动器。
I2C是一个同步串行接口,这意味着它依赖一个共享的时钟信号来在设备间同步传输数据。控制触发时钟信号的器件称为主设备,所有连接上的外设称为从设备。每一个设备连接到同一组数据信号以形成总线。
I2C设备使用3线接口连接,包括:
因为所有数据都通过一根线传输,I2C仅仅支持半双工通信。所有的通信都由主设备启动,主设备传输完成后从设备必须响应。
I2C支持同一个总线连接多个从设备。不同于 SPI ,从设备使用 I2C 软件协议来寻址。每一个设备都有一个唯一的地址,并且仅响应主设备发送到的地址。
为了打开一个到普通 I2C 从设备的连接,你必须知道那个总线的唯一的名字。在开发的初始阶段,或者移植一个app到新设备的时候,使用PeripheralManagerService 的getI2CBusList()方法获取所用可用的设备名是很有帮助的。
PeripheralManagerService manager = new PeripheralManagerService();
List<String> deviceList = manager.getI2cBusList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No I2C bus available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}
一旦你知道了目标设备的名称,使用PeripheralManagerService 来连接到这个设备。当你完成与外设的通信后,应该关闭连接释放资源。另外,你不能在同一个设备连接关闭之前再次打开一个新的连接。使用 close() 方法来关闭连接:
public class HomeActivity extends Activity {
// I2C Device Name
private static final String I2C_DEVICE_NAME = ...;
// I2C Slave Address
private static final int I2C_ADDRESS = ...;
private I2cDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the I2C device
try {
PeripheralManagerService manager = new PeripheralManagerService();
mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS);
} catch (IOException e) {
Log.w(TAG, "Unable to access I2C device", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDevice != null) {
try {
mDevice.close();
mDevice = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close I2C device", e);
}
}
}
}
注意:设备名称代表了 I2C 总线,地址代表了总线上的指定从设备。因此,一个 I2cDevice 是一个连接到相应 I2C 总线上的指定从设备 。
I2C 从设备将他们的内容放到可写或者可读的寄存器中。
Register Address标识了要访问的寄存器的地址,Data[N] 是要读取或者写入的数据。从设备上的逻辑数据通常由多位组成,因此可以包含多个寄存器地址。
注意:对于SMBus协议,设备将会在地址(Register Address)和数据(Data[N])之间发送一个重复的开始位。
外围I/O接口为访问寄存器数据提供SMBus命令的三种类型。
// Modify the contents of a single register
public void setRegisterFlag(I2cDevice device, int address) throws IOException {
// Read one register from slave
byte value = device.readRegByte(address);
// Set bit 6
value |= 0x40;
// Write the updated value back to slave
device.writeRegByte(address, value);
}
// Read a register block
public byte[] readCalibration(I2cDevice device, int startAddress) throws IOException {
// Read three consecutive register values
byte[] data = new byte[3];
device.readRegBuffer(startAddress, data, data.length);
return data;
}
当需要与定义了跟SMBus不同的寄存器的 I2C 外设交互时,或者根本不用寄存器,使用原始的 read() 和 write()方法来完全控制在线缆上传输的字节数据。这些方法将会向下面那样执行一个单个的 I2C 事物:
使用原始传输方法,设备将在传输之前发送开始标志,并在传输完成之后发送停止标志。这使得通过 传输重复的开始标识来结合多个事物变的不再可能。
注意:原始传输方式能处理的数据没有明确的最大长度,但是在你设备上的 I2C 控制硬件在它能处理的字节数上有一个限制。如果你的外设要求传输大量的数据,那么请查看硬件的说明书。
下面的代码展示了如何构造一个原始的字节缓冲区,并把它写入到 I2C 从设备:
public void writeBuffer(I2cDevice device, byte[] buffer) throws IOException {
int count = device.write(buffer, buffer.length);
Log.d(TAG, "Wrote " + count + " bytes over I2C.");
}
SPI(Serial Peripheral Interface )是串行外设接口的缩写。SPI,是一种高速的,全双工,同步的通信总线,经常用于需要高速传输数据的设备。SPI非常适合高带宽的使用场景,比如外部非易失性存储器以及图形显示设备。很多传感器设备除了支持 I2C 之外还支持SPI。
SPI是一个同步串行接口,这意味着它依赖于一个共享时钟信号来同步设备间的数据传输。一个主设备控制时钟信号的触发,所有的连接上的设备都作为从设备。每个设备连接到同一组数据信号以形成总线。
理论上,SPI传输数据的速率仅仅受限于主设备能以多快的速率切换时钟信号。时钟速率的范围一般在16MHZ到25MHZ之间。这种高速时钟信号允许SPI外设更快的传输数据而且还比UART误码率少。
SPI支持全双工数据传输,这意味着主设备与从设备之间可以同时交换信息。为了支持全双工传输,总线必须提供如下单独的信号,这使得SPI最少有4根线缆接口:
SPI支持多个从设备连接到同一个总线。不像 I2C 设备需要使用硬件寻址。每个从设备需要一个额外的片选信号,以允许主设备寻址到该特定设备,并将该设备作为数据传输目标。如果仅使用单个从设备,则不需要此信号。
为了打开一个到特定SPI从设备的连接,你需要知道总线的唯一的名字。在开发的前期阶段,后者移植一个app到一个新设备时,使用PeripheralManagerService 的getSpiBusList()方法获取所用可用的设备名是很有帮助的。
PeripheralManagerService manager = new PeripheralManagerService();
List<String> deviceList = manager.getSpiBusList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No SPI bus available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}
支持多个硬件片选信号的SPI控制器会将每个可用从设备端口报告为单独的设备名称。例如,一个主板上同一个SPI总线上的设备CS0,CS1以及CS2,使用getSpiBusList()方法会获取到类似”SPI0.0”, “SPI0.1”和 “SPI0.2” 的名称。
一旦你知道了目标设备的名称,使用PeripheralManagerService 来连接到该设备。当你完成与外设的通信后,要断开连接释放资源。另外,你不能在同一个设备连接关闭之前再次打开一个新的连接。使用 close() 方法来关闭连接:
public class HomeActivity extends Activity {
// SPI Device Name
private static final String SPI_DEVICE_NAME = ...;
private SpiDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the SPI device
try {
PeripheralManagerService manager = new PeripheralManagerService();
mDevice = manager.openSpiDevice(SPI_DEVICE_NAME);
} catch (IOException e) {
Log.w(TAG, "Unable to access SPI device", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDevice != null) {
try {
mDevice.close();
mDevice = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close SPI device", e);
}
}
}
}
当在SPI总线上建立一个连接以后,配置数据传输速率和操作模式来匹配同一个总线上的从设备。要是数据传输成功,总线上的所有设备必须具有相同的时钟和数据传输格式。
注意:一些从设备无法配置他们的SPI操作模式,所以当你选择外设时要查看文档。
设置SPI模式,模式定义了时钟信号的极性和相位。你选择的模式基于三个属性:
Trailing Edge:每个时钟脉冲与前沿相反的转换。
支持下面的模式:
设置如下SPI设备参数:
下面的代码配置了SPI连接的模式为 MODE0,16MHZ时钟,一个字8位,并且是先传输最高有效位。
public void configureSpiDevice(SpiDevice device) throws IOException {
// Low clock, leading edge transfer
device.setMode(SpiDevice.MODE0);
device.setFrequency(16000000); // 16MHz
device.setBitsPerWord(8); // 8 BPW
device.setBitJustification(false); // MSB first
}
SPI支持半双工和全双工数据传输。大多数app应该使用半双工write()或者read()方法来与一个从设备交互数据。
// Half-duplex data transfer
public void sendCommand(SpiDevice device, byte[] buffer) throws IOException {
// Shift data out to slave
device.write(buffer, buffer.length);
// Read the response
byte[] response = new byte[32];
device.read(response, response.length);
...
}
要执行全双工交互,使用transfer()方法来代替。这个方法接收两个缓冲区来读和写。写缓存包含了要发送给从设备的信息,当读缓冲空时,接收从设备的数据。
数据长度必须小于或等于最小缓冲大小。通常全双工传输缓冲区大小相等。
// Full-duplex data transfer
public void sendCommand(SpiDevice device, byte[] buffer) throws IOException {
byte[] response = new byte[buffer.length];
device.transfer(buffer, response, buffer.length);
...
}
UART是通用异步收发器(Universal Asynchronous Receiver Transmitter)的缩写。复杂的外设比如GPS模块,LCD显示器以及收音机通常使用UART端口来通信。
UART是用于与一个设备交换原始数据的通用接口。说它通用是因为它的数据传输速率和数据字节格式都可以配置。说它异步是因为没有时钟信号用于同步两个设备之间的数据传输。设备硬件会用先进先出(FIFO)缓冲区来收集所有传入的数据集直到你的app读取。
UART数据传输是全双工的,这意味着数据可以在同一时刻被发送或者接受。它显然要比 I2C 快得多,但是缺少共享钟意味着两个设备必须有统一的数据传输速率,这样每个设备可以以最小的时序误差最为独立的宿主机。
UART外设通常有以下两种:
不像 SPI 和 I2C ,UART仅仅支持两个设备间的点到点(point-to-point)之间的通信。
为了打开一个到特定UART设备的连接,你需要知道唯一的端口名。在开发的前期阶段,后者移植一个app到一个新设备时,使用PeripheralManagerService 的 getUartDeviceList() 方法获取所用可用的设备名是很有帮助的。
PeripheralManagerService manager = new PeripheralManagerService();
List<String> deviceList = manager.getUartDeviceList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No UART port available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}
一旦你知道了目标名称,使用 PeripheralManagerService 来连接到该设备。当你完成与外设的通信后,要断开连接释放资源。另外,你不能在同一个设备连接关闭之前再次打开一个新的连接。使用 close() 方法来关闭连接:
public class HomeActivity extends Activity {
// UART Device Name
private static final String UART_DEVICE_NAME = ...;
private UartDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the UART device
try {
PeripheralManagerService manager = new PeripheralManagerService();
mDevice = manager.openUartDevice(UART_DEVICE_NAME);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDevice != null) {
try {
mDevice.close();
mDevice = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close UART device", e);
}
}
}
}
当一个连接建立后,配置数据传输速率和帧格式来匹配连接上的外部设备。
数据帧
每个通过UART发送的字符都被一个数据帧包裹,数据帧包括以下组成部分:
注意:大多数UART设备默认配置8位数据位,不需要检验,1位停止位(8N1)。
UART上的数据传输速率被称为波特率(Baud rate)。它代表了接收和发送的速度(单位为:位/秒)。
由于通过UART连接的两个设备之间没有共享时钟,所以必须提前配置,以便使用相同的波特率来正确的解码数据。
通用的波特率包括9600,19200,38400,57600,115200和921600。这些速率包括数据帧(开始,停止,奇偶校验为)的开销,所以有效的数据传输速率将会略低并且根据你配置的帧位数量而有所不同。
下面的代码配置UART连接的波特率为115200,8位数据位,没有校验,1个停止位(8N1):
public void configureUartFrame(UartDevice uart) throws IOException {
// Configure the UART port
uart.setBaudrate(115200);
uart.setDataSize(8);
uart.setParity(UartDevice.PARITY_NONE);
uart.setStopBits(1);
}
注意:选择不通用的波特率在传输过程中可能造成较高的错误率。你应该总是确认你选择的波特率是否是你的设备硬件支持的。
如果你的设备支持 5线缆 UART端口,激活硬件流控制可以增加数据传输的可靠性。这也意味着你可以安全的使用高波特率传输,并且丢失数据的机会更少。
当你激活了硬件流控制,当设备上的接收缓冲区满并且不再接收更多数据时UART会发送请求发送(RTS)信号。一旦缓冲区被清空,该信号会被清除。同样,UART会监听清除发送(CTS)信号,and will pause transmitting data if it sees that line asserted by the peripheral device.(并且如果得知外围设备断言的线路它会停止发射数据。【不知assert如何翻译才好,求各位指教。】)
要激活硬件流控制,使用 setHardwareFlowControl() 方法并且传递参数 HW_FLOW_CONTROL_AUTO_RTSCTS 。默认值是 HW_FLOW_CONTROL_NONE ,也就是说硬件流控制在默认情况下是不可用的:
public void setFlowControlEnabled(UartDevice uart, boolean enable) throws IOException {
if (enable) {
// Enable hardware flow control
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_AUTO_RTSCTS);
} else {
// Disable flow control
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_NONE);
}
}
要通过UART将数据缓冲区传输到外设,使用write() 方法:
public void writeUartData(UartDevice uart) throws IOException {
byte[] buffer = {...};
int count = uart.write(buffer, buffer.length);
Log.d(TAG, "Wrote " + count + " bytes to peripheral");
}
注意:一个Java字节占8位,如果你使用setDataSize() 方法配置一个较小的数据,那么每个字节的高位会被截断。
使用read() 方法从UART的FIFO缓冲区中拉取数据到应用程序。这个方法接收一个空的缓冲用于填充输入的数据以及一个最大能够读取的字节数作为参数。UART读取是非阻塞的,如果FIFO缓冲区中没有可用数据,它将立即返回。
UartDevice 在读取的时候将会返回FIFO缓冲区可用的数据字节数。为了确保所有的数据都接收到,在UART上循环直到当前没有更多可读数据:
public void readUartBuffer(UartDevice uart) throws IOException {
// Maximum amount of data to read at one time
final int maxCount = ...;
byte[] buffer = new byte[maxCount];
int count;
while ((count = uart.read(buffer, buffer.length)) > 0) {
Log.d(TAG, "Read " + count + " bytes from peripheral");
}
}
为了在缓冲区空时避免没有必要的轮训UART,使用 UartDevice 注册一个UartDeviceCallback 。这个回调在存在可读数据时将会调用onUartDeviceDataAvailable() 方法。在你的应用程序不再监听输入数据时你应该取消回调函数的注册。
public class HomeActivity extends Activity {
private UartDevice mDevice;
...
@Override
protected void onStart() {
super.onStart();
// Begin listening for interrupt events
mDevice.registerUartDeviceCallback(mUartCallback);
}
@Override
protected void onStop() {
super.onStop();
// Interrupt events no longer necessary
mDevice.unregisterUartDeviceCallback(mUartCallback);
}
private UartDeviceCallback mUartCallback = new UartDeviceCallback() {
@Override
public boolean onUartDeviceDataAvailable(UartDevice uart) {
// Read available data from the UART device
try {
readUartBuffer(uart);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
// Continue listening for more interrupts
return true;
}
@Override
public void onUartDeviceError(UartDevice uart, int error) {
Log.w(TAG, uart + ": Error event " + error);
}
};
}
onUartDeviceDataAvailable() 方法返回一个boolean值用于表明回调是否应该从接受未来的中断事件中自动取消注册。返回true表明在UART的FIFO缓冲区中每次出现数据时继续接收事件。
原文地址:https://developer.android.com/things/sdk/pio/uart.html
在此之前一定要先参照文章 Android Things物联网开发–硬件和软件环境(二)部署好硬件和软件开发环境。因为本人使用的是树莓派3b,所以这里以树莓派3b为例,在Windows下的开发过程。
1. 给树莓派插上网线,HDMI线并接通电源,启动Android Things。
如果显示没有可用设备的话,需要使用adb命令根据IP地址连接到树莓派:
首先找到Android sdk(安装Android Studio时指定的目录)所在目录,打开 platform-tools 文件夹之后打开命令行工具,如下图:
在命令行中输入一下命令:
adb connect 你的树莓派的IP地址
回车,出现: connected to 你的IP地址:5555,说明连接成功:
此时Android Studio中就存在该设备了,再按照部署Android app到手机一样的步骤即可将app部署到树莓派。
下图是我写的一个简单的demo部署到树莓派的效果:
这里给出简单的代码:
AndroidManifest.xml文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.heqing.androidthings.firstandroidthings">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<uses-library android:name="com.google.android.things"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
activity>
application>
manifest>
MainActivity.java文件:
public class MainActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.gpio);
PeripheralManagerService peripheralManagerService = new PeripheralManagerService();
List gpioList = peripheralManagerService.getGpioList();
StringBuilder builder = new StringBuilder(100);
if (gpioList.isEmpty()) {
builder.append("没有可用的GPIO端口");
Log.i("tag", "没有gpio端口");
} else {
builder.append("available GPIO port : \n\t");
for (String s : gpioList) {
builder.append(s).append(" ,");
Log.i("tag", "GPIO端口名:" + s);
}
builder.deleteCharAt(builder.length() - 1);
}
textView.setText(builder.toString());
builder.setLength(0);
}
}
activity_main.xml文件:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello,Android Things!"
android:textColor="@android:color/holo_red_light"
android:textSize="30dp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:background="@android:color/holo_red_dark" />
<TextView
android:id="@+id/gpio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Hello,Android Things!"
android:textColor="@android:color/holo_red_light"
android:textSize="20dp"
android:textStyle="bold" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/hero" />
LinearLayout>
FrameLayout>
build.gradle文件:
android {
compileSdkVersion 24
buildToolsVersion '25.0.0'
...
...
}
dependencies {
...
...
provided 'com.google.android.things:androidthings:0.2-devpreview'
}
扫码关注本人微信公众号,有惊喜奥!公众号每天定时发送精致文章!回复关键词可获得海量各类编程开发学习资料!
例如:想获得Python入门至精通学习资料,请回复关键词Python即可。