<概要>
ここではArduinoをBLESerial経由でiPhoneにつないでみます。
BLEを使うので、AppleとのMFi契約なしに、iPhoneに接続するガジェットを作ることが可能になります。現在、BLE対応のiPhoneは4S以上となります。
<注意>
ここでご紹介するプログラムはあくまでもサンプルプログラムとなります。個別のご質問などにはお答えできませんのでご了承下さい。
機種やOSのバージョン違いや環境の違いなどにより動作しない可能性もあります。また、今後のOSバージョンアップなどに対しての動作も定かではありません。
紹介動画
<ブロック図>
Arduinoからは一定周期でセンサー読み取り値(8bit値)が送信されつづけます。マイコンボードが0x01を受信するとLEDを点灯、0x00で消灯します。
<接続図>
Arduino側接続詳細
■シリアル通信部
それぞれ、TXをRXへ、RXをTXへつなぎます。今回、Arduino側のシリアルポートはハードウェアシリアルポートを使いました。
■LED
LEDはアノードを13番端子、カソードを電流制限抵抗経由でGNDにつないでいますので、13をHiにするとLEDが点灯し、Loにすると消灯します。
■センサー
今回はセンサーダミーとしてスライドボリュームをつけてみました。電圧変化型のセンサーなら同じように値を読み取ることができます。
※フロー制御はしないので、BLESerialのRTSとCTSを接続します。これを行わないとBLESerialから送信ができません。
<Arduino側プログラム説明>
開発環境はArduinoIDE-1.0.5を使いました。
送信、受信、両方のデモを行うため、センサー値を送信し、受信した値によりLEDを点灯させるプログラムを作りました。
const int LED=13;
int rsvData = 0;
int sensorVal = 0;
void setup()
{
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop()
{
// 受信処理
if(Serial.available() > 0){
rsvData = Serial.read();
if(rsvData == 1){
digitalWrite(LED, HIGH);
}else if(rsvData == 0){
digitalWrite(LED, LOW);
}
}
//センサー読み取り〜送信
sensorVal = analogRead(0);
sensorVal = sensorVal>>2; //10bit->8bit値
Serial.write(sensorVal);
//ウェイト
delay(100);
}
■setup
WiMasterの通信速度は標準で9600bpsなので、シリアルを9600bpsに設定しています。また、13番ポートをLEDに割り当てて出力設定にしています。
■受信
Serial.available( )で受信しているか調べて、1バイト以上受信していたらrsvData変数に入れます。それを確認して1ならLEDポートをHigh(LEDが点灯)、0ならLow(LEDが消灯)にしています。
■センサー読み取り〜送信
analogRead関数で読み取った値をSerial.Writeで送信しているだけです。次のウェイト関数で100mS待ちますので、100mS間隔でセンサーデータが送られることになります。
ArduinoのADCは10bitなので、2bitシフトして8bit値にしてから送信しています。
<iPhone側プログラム説明>
XCodeで作成したプロジェクトファイル全体を圧縮したものがこちらになります。
BLESerial_test_iPhone5.zip
画面イメージ
スライダを動かすとサーボが動き、「センサー読み取り」ボタンを押すと#3のセンサーの値をテキストフィールドに表示します。
以下、プログラムの説明をします。
XCodeのバージョンは5.0.1、iPhone5のiOSバージョンは7.0.3です。バージョンが変わると書き方がかわってくる可能性があるので注意してください。実際に、筆者がこのプログラムを作る前にiOS6でテストしていたプログラムは、iPhone5をiOS7にバージョンアップしたらそのままではコンパイルできない、ということがありました。ソースはあくまでも参考ということでご了解下さい。
このプロジェクトは次の3つのクラスで構成されています。各クラスにはメソッドやイベントハンドラが記述されていますが、使用していないメソッドなどについては説明を省略しております。
1)AppDelegate :プロジェクト作成時に自動で作られる。(説明を割愛します。)
2)ViewController :画面操作や表示
3)BLEBaseClass :WiMasterとの通信関連
ViewControllerとBLEBaseClassの構成は次のようになります。BLEBaseClassのファイルには、2つのクラスが記述されています。
BLEDeviceClass :Device(iPhoneと通信する相手側)の実態を保持し、送信と、受信イベントの処理をおこなう。
BLEBaseClass :アドバタイズメントのスキャン、接続、切断、デバイス情報の保持などを管理する。
以降、各動作状態別に説明します。
■ID
BLEには「サービス」と「キャラクタリスティック」という概念があります。それぞれには固有のIDがつけられます。Bluetoothの団体で決められたIDもありますが、128bit値の個別IDを使うこともできます。
BLESerialはバーチャルシリアルポート(VSP)というオリジナルサービスが搭載されています。
受信(BLESerialから見て受信:iPhone->BLESerial)と、送信(BLESerialから見て送信:iPhone<-BLESerial)の2つのキャラクタリスティックを持ち、各IDは次の通りとなります。(ViewController.m内に記述)
#define UUID_VSP_SERVICE @"569a1101-b87f-490c-92cb-11ba5ea5167c"
//VSPサービス
#define UUID_RX @"569a2001-b87f-490c-92cb-11ba5ea5167c" //RX
キャラクタリスティック
#define UUID_TX @"569a2000-b87f-490c-92cb-11ba5ea5167c" //TX
キャラクタリスティック
iPhoneから見てRXとTXの解釈が逆になりますので注意してください。
1パケット20バイトまで送受信可能です。フロー制御をしていない場合は、連続で20バイト超過をやりとりするとデータ漏れが発生する場合がありますので注意してください。
■起動
Arduinoの電源を入れると、接続されたBLESerialにも電源が供給され、アドバタイズメント状態に正常に移行できた後にBLESerial上の赤LEDが点灯します。
起動すると、まずViewControllerのViewDidLoadが呼び出され、画面の描画が行われます。
画面上から、
_textField :センサー値結果を表示。非通信時は「---」を表示。
_connectButton :「CONNECT」を押すと接続動作が開始される。
_disconnectButton :「DIS CONNECT」を押すと切断される。
_ledOnButton :「LED ON」を押すと、Auduinoへ0x01が送信される。
_ledOffButton :「LED OFF」を押すと、Arduinoへ0x00が送信される。
となっており、ViewDidLoad内で設定、表示しています。詳しくはソースを参照願います。その後、BLEBaseClassを初期化して、アドバタイズメントしているBLEデバイスの検出を始めます。
// BLEBaseClassの初期化
_BaseClass = [[BLEBaseClass alloc] init];
// 周りのBLEデバイスからのadvertise情報のスキャンを開始する
[_BaseClass scanDevices:nil];
_Device = 0;
■スキャン
起動後、ViewDidLoad内ですぐにスキャンが開始されます。
@起動時にBLEBaseClassを生成します。
ABLEBaseクラスのscanDevicesを実行し、CBCentralManagerのscanForPeripheralsWithServicesを実行するとiPhoneからのスキャン動作が開始されます。
[_CentralManager scanForPeripheralsWithServices:services
options:nil];
BPeripheral、つまりBLESerialなどのBLE機器が検出されるたびにdidDiscoverPeripheralが呼び出されます。
- (void)centralManager:(CBCentralManager
*)central didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber
*)RSSI
{
// scan result
BLEDeviceClass* newDevice = [[BLEDeviceClass alloc] initWithPeripheral:peripheral
advertisement:advertisementData RSSI:RSSI];
for (BLEDeviceClass* Device in _Devices) {
if (memcmp((__bridge const void *)(Device.peripheral),
(__bridge const void *)(peripheral), 16) == 0) {
[_Devices removeObject:Device];
}
}
[_Devices addObject:newDevice];
}
RSSIというのは電波強度で、マイナスの値で記されます。値が大きい(マイナスの数字部分が小さい)方が強度が高くなります。
検出内容は冒頭の
(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI
これら3つのパラメータで表されます。これら3つのパラメータを使い、とりあえずBLEDeviceクラスnewDeviceを作成します。次のfor文で、すでに検出されたものかどうか判断し、ダブっていたらリストから削除します。最後に
[_Devices addObject:newDevice];
でデバイスリストに登録し、検出内容を記憶しています。
■接続
起動後、接続動作をしないでそのままにしておくと、上のスキャン状態が続きます。
BLEデバイス(BLESerial側)は、接続されるまでは「アドバタイズメント」状態をキープし、どのホスト(iPhone側)からのスキャンにも答えます。
一度、どこかのホストと接続した後は、アドバタイズメント状態を終了し、他のホストとは接続できない状態になります。
「CONNECT」ボタンを押すと、ViewControllerのonButtnClickイベントが発生し、connectメソッドが実行されます。
A
-(void)connect{
// UUID_DEMO_SERVICEサービスを持っているデバイスに接続する
_Device = [_BaseClass connectService:UUID_VSP_SERVICE];
if (_Device) {
// 接続されたのでスキャンを停止する
[_BaseClass scanStop];
// キャラクタリスティックの値を読み込んだときに自身をデリゲートに指定
_Device.delegate = self; // [_BaseClass printDevices];
//ボタンの状態変更 _
connectButton.enabled = FALSE;
_disconnectButton.enabled = TRUE;
_ledOnButton.enabled = TRUE;
_ledOffButton.enabled = TRUE;
// tx(Device->iPhone)のnotifyをセット
CBCharacteristic* tx = [_Device getCharacteristic:UUID_VSP_SERVICE
characteristic:UUID_TX];
if (tx) {
[_Device notifyRequest:tx];
}
}
}
Bサービスへの接続を試みます。BLEBaseClassのconnectServiceメソッドで接続を開始します。
_Device = [_BaseClass connectService:UUID_VSP_SERVICE];
connectServiceの中身はこんな感じです。
- (BLEDeviceClass*)connectService:(NSString*)uuid
{
if ([_CentralManager state] == CBCentralManagerStatePoweredOn)
{
for (BLEDeviceClass* Device in _Devices) {
NSArray* services = Device.peripheral.services;
if (services == nil) {
services = [Device.advertisementData objectForKey:@"kCBAdvDataServiceUUIDs"];
}
if ([services containsObject:[CBUUID UUIDWithString:uuid]])
{
// connect
Device.state = disconnected;
[_CentralManager connectPeripheral:Device.peripheral options:nil];
while (Device.state == disconnected) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
return (Device.state == connected) ? Device : nil;
}
}
}
return FALSE;
}
全デバイス情報を検索し、サービスUUIDを引数に、そのサービスを持ったperiferalと接続を試みます。
[_CentralManager connectPeripheral:Device.peripheral
options:nil];
その後、接続が開始されるのを待ってから、接続されたデバイスを返します。
説明中で、デバイスやらペリフェラルやら文言がややこしいですが、プログラム中のdeviceやperiferalはiPhoneに接続するBLE機器側(BLESerial側)と考えてください。
C無事接続されたら、スキャン動作を停止します。(詳細はソース参照)
Dその後、ノーティファイを設定します。通信中にPeriferal側からデータが送られてiPhoneに到着したことを通知させる設定を行います。
- (BOOL)readRequest:(CBCharacteristic*)characteristic
{
if (characteristic) {
[_peripheral readValueForCharacteristic:characteristic];
return TRUE;
}
return FALSE;
}
接続が完了すると、BLESerial上では緑LEDが点灯します。また、iPhone画面上のTextFieldに、受信したセンサー値が表示されます。
■データ受信
Arduinoは起動後にセンサー読み取りデータを送り続けています。接続が完了した後すぐにこのデータが送られはじめます。
ViewControllerは BLEDeviceClassDelegate
が設定されています。BLESerialからの受信のノーティファイが設定されているので、受信が発生した場合に didUpdateValueForCharacteristic
が呼び出されます。
- (void)didUpdateValueForCharacteristic:(BLEDeviceClass
*)device Characteristic:(CBCharacteristic *)characteristic
{
if (device == _Device) {
// キャラクタリスティックを扱う為のクラスを取得し
// 通知されたキャラクタリスティックと比較し同じであれば
// bufに結果を格納
//iPhone->Device
CBCharacteristic* rx = [_Device getCharacteristic:UUID_VSP_SERVICE
characteristic:UUID_RX];
if (characteristic == rx) {
// uint8_t* buf = (uint8_t*)[characteristic.value bytes];
//bufに結果が入る
// NSLog(@"value=%@",characteristic.value);
return;
}
//Device->iPhone
CBCharacteristic* tx = [_Device getCharacteristic:UUID_VSP_SERVICE
characteristic:UUID_TX];
if (characteristic == tx) {
// NSLog(@"Receive value=%@",characteristic.value);
uint8_t* buf = (uint8_t*)[characteristic.value bytes];
//bufに結果が入る
_textField.text = [NSString stringWithFormat:@"%d", buf[0]];
return;
}
}
}
iPhoneにデータを受信した場合は後半の方のルーチンになります。(//Device->iPone以下)
まず、BLESerialからの送信キャラクタリスティックをtxで作り、受信したキャラクタリスティックと比較します。同じであったら受信イベント発生と判断し、textFieldに値を表示させています。
複数バイトを受信した場合はbufの長さが受信したバイト長分になります。
実験回路でスライドボリュームを動かすと、表示値も変わります。
■データ送信
Arduinoは起動後にセンサー読み取りデータを送り続けています。接続が完了した後すぐにこのデータが送られはじめます。
@〜Aは割愛します。ボタンを押してwriteWithoutResponseメソッドを呼び出しています。詳細はソースを参照願います。
BwriteWithoutResponseメソッドの中身は次のような感じです。
- (BOOL)writeWithoutResponse:(CBCharacteristic*)characteristic
value:(NSData*)data
{
if (characteristic) {
[_peripheral writeValue:data forCharacteristic:characteristic
type:CBCharacteristicWriteWithoutResponse];
return TRUE;
}
return FALSE;
}
CBPripheralのwriteValueメソッドにデータ配列を渡すと送信されます。
■切断
切断は、一度切断し、その後スキャンを再開して次の接続にそなえています。
切断はCBCentralManagerのcancelPeripheralConnectionメソッドを呼び出します。
[_CentralManager cancelPeripheralConnection:Device.peripheral];
このサンプルプログラムでは、簡単に説明するために、本来は入れるべきエラー処理などを行っていません。実際に使う場合はこれらを参考にして実用的なプログラムを作ってください。
Android端末でも同じように、同じハードウェアを動かすことができます。BLESerialのページのサンプルプログラムの節に、他のデバイスでの例を掲載しています。
以上
|