Android Things on Raspberry Pi3 - UARTとBluetoothの共存 -

B!
スポンサーリンク

この記事の開発環境

  • Android Things:1.02
  • Android Studio:3.1.3
  • Kotlin:1.2.41
  • buildToolsVersion:27.0.3
  • compileSdkVersion:27
  • minSdkVersionが:26
  • targetSdkVersion:27

概要

UARTとBLE(Bluetooth)をそれぞれ使用して外部の機器を制御させたかったのですが、UART0とBLEを普通に起動してもUART/BLEどちらも上手く動きません。

LogでBluetoothAdapter.isEnableの状態をデバッグコンソールに吐き出した所falseになっており、BLEが起動できていませんでした。

BluetoothAdapter.enable()を実行しても状態はfalseのままです。

Stack Overflowなどで色々調査したところ、Android Things公式のRaspberry Pi3機能マトリクスのページにたどり着きました。

Raspberry Pi3にAndroid Thingsを搭載した場合、周辺機能の有効/無効により他の機能の使用に影響する場合があるようです。

Raspberry Pi3 機能マトリクス - Android Things 公式サイト -

UART0/Bluetooth同時使用時の障害

UART0、BLEを同時に使おうとした場合、それぞれに以下のような障害が発生します。

  • UARTの出力が460800bps(波形の1bitあたりの時間から推測)に強制変更される
  • BLEが無効(enableを実行しても無効のまま)

なお、1bitの時間を測定した波形は以下です。

 

Raspberri Pi3はのUART機能は内部的にUART0とMINIUARTの2つのUARTを持っており(ピンは同じBCM14/BCM15)、BLEと併用する場合はUART0でなくMINIUARTを使用する必要があります。

UART0とMINIUARTでは性能が異なるそうですが、115200bpsで特に問題無く通信できました。

波形を測定した所わずかにクロック精度が異なるようですが、許容範囲内です。

 

UART設定の注意点

一般的にRaspberry Pi3でUARTを使う場合には注意事項があります。

電圧は3.3V

Raspberry Pi3のUARTは電圧が3.3Vです。

5V系のデバイスと通信を行う場合は電圧変換を行いましょう。

デバッグコンソール設定の削除

デフォルトではUART0がデバッグコンソール用に割り当てられています。

外部機器との通信用にUARTを使用する場合はSDカードに書き込まれたOS上のCMDLINE.TXTのコードを変更する必要があります。

PCにSDカードを挿してCMDLINE.TXTをテキストエディタで開き、console=serial0,115200の部分を削除します。

dwc_otg.lpm_enable=0 console = serial0,115200 ro rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait earlyprintk init=/init androidboot.hardware=rpi3 androidboot.selinux=permissive loglevel=3

 

dwc_otg.lpm_enable=0 ro rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait earlyprintk init=/init androidboot.hardware=rpi3 androidboot.selinux=permissive loglevel=3

UART0とMINIUARTの出力波形

オシロスコープで測定しました。

通信速度[bps] 115200
データサイズ[bit] 8
パリティ なし
ストップビット数 1

UART0のTxD波形

9バイト送信時、約972us。

1bitの時間。通信速度の誤差は測定誤差でしょう。

約8.68us/bit。(115207.37bps/bit)

MINIUARTのTxD波形

約983us。

UART0より約11us長くなっていますが、通信には影響ないレベルの誤差です。

ちなみに、1bit時間を測定しましたが、2us/divのレンジではUART0と変わらなかった為画像は省略します。

 

ソースコード

UARTとBLE共存時の動作を確認したソースコードを貼ります。

ロジックの言語はKotlinです。

AndroidManifest.xml

UARTを使用する為にcom.google.android.things.permission.USE_PERIPHERAL_IO、Bluetoothを使用する為にandroid.permission.BLUETOOTH、android.permission.BLUETOOTH_ADMIN、android.permission.ACCESS_FINE_LOCATIONのパーミッションが必要です。

