2014年10月10日金曜日

Beetle Moduleを使って出退勤システムを作ってみた

こんにちは。長田です。
今日は、Beetle Moduleを使って、簡単な出退勤システムを作ってみました。















手前の4つのボリュームを回すことで自分のID(2~8の範囲の値を4ケタ)を設定し、送信ボタンを押すことでPCに送信します。

PC側ではそれを受信してアプリ内で検証し、ファイルを出力して入力部に結果をフィードバックします。

大まかな処理ははこのようになっています。

















以下、システムについて見ていきます。

1.システム概要

全体の大まかな概要としては、入力部から無線通信でPCにIDデータを送り、
PC側では入力されたIDがメンバーリストに存在するかどうか照合して、結果をファイルに書き出す、といったものです。
この写真のものが入力部となっていて、ボリュームを回すことでIDを1ケタずつ設定し、
右下のボタンを押すことで送信します。結果に応じて、赤と緑のLEDが点滅します。

                                                     



これがメンバーリストのテキストです。「4ケタのID:名前」の書式で書いて、Windowsの環境変数の設定のようにセミコロン(;)で区切ってあります。このファイルを編集することでメンバーを変更できます。IDを受け取った際にこのメンバーリストを指定文字で区切って読み取り、
受け取ったデータが存在するかチェックします。












アプリの画面出力はこんな感じ。これと同時にテキストファイルにも書き出し、入力部に結果をフィードバックします。

2.実践

食堂に装置を置いて、隣の部屋にあるもう一方の、PCと接続したモジュールと通信します。
















写真だとすごく見づらいですが、ボリュームの値をすべて8に設定してあるので、IDは「8888」になります。メンバーリストにはID8888は設定されているので正常に登録できるはず……恐る恐る送信ボタンをポチッ。






















緑のLEDが光ったので、正常に登録が完了しているはず。PCの様子を見に行きます。











アプリの画面には正常に結果が出力されています。
テキストファイルも、










正常に書き込まれていました。タイムスタンプもしっかり押されています。
他に適当な値を入れて実験を。ID8762を送信してみます。このIDはメンバーリストにはありません。


























赤いLEDが点灯しました。一度しか光っていないのでID整合エラーです。PC側のアプリおよびテキストファイルの出力は、






このようになっています。
アプリ側ではIDが存在しないといわれ、テキストファイルの内容も変化していません。では、もう一度IDを8888に設定して送信してみます。すると、












 先ほど出勤登録がされていたので、今度は退勤となりました。ちなみに写真だとわからないので割愛しますが、出勤登録では緑のLEDが一回点灯したのに対して、退勤登録の場合は2回点灯しています。
また、例外やデータの損失などがあった場合には、赤LEDが何度か点滅するようになっているので、少し待ってから再送信するといいでしょう。










ちなみにテキストファイルは下図のように登録が積み重なっていきます。
これで誰が何時に出退勤したかという情報がわかるというわけです。

3.終わりに

 全体の製作期間は4日ほど。途中わからないことをいつまでも悩んでかなり時間を取られてしまいました。

 あと、可変抵抗の特性の種類をよく理解せずに買ってしまい、買ったのがAカーブだったため、回転角度と抵抗のグラフが二次曲線を描く特性とAD変換による損失などから、ボリュームの両端付近(IDでいうと0,1,9)の入力の変化をとらえるのが難しく、結局IDの入力範囲を2~8とすることになりました。無念…。ちゃんと調べて、Bカーブ(直線を描く)の抵抗を買うべきでした…。

 社員の方に、「問題を切り分けて書き出すなりして整理すれば、わからないことを聞くことができる」と言われ、自分がなかなか質問に踏み出せなかった理由がわかった気がしました。

以上で、私のBeetle Module体験記(?)は終わりです。
最後まで読んでくださりありがとうございました。


2014年9月26日金曜日

Beetle Moduleの間欠モードを使ってみた&Arduinoで制御してみた

