Android Things物联网开发经验总结

目录

Android Things物联网开发--基础介绍(一)

Android Things

Android Things支持包

行为变更

权限

通知

Android Things物联网开发--硬件和软件环境(二)

硬件环境

软件环境

Android Things物联网开发--外围I/O接口之GPIO(三)

外围I/O接口

GPIO

管理连接

读取输入

监听输入的状态变化

写输出端口

Android Things物联网开发--外围I/O接口之PWM(四)

脉宽调制信号PWM(Pulse Width Modulation)

管理连接

配置以及控制PWM信号

Android Things物联网开发--外围I/O接口之I2C(五)

管理从设备的连接

与寄存器进行交互

传输原始数据

Android Things物联网开发--外围I/O接口之SPI(六)

SPI接口

管理设备连接

配置时钟和数据模式

传输数据

 

Android Things物联网开发--外围I/O接口之UART(七)

UART(Universal Asynchronous Receiver Transmitter)

管理连接

配置端口参数

硬件流控制(Hardware Flow Control)

发送输出数据

监听输入数据

Android Things物联网开发--实战之部署app到开发板(八)

如何部署app到开发板


Android Things物联网开发--基础介绍(一)

Android Things

Android Things 是 2016 年 12 月份Google 推出的物联网操作系统。它是通过 Google 开发的物联网操作系统 Brillo 改进优化的。
Android Things使用和Android开发一样的工具,Android框架和Google APIs,这些都使得开发物联网变得更简单。

Android things的平台架构如下: 
Android Things物联网开发经验总结_第1张图片 
为嵌入式设备开发app比为手机或者电脑开发更加拉近开发者跟硬件外设和驱动的距离。此外,嵌入式设备对用户来说通常代表了一个单独的APP体验。接下来看看Android 开发和Android Things开发的不同。

Android Things继承了Android核心框架层并且通过支持包提供额外的APIs。这些APIs允许apps与新型的硬件之间交互,但是这些硬件并不存在于移动设备上。

Android Things平台仍然为单一应用程序使用。系统app不存在,并且你的app将会自动启动来给你的用户带来沉浸式体验。

Android Things支持包

外围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中。

  • CalendarContract
  • ContactsContract
  • DocumentsContract
  • DownloadManager
  • MediaStore
  • Settings
  • Telephony
  • UserDictionary
  • VoicemailContract

显示是可选的 
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物联网开发经验总结_第2张图片
进行Android Things物联网开发,首先要准备一些硬件设备,Android Things目前支持四款硬件平台: 
Android Things物联网开发经验总结_第3张图片
这些硬件可以在网上买到,当然仅仅有开发板还不行,还需要一些其他的配件,比如网线,HDMI线,电源线,SD卡,摄像头,显示屏等等,不过刚开始不用一次性全部买齐,等到开发时缺什么再去买什么,这样可以避免买错。笔者买的是树莓派3b(Raspberry Pi 3 b)的一个套餐(散热片,HDMI线,网线,16G SD卡,主板,风扇,外壳,电源线)300多。 
Android Things物联网开发经验总结_第4张图片

烧写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

针对不同的开发板,有不同的烧写方法,具体如下:

  • Intel Edison
  • Intel Joule
  • NXP Pico
  • Raspberry Pi 3

软件环境


先决条件 
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中。添加 是为了在app运行时让这个预构建的库可见。

申明主界面 
一个应用程序要想在一个嵌入式设备上运行,必须在它的manifest文件中申明一个activity来作为设备启动后程序的主入口。提供一个包含以下元素的intent filter:

  • Action:ACTION_MAIN
  • Category: CATEGORY_DEFAULT
  • Category: IOT_LAUNCHER 
    为了便于开发,这个activity应该包含一个 CATEGORY_LAUNCHER 的intent filter,这样Android Studio可以在部署或者调试的时候把它作为默认的activity启动起来。

    
    
        
        
            
            
        

        
        
            
            
            
        
    

致此,硬件环境和软件环境已经搭建好,可以使用Android Things中的API了。

 

Android Things物联网开发--外围I/O接口之GPIO(三)

外围I/O接口


