■ホビーロボット部品の製造・販売 
  モータコントローラ、センサ、音声、画像、無線モジュールなど、
■ホビーロボット制作記事のページ (各種マイコン、PCとの接続事例)
■特殊メイク、特殊材料の販売 ※特殊メイクのコーナーはこちらに移りました。
Top(お知らせ) 製品一覧 使い方とサンプルプログラムWebショップ リンク ブログ 会社案内
 Easy Robotics for all enthusiastic people!!!  ---HOBBY ROBOT PARTS SHOP ASAKUSAGIKEN---   Since 2003...


PICマイコンとiPhone端末をWiFiでつないでみる
 

<概要>

  ここではPICマイコンをWiMaster経由でiPhoneにつないでみます。今回は携帯機器を意識して、単三乾電池2本で動かしてみます。

 PICマイコンは浅草ギ研で販売しているPIC16F1936を搭載したPicoloを使いました。


<注意>

 ここでご紹介するプログラムはあくまでもサンプルプログラムとなります。個別のご質問などにはお答えできませんのでご了承下さい。

 機種やOSのバージョン違いや環境の違いなどにより動作しない可能性もあります。また、今後のiPhoneのOSバージョンアップなどに対しての動作も定かではありません。

 紹介動画

 


<ブロック図>

 マイコンボードからは一定周期でセンサー読み取り値(8bit値)が送信されつづけます。マイコンボードが0x01を受信するとLEDを点灯、0x00で消灯します。





  

  


<接続図>

 PIC側接続詳細



■電源部

  ホビー用のマイコンボードは5V動作が多いので、WiMasterの電源入力は5Vになっています。実際にはWiMasterに供給された電源をU2(基板上の白い文字を参照)のレギュレータで3.3Vにし、内部は3.3V動作で動いています。無線部は”大体”3Vぐらいで動くように作られているので、単三乾電池2本直列で3Vをつくり、これを電源としても動きます。

 また、Picolo(PIC16F1936マイコンボード)は1.8〜5.5Vで動きますので、こちらも乾電池2本で問題ありません。

 WiMasterに直接3.3V(又は3Vぐらい)を入れる場合は、端子が出ていませんが、C2の部品の右側が内部電源+につながっているので、ここに3Vを供給します。PicoloのシリアルポートのVDDは電源+端子につながっているのでここから乾電池の+電圧が出ていますので、これをC2右につなぎます。

■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側プログラム説明>




 ここでは、iPhone端末とWiMasterはすでにアソシエーションまで済んでいるとします。この操作についてはWiMasterの説明ページの<動作説明>の部分を参照願います。


 通信はSocket通信で行います。
 

 XCodeで作成したプロジェクトファイル全体を圧縮したものがこちらになります。

 WiMaster_Test_iPhone4.zip



 画面イメージ
 

 スライダを動かすとサーボが動き、「センサー読み取り」ボタンを押すと#3のセンサーの値をテキストフィールドに表示します。


 以下、プログラムの説明をします。ARCを使用しています。
 このプロジェクトは次の3つのクラスで構成されています。

 

 1)AppDelegate :プロジェクト作成時に自動で作られる。(説明を割愛します。)

 2)ViewController :画面操作や表示

 3)WiFiConnect :WiMasterとの通信関連

 

 ViewControllerとWiFiConnectの構成は次のようになります。

 

 

 LEDを操作するときはソケットを通じてコマンドのバイト列(今回は1バイトのみ)を送ります。

 センサーから返ってきたデータはWiFiConnectで受けますが、それをViewControllerのテキストフィールドに表示させるために、<WiFiReceiveDelegate>というデリゲートを用意しました。WiFiConnect側でプロトコル宣言し、ViewController内で実装します。詳しくは後ほど説明します。

 



 ■起動〜ソケット接続まで

 起動すると、まずViewControllerのViewDidLoadが呼び出され、スライダなどのユーザーインターフェイスを配置し、WiFiConnectクラスを実体化してソケットを開通させています。

 

 ユーザーインターフェイスの配置などは一般的な事項ですので説明を割愛します。この部分はソースを参照願います。
  Aの部分から説明します。ViewControllerの起動時にconnectメソッドを呼び出しています。この部分は次のようになっています。