こんにちは。石川高専の長田です。前回に引き続き、Beetle Moduleについて書いていきます。

 Beetle Moduleには、定期的に通信相手に信号を送り、必要のないときにはスリープ状態にするモードが有り、今日はそれを使ってみようと思います。


1. スリープモードに移行する手順

Beetle Module には3種類のモードがあります。
  1. トランスペアレントモード:1対1の通信を行う。(前回やったのがこれ!)
  2. ATコマンドモード:ATコマンドを用いて端末の設定を行う。
  3. 間欠モード(スリープモード):スリープしつつ定期的にデータを送信する。
スリープモードへ移行するためには、まずATコマンドモードに移行してから、スリープモードへ移行するためのコマンドを実行します。


2. ATコマンドモードへの移行および操作

 PCからシリアルポートを通して端末に「+++」という文字列を送ると、ATコマンドモードへと移行します。
 ATコマンドモードで設定できる項目は多数ありますが、今回はスリープモードへ移行するコマンドと、ATコマンドモードを終了するコマンドのみを紹介します。
 ATと入力した後ろに行いたい処理に対応するコマンドを打ち、OKと表示されれば受理されているのでCRで締めます。
 SMコマンドでスリープに関する設定をして、CNコマンドを実行するとATコマンドモードを終了します。

 TeraTermでやるとこんな感じです。SMコマンドの後ろの"1"は引数を表しています。1を指定することでスリープモードへ移行し、0を指定することでスリープモードを解除します。ちなみに"2"でもスリープモードに移行しますが、スリープ開始と復帰のトリガーが逆になります。

 図でスリープモードに移行しているので、あとは起こすまで何か打っても反応しません。


3. スリープモードでの動作を確認

 前回と同じように、2つのモジュールを無線で接続して、送受信の様子を見てみます。
前述したとおり、スリープモード中は一定時間ごとに相手にデータを送ります。

こんな感じです。

 この画像だと時間の動きがわかりづらいですが、「10D0」というデータを先頭とする5行のデータが、一定時間ごとに受信されています。この画像だとおよそ4回分になります。
 この文字列……いったいなんの値だろう。とお思いのことと思いますが、これは、Beetle Moduleの特定のピンに入力されている信号の値を表しています。それについては後述します。
 とりあえず一定時間ごとに通信していることがわかったので、スリープモードを解除します。

 Beetle Module には0~20のピンがありますが、その中の9番ピンが「SLEEP RQ」という機能ピンになっており、このピンにHIGHかLOWを入力することで、モジュールを眠りから起こすことができます(HIGHで起きるかLOWで起きるかはSMコマンドの引数による)。あとはその状態でATコマンドモードに移行しスリープモードを解除すれば完了です。

 ここで、どんな文字を送ればそんな特定のピンに信号を入力できるんだ…と自分は悩んでしまいましたがここでArduinoの出番です。
 Arduinoにスケッチを書き込んで、9番ピンが常にHIGHになるようにします。スケッチはこんな感じ。
void setup() {
 pinMode(7,OUTPUT);
}
void loop() {
 digitalWrite(7,HIGH);
}
実にシンプルですが、ArduinoのDIGITALピンの7番ピンとBeetle Moduleの9番ピンを接続してこのスケッチを書き込めば常に起きっぱなしの状態になり、ATコマンドモードへの移行が可能になります。


4. Arduinoを使って入力データを制御する



 














さて、次にArduinoを使ってピンに入力する値を制御し、PC側で出力されるデータを制御します。先ほどの出力された文字列の一部を抜粋します。

 一定時間ごとに、このように5行ずつ出力されています。最初の1行以外、似たような値が出力されていますね。
 下の4行については、マニュアルによると、4つのADC0~3ピンそれぞれに入力された電圧値が0~1024の間で、16進数で表示されています。
 最初の1行については、5つのDIピンに入力された信号がHIGH(1)かLOW(0)かを見て、その値を16bitの整数の対応した位に代入して表示しています。

 対応図は以下のようになっています。

