iOSで外部デバイスを接続する場合、下記の2択から考えることが多いと思います。
- WiFiで繋ぐ
- Bluetooth Low Energy(以下BLE)で繋ぐ
WiFiで繋ぐ場合、接続中にiOSデバイスがインターネットに繋げないため、BLEを選択する場合が多くなると思います。しかしながら、BLEを使いこなすには結構な勉強が必要で、パッと思いついたアイデアをチャチャッとプロトタイプしたいときに大変です。
そこで,浅草ギ研さんが出しているBLE-Serialというモジュールを使ったらBLE詳しくなくても簡単にプロトタイプできた!という話を書きたいと思います。GATTプロファイルの作り込みなどもプロトタイプ内でしたい場合は、konashiをご検討ください。
目標
iOSからマイクロマウスを操作できるようにします。また、マイクロマウス側からセンサーデータ(今回はロータリーエンコーダから回転数)を取得して、iOS側でそれを表示させます。
こんなのを作ります。iOSデバイスから、マイクロマウスを操縦します。
必要なもの
環境構築
- iOS側はXcode6.1.1を使用
- マイクロマウス側はこちらのサイトからLPCxpressoを使用
- iOS側言語:swift
- マイクロマウス側言語:c
マイクロマウス側の開発
回路
BLE-Serialとマイクロマウスをシリアルで接続する必要があります。
下記4つを結線します。色がわかりにくくてごめんなさい。半田付けもひどいです。
- 5V(オレンジ)
- GND(緑)
- RX(青)
- TX(白)
コード
こちらのサイトのサンプルコードの「IXポート(SCI)シリアル通信サンプル」サンプルのmain.cを下記のように書き換えました。
受信処理は、すごく雑なコードですが、'a'を受け取ったら前進、'z'を受け取ったら後退、以下....。
送信処理では、TIMER32_1_IRQHandlerで1秒に一回のタイマー割り込みを用意し、
その中で、sprintf(str,"r:%d,l:%d",right_b - right , left_b - left);
とし、その1秒間のエンコーダの回転数を送っています。
マジックナンバーばかりで意味不明な部分が多いですが、right,leftの変数に大きな数字を入れるほど、モーターが前向きに回転速度をあげ、マイナスの数字を入れると後ろ向きに回転速度を上げるようになっています。
int main (void)
{
//制御周期の設定[単位:Hz 範囲:30.0~]
const unsigned short MainCycle = 60;
Init(MainCycle); //CPUの初期設定
InitTimer1();
InitEncoder();
ClearEncoder();
//シリアル通信初期化
InitSci3(CBR_9600,non,1);
int rotateFlag = 0;
long right , left;
right = left = 0;
long r , l;
r = l = 0;
unsigned char data;
//ループ
while(1){
Sync();
GetEncoder(&right,&left);
if(SciByteRx(&data) > 0){
{
if(data == 'a')//前進
{
ClearEncoder();
r = 6000;
l = 6000;
rotateFlag = 0;
}
else if(data == 'z')//後退
{
ClearEncoder();
r = -6000;
l = -6000;
rotateFlag = 0;
}
else if(data == 'r')//回転
{
ClearEncoder();
r = -8000;
l = 8000;
rotateFlag = 1;
}
else//停止
{
r = 0;
l = 0;
ClearEncoder();
rotateFlag = 0;
}
}
}
if(rotateFlag)
Mtr_Run_lv(r,-l,0,0,0,0);// 回転中は制御しない
else
Mtr_Run_lv(r+Proportinal(right ,-left),-(l+Proportinal(-left , right)),0,0,0,0);
}
}
long Proportinal(long value , long target)
{
long ret = ( target - value ) * 10;
return ret;
}
void InitTimer1()
{
NVIC_EnableIRQ(TIMER_32_1_IRQn);
LPC_SYSCON->SYSAHBCLKCTRL |=0x400;
LPC_TMR32B1->TCR =(1 << 1); // TCR Reset
LPC_TMR32B1->PR = 7200 -1;
LPC_TMR32B1->MR0 =10000-1;
LPC_TMR32B1->CTCR=0;
LPC_TMR32B1->MCR |=(1 << 1)|(1 << 0);
//LPC_TMR32B1->IR = 0xff;
LPC_TMR32B1->TCR =(1 << 0); // TCR start
}
void TIMER32_1_IRQHandler(void)
{
static long right = 0 , left = 0;
long right_b = 0 , left_b = 0;
GetEncoder(&right_b,&left_b);
char str[256];
sprintf(str,"r:%d,l:%d",right_b - right , left_b - left);
SciStrTx(str,strlen(str));
right = right_b;
left = left_b;
n++;
LED(n % 4);
LPC_TMR32B1->IR = 0xff;
}
iOS側の開発
コードをgithubにあげました。
是非是非使ってください。
使い方は簡単です。viewDidLoadなどで、connectする。
readは、callback型になっているので、事前にクロージャーを設定しておくと、
データが送られてくるたびにこのクロージャーが呼ばれます。
データの本体は、stringの中に文字列として入ってきます。
override func viewDidLoad() {
super.viewDidLoad()
var communicator : BLESerialCommunicator = BLESerialCommunicator.sharedInstance
communicator.connect()
// string には、r:xxxx,l;xxxx の形で入ってくる
communicator.readBlock = {string in
self.dispatch_async_main
{
println("\(string)")
}
}
}
あとは、適時送信したいときに、writeするだけです。
今回は、ボタンを押したらマイクロマウスを前進、後退させたいのでボタンのAction内で'a'や'z'を送信しています。
@IBAction func prev(sender:AnyObject)
{
var communicator : BLESerialCommunicator = BLESerialCommunicator.sharedInstance
communicator.write("a")
}
@IBAction func back(sender:AnyObject)
{
var communicator : BLESerialCommunicator = BLESerialCommunicator.sharedInstance
communicator.write("z")
}