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

