0
0
0
1
0
0
0
0
1
1
0
1
0
0
0
0



DI12
DI11



DI7
DI6

DI4





今回は、DI4、DI6、DI7、DI12のピンがHIGHなので1、DI11がLOWなので0となり、表の数値が16進数に変換されて10D0が出力されている、というわけです。
 作成したArduinoのスケッチを以下にまとめます。

【Io_output.ino】
int pin_AD0=3;
int pin_AD1=4;
int pin_AD2=5;
int pin_AD3=6;
int pin_SLEEPRQ=7;
int pin_DI4=8;
int pin_DI6=9;
int pin_DI7=10;
int pin_DI11=11;
int pin_DI12=12;

void setup() {
 pinMode(pin_SLEEPRQ,OUTPUT);
 pinMode(pin_DI4,OUTPUT);
 pinMode(pin_DI6,OUTPUT);
 pinMode(pin_DI7,OUTPUT)
 pinMode(pin_DI11,OUTPUT);
 pinMode(pin_DI12,OUTPUT);
 pinMode(pin_AD0,OUTPUT);
 pinMode(pin_AD1,OUTPUT);
 pinMode(pin_AD2,OUTPUT);
 pinMode(pin_AD3,OUTPUT);
}
void loop() {
 digitalWrite(pin_SLEEPRQ,LOW);
 digitalWrite(pin_DI4,HIGH);
 digitalWrite(pin_DI6,HIGH);
 digitalWrite(pin_DI7,HIGH);
 digitalWrite(pin_DI11,HIGH);
 digitalWrite(pin_DI12,HIGH);

 analogWrite(pin_AD0,20);
 analogWrite(pin_AD1,80);
 analogWrite(pin_AD2,150);
 analogWrite(pin_AD3,255);
}

 今回は、DIピンはすべてHIGHにして、ADピンにはそれぞれ入力値を変えて電圧をかけます。analogWrite関数の引数2はデューティー比となっており、AD3の255が最大値で、出力値は1023になります。これをArduinoに書き込んで読み取ったところ、図のようになりました。

 DIデータの読み取りはすべてのピンがHIGHなので18F0と正しい数値が表示されていますがADCデータの読み取りが、思っていたのと違う結果に…
 DI0が1番小さく、順に値が大きくなりDI3で最大値が表示されるはずでしたが、DI0,1はほぼすべて0で、DI2は0か03FF(10進数で1024)になっており、正しい値が出力されているのはD3のみです。
 これは不思議だ…と思って少し悩んだんですが、これはおそらくArduinoの仕様によるものだと思います。

 Arduinoのアナログ出力は、厳密なアナログ信号ではなく、PWM制御を使っています。つまり、設定したデューティ比に応じて単位時間あたりにパルス波がHIGHとなっている長さを変えることで、疑似的に電圧値を制御しています。
 なので、ADデータを読み取った瞬間にHIGHならば03FF、LOWならば0が出力されてしまっているのだと予想してみます(あくまで予想です)。なのでデューティ比が最大値に設定されているDI3は常に03FFが出力され、デューティ比が高めのDI2は時々03FFを出力、デューティ比の低いDI1とDI0はほぼ常に0です。

 これをうまく使って何か面白いことできないかな~とか密かに思ったりしてます。

 今日はここまででしたが、これでArduinoのスケッチを使ってBeetle Moduleの送信値をある程度操作できるようになったので、次回はこれを使って何かシステムを作ってみようと思います。

Beetle Module 使ってみた

 初めまして。石川高専の長田といいます。
 私、インターンシップのため24日から横山商会さんで研修させて頂いているのですが、今日は、その課題の一つとして、横山商会さんが開発しておられる無線モジュールのBeetle Moduleを使わせて頂いたので、それについていろいろ書いてみます。