Android Things提供外围I/O接口APIs 使用工业标准协议和接口来与传感器和驱动器进行交互。

  • 通用输入输出(GPIO:General Purpose Input/Output):使用这些API来与一些简单的传感器交互,比如运动检测器,距离检测器以及开关等,这些可以使用二进制数值来报告它们当前的状态–高或者低。
  • 脉宽调制信号(PWM:Pulse Width Modulation):使用这些API来与那些需要成比例的信号来提供对输出的细粒度控制,比如伺服机(servo motors),直流电动机(DC motors)以及灯。
  • 串行通信(Serial Communication):使用这些API在连接在同一个本地总线上的两个或者多个智能设备之间传输更大负荷的数据。下面的表格列出了所支持的串行协议的基本属性:
协议名称 传输类型 线缆数 外设数量 传输速度
I2C 同步 2 最大127 速度较低
SPI 同步 4+ 无限 速度最高
UART 异步 2或者4 1 速度中等

GPIO


通用输入输出(GPIO)针脚提供了一个可编程的接口来读取一个二进制输入设备的状态(比如一个按钮开关)或者控制二进制输出设备的打开或者关闭状态(比如一个LED灯)。 
Android Things物联网开发经验总结_第5张图片 
你可以配置具有高或低状态的 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()方法申明哪一种状态的改变会触发一个中断事件。边沿触发支持一下四种类型:

  • EDGE_NONE: 不产生中断事件,这是默认值。
  • EDGE_RISING: 当信号从低到高过度时触发中断事件。
  • EDGE_FALLING: 当信号从高到低过度时触发中断事件。
  • EDGE_BOTH:所有的过度都会触发。

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);
}

  • 原文地址: 
    https://developer.android.com/things/sdk/pio/gpio.html 
  •  
  • Android Things物联网开发--外围I/O接口之PWM(四)

     

    脉宽调制信号PWM(Pulse Width Modulation)

     



    PWM是用于使用数字输出引脚将比例控制信号应用于外部设备的常用方法。例如:伺服机使用输入PWM信号的脉冲宽度来确定它们的旋转角度。LCD显示屏根据PWM信号的平均值来调整它们的亮度。 
    PWM是一个根据给定的频率和占空比震荡的数字(即方波)信号:

     

  • 频率(用HZ表示)描述了输出脉冲重复的频率。
  • 周期是每一个周期所需的时间,是频率的倒数。
  • 占空比(使用一个百分比表示)描述了该频率窗口内地脉冲宽度。 
    例如,设置为50%占空比的PWM信号对于每个周期有一般时间为高电平: 
    Android Things物联网开发经验总结_第6张图片
    你可以调整占空比来增加或者减少信号的平均高电平时间,下面的图表分别展示了0%,25%和100%的占空比: 
    Android Things物联网开发经验总结_第7张图片
    注意:大多数PWM硬件在每一个周期内至少会切换一次,所以即使占空比是0%和100%,它在每一个周期的开始仍然会有一个小的过度。
  • 管理连接

    为了打开一个到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信号

    在获取到连接之后,为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);
    }

  • 原文地址: 
    https://developer.android.com/things/sdk/pio/pwm.html 
  • Android Things物联网开发--外围I/O接口之I2C(五)


    I2C总线连接一些简单的设备,这些设备的数据负载较小。传感器和执行器用的就是I2C,还有其他的比如加速度计,温度计,LED显示器和电机驱动器。

     

    I2C是一个同步串行接口,这意味着它依赖一个共享的时钟信号来在设备间同步传输数据。控制触发时钟信号的器件称为主设备,所有连接上的外设称为从设备。每一个设备连接到同一组数据信号以形成总线。

    I2C设备使用3线接口连接,包括:

  • 共享时钟信号(SCL)
  • 共享数据总线(SDA)
  • 地线(GND) 
    Android Things物联网开发经验总结_第8张图片
  • 因为所有数据都通过一根线传输,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 从设备将他们的内容放到可写或者可读的寄存器中。

  • 可读寄存器–包含了从设备想要传输给主设备的数据,比如传感器的值或者状态位。
  • 可写寄存器–包含了主设备可以控制的配置数据。 
    在 I2C 之上有一个通用的协议实现叫做 系统管理总线(SMBus),它以标准的方式与寄存器进行交互。SMBus由以下两个 I2C 事物组成: 
    这里写图片描述
  • Register Address标识了要访问的寄存器的地址,Data[N] 是要读取或者写入的数据。从设备上的逻辑数据通常由多位组成,因此可以包含多个寄存器地址。

    注意:对于SMBus协议,设备将会在地址(Register Address)和数据(Data[N])之间发送一个重复的开始位。

    外围I/O接口为访问寄存器数据提供SMBus命令的三种类型。

  • 读写字节数据(Byte Data): readRegByte()  writeRegByte() 方法读或者写单字节(8-Bit)的寄存器数据;
  • 读写字节数据(Word Data): readRegWord()  writeRegWord() 方法读或者写2个连续的寄存器值作为一个16位的小端存储(little-endian)的字。第一个寄存器地址对应字中的低位字节(LSB),后面的寄存器对应着高位字节(MSB)。
  • 读写块数据(Block Data): readRegBuffer()  writeRegBuffer() 方法以数组的形式读或写最多32个连续寄存器的值。
  • // 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.");
    }

 

