<概要>
ここではPICマイコンをBLESerial経由でiPhoneにつないでみます。今回は携帯機器を意識して、単三乾電池3本で動かしてみます。
BLEを使うので、AppleとのMFi契約なしに、iPhoneに接続するガジェットを作ることが可能になります。現在、BLE対応のiPhoneは4S以上となります。
PICマイコンは浅草ギ研で販売しているPIC16F1936を搭載したPicoloを使いました。
<注意>
ここでご紹介するプログラムはあくまでもサンプルプログラムとなります。個別のご質問などにはお答えできませんのでご了承下さい。
機種やOSのバージョン違いや環境の違いなどにより動作しない可能性もあります。また、今後のiPhoneのOSバージョンアップなどに対しての動作も定かではありません。
紹介動画
<ブロック図>
マイコンボードからは一定周期でセンサー読み取り値(8bit値)が送信されつづけます。マイコンボードが0x01を受信するとLEDを点灯、0x00で消灯します。
<接続図>
PIC側接続詳細
■電源部
Picolo(PIC16F1936マイコンボード)は1.8〜5.5Vで動きます。
BLESerialは5V動作ですが、内部は3.3Vで動作しており、ドロップアウト電圧が1Vの3.3Vレギュレータが搭載されています。つまり、4.3V以上あれば動作させることができます。
よって、ここでは単三乾電池x3個で4.5Vをつくり、動作させています。
■LED
LEDはアノードをRB7端子、カソードを電流制限抵抗経由でGNDにつないでいますので、RB7をHiにするとLEDが点灯し、Loにすると消灯します。
■センサー
今回はセンサーダミーとしてスライドボリュームをつけてみました。電圧変化型のセンサーなら同じように値を読み取ることができます。
<PIC側プログラム説明>
開発環境はMPLABv8.90、コンパイラはHI-TECH Cを使用しました。どちらも無償で入手できます。PIC側のプロジェクト全体を圧縮したものはこちら。
WiMaster_test4.zip
送信、受信、両方のデモを行うため、センサー値を送信し、受信した値によりLEDを点灯させるプログラムを作りました。
■初期設定
初期設定部分は次のようになります。
//////////////////////////////////////
// define
// //////////////////////////////////////
#define LED PORTBbits.RB7
#define ON 1
#define OFF 0
//////////////////////////////////////
// initial setting //
//////////////////////////////////////
void init_IO(){
//clock
OSCCON = 0b11110000; //32MHz(4xPLL=enable FOSC=8MHz OSCSetting=CONFIGbit)
//IO
ANSELA = 0b00000000; //1:Analog, 0:Digital
ANSELB = 0b00100000; //1:Analog, 0:Digital *RB5 is analog
TRISB = 0b01111111; //0:output 1:input *TB7 is outout
//UART
SPBRG = 51; //9600bps (FOSC=8MHz (without PLLx4))
TXEN = 1;
SYNC = 0;
SPEN = 1;
CREN=1;
RCIE=1; //RC intrrupt enable
//ADC setting
ADCON0 = 0b00110101; //AN13 select, DONE=0, ADON=1
ADCON1 = 0b00100000; //Left, FOSC/2, VREF-=VSS, VREF+=AVDD
//Intrrupt
PEIE=1;
GIE=1;
}
内部8MHz駆動で、UARTは9600bps、LED用にRB7をデジタル出力へ設定、センサー用にRB5(AN13)をanalog入力ポートに設定しています。
受信は割り込みで受けるので、割り込みも有効に設定しています。
■データ受信
//////////////////////////////////////
// interrupt //
//////////////////////////////////////
static void interrupt
isr(void){
//受信割り込み
if(RCIF == 1){
unsigned char getData;
RCIF = 0;
getData = RCREG;
//LED判定
if(getData==0x01){
LED=ON;
}else if(getData==0x00){
LED=OFF;
}
}
}
受信は割り込みで受けて、0x01ならLEDをON、0x00ならオフしています。HI-TECH Cの場合、割り込み後にどのフラグが立ったかで何の割り込みかを判定しています。UARTの受信割り込み発生時にはRCIFフラグがたちます。このフラグはRCIF
= 0;のようにプログラムでリセットする必要があります。
■メイン(一定周期でセンサーデータを読んで送信)
//////////////////////////////////////
// main //
//////////////////////////////////////
void main(){
//初期設定
init_IO();
//300mSごとにセンサーデータを送る。受信は割り込みで処理。
while(1){
TXREG=readADC();
wait_ms(100);
}
}
readADC( )はADCで読んだ値を返す関数です。 wait_ms( )は引数mS間ウェイトする関数です。中身はソースを参照願います。TXREGに値をセットすると自動で送信されます。
メインループでは一定周期でセンサーデータを送っているだけです。受信は先の割り込みルーチンで処理されます。
<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のページのサンプルプログラムの節に、他のデバイスでの例を掲載しています。
以上
|