Arduino で Bluetooth シリアル変換モジュール(HC-05)を使う

本来ならば Bluetooth LE を使うところなのでしょうが、価格が高い(苦笑)なので安い方から手を入れていきます。

<b>連載</b>Bluetooth LE (5) Android 4.3 で Bluetooth LE 機器を使う (フェンリル | デベロッパーズブログ)
http://blog.fenrir-inc.com/jp/2013/10/bluetooth-le-android.html
Bluetoothでデバイスと通信するには | garicchi.com
http://garicchi.com/?p=17111

なところで、BLE を使っているのでいずれ追いつこうということで。手始めには、先に作ったモーターの駆動系を Bluetooth で制御していきます。同時に Raspberry Pi の Bluetooth シールドも買ったのですが、まあ、これは要らなかったかも。同じ HC-05 の変換モジュールだけで ok みたいです。

Bluetoothシリアル変換モジュール(マスタ/スレーブ) – Androciti Wiki
http://wiki.androciti.com/index.php?Bluetooth%A5%B7%A5%EA%A5%A2%A5%EB%CA%D1%B4%B9%A5%E2%A5%B8%A5%E5%A1%BC%A5%EB(%A5%DE%A5%B9%A5%BF%2F%A5%B9%A5%EC%A1%BC%A5%D6)

価格的には 1,500円ぐらいですね。スレーブ専用の HC-06 だと 1,000 円ちょっとなので複数使うときに良さそうです。
実は、BLE とそれ以前の Bluetooth の違いを知らなくて、去年 Xamarin のカンファレンスで SensorTag を見せてもらったときに「うーん、Bluetooth なのか」と思ってただけなんですよ。実は、グロサミで解説してもらった(英語だったけど)Rolling Spider も BLE を使っていて、それの SDK を使って Bluetooth 対応のコントローラーで動かした、ってのミソだったんですね…と今更思い直しました。SensorTag は試しに買ってみたので、到着したら試してみる予定です。あと、BLE 対応のシールドも。

シリアルポートでつなげる

とはいえ、Bluetooth に様々なプロファイルがあって、それを使えば様々なことができる、ってことは以前に Bluetooth を調べたときに分かってはいたのですが、その時には具体的な端末がなくて、オーディオ関連とかファイル転送をしてもなぁ、ってな感じでした。あまりハードウェアに詳しくなかったので、何かを制御するには WiFi ぐらいだろうと思っていたわけで、RealSense 絡みも WiFi で作っています。
が、とある人から、がりっちさんの RFCOMM 通信の記事を知って、ちょっと通信部分の制御を変えてみようかと思ったわけです。WiFi+RPi の組み合わせや、Bluetooth+RPi+PSコントローラーのみかと思っていたら、RFCOMM プロトコルでつなげれば、結構手軽に iPhone/Android から繋げられそうです。実は Windows からは Windows Windows ストアアプリから接続しないといけない(デスクトップアプリでもできるはずなんですけどね?)ので、あれこれとややこしいのですが、さっくりと繋がります。
COM ポートとして使えるので、そのまま適当なコマンドを作ってやり取りすれば自前のモーター制御/サーボ制御ぐらいはできそうです。

Bluetooth シリアル変換モジュール(HC-05)をArduinoにつなげる

HC-05 Arudino
RX — TX
TX — RX
GND — GND
VCC — 3.3V

につなげます。RX/TX が送受信でワンセットになっているのと電源用の VCC/GND を差すだけで十分です。これは Raspberry Pi の場合も同じなので、結構さっくりと認識するはずです。

ちなみに、Windows 8 から Bluetooth 機器につなげるときはペアリングが必須なんですが(Windows 10の場合は BLE に対応するので、ペアリングは必要ないらしい?)、Bluetooth 機器だけを認識させる場合は、電源の VCC/GND の間に 3.3V – 6V ぐらい流せば ok です。電波を弱くして Bluetooth 機器の近くに入ったよ、ってのは、これでで十分かもしれませんね(実際、BLE の使い方でそういうのはあると思う)。

Android 側のスケッチを作る

シリアルポートは1バイト単位で読み込むのですが、コマンドっぽく送受信したかったので8バイト単位で受信しています。このあたりはマインドストーム EV3 を制御するときも同じみたいですね。こっちもいずれやってみたいところです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
int ledPin=13;
int mPin  = 8; // モーター
 
void setup(){
  pinMode(ledPin,OUTPUT);
  pinMode(mPin,OUTPUT);
  Serial.begin(9600);
}
  