<?xml "1.0" encoding="utf-8"?>https://gourmet-technology-crypto.jp/wp-admin/post.php?post=369&action=edit#
<manifest package="jp.cryptocat.shareuartandble"
          xmlns:android="http://schemas.android.com/apk/res/android">

	<uses-permission android:name="com.google.android.things.permission.USE_PERIPHERAL_IO" />
	<uses-permission android:name="android.permission.BLUETOOTH"/>
	<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

	<application>
		<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.DEFAULT"/>
			</intent-filter>
		</activity>
	</application>

</manifest>

MainActivity.kt

UARTとBLEをMainActivityにまとめて書いています。

package jp.cryptocat.shareuartandble

import android.app.Activity
import android.bluetooth.*
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.util.Log
import com.google.android.things.pio.PeripheralManager
import com.google.android.things.pio.UartDevice
import com.google.android.things.pio.UartDeviceCallback
import java.util.*

private val TAG = MainActivity::class.java.simpleName

class MainActivity : Activity() {
    // UART
    private var mPeripheralManager : PeripheralManager = PeripheralManager.getInstance()
    private var mUartDevice : UartDevice? = null

    //Bluetooth
    private var mBluetoothManager : BluetoothManager? = null
    private var mBluetoothAdapter: BluetoothAdapter? = null
    private var bleGatt: BluetoothGatt? = null
    private var bleIsEnabled = false
    private var bleService : BluetoothGattService? = null
    private var bleCharacteristic : BluetoothGattCharacteristic? = null
    private val bleServiceUuid : String = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    private val bleCharacteristicUuid : String = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
    var mBluetoothGattDescriptor : BluetoothGattDescriptor? = null
    private var bleConnectionChangedCallback : (() -> Unit)? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        openUart()
        openBle()
    }

    private fun openUart(){
        try {
            if (mUartDevice == null) {
                // Raspberry Pi3で使えるUART名をコンソールに出力
                val device = mPeripheralManager.uartDeviceList
                device.forEach { Log.i("UART", it.toString()) }

                // MINIUARTを開く
                mUartDevice = mPeripheralManager.openUartDevice("MINIUART")
                Log.i("UART", "open:" + mUartDevice?.name)
            }

            mUartDevice?.setBaudrate(115200)
            mUartDevice?.setDataSize(8)
            mUartDevice?.setParity(UartDevice.PARITY_NONE)
            mUartDevice?.setStopBits(1)
            mUartDevice?.registerUartDeviceCallback(onReceiveUart)
        }
        catch (e : Exception){
            Log.e("UART","UART Open Error:" + e.toString())
        }
    }


    // UART受信イベント
    private var onReceiveUart = object : UartDeviceCallback {
        override fun onUartDeviceDataAvailable(uart : UartDevice?) : Boolean {
            Log.i("UART", "Received")
            try {
                // 受信処理
                var data = byteArrayOf(0x53, 0x45)
                mUartDevice?.write(data, data.size)
            } catch (e: Exception) {

            }
            return true
        }
        override fun onUartDeviceError(uart : UartDevice?, error : Int){
            Log.w("UART", "onUartErrorEvent:" + uart.toString() + error)
        }
    }

    private fun openBle(){
        mBluetoothManager = applicationContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        mBluetoothAdapter = mBluetoothManager?.adapter

        // Bluetoothの状態が変わった時のイベントを登録
        val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
        registerReceiver(bluetoothBroadcastReceiver, filter)

        // Bluetoothが無効の時は有効にする
        if (mBluetoothAdapter?.isEnabled == false) {
            mBluetoothAdapter?.enable()
        }
        else {
            // scan開始
            mBluetoothAdapter?.bluetoothLeScanner?.startScan(bleScanCallback)
        }
        Log.i("BLE", "mBluetoothAdapter?.isEnabled=" + mBluetoothAdapter?.isEnabled.toString())

    }

    private val bluetoothBroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF)
            Log.i("BLE", "mBluetoothAdapter?.isEnabled=" + mBluetoothAdapter?.isEnabled.toString())

            when (state) {
                BluetoothAdapter.STATE_ON -> {
                    // scan開始
                    mBluetoothAdapter?.bluetoothLeScanner?.startScan(bleScanCallback)
                }
                BluetoothAdapter.STATE_OFF -> {
                    mBluetoothAdapter?.enable()
                }
            }
        }
    }

    private val bleScanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult?) {
            super.onScanResult(callbackType, result)
            Log.d("BLE","onScanResult")

            // スキャンしたデバイスがBLEに対応している事をチェック
            if ((result?.device?.type != null) && (result?.device?.type!! >= 2)) {
                Log.i("BLE", "type=" + result?.device?.type.toString() + " ... 0:unknown, 1:classic, 2:ble, 3:dual")

                if (result?.device?.name != null) {
                    Log.i("BLE", "name=" + result?.device?.name)
                    // スキャン中に見つかったデバイスが意図した名前なら接続する.第三引数には接続後に呼ばれるBluetoothGattCallbackを指定する.
                    val regex = Regex(result?.device?.name!!)
                    if (regex.containsMatchIn("BLE_Device") == true) {
                        result?.device?.connectGatt(applicationContext, false, gattCallback)
                    }
                }
            }
        }

        private val gattCallback = object : BluetoothGattCallback() {
            override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
                super.onConnectionStateChange(gatt, status, newState)
                // 接続状況が変化したら実行.
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    Log.d("BLE", "onConnectionStateChange STATE_CONNECTED")
                    if (gatt?.device?.name != null) {
                        Log.i("BLE", "Success! Connect to " + gatt?.device?.name!!)
                    }

                    // 接続に成功したらサービスを検索する.
                    gatt?.discoverServices()
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    Log.d("BLE", "onConnectionStateChange STATE_DISCONNECTED")
                    // 接続が切れたらGATTを空にする.
                    bleGatt?.close()
                    bleIsEnabled = false

                    bleConnectionChangedCallback?.invoke()
                }
            }

            // characteristic変更イベント
            override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
                super.onCharacteristicChanged(gatt, characteristic)
                if (gatt != null) {
                    Log.i("BLE", "onCharacteristicChanged " + "gatt=" + gatt?.toString())
                }
            }

            // characteristic書き込みイベント
            override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
                super.onCharacteristicWrite(gatt, characteristic, status)
                Log.i("BLE", "onCharacteristicWrite: status=" + status.toString())

            }

            // characteristic読み出しイベント
            override fun onCharacteristicRead(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
                super.onCharacteristicRead(gatt, characteristic, status)
                Log.i("BLE", "onCharacteristicRead: status=" + status.toString())

                if (characteristic != null) {
                    bleCharacteristic = characteristic
                }
            }

            override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
                super.onServicesDiscovered(gatt, status)
                Log.d("BLE", "service discover state = $status")
                // Serviceが見つかったら実行.
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    // UUIDが同じかどうかを確認する.
                    bleService = gatt?.getService(UUID.fromString(bleServiceUuid))
                    if (bleService != null) {
                        // 指定したUUIDを持つCharacteristicを確認する.
                        bleCharacteristic = bleService?.getCharacteristic(UUID.fromString(bleCharacteristicUuid))
                        if (bleCharacteristic != null) {
                            // Service, CharacteristicのUUIDが意図したものならBluetoothGattを更新する.
                            bleGatt = gatt

                            // 接続が完了したらデータ送信を開始する.
                            bleIsEnabled = true
                            Log.i("BLE", "GATT Update")

                            //characteristicの変更通知設定
                            bleGatt?.setCharacteristicNotification(bleCharacteristic, true)
                            mBluetoothGattDescriptor = bleCharacteristic!!.descriptors[0]
                            mBluetoothGattDescriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                            bleGatt?.writeDescriptor(mBluetoothGattDescriptor)

//                        // scan 終了
//                        Log.d(TAG, "call stop scan")
//                        mBluetoothManager.adapter.bluetoothLeScanner.stopScan(bleScanCallback)
                        }
                    }
                }
            }
        }
    }



    override fun onDestroy() {
        super.onDestroy()
    }

}

 

 

 

 

スポンサーリンク
最新の記事はこちらから