目录
- 概述
- GPIO: 简单0/1状态协议
- PWM:方波信号接口
- I2C:低速同步串行接口
- UART:异步串行接口
1.概述
Android Things 提供了几种对外I/O接口协议来连接各种外部设备。通过这些协议Android Things和外部设备就能互相交流。Android Things像一个躯干、一个核心,我们通过这些协议给他安装各种肢体和外延功能。这里介绍4种Android Things支持的协议:GPIO是一个最简单的协议,只能读写高/低两种电平信号;PWM只能简单的对外发出方波信号;I2C、UART是串行接口协议,能连续的读写大量数据,用于比较复杂的设备。下面具体介绍。
2.GPIO
GPIO可用一句话概括:每次只可以读或写一个高电平或低电平信号。一般用于简单的外部设备,比如开关、LED灯。
要获得一个GPIO端口需要知道端口的唯一端口名,PeripheralManagerService 的getGpioList()方法可以获取所有当前的GPIO端口名,有了这个唯一名称就可以获取这个端口:
PeripheralManagerService manager = new PeripheralManagerService();
List 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);
}
...
Gpio mGpio = manager.openGpio(GPIO_NAME);
获得Gpio对象后,就可以操作这个端口:包括1. 设置读写方向,即设置此端口的功能是读还是写;2. 设置读写类型,即指定Gpio对像读/写的值代表高电平还是低电平,下面是读的例子:
// 设置方向:DIRECTION_IN为读,DIRECTION_OUT为写
mGpio.setDirection(Gpio.DIRECTION_IN);
// 设置类型:指定getValue()==true或setValue(true)的意义,ACTIVE_HIGH即true代表高电平,ACTIVE_LOW即true代表低电平
mGpio.setActiveType(Gpio.ACTIVE_HIGH);
...
if (mGpio.getValue()) {
//读到了高电平
} else {
// 读到了低电平
}
为了能够监听电平高低的变化,我们可以设置监听回调:
public void configureInput(Gpio gpio) throws IOException {
//设置监听类型:EDGE_NONE:不回调;EDGE_RISING:低->高回调;EDGE_FALLING:高->低回调;EDGE_BOTH:高低变化都回调
gpio.setEdgeTriggerType(Gpio.EDGE_BOTH);
gpio.registerGpioCallback(mGpioCallback);
}
private GpioCallback mGpioCallback = new GpioCallback() {
@Override
public boolean onGpioEdge(Gpio gpio) {
//根据读类型读取状态
if(gpio.getValue()){...}
// 返回true则继续监听,false则不再监听
return true;
}
@Override
public void onGpioError(Gpio gpio, int error) {
Log.w(TAG, gpio + ": Error event " + error);
}
};
3.PWM
PWM是用来发出方波控制信号的,而且只能发出方波信号,不能读取。先看一下方波信号,如下图,高低电平周期出现:
这个波形可以通过设置周期和占空比进行调节,占空比即每个周期高电平占的比例。
获取一个PWM端口也是通过唯一名称的,依然是PeripheralManagerService 的 getPwmList()方法:
PeripheralManagerService manager = new PeripheralManagerService();
List 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);
}
...
Pwm mPwm = mPeripheralManager.openPwm(PWM_NAME);
//设置频率
mPwm .setPwmFrequencyHz(120);
//设置占空比
mPwm .setPwmDutyCycle(25);
//设置PWM有效
mPwm .setEnabled(true);
4. I2C
I2C是同步串行接口,使用共享同步时钟同步数据,适合数据量较小的外部设备。
I2C接口有三根线,分别是
- SCL:同步时钟信号线
- SDA:数据传输线
- GND:地线
由于I2C只有一根数据线,所以只能是半双工的。
I2C可以同时接入多个设备,如图:
连接的每个设备都对应一个唯一地址。
和上面两个协议相同,I2C也可以通过PeripheralManagerService 的getI2CBusList()获取I2C端口列表,由于一个I2C端口链接多个设备,所以还需要一个地址来定位某个设备。获取设备的代码如下:
PeripheralManagerService manager = new PeripheralManagerService();
I2cDevice mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS);
I2C协议可以读写设备的寄存器,使用下图的数据帧格式进行读写,其中前面两个地址定位哪个设备的哪个寄存器,后面的一个地址一个数据代表要在这个设备的这个寄存器读写的内容。
具体函数如下:
字节数据:readRegByte()和writeRegByte()来读或者写一个单独的8位寄存器数据。
字数据:readRegWord()和writeRegWord()以一个16位的字来读或者写两个连续寄存器的值。第一个寄存器的地址被翻译为字中的最小有效字节(LSB),其次是最重要的字节(MSB)。
块数据:readRegBuffer()和writeRegBuffer()读或者写最多32个连续寄存器的值作为一个数组。
I2C还支持从数据线读写原始数据,数据帧格式如下:
需要注意的是:打开一个设备的连接后,不可以同时再打开另一个,需要先关闭一个才能连接另一个。关闭调用I2cDevice 的 close()方法.
5. UART
UART一般用作和外部设备交换原始数据,它和其他的几个协议不同之处在于数据传输速度和数据格式都可以自定义,而且它是异步传输数据的,是没有同步时钟信号的,设备会收集所有进来的数据到一个先进先出的缓存里,直到你的应用来读取。
UART是全双工的,读数据和写数据各用一根线。由于读和写可以同时进行,一般它比I2C要快,但是需要两边的设备都遵循一个传输速率以防止数据错误,而且只能连接一个设备。如下是设备连接图:
打开一个UART端口也是同样需要知道唯一的端口名称,类似的也是PeripheralManagerService的getUartDeviceList()方法可以获得UART端口列表,然后openUartDevice(UART_DEVICE_NAME)即可获得UartDevice类型的对象。
UART传输的数据帧格式如下图:
Start位:发送数据前,数据线被拉起 1 bit的固定的时间间隔来指明真正要发送的数据的开始。
Data部分:要传输的数据,可以传5-9 bit数据,数据位少,传输的数据就少,但是可以提高速率。
Parity位:校验位,如果UART设置奇偶校验,那数据帧就会加上这一位,当然也可以不设置,则没有这一位。
Stop位:所有数据传输完毕后,数据线会被重置一段时间表明数据传输结束,这段时间可以是1-2bit的时间。
默认的数据帧一般是1bit start、8bit数据和1bit stop位,没有校验位。
UART的传输速率称为波特率,单位是 bit/秒,接收端和发送端必须使用相同的波特率。
代码中实际使用时一般如下:
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); //设置stop位大小
}
UART有一种五根线的设备,如下图:
多出的两根线能保证较高速度传输的情况下更少的传输失败,可以称为FlowControl功能。
可以使用如下代码开启或关闭着两根线:
public void setFlowControlEnabled(UartDevice uart, boolean enable) throws IOException {
if (enable) {
// 打开 FlowControl功能
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_AUTO_RTSCTS);
} else {
// 取消FlowControl功能
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_NONE);
}
}
UART的读写功能也比较简单如下代码:
//写数据
public void writeUartData(UartDevice uart) throws IOException {
byte[] buffer = {...};
int count = uart.write(buffer, buffer.length);
Log.d(TAG, "Wrote " + count + " bytes to peripheral");
}
//读取数据一般在回调里监听
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) {
// 开始读取
try {
readUartBuffer(uart);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
// 返回true则继续监听,false则不再继续监听
return true;
}
@Override
public void onUartDeviceError(UartDevice uart, int error) {
Log.w(TAG, uart + ": Error event " + error);
}
};
}
到这里这几种协议就介绍完了,利用这几种协议的特性并搭配好的想法和电路,就可以创造出各种有趣或实用的功能。