1.Beetle Moduleについて


 このビジュアル…まさにビートル!! かっこいい。

 はい。見た目は置いておいて、Beetle Moduleとはなんぞやと思われる方もいると思いますので、説明させていただきますと、無線通信を行うために基板に接続して使用するモジュールです。

 ARIB STD-T108規格に対応し、モジュール単体で電波を飛ばせる距離が長く、中継なしでも長距離通信を実現できます。また、使用する周波数帯に応じて3つの通信モードを選択可能なので、幅広い用途に使えます。

 今回は、一台のPCに、Beetle Moduleを装着した基板を二つ繋いで、互いに無線通信を行います。その際にやりとりする信号を、PCとのシリアル通信で制御します。

 まずはそのために必要なものを揃えます。
  1. Beetle Moduleを装着した基板およびUSBケーブル
  2. PCとモジュールとのシリアル通信を可能にする通信ソフト
 大まかにこんな感じです。まず、PCと接続してみます。

 このように二つ繋ぎます。
 接続したら、ちゃんとPCが認識しているか確認します。
 デバイスマネージャを開いて、ポートの項目を開きます。
 COM4とCOM5に、しっかり認識されていますね。

 では、さっそく無線通信…の前に、PCからシリアル信号を送るための環境を揃えねば。
ポートを指定して通信を行うTeraTermというソフトがあるので、それをインストールします。
まずはTeraTermで検索して出てくる公式サイトを開きます。

2.環境構築


 とりあえず最新版を選択し、ダウンロード→インストールを行いました。
 では、必要なモノは揃ったので、TeraTermを起動しさっそく無線通信を試してみます。

 先ほどのデバイスマネージャで確認したポートを選択して、OK。
 さらに追加でTeraTermを起動し、もう一つのポートを選択。

 こうなります。
 これでもう文字を打ち込めばそのままデータが送受信されますが、その前にちょっとした下準備を。[設定]→[端末]を開きます。

 赤丸で囲った部分を、図のように変更します。
 受信した際の改行コードをCR+CLに設定することで、改行コードを読み取って改行します。また、ローカルエコーにチェックを入れることで、送信側のウインドウに、送信した内容が表示されるようになるので、使いやすくなるでしょう。
 以上で設定は終わりです。
 では、さっそくウインドウに文字を打ち込んでみます。

3.実践

 図では、COM4に接続したモジュールからもう一方のモジュールに無線信号を送り、受信したデータをCOM5から表示しています。
 応答速度は特に違和感なく、遅延などは気になりません。
 今回は同一PCで実験していますが当然別々のPCで繋いでも同様の通信が行えます。
 以上で、BeetleModuleの導入は終わりです。シンプルでわかりやすかったです。

 …ですが、導入してはい終わり、では寂しいので、この無線通信を使ってアプリを作ってみます。
C#を使って簡単なチャットアプリを作ってみました。(ソースコードをページ末尾に載せてあります)

 中身はなんてことはないただのシリアル送受信アプリですが、これを使ってちょっとした実験をば。
 どれほどの容量、速度までなら特に不都合を感じることなく通信できるのでしょう。
 アプリに、間隔を指定して連続で信号を送るボタンを作っておいたので、それで試してみます。

 まずは、0~9の数字を順番に100ミリ秒間隔で3秒間送信してみます。
 順番に値が表示され、最終的な結果はこんな感じ。0~9の数値が二回ずつと、最後は7で打ち切られ合計28字が出力されています。
 3秒間で合計30回送信しているはずなのに28字しか出力されていないのが不思議です。とりあえず少し送信速度を落として、100ミリ秒ではなく200ミリ秒間隔で3秒間送信してみます。

 出力結果は012345678901234で、合計15文字。入力と一致しています。どうやら先ほどの場合は送信速度が速すぎたようですね。
試しに、今度は70ミリ秒間隔で3秒間送信してみます。
ん……?データが表示されないぞ……
と思ったら3秒経過と同時にまとめて表示されました。(図だとわかりにくいですが…)

