写在前面的话:由于外设I/O涉及到GPIO、PWM、和串行通信三部分,而串行通信有讲了I2C(IIC)、SPI、UART,这样导致本文的篇幅过长不便于阅读,特此将本文分成几部分来方便阅读
- Android Things 外设I/O-GPIO
- Android Things 外设I/O-PWM
- Android Things 外设I/O-I2C(IIC)
- Android Things 外设I/O-SPI
- Android Things 外设I/O-UART
串行通信
使用这些API在连接在同一本地总线上的两个或多个智能设备之间传输更大的数据有效负载。 下表概述了每个支持的串行协议的基本属性:
协议 | 传输类型 | 电线数量 | 外围设备数量 | 传输速度 |
---|---|---|---|---|
I2C | 同步 | 2 | Up to 127 | Low |
SPI | 同步 | 4+ | Unlimited | High |
UART | 异步 | 2 or 4 | 1 | Medium |
UART
复杂的外围设备,如GPS模块,LCD显示器和XBee无线电通常使用通用异步收发器(UART)端口(通常简称为串行端口)进行通信。
UART是用于与外围设备交换原始数据的通用接口。 它是通用的,因为数据传输速度和数据字节格式都是可配置的。 它是异步的,因为不存在用于同步两个设备之间的数据传输的时钟信号。 设备硬件以先进先出(FIFO)缓冲区收集所有传入数据,直到应用程序读取为止。
UART数据传输是全双工的,意味着数据可以同时发送和接收。 它通常比I2C快,但缺乏共享时钟意味着两个设备必须达成一个共同的数据传输速率,每个设备可以独立遵守与最小的定时误差。
UART外设通常有两种类型:
- 3线端口包括数据接收(RX),数据发送(TX)和接地参考(GND)信号。
- 5线端口添加用于硬件流控制的请求发送(RTS)和清除发送(CTS)信号。 流控制允许接收设备指示其FIFO缓冲器临时满,并且发送设备在发送任何更多数据之前应该等待。
与SPI和I2C不同,UART仅支持两个器件之间的点对点通信。
管理连接
为了打开到特定UART的连接,您需要知道唯一的端口名称。 在开发的初始阶段或将应用程序移植到新硬件时,可以通过 getUartDeviceList()从PeripheralManagerService找到所有可用的设备名称:
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设备名称
private static final String UART_DEVICE_NAME = ...;
private UartDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 尝试访问UART设备
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发送的每个字符都包裹在一个数据帧中,该数据帧包含以下组件:
- 启动位 - 在发送数据之前,该行保持活动状态,持续1位持续时间的固定时间间隔,以指示新字符的开始。
- 数据位 - 表示数据字符的各位。 UART可以配置为在5-9个数据位之间发送以表示字符。 较少的位减少了数据的范围,但可以增加有效的数据传输速率。
- 奇偶校验位 - 可选错误校验值。 如果UART配置为偶校验或奇校验,则会在帧中添加一个额外的位,以指示数据位的内容是否为偶数或奇数值。 将此设置为none会从帧中删除该位。
- 停止位 - 在传输所有数据之后,线路被重置一个可配置的时间间隔,以指示该字符的结束。 这可以配置为保持空闲1或2位持续时间。
注:大多数UART器件的默认配置为8个数据位,无奇偶校验和1个停止位(8N1)。
UART上的数据传输速率称为波特率。 它表示接收和发送的速度(以位/秒为单位)。 由于在通过UART连接的两个器件之间没有共享时钟,因此必须提前配置这两个器件,才能使用相同的波特率正确解码数据。
常用波特率包括9600,19200,38400,57600,115200和921600.该速率包括数据帧(开始,停止和奇偶校验位)的开销,因此有效数据传输速率将略低,并且根据 您配置的帧位数。
以下代码将UART连接配置为以115200波特,8个数据位,无奇偶校验和1个停止位(8N1)操作:
public void configureUartFrame(UartDevice uart) throws IOException {
// 配置UART端口
uart.setBaudrate(115200);
uart.setDataSize(8);
uart.setParity(UartDevice.PARITY_NONE);
uart.setStopBits(1);
}
注意:选择不常见的波特率可能导致数据传输中的高错误率。 您应该始终验证您选择的波特率是否得到设备硬件的良好支持。
硬件流控制
如果您的设备支持5线UART端口,则启用硬件流控制可以提高数据传输的可靠性。 通常这也意味着您可以安全地使用更快的波特率,而丢失传入数据的机会更低。
在硬件流控制使能的情况下,当器件上的接收缓冲器已满并且不能接受任何更多数据时,UART将发送请求发送(RTS)信号。 一旦缓冲液被排空,信号将被清除。 类似地,UART监视清除发送(CTS)信号,并且如果它看到由外围设备断言的线,则将暂停发送数据。
要启用硬件流控制,请使用带有HW_FLOW_CONTROL_AUTO_RTSCTS的setHardwareFlowControl()方法。 默认值为HW_FLOW_CONTROL_NONE,表示禁用流控制。
public void setFlowControlEnabled(UartDevice uart, boolean enable) throws IOException {
if (enable) {
// 启用硬件流控制
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_AUTO_RTSCTS);
} else {
// 禁用流量控制
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 {
// 一次读取的最大数据量
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();
// 开始监听中断事件
mDevice.registerUartDeviceCallback(mUartCallback);
}
@Override
protected void onStop() {
super.onStop();
// 中断事件不再需要
mDevice.unregisterUartDeviceCallback(mUartCallback);
}
private UartDeviceCallback mUartCallback = new UartDeviceCallback() {
@Override
public boolean onUartDeviceDataAvailable(UartDevice uart) {
// 从UART设备读取可用数据
try {
readUartBuffer(uart);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
// 继续侦听更多中断
return true;
}
@Override
public void onUartDeviceError(UartDevice uart, int error) {
Log.w(TAG, uart + ": Error event " + error);
}
};
}
onUartDeviceDataAvailable()回调返回一个布尔值,指示回调是否应该在接收未来中断事件时自动取消注册。 此处返回true,以便在每次UART FIFO中显示数据时继续接收事件。