Android Things 外部I/O接口協(xié)議

目錄

  • 概述
  • GPIO: 簡(jiǎn)單0/1狀態(tài)協(xié)議
  • PWM:方波信號(hào)接口
  • I2C:低速同步串行接口
  • UART:異步串行接口

1.概述

Android Things 提供了幾種對(duì)外I/O接口協(xié)議來連接各種外部設(shè)備。通過這些協(xié)議Android Things和外部設(shè)備就能互相交流。Android Things像一個(gè)軀干、一個(gè)核心,我們通過這些協(xié)議給他安裝各種肢體和外延功能。這里介紹4種Android Things支持的協(xié)議:GPIO是一個(gè)最簡(jiǎn)單的協(xié)議,只能讀寫高/低兩種電平信號(hào);PWM只能簡(jiǎn)單的對(duì)外發(fā)出方波信號(hào);I2C、UART是串行接口協(xié)議,能連續(xù)的讀寫大量數(shù)據(jù),用于比較復(fù)雜的設(shè)備。下面具體介紹。

2.GPIO

GPIO可用一句話概括:每次只可以讀或?qū)懸粋€(gè)高電平或低電平信號(hào)。一般用于簡(jiǎn)單的外部設(shè)備,比如開關(guān)、LED燈。
要獲得一個(gè)GPIO端口需要知道端口的唯一端口名,PeripheralManagerService 的getGpioList()方法可以獲取所有當(dāng)前的GPIO端口名,有了這個(gè)唯一名稱就可以獲取這個(gè)端口:

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對(duì)象后,就可以操作這個(gè)端口:包括1. 設(shè)置讀寫方向,即設(shè)置此端口的功能是讀還是寫;2. 設(shè)置讀寫類型,即指定Gpio對(duì)像讀/寫的值代表高電平還是低電平,下面是讀的例子:

// 設(shè)置方向:DIRECTION_IN為讀,DIRECTION_OUT為寫
    mGpio.setDirection(Gpio.DIRECTION_IN);
// 設(shè)置類型:指定getValue()==true或setValue(true)的意義,ACTIVE_HIGH即true代表高電平,ACTIVE_LOW即true代表低電平
    mGpio.setActiveType(Gpio.ACTIVE_HIGH);

    ...

    if (mGpio.getValue()) {
        //讀到了高電平
    } else {
        // 讀到了低電平
    }

為了能夠監(jiān)聽電平高低的變化,我們可以設(shè)置監(jiān)聽回調(diào):

public void configureInput(Gpio gpio) throws IOException {
   //設(shè)置監(jiān)聽類型:EDGE_NONE:不回調(diào);EDGE_RISING:低->高回調(diào);EDGE_FALLING:高->低回調(diào);EDGE_BOTH:高低變化都回調(diào)
    gpio.setEdgeTriggerType(Gpio.EDGE_BOTH);
    gpio.registerGpioCallback(mGpioCallback);
}
private GpioCallback mGpioCallback = new GpioCallback() {
    @Override
    public boolean onGpioEdge(Gpio gpio) {
        //根據(jù)讀類型讀取狀態(tài)
        if(gpio.getValue()){...}
           
        // 返回true則繼續(xù)監(jiān)聽,false則不再監(jiān)聽
        return true;
    }

    @Override
    public void onGpioError(Gpio gpio, int error) {
        Log.w(TAG, gpio + ": Error event " + error);
    }
};

3.PWM

PWM是用來發(fā)出方波控制信號(hào)的,而且只能發(fā)出方波信號(hào),不能讀取。先看一下方波信號(hào),如下圖,高低電平周期出現(xiàn):

pwm-signal.png

這個(gè)波形可以通過設(shè)置周期和占空比進(jìn)行調(diào)節(jié),占空比即每個(gè)周期高電平占的比例。

獲取一個(gè)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);

//設(shè)置頻率
mPwm .setPwmFrequencyHz(120);
//設(shè)置占空比
mPwm .setPwmDutyCycle(25);
 //設(shè)置PWM有效
mPwm .setEnabled(true);

4. I2C

I2C是同步串行接口,使用共享同步時(shí)鐘同步數(shù)據(jù),適合數(shù)據(jù)量較小的外部設(shè)備。
I2C接口有三根線,分別是

  1. SCL:同步時(shí)鐘信號(hào)線
  2. SDA:數(shù)據(jù)傳輸線
  3. GND:地線

由于I2C只有一根數(shù)據(jù)線,所以只能是半雙工的。

I2C可以同時(shí)接入多個(gè)設(shè)備,如圖:


i2c-connections.png

連接的每個(gè)設(shè)備都對(duì)應(yīng)一個(gè)唯一地址。

和上面兩個(gè)協(xié)議相同,I2C也可以通過PeripheralManagerService 的getI2CBusList()獲取I2C端口列表,由于一個(gè)I2C端口鏈接多個(gè)設(shè)備,所以還需要一個(gè)地址來定位某個(gè)設(shè)備。獲取設(shè)備的代碼如下:

PeripheralManagerService manager = new PeripheralManagerService();
I2cDevice mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS);

I2C協(xié)議可以讀寫設(shè)備的寄存器,使用下圖的數(shù)據(jù)幀格式進(jìn)行讀寫,其中前面兩個(gè)地址定位哪個(gè)設(shè)備的哪個(gè)寄存器,后面的一個(gè)地址一個(gè)數(shù)據(jù)代表要在這個(gè)設(shè)備的這個(gè)寄存器讀寫的內(nèi)容。

i2c-smbus.png