これはどういうことだろう。基板の挙動に注目してみます。
 上記のいくつかの入力を何度か試してみたところ、どうやら、上の写真の赤丸で囲った部分が無線通信の状態を表しているようです。データの送受信を行っている際に、送信側は左の緑と赤のランプが点灯し、受信側は黄色のランプが点灯していました。

 そこで最後の例をもう一度試して注目してみたところ、初めの数秒間は送信側の緑のランプだけが点灯し、3秒後に受信側の黄色のランプと送信側の赤のランプが点灯していました。
 製品開発部の方にお伺いしたところ、受信したデータの間隔が速すぎる場合には、受信した信号をその都度ポートに送るのではなく、ある程度バッファに溜めてから送っているそうです。
 今回の場合は、バッファの限界値に到達する前に3秒経過したために受け取ったデータが処理されシリアルポートに送られたのでしょう。
 それを確かめるために、今度はより速い50ミリ秒間隔で3秒間通信を行ってみます。

 画像だとわかりづらいですが、おおよそ10文字ずつ一気に表示され、画像の結果となっています。

 また、送信するデータを1文字ずつではなく、2文字ずつ、3文字ずつと試してみたところ、100ミリ秒間隔やそれより遅い間隔で送信しても同じような、つまり入力したデータを順次表示するのではなく、まとめて表示するようになりました。
 バッファに溜めて処理するか否かの判断は送信速度だけではなく、データの容量にもよるようです。
 
 導入も手軽で使いやすく、高速・大容量の通信にも無理なく対応していました。
他にもある様々な機能があるらしいので、とても楽しみです。

以下にテスト時に使用したソースコードを付録します。

public partial class Form1 : Form
{
    private delegate void Del();
    public Boolean portOpen = false;
    public String sendStr = "";
    public int sendInt = 0;
    public Form1()
    {
        InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
        serialPort1.DataReceived += serialPort1_DataReceived;//シリアルポートの設定
    }
    private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {//シリアルポートからの入力があったときの処理
        Byte[] dat = new Byte[serialPort1.BytesToRead];
        string str;//表示するテキスト
        serialPort1.Read(dat, 0, dat.GetLength(0));
        str = System.Text.Encoding.GetEncoding("UTF-8").GetString(dat);
        new Thread(new ThreadStart(delegate
        {
            Invoke((Del)delegate
            { // 匿名メソッドを Del 型にキャストし, それを呼び出す。
                textBox1.Text += str;
            });
        })).Start();
    }
    private void button1_Click(object sender, EventArgs e)
    {
        string portName;
        portOpen = !portOpen;
        if (portOpen == true)
        {
            portName = comboBox1.SelectedItem.ToString();
            serialPort1.PortName = portName;
            serialPort1.Open();
            connection.Text = "接続中";
        }
        else
        {
            serialPort1.Close();
            connection.Text = "未接続";
        }
    }
    private void button2_Click(object sender, EventArgs e)
    {
        sendStr = textBox2.Text;
        serialPort1.Write(sendStr);
    }
    private void timer1_Tick(object sender, EventArgs e)
    {
        if (IntOrStr.Checked == true)
        {
            serialPort1.Write(sendInt.ToString());
            sendInt++;
            if (sendInt >= 10)
                sendInt = 0;
        }
        else
        {
            serialPort1.Write(sendStr);
        }
    }
    private void rush_Click(object sender, EventArgs e)
    {//連続送信ボタンを押したとき
        timer1.Interval = (int)timerInterval.Value;//送信する間隔を設定
        timer2.Interval = 1000 * (int)timerLength.Value;//送信する時間を設定
        timer1.Enabled = !timer1.Enabled;//オンオフの切り替え
        timer2.Enabled = !timer2.Enabled;//同上
        if (IntOrStr.Checked == true)//0~9の整数を順に送るとき
        {
            sendInt = 0;
            serialPort1.Write(sendInt.ToString());//整数型で
            sendInt++;
        }
        else
        {
            sendStr = textBox2.Text;//送信する文字列を設定
        }
    }
    private void timer2_Tick(object sender, EventArgs e)
    {
        timer1.Enabled = false;
        timer2.Enabled = false;
        sendInt = 0;
    }
}