##本記事について
Arduinoを USB/HIDデバイスとして活用する第三回です。第二回はここ。マイコンとブラウザーの間の双方向通信処理を作ってみました。BME280で測定値のうち、ブラウザーからの指示に基づいた項目を Browserに定時間毎に数値表示するアプリを紹介します。
##第三回)「Toneを利用し、ArduinoとBrowserとの双方向リアルタイムモニター」
前回までで、Arduinoから USB/HID を使って文字列を出力することができました。逆に Arduinoへの入力の例として、 Tone(音)を使ってみました。 Toneは "音が鳴るか鳴らないか” の 1ビット信号として使っています。
マイコンは前回同様、Arduino互換 Adafruit Trinket M0を使用します。
##機能概要
TrinketM0で収集した測定値を文字列としてPCのブラウザーにHID/USB出力することによってブラウザーに表示する、リアルタイム・モニターを実現しました。測定値は温度/湿度などがあり、どれを表示するかはブラウザーから指定することができます。
・Setup処理
・SWの押下を待って処理開始。まず音量をUPして音が聞こえる状態にする。
・WebデータのURLをHID/USB出力し画面が表示された後、Loop処理に移行
・Loop処理
・定時間隔毎、処理を繰り返し実施
・ブラウザーからデータ種別を受信する
・I2Cデバイス(BME280)から気温/湿度などの測定値を取集
・定時間隔毎に指定データ種別の「ラベル文字列」「測定値文字列」「単位文字列」をHID/USB出力
##準備するもの
PC (Windows10) ブラウザーは、Microsoft Edge,Chrome
Adafruit Trinket M0
開発環境は Windows版の Arduino IDE v1.8.9
WebServer(ローカスPC上でも可)
##全体の仕組み
htmlをWebサーバーに置いておく。内部のローカルサーバーでもよい。Adafruit Trinket M0には、テスト用のSWがあり、温度・湿度などを測定するBME280をI2C接続する。PCのイヤフォンジャックと Adafruit Trinket M0を接続する。
WebServerは、WindowsPCのローカルにapacheを立てて確認しました。
##ハード構成
Adafruit Trinket M0 周りのハード構成を示す。
動作確認
##ソフトウエア
HTML内JavaSciptで音を鳴らす
<script type="text/javascript">
//グローバルに定義
var audioCtx=new(window.AudioContext || window.webkitAudioContext)();
function mybeepOnce(pd){
var oscillator=audioCtx.createOscillator();
if(audioCtx.state=="suspended") audioCtx.resume();
oscillator.connect(audioCtx.destination);
oscillator.frequency.value = 1000; // 値はHz(ヘルツ)
oscillator.start();
oscillator.stop(audioCtx.currentTime + pd);
}
//
//ONbitを送りたい場合の呼び出し
mybeepOnce(0.05);
//OFFbitを送りたい場合の呼び出し
mybeepOnce(0.025);
</script>
1ビットの処理
・Enter押してから少したつと、Trinket M0側に音が聞こえる。
・ON:50msecの間音が鳴る。音が鳴り始めてから、35msec 後に再度、音がなっていたらONと判定する。
・OFF: 25msecの間音がなる。音が鳴り始めてから、35msec 後に音がなっていなかったらOFFと判定する。
データは最大8ビット
・ブラウザーに Inqu で問い合わせた後、
・"1" + Tab + Enter => M0 で音の着信待ち。ONO/FFを判定する。
・"2" , "3" ,,,,, "8" と繰り返し、8ビット分のON/OFFデータを受信する。
/*
*公開第三回)双方向リアルタイムモニター
*/
// WindowsPC用
#define USBHOST_WINPC
//環境に合わせて内容を確認または修正すること ーーここから -----------------------------
//①BME280 I2Cアドレスの設定 (0x76 or 0x77 のどちらか)
#define BME280DEVADDR 0x76
//#define BME280DEVADDR 0x77
//②Webserverに合わせて、URLを記述すること
//String g_url_string = "http'//192.168.xxx.xxx/xxxxxxxx.htm"; //内部WebServer用
String g_url_string = "http'//localhost/example0301.htm"; //localhost用
//③ハード設定
#define LED_PIN 13
#define SW_PIN 1
#define TONE_PIN 4
//環境に合わせて内容を確認または修正すること ーーここまで -----------------------------
#define WCS_DELAY_T1 150 //T1 ブラウザー応答時間150
#define WCS_DELAY_GAMEN 3000 //URLたたいてから画面が表示されるまで待つ
#define WCS_BITREAD_MAXCOUNT 5 //bit read のリトライ回数上限
#define WCS_SWWAIT_MAXCOUNT 30 //sw押下待ちのリトライ回数上限 30秒
#include <Wire.h>
#include <ZeroTimer.h>
#include <SparkFunBME280.h>
#include "lib_keyboard.h"
#include "ToneManager.h"
#include "VolumeControl.h"
BME280 mySensor;
ToneManager myToneM;
VolumeControl myVolumeC;
int16_t g_pass; //HID出力したら後の待ち時間を制御する
boolean g_I2CNormal;
volatile int g_i; //timerカウント
volatile boolean g_high_spead;
int g_dataSyubetu = 1; //1-4。初期値は温度
int g_retry_count;
//
//
//
void setup(){
g_pass = 1;
g_i = 0;
g_high_spead = 0;
g_retry_count = 0;
pinMode(LED_PIN,OUTPUT);
pinMode(SW_PIN, INPUT_PULLUP);
myToneM.begin(TONE_PIN, 8); //8bit
Wire.begin();
delay(100); //I2Cデバイスの初期化待ち
mySensor.setI2CAddress(BME280DEVADDR);
//BME280の初期化ができない場合、値ゼロで動かす
if (mySensor.beginI2C() == false) g_I2CNormal = false;
else g_I2CNormal = true;
//
sub_fw_Blink(LED_PIN, 3, 50); //動き始めたことを知らせる
digitalWrite(LED_PIN, HIGH); //明確に点灯
//SWが押されるまで待つ 10msec x 100 = 1sec でエラーとする
boolean sw_pushed = false;
for (int j = 0; j< (WCS_SWWAIT_MAXCOUNT * 100); j++) {
if (sub_fw_SWcheck(SW_PIN) == 1) {
sw_pushed = true;
break;
}
delay(10);
}
if (sw_pushed == false )
while(1) sub_fw_Blink(LED_PIN, 10, 50); //LED点滅し続ける
// 音量UPのため、HIDデバイスを定義する
myVolumeC.begin();
#if defined USBHOST_WINPC
myVolumeC.volumeUP(1); //windows
#elif defined USBHOST_MAC
myVolumeC.volumeUP(2); //MAC
#else
// 何もしない。後で音が拾えない可能性がある
#endif
//
delay(1000); //USB/HID再定義のための待ち時間。
#if defined USBHOST_WINPC
sub_kbd_begin(1); //Windows用に初期化
#elif defined USBHOST_MAC
sub_kbd_begin(2); //Mac用に初期化
#else
// 何もしない。後でKEYBOARDが動作しない可能性大
#endif
delay(100); //HIDデバイスの初期化待ち
//
sub_fw_timerset(); //タイマー起動
}
環境に合わせて、以下の内容を確認または修正してください
①BME280 I2Cアドレスの設定 (0x76 or 0x77 のどちらか有効にすること)
//#define BME280DEVADDR 0x76
//#define BME280DEVADDR 0x77
②Webserverに合わせて、URLを記述すること
//String g_url_string = "http'//192.168.xxx.xxx/xxxxxxxx.htm"; //内部WebServer用
//String g_url_string = "http'//localhost/example0301.htm"; //localhost用
③ハード設定が間違いないか確認
LED_PIN 13
SW_PIN 1
TONE_PIN 4
//
//main loop
//
void loop(){
// 40msec毎に処理を行う
if (sub_fw_event(2)) sub_proc();
}
//
//USB/HID処理
//
void sub_proc() {
static byte s_command = 0; //処理振り分け
static byte s_first = 0;
int j;
//待ち時間がゼロになるまで何もしない。
if (g_pass-- >= 0 ) return;
//処理振り分け
if (s_command == 0) {
s_command = 1;
s_first = 1;
sub_initurl(); //一回だけ実施
} else if (s_command == 1) {
sub_out_kbd(1); //start
if (sub_check_tone()) s_command = 20;
else s_command = 90;
} else if (s_command == 10) {
sub_out_kbd(10); //Data
if (sub_check_tone()) s_command = 11;
else s_command = 90;
} else if (s_command == 11) {
sub_out_kbd(11); //センサー値UP
if (sub_check_tone()) s_command = 20;
else s_command = 90;
} else if (s_command == 20) {
sub_out_kbd(20); //Inqu
if (sub_check_tone()) s_command = 21;
else s_command = 90;
myToneM.clear();
} else if ((s_command >= 21) && (s_command <= 28)) {
//制御情報を1ビットつづ受けとる。8ビット
sub_out_kbd(s_command); //Inq no1 to no8
if (s_command == 21) {
}
g_high_spead = 1; //問い合わせは待ち時間なしで動かす。本当はS_command==21のみでいい
if (myToneM.readBit(s_command - 20)) {
//正常
s_command = s_command+1;
//8bitは無駄なので、4bitで次へ
if (s_command == 25 ) s_command = 29;
} else {
//異常時は同じビットを再送要求する
if (g_retry_count++ >= WCS_BITREAD_MAXCOUNT) s_command = 90; //リトライオーバーで異常へ
}
} else if (s_command == 29) {
g_high_spead = 0; //問い合わせは待ち時間なしで動かすモードを終了する
//制御情報を1ビットつづ受けとる。8ビット
sub_out_kbd(29); //Inqe end
g_dataSyubetu = myToneM.getToneVal(); //受信した制御値を取り出す
s_command = 10; //次のセンサーデータUPへ、
} else if (s_command == 90) {
//フォーカスがおかしくて音が拾えない場合にここにくる
sub_out_kbd(90); //フォーカスをコマンドフィールドへ
s_first = 1;
s_command = 1; //startコマンドから再開する
} else {
//その他はエラー
sub_fw_Blink(LED_PIN, 10, 40);
}
if (s_command == 1) {
g_pass = 50; //40msec*50= 2sec
} else if (s_command == 20) {
if (s_first == 1) {
g_pass = 50; //40msec*50= 2sec
s_first = 0;
} else {
g_pass = 200; //40msec*200= 8sec
}
} else if ((s_command > 9) && (s_command < 12)) {
g_pass = 25; //40msec*25= 1sec
} else if ((s_command > 20) && (s_command < 30)) {
g_pass = 1; //high speed
} else if (s_command == 90) {
g_pass = 25; //40msec*25= 1sec
} else {
//フォーカスおかしいときも、一旦ここにきて待つ
g_pass = 200; //40msec*200= 8sec
}
}
(以下、省略。ソースコードを参照ください)
ソースコードは、GitHubから取り出すことができます。 Windows用とMac用にわかれています。各フォルダー内のすべてのファイルを同一フォルダに保存して、Arduino IDE でコンパイルしてください。
注意)
・BME280のI2Cアドレスが間違った場合は、測定値ゼロとします。
・ツール/ボードの設定は、 Adafruit Trinket M0 です。
・サンプルのHTMLファイルは、htmlフォルダにあります。
##実行
準備
・WebServerが使えて、PCと接続できていること。
WindowsPCのapacheを立てることが簡単かと思います。(下記参考欄)
・既定のブラウザーは、Microsoft Edge または、Chromeになっていること。
・WindowsPCの入力モードは、英数字である必要があります。日本語モードになっていないことを確認してください。
1.PCに Adafruit Trinket M0 をUSB接続、イヤフォンジャック接続する。SW押下で動き出す
2.音量UPコマンドを数回出す。
3.Windowsキー + ‘r’ でcmd窓を開き、URL文字列を入力する
4.音出しを繰り返し、安定して音が受信できることを確認する
・‘Start’ + Tab + Enterを入力する
・Enter押下時に、Browser側で音を出す。M0側では音を拾う。
・音が聞こえれば次へ。聞こえなければ繰り返す
5.定時間隔で以下を繰り返す
・下りデータ受信
‘Inqu’ + Tab + Enterを入力する
‘1’ + Tab + Enterを入力すると、送信データ値にしたがって 音を出す。
M0側で音を拾い、 ON/ OFFを判定する
必要なビット数 (ここでは4回)繰り返す。データ種別が把握できる。
・上りデータ送信
‘Data’ + Tab + Enterを入力する
ラベル + Tab + 測定値 + Tab + 単位 + Tab + Enter を入力する
(送信すべきデータ種別にしたがって、ラベル /測定値 /単位を送信する )
##まとめ
Arduinoとブラウザーの連携において、USB/HIDの上りデータとToneの下りデータを使うことで、双方向の通信基盤が確認できた。これにより、マイコン制御の基本的な仕組みは実現できると考えます。Toneを使った下り通信は、安定性や情報量が少ないなどの課題があるため、次回は 下りデータをToneから発展させることに挑戦します。ESP8266/ESP32のWiFiをうまいこと使えないかと検討中です。
##参考
音量調整ボタンについて、以下を参照にしました。
「ArduinoでUSB接続のPC音量調整ボタンを作る方法」
https://qiita.com/kwbt/items/0c9f930a236ca989e402
WindowsPCにapacheを立てる場合の例は、以下を参考にしました。
https://www.adminweb.jp/apache/install/
Trinket M0の開発環境については、以下を参考にしました。
https://www.denshi.club/cookbook/arduino/trinketm0/trinket-m01arduino-ide.html
2つのライブラリが必要です。以下を参考にしました。
・ZeroTimer.h
https://ehbtj.com/electronics/arduino-zero-timer-interrupt-library/
・SparkFunBME280.h
BME280のライブラリーは豊富にでているので、GitHubなどで探してみてください。