具體函數(shù)如下:

  • 字節(jié)數(shù)據(jù):readRegByte()和writeRegByte()來讀或者寫一個(gè)單獨(dú)的8位寄存器數(shù)據(jù)。

  • 字?jǐn)?shù)據(jù):readRegWord()和writeRegWord()以一個(gè)16位的字來讀或者寫兩個(gè)連續(xù)寄存器的值。第一個(gè)寄存器的地址被翻譯為字中的最小有效字節(jié)(LSB),其次是最重要的字節(jié)(MSB)。

  • 塊數(shù)據(jù):readRegBuffer()和writeRegBuffer()讀或者寫最多32個(gè)連續(xù)寄存器的值作為一個(gè)數(shù)組。

I2C還支持從數(shù)據(jù)線讀寫原始數(shù)據(jù),數(shù)據(jù)幀格式如下:

i2c-raw.png

需要注意的是:打開一個(gè)設(shè)備的連接后,不可以同時(shí)再打開另一個(gè),需要先關(guān)閉一個(gè)才能連接另一個(gè)。關(guān)閉調(diào)用I2cDevice 的 close()方法.

5. UART

UART一般用作和外部設(shè)備交換原始數(shù)據(jù),它和其他的幾個(gè)協(xié)議不同之處在于數(shù)據(jù)傳輸速度和數(shù)據(jù)格式都可以自定義,而且它是異步傳輸數(shù)據(jù)的,是沒有同步時(shí)鐘信號(hào)的,設(shè)備會(huì)收集所有進(jìn)來的數(shù)據(jù)到一個(gè)先進(jìn)先出的緩存里,直到你的應(yīng)用來讀取。

UART是全雙工的,讀數(shù)據(jù)和寫數(shù)據(jù)各用一根線。由于讀和寫可以同時(shí)進(jìn)行,一般它比I2C要快,但是需要兩邊的設(shè)備都遵循一個(gè)傳輸速率以防止數(shù)據(jù)錯(cuò)誤,而且只能連接一個(gè)設(shè)備。如下是設(shè)備連接圖:

uart-connections.png

打開一個(gè)UART端口也是同樣需要知道唯一的端口名稱,類似的也是PeripheralManagerService的getUartDeviceList()方法可以獲得UART端口列表,然后openUartDevice(UART_DEVICE_NAME)即可獲得UartDevice類型的對(duì)象。

UART傳輸?shù)臄?shù)據(jù)幀格式如下圖:

uart-frame.png

Start位:發(fā)送數(shù)據(jù)前,數(shù)據(jù)線被拉起 1 bit的固定的時(shí)間間隔來指明真正要發(fā)送的數(shù)據(jù)的開始。
Data部分:要傳輸?shù)臄?shù)據(jù),可以傳5-9 bit數(shù)據(jù),數(shù)據(jù)位少,傳輸?shù)臄?shù)據(jù)就少,但是可以提高速率。
Parity位:校驗(yàn)位,如果UART設(shè)置奇偶校驗(yàn),那數(shù)據(jù)幀就會(huì)加上這一位,當(dāng)然也可以不設(shè)置,則沒有這一位。
Stop位:所有數(shù)據(jù)傳輸完畢后,數(shù)據(jù)線會(huì)被重置一段時(shí)間表明數(shù)據(jù)傳輸結(jié)束,這段時(shí)間可以是1-2bit的時(shí)間。

默認(rèn)的數(shù)據(jù)幀一般是1bit start、8bit數(shù)據(jù)和1bit stop位,沒有校驗(yàn)位。

UART的傳輸速率稱為波特率,單位是 bit/秒,接收端和發(fā)送端必須使用相同的波特率。

代碼中實(shí)際使用時(shí)一般如下:

public void configureUartFrame(UartDevice uart) throws IOException {
    // Configure the UART port
    uart.setBaudrate(115200);  //設(shè)置波特率
    uart.setDataSize(8);    //設(shè)置數(shù)據(jù)大小
    uart.setParity(UartDevice.PARITY_NONE);  //設(shè)置有無校驗(yàn)位
    uart.setStopBits(1);     //設(shè)置stop位大小
}

UART有一種五根線的設(shè)備,如下圖:

uart-flow-control.png

多出的兩根線能保證較高速度傳輸?shù)那闆r下更少的傳輸失敗,可以稱為FlowControl功能。

可以使用如下代碼開啟或關(guān)閉著兩根線:

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的讀寫功能也比較簡(jiǎn)單如下代碼:

//寫數(shù)據(jù)
public void writeUartData(UartDevice uart) throws IOException {
    byte[] buffer = {...};
    int count = uart.write(buffer, buffer.length);
    Log.d(TAG, "Wrote " + count + " bytes to peripheral");
}

//讀取數(shù)據(jù)一般在回調(diào)里監(jiān)聽
public class HomeActivity extends Activity {
    private UartDevice mDevice;
    ...

    @Override
    protected void onStart() {
        super.onStart();
        // 注冊(cè)回調(diào)
        mDevice.registerUartDeviceCallback(mUartCallback);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 取消回調(diào)
        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則繼續(xù)監(jiān)聽,false則不再繼續(xù)監(jiān)聽
            return true;
        }

        @Override
        public void onUartDeviceError(UartDevice uart, int error) {
            Log.w(TAG, uart + ": Error event " + error);
        }
    };
} 

到這里這幾種協(xié)議就介紹完了,利用這幾種協(xié)議的特性并搭配好的想法和電路,就可以創(chuàng)造出各種有趣或?qū)嵱玫墓δ堋?/p>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容