目錄
- 概述
- 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<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);
}
...
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<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);
}
...
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);
}
};
}
到這里這幾種協議就介紹完了,利用這幾種協議的特性并搭配好的想法和電路,就可以創造出各種有趣或實用的功能。