目次
この記事の開発環境
- 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 version="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() } }