Android Things物联网开发--外围I/O接口之SPI(六)

SPI接口

 



SPI(Serial Peripheral Interface )是串行外设接口的缩写。SPI,是一种高速的,全双工,同步的通信总线,经常用于需要高速传输数据的设备。SPI非常适合高带宽的使用场景,比如外部非易失性存储器以及图形显示设备。很多传感器设备除了支持 I2C 之外还支持SPI。

 

SPI是一个同步串行接口,这意味着它依赖于一个共享时钟信号来同步设备间的数据传输。一个主设备控制时钟信号的触发,所有的连接上的设备都作为从设备。每个设备连接到同一组数据信号以形成总线。

理论上,SPI传输数据的速率仅仅受限于主设备能以多快的速率切换时钟信号。时钟速率的范围一般在16MHZ到25MHZ之间。这种高速时钟信号允许SPI外设更快的传输数据而且还比UART误码率少。 
Android Things物联网开发经验总结_第9张图片

SPI支持全双工数据传输,这意味着主设备与从设备之间可以同时交换信息。为了支持全双工传输,总线必须提供如下单独的信号,这使得SPI最少有4根线缆接口:

  • Master Out Slave In (MOSI):主设备输出从设备输入
  • Master In Slave Out (MISO):主设备输入从设备输出
  • Shared clock signal (CLK):共享时钟信号
  • Common ground reference (GND):地线

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操作模式,所以当你选择外设时要查看文档。

  1. 设置SPI模式,模式定义了时钟信号的极性和相位。你选择的模式基于三个属性: 
    Android Things物联网开发经验总结_第10张图片

    1. Idle Level:当没有数据传输时,时钟信号的电平(低或高)
    2. Leading Edge:每个时钟脉冲的前沿。
    3. Trailing Edge:每个时钟脉冲与前沿相反的转换。 
      支持下面的模式:

      • MODE0–空闲状态时钟信号为低电平,数据在Leading Edge时传输。
      • MODE1–空闲状态时钟信号为低电平,数据在Trailing Edge时传输。
      • MODE0–空闲状态时钟信号为高电平,数据在Leading Edge时传输。
      • MODE0–空闲状态时钟信号为高电平,数据在Leading Edge时传输。
  2. 设置如下SPI设备参数:

    • Frequency (频率)–指定共享时钟信号的频率,单位为HZ。时钟信号功能在不同的设备硬件上有所不同。在设置这个值之前你应该确认你的特定设备支持的频率。
    • Justification (对齐)–指定每个字节中通过总线传输的各个位的顺序。这也被称为字节顺序。默认的,数据将最先发送最高有效位。
    • Bits per Word(一个字对应的位数)–默认一个字为8位。

下面的代码配置了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);
    ...
}

 

Android Things物联网开发--外围I/O接口之UART(七)

UART(Universal Asynchronous Receiver Transmitter)



