<概要>
ここではArduinoをWiMaster経由でiPhoneにつないでみます。
<注意>
ここでご紹介するプログラムはあくまでもサンプルプログラムとなります。個別のご質問などにはお答えできませんのでご了承下さい。
機種やOSのバージョン違いや環境の違いなどにより動作しない可能性もあります。また、今後のOSバージョンアップなどに対しての動作も定かではありません。
<ブロック図>
Arduinoからは一定周期でセンサー読み取り値(8bit値)が送信されつづけます。マイコンボードが0x01を受信するとLEDを点灯、0x00で消灯します。
<接続図>
Arduino側接続詳細
■シリアル通信部
それぞれ、TXをRXへ、RXをTXへつなぎます。今回、Arduino側のシリアルポートはハードウェアシリアルポートを使いました。
■LED
LEDはアノードを13番端子、カソードを電流制限抵抗経由でGNDにつないでいますので、13をHiにするとLEDが点灯し、Loにすると消灯します。
■センサー
今回はセンサーダミーとしてスライドボリュームをつけてみました。電圧変化型のセンサーなら同じように値を読み取ることができます。
<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);
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間隔でセンサーデータが送られることになります。
<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のページのサンプルプログラムの節に、他のデバイスでの例を掲載しています。
以上
|