void loop(){
  int n = Serial.available();
  if ( n >= 8 ) {
    int ch[8];
    int i;
    // 8バイトずつ読み込む
    for ( i=0; i<8; i++) {
      ch&#91;i&#93; = Serial.read();
    }
    // コマンドを判別
    if ( ch&#91;0&#93; == 'm' ) {
      if ( ch&#91;1&#93; == '1' ) {
        if ( ch&#91;2&#93; == 'o' && ch&#91;3&#93; == 'n' ) {
          // pin8 on
          digitalWrite(mPin,HIGH);
        } else {
          // pin8 off
          digitalWrite(mPin,LOW);
        }
      }
    }
    // 通信時に光らせる
    digitalWrite(ledPin,HIGH);
    delay(300);
    digitalWrite(ledPin,LOW);
    // エコーを送信
    for ( i=0; i<8; i++) {
      Serial.write(ch&#91;i&#93;);
    }
  }
}
&#91;/code&#93;
<ul>
<li>m1on  でモータ-をON</li>
<li>m1off でモーターをOFF</li>
</ul>
<p>
な単純な仕組みです。最終的には、モーター制御とアームのサーボ制御も入れたいのと、対物センサーの受信もいれておきたいですね。ここまで来ると、素直に BLE にしたほうがいいような気もするのですが、まあ、HC-05 モジュールが安いので。
</p>
<p>
<h2>Windows ストアアプリから制御する</h2>
</p>
<p>
Windows 8 の制限から、ストアアプリから制御します。たぶん、Windows Phone からも同じ制御ができるはずです。
</p>
[code lang="csharp"]
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }
    // RFCOMM のサービスID
    Guid serviceGuid = Guid.Parse("00001101-0000-1000-8000-00805f9b34fb");
    RfcommDeviceService rfcommService;
    StreamSocket socket;
    DataWriter writer;
    DataReader reader;
 
    // 接続する
    private async void btn_findDevice_Click(object sender, RoutedEventArgs e)
    {
        string selector = RfcommDeviceService.GetDeviceSelector(RfcommServiceId.FromUuid(serviceGuid));
        DeviceInformationCollection collection = await DeviceInformation.FindAllAsync(selector);
        if (collection.Count > 0)
        {
            DeviceInformation info = collection.First();
            rfcommService = await RfcommDeviceService.FromIdAsync(info.Id);
 
            socket = new StreamSocket();
            await socket.ConnectAsync(rfcommService.ConnectionHostName, rfcommService.ConnectionServiceName);
            writer = new DataWriter(socket.OutputStream);
            reader = new DataReader(socket.InputStream);
            textOut.Text = "接続しました";
        }
        else
        {
            MessageDialog dialog = new MessageDialog("デバイスが見つかりませんでした");
            await dialog.ShowAsync();
        }
    }
    // 切断する
    private void btn_close_Click(object sender, RoutedEventArgs e)
    {
        writer.Dispose();
        reader.Dispose();
    }
 
    // 送信する
    private async void btn_sendMessage_Click(object sender, RoutedEventArgs e)
    {
        // 8文字にして送る
        string text = textIn.Text;
        await SendCommand( text );
    }
    // コマンド送信
    async Task SendCommand(string text)
    {
        textIn.Text = text;
        // 8文字にして送る
        if (text.Length < 8)
        {
            text = text.PadRight(8, '*');
        }
        else
        {
            text = text.Substring(0, 8);
        }
        writer.WriteString(text);
        await writer.StoreAsync();
        // そのまま受信待ち
        var res = reader.LoadAsync(8);
        res.Completed = async delegate
        {
            await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
                string text2 = reader.ReadString(8);
                textOut.Text = text2;
            });
        };
    }
 
    private async void clickM1on(object sender, RoutedEventArgs e)
    {
        await SendCommand("m1on");
    }
 
    private async void clickM1off(object sender, RoutedEventArgs e)
    {
        await SendCommand("m1off");
    }
}

基本は、がりっちさんの記事 http://garicchi.com/?p=17111 と変わりません。デバイスを見つけて、最初のデバイスに自動的に接続します。最初の1回だけはストアアプリから接続許可を求めるダイアログが出ます。

あと、RFCOMM を使うために Package.appxmanifest に以下のように追加しておきます。

1
2
3
4
5
6
7
8
<Capabilities>
  <Capability Name="internetClient" />
  <m2:DeviceCapability Name="bluetooth.rfcomm">
    <m2:Device Id="any">
      <m2:Function Type="serviceId:00001101-0000-1000-8000-00805f9b34fb" />
    </m2:Device>
  </m2:DeviceCapability>
</Capabilities>

うまくできあがると、m1on/m1off のボタンを押すことでモーターが動いたり止まったりします。ここは LED で動かしてて確認してもよいですね。ブレッドボード上でモーターが動いても何も面白くない(娘談)なので、ええ、キャタピラを付けてコントローラーで制御するところまでやりますよ。

ちなみに BLE 機器を見つけるだけならばペアリングが不要(通信には必要?)なので、忘れ物タグとかについているんですね。なるほど。これ Windows 8.1 の場合はまだ見つけるだけでもペアリングが必要で、Windows 10 ではそれに対応するらしいのですが…適当な BLE 対応のスマートフォンを媒介すれば、Windows 8.1 でもできるかなと。まあ、半年後に Windows 10 が出るのでそれ待ちってのもあるし、手元のノートPCにWindows 10 TP 版を入れて確かめてみるってのもありですね。

カテゴリー: 開発, Arduino, WinRT パーマリンク