UART是通用异步收发器(Universal Asynchronous Receiver Transmitter)的缩写。复杂的外设比如GPS模块,LCD显示器以及收音机通常使用UART端口来通信。

 

UART是用于与一个设备交换原始数据的通用接口。说它通用是因为它的数据传输速率和数据字节格式都可以配置。说它异步是因为没有时钟信号用于同步两个设备之间的数据传输。设备硬件会用先进先出(FIFO)缓冲区来收集所有传入的数据集直到你的app读取。

UART数据传输是全双工的,这意味着数据可以在同一时刻被发送或者接受。它显然要比 I2C 快得多,但是缺少共享钟意味着两个设备必须有统一的数据传输速率,这样每个设备可以以最小的时序误差最为独立的宿主机。 
Android Things物联网开发经验总结_第11张图片 
UART外设通常有以下两种:

  • 3线缆端口:包含数据接收(RX),数据发射(TX)以及地线(GND)
  • 5线缆端口:相比3线端口添加了请求发送(RTS)和清除发射(CTS)信号用于硬件流控制。流控制允许接收设备去表明它的FIFO缓冲区暂时满了,而此时发射设备应该在发送更多数据之前等待。

不像 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发送的字符都被一个数据帧包裹,数据帧包括以下组成部分: 
这里写图片描述

  • 开始位(Start Bit)–在发送数据之前,该行保持1位持续时间的固定时间间隔,用来指示新字符的开始。
  • 数据位(Data Bits)–表示数据字符的各个位。UART会被配置发送5到9位数据来表示一个字符。位数越少表示的数据的范围越小,但是可以提高数据传输的效率。
  • 校验位(Parity Bit)–可选错误检查值。如果UART被配置为奇校验或者偶校验,额外的一位数据会被添加到帧中用于表明数据位的内容总和为偶数还是奇数值。设置它为none来从数据帧中移除这一位。
  • 停止位(Stop Bit)–在所有数据都传输完成之后,该行被重置为可配置的时间间隔以指示该字符的结尾。这可以配置为在1或2位持续时间内保持空闲。

注意:大多数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);
}

注意:选择不通用的波特率在传输过程中可能造成较高的错误率。你应该总是确认你选择的波特率是否是你的设备硬件支持的。

硬件流控制(Hardware Flow Control)


如果你的设备支持 5线缆 UART端口,激活硬件流控制可以增加数据传输的可靠性。这也意味着你可以安全的使用高波特率传输,并且丢失数据的机会更少。

Android Things物联网开发经验总结_第12张图片

当你激活了硬件流控制,当设备上的接收缓冲区满并且不再接收更多数据时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物联网开发--实战之部署app到开发板(八)

如何部署app到开发板


在此之前一定要先参照文章 Android Things物联网开发–硬件和软件环境(二)部署好硬件和软件开发环境。因为本人使用的是树莓派3b,所以这里以树莓派3b为例,在Windows下的开发过程。 

1. 给树莓派插上网线,HDMI线并接通电源,启动Android Things。

Android Things物联网开发经验总结_第13张图片 
Android Things物联网开发经验总结_第14张图片

  1. 打开Android Studio,配好环境后开始写一些简单的代码。
  2. 如下图运行代码:

Android Things物联网开发经验总结_第15张图片 
Android Things物联网开发经验总结_第16张图片

如果显示没有可用设备的话,需要使用adb命令根据IP地址连接到树莓派: 
首先找到Android sdk(安装Android Studio时指定的目录)所在目录,打开 platform-tools 文件夹之后打开命令行工具,如下图:

Android Things物联网开发经验总结_第17张图片

在命令行中输入一下命令: 
adb connect 你的树莓派的IP地址 
回车,出现: connected to 你的IP地址:5555,说明连接成功: 
Android Things物联网开发经验总结_第18张图片

此时Android Studio中就存在该设备了,再按照部署Android app到手机一样的步骤即可将app部署到树莓派。

下图是我写的一个简单的demo部署到树莓派的效果:

Android Things物联网开发经验总结_第19张图片

这里给出简单的代码:

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即可。

 

你可能感兴趣的:(嵌入式,嵌入式开发技术)