//////////////////////////////////////////////////////////////
// WiMasterと接続
//////////////////////////////////////////////////////////////
-(void)connect
{
 _conn = [[WiFiConnect alloc] initWithHostName:@"n.n.n.n" port:NNNN timeout:30];
 _conn.delegate = self; //受信イベント受け取りの為、デリゲートをセット
  if([_conn openSocket]){
   NSLog(@"openSocket success");
  }else{
   NSLog(@"openSocket Error");
  }
}

 n.n.n.nの部分にはIPアドレスが、NNNNの部分にはポート番号が入ります。IPアドレスとポート番号はWiMaster購入時に添付される紙に印刷されていますので、その値を記入します。ここではWiFiConnectクラスを実体化し、_connを作成しています。

 実際にSocket接続する部分はWiFiConnect内になります。

 

 BWiFiConnectのコンストラクタではIPとPORT番号を引数に、初期化を行います。

//////////////////////////////////////////////////////////////
// コンストラクタ
//////////////////////////////////////////////////////////////
-(id)initWithHostName:(NSString *)hostName port:(UInt32)port timeout:(int)timeoutSec{
 self = [super init];
 if(self){
   _hostName = hostNam;
  _port = port;
  _readStream = nil;
  _writeStream = nil;
 }
 return self;
}

 

 次がSocketを開く部分です。

 CiPhoneでソケット通信をする方法はいくつかあるらしいのですが、今回はNSStreamを使っています。iPhoneのWiFi機能とNSStreamは直接接続することができないので、CoreFoundationのクラスでSocketを作った後に、NSStreamに__bridge_transferで渡しています。transferなのでCFStreamはNSStreamに渡した後に(たぶん?)消滅します。


//////////////////////////////////////////////////////////////
// ソケットオープン
//////////////////////////////////////////////////////////////
-(BOOL)openSocket
{
 //CFStreamで開いて、NSStreamに渡す
 CFReadStreamRef readStream;
 CFWriteStreamRef writeStream;

 CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)_hostName, _port, &readStream,&writeStream);

 _readStream = (__bridge_transfer NSInputStream*)readStream;
 _readStream.delegate = self;
 [_readStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

 _writeStream = (__bridge_transfer NSOutputStream*)writeStream;
 _writeStream.delegate = self;
 [_writeStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

 [_readStream open];
 [_writeStream open];

 return YES;

}

 Socketが開いたら、入力ストリーム・出力ストリームそれぞれをRunLoop(イベントを処理するために裏で動いているループ)に登録して、それぞれをオープンします。これでSocket通信の準備ができました。この後、WiMasterの緑LEDが、点滅から点灯(つきっぱなし)に変わります。

 

 

 

 ■データを送ってLEDを操作する

 「LED ON」ボタンを押すと、WiFi経由でPICに0x01が送られます。PICは0x01を受信するとLEDを点灯します。

 「LED OFF」ボタンを押すと、WiFi経由でPICに0x00が送られます。PICは0x00を受信するとLEDを消灯します。

 

 この部分の動きは次のようになります。

 

 

 @ボタンを押すとonButtonClickイベントが発生します。ここでどちらのボタンを押したか判定します。

//////////////////////////////////////////////////////////////
// ボタンクリックイベント
//////////////////////////////////////////////////////////////
-(IBAction)onButtonClick:(UIButton*)sender{
 if(sender.tag==ON_BUTTON){
  [self send:0x01];
 }else if(sender.tag==OFF_BUTTON){
  [self send:0x00];
 }
}

 

 AWiFiConnectのインスタンスである_connのwriteDataメソッドに、命令のバイト列を渡すと送信されます。

-(void) send:(int)data
{
 uint8_t sndData[1];
 sndData[0]=data;
 [_conn writeData:sndData length:1];
}

 B実際にSocket通信で送信しているのはWiFiConnect内になります。

 

//////////////////////////////////////////////////////////////
// データ送信
//////////////////////////////////////////////////////////////
- (bool)writeData:(const void*)data length:(NSUInteger)len
{
 BOOL ret = NO;
 NSInteger leftlen = len;
 if(leftlen <= 0) return YES;
 while(TRUE){
  NSStreamStatus stat = _writeStream.streamStatus;
  if(stat == NSStreamStatusOpen || stat == NSStreamStatusWriting){
   if([_writeStream hasSpaceAvailable]){
    NSInteger count = [_writeStream write:(data + (len - leftlen)) maxLength:leftlen];
    if(count >= 0){
     leftlen -= count;
     if(leftlen <= 0){
      ret = YES;
      break;
     }
    }else{
     NSLog(@"writeData error %@",_writeStream.streamError.description);
     break;
    }
   }
  }else{
   NSLog(@"writeData error %u",stat);
   break;
  }
 }
 return ret;
}

 

 NSOutputStreamのインスタンス_writeStreamのwriteメソッドを使って送信しています。

 

 

 ■センサーのデータを受信する

 PICからはセンサーデータが周期的に送られてきます。このデータはWiFiConnectクラスのstreamイベントで受信します。

 

 

 

 @WiFi経由でセンサーデータを受信すると、<NSStremaDelegate>のstream:handleEventメソッドが走ります。

 

//////////////////////////////////////////////////////////////
// Streamのイベント
//////////////////////////////////////////////////////////////
-(void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
 switch(eventCode){
  case NSStreamEventHasBytesAvailable: //なにか受信していた時
   if(stream == self.readStream){
    [self readBuffer];
   }
   break;
  case NSStreamEventHasSpaceAvailable:
   //
   break;
  ・
  ・
  ・
 }
}



 <NSStremaDelegate>を実装すると、通信時のいろいろなイベントが拾えるようになります。その中で、
NSStreamEventHasBytesAvailableは、何かを受信したときに発生します。実際の読み込みはreadBufferというメソッドを作りそちらで処理しています。

//////////////////////////////////////////////////////////////
// 受信データ読み込み
//////////////////////////////////////////////////////////////
-(void)readBuffer
{
 uint8_t buf[1024];
 unsigned int length = [self.readStream read:buf maxLength:1024];
 NSMutableData *data = [[NSMutableData alloc] initWithLength:length];
 [data appendBytes:buf length:length]; //ここまででdataの中に受信済みデータが入る
 //ViewControllerにイベントを渡す
 if( [self.delegate respondsToSelector:@selector(WiFiDataReceived:)] ){ //ViewControllerでメソッドが存在するかチェック
  [self.delegate WiFiDataReceived:data]; //デリゲートで受信データを渡す
 }
}

 受信データは、inputStreamのインスタンスであるreadStreamのreasメソッドを使い、MSMutableData型のdataに書き込んでいます。

 A受信したデータは、独自で作ったデリゲートである<WiFiReceiveDelegate>を使ってViewControllerに渡されます。この部分が渡すところです。

 [self.delegate WiFiDataReceived:data];

 このイベントはBWiFiDataReceivedで処理されます。

 このイベントのデリゲート<WiFiReceivedDelegate>は、WiFiConnectのヘッダで@protocol宣言されています。この部分は次のようになっています。

@protocol WiFiReceivedDelegate<NSObject>

-(void)WiFiDataReceived:(NSMutableData*)data;

@end

@interface WiFiConnect : NSObject<NSStreamDelegate>

  ・
  ・

@property (nonatmic, assign) id<WiFiReceivedDelegate>delegate;

  ・
  ・

@end

 

 このデリゲートを通じて、WiFiConnect側からViewControllerのWiFiDataReceived:が呼び出されます。

 

//////////////////////////////////////////////////////////////
// データ受信イベント(WiFiConnectより)
//////////////////////////////////////////////////////////////
-(void)WiFiDataReceived:(NSMutableData *)data{
 NSLog(@"data=%@",data);
  //センサーデータ抽出
  int result = [self checkSensorValue:data];
  //画面に表示
  [_textField setText:[NSString stringWithFormat:@"%d",result]];
}

//返信dataから先頭から2バイト目を返す
-(int)checkSensorValue:(NSMutableData *)data sensorNumber:(int)sNum{
 //MutableDataをbyte配列に変換
  int len = [data length];
  Byte rsvData[len];
  [data getBytes:&rsvData length:len];
  return rsvData[1]; //2バイト目
}

 受信したデータはWiFiConnectクラスからMutableData型で渡されるので、checkSensorValueメソッド内でバイト配列に変換しています。また、PICからは1バイトしか送っていませんが、Socket通信のログを見ると先頭に0x00が入っているので、先頭バイトを無視して2バイト目を読んでいます。

 その抽出データをUITextFieldのインスタンス_textFieldに表示させています。

 

 このサンプルプログラムでは、簡単に説明するために、本来は入れるべきエラー処理や非同期処理などを行っていません。実際に使う場合はこれらを参考にして実用的なプログラムを作ってください。

 

 Android端末でも同じようにソケット通信で、同じハードウェアを動かすことができます。WiMasterのページのサンプルプログラムの節に、他のデバイスでの例を掲載しています。

 


<動作時間>


 このWiFiモジュールはBluetoothなみの低消費電力で動きます。電波を発射するので、送信の方が受信よりも電力を使います。

 今回の例では100mS毎にセンサーデータを送信していますが、これを1秒間隔で送信に変更し、実験したところ、乾電池2本で連続で33時間駆動できました。携帯機器の性能としてはまあまあだと思います。電池部を充電可能なものにしたり、大きなバッテリで動くものに搭載して一定周期でセンサーデータを送る、などもできると思います。

 



以上







(C)Copylight 2003. 有限会社浅草ギ研 | 通信販売の法規(訪問販売法第8条)に基づく通信販売業者の表示