ここでは、サンプルプログラムの動作原理について順を追って説明します。
プログラミング初心者の方にとって、興味を持っていただくためのきっかけとなる内容になることはもちろんですが、ベテランの方であっても、専用設計されたハードウェアで動作確認済のサンプルコードがあることは非常に心強いはずです。
判断に迷ったら参考にしてください。
| ■ベテランプログラマーの方へ 本書は、右も左も分からない方でも、少し頑張れば感覚的に理解できることを目標に作成されています。 見難かったり、回りくどいところもあるかもしれませんが、ご容赦いただければ幸いです。 本書は、「プロトコル・ガイド」を補完するリファレンスとしてお役立てください。 |
プログラムは、ファイルの先頭からではなく、中ほどにある
void setup() {から始まります。
ここまでの行で行う処理は、プログラムを実行させるために必要なライブラリを読み込んだり、使用する変数を初期化する処理などの下処理が記述されています。
この文章の意味が飲み込めない場合は、こちらを改めて読み返してください。
マイコン・プログラミング講座の後半部分で説明した通り、Arduinoのプログラムは、電源を入れてから一度だけ実行するプログラムグループ(void setup)と、延々と繰り返し処理される主処理ループ(void loop)の2種類があります。
まず、電源を入れてから一度だけ実行するプログラムを記述します。
電源をONにしたら、まずWiFiに接続します。
以下のプログラムを実行することでWiFiに接続します。
WiFi.begin(アクセスポイント名, パスワード);WiFiへの接続は、何度も繰り返し行う処理ではないため、void setup()の部分に記述します。
void setup() {
WiFi.begin(ssid, passwd);
}ssidとpasswordがそのままの文字であることに違和感を感じると思います。
もちろん、このままでは接続できません。
お使いのWiFiアクセスポイント名と、それに対応するパスワードに置き換える必要があります。
しかし、そのまま書くことはできません。
代わりに、SSIDやパスワードが入っている変数(数字や文字を保存する器の名前)をここに書く必要があります。
マイコン・プログラミング基礎で、新たなメモリ領域を確保する方法について書かれていたことを思い出してください。
これと同じ方法で器を用意して、その器に対してssid、passwdと名前を付けます。
これと同じように、
int ssid=0;
int passwd=0;とすることで、
WiFi.begin(ssid, passwd);から参照可能となります。
・・・と、原理的にはこれで正解なのですが、これではエラーになります。
なぜなら、「int」は、integerの略で専ら計算処理で使用する目的で最適化された形式の領域(整数)を示すため、文字情報を入れることはできないからです。
文字データを保存する時は、intではなく、charを用います。
char メモリ領域名 = “初期値”「char」で確保したメモリ領域は、文字コード1バイト分のデータが保存できます。
1バイトではWiFiのアクセスポイント名が入りませんので、複数文字分の連続した領域の確保が必要となります。
メモリ領域名の末尾に[]を付けることで、文字の長さに合わせて領域を自動増減することができるようになります。
それから、記録されているものをプログラム上で変更する予定がなければ、先頭にconstを付けて読み出し専用にします。
const char ssid[] = “INETGATE4”;
const char passwd[] = “p24zc53b”;これをプログラムの先頭の方に書きます。
ここまでの一連の処理をプログラムにまとめると、以下の通りとなります。
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
}一見、何の問題もないように見えるプログラムですが、WiFi.beginの命令は、C言語標準の命令ではないため、コンピュータがどう動けば良いのか分からずに、このまま実行するとエラーになってしまいます。
WiFi.beginが何者であるか、コンピュータに理解させる処理が必要となります。
C言語に限らず、どのプログラム言語であっても、標準状態では簡単な計算処理しかできません。
高精細なグラフィック処理やゲームも、RGBの値の変化を超高速で計算することで実現しているのです。
単純な足し算や引き算しかできないはずのコンピュータが高精細な美しいグラフィックを表現したり、WiFiの信号処理をしてインターネット接続をしているわけですから、その間をつなぐ計算式は想像を絶するほど複雑になることは予想つくでしょう。
このようなオプション的な計算式は、ライブラリと呼ばれる辞書のようなものを外から読み込むことで、使用できるようになるのです。
ライブラリのおかげで、単純な足し算と引き算しかできないコンピュータ(電子計算機)が、ビデオやゲーム、スマートフォンの主要機能の実現に役立っているのです。
プログラムの先頭に以下の1行を書くことで、WiFi.beginが定義されているライブラリが読み込まれます。
#include <WiFi.h>次に、データ送信を行う時に使う受け口を設けます。
例えば、1という数字をインターネットに送信する時、どのようなプログラムを書けば良いでしょうか。
Aというメモリ領域に数字の1を入れたければ、A=1;と書けばAの中身は1になります。
これと同じように、「誰に?」に相当するものを用意しなければいけません。
ここで、
WiFiClient client;このような1行を追加します。
先頭の「WiFiClient」は、これまで出てきた「int」や「char」同様、メモリ領域の確保のようなもので、これに続く「client」がこのメモリ領域に対する名称です。
こうすることで、「client」に入れたものは何でもそのままインターネットに送信されるようになります。
これでインターネットに接続する準備が整いました。
ここまでの内容を整理してプログラムにすると以下の通りとなります。
#include <WiFi.h>
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
}ここで一つ、追加しておきたい処理があります。
スマートフォンやパソコンをWiFiに接続する時を思い出していただければわかる通り、WiFiへの接続は若干時間がかかります。
マイコンの処理は非常に高速ですので、WiFi.beginで接続命令を出しても、つながったことを確認もせず次に進んでしまい、次の処理でエラーになってしまいます。
これを防ぐため、次の処理を記述する前に、きちんとつながったことを確認する処理を記述します。
以下の命令を使うと、WiFiの状態を確認することができます。
WiFi.status()「WiFi.status」でインターネット検索すると、Arduino公式ページのガイドに辿り着くことができます。

WiFi.statusの説明を読むと、この処理を実行することでいくつかのメッセージを返してくることがわかります。
Returnsの項目に書かれている「WL_CONNECTED」が接続成功を示します。
これが出るまで待機してから次に進めば安心して使えます。
「ある条件が満たされたら(満たされるまで)」という処理を行う場合は、「while」を使います。
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}「!=」は「そうでなければ」という意味になります。
つまり、「WiFi.status() != WL_CONNECTED」は、「WiFiがつながっていなければ」という意味になります。
そして、それがwhileで囲われており、そのカッコの中身がdelay(1000);なので、「1秒おきにつながったかどうか確認しなさい」という意味になります。
無事、WiFiがつながったので、センサーのデータを取得してアップロードする部分を作っていきましょう。
| プログラミングは、大文字・小文字を正確に書かなければエラーになります。 長時間のプログラミング作業などで疲れている時は、何度繰り返して見返しても見落としてしまい、何時間も原因究明できないことがあります。 そのような状態に陥ったら、休憩してください。 翌朝起きて見返したら、2分もかからずに原因がわかったということはよくあることです。 そのような体調で続けると、苦しさが先に立ってしまい、挫折の原因になります。 長丁場で考えましょう。 1日に詰め込むより、10分でもいいので毎日やることが大切です。 |
次に、Ambientを使うために必要な処理を記述します。
WiFiの接続処理と同じように、接続コマンドを追加します。
Ambientは、1つのプログラムで複数のチャネルキーを使うことも考えられます。
そのため、ambient一式につき1つの固有の名称を付けて使い分けができるようになっています。
例えば、ambient1、ambient2のように番号を付けて別々のものとして扱うようなイメージです。
これをハンドラと言います。
接続操作も書き込み操作も、ハンドラに対して行われます。
まず最初に、ハンドラを作成します。
以下のプログラムでハンドラ作成ができます。
Ambient ambient;| 少し前に出てきた WiFiClient client; こちらも、正しくはハンドラです。 変数にとても似ていますが、単に1つの情報だけを持つのではなく、接続ステータスやエラー、データを保存する領域など数多くのものがカプセル化されたものとなります。 |
後の方になって判りにくくならないようにハンドラ名を「ambient」としましたが、「hogehoge」などでも大丈夫です。
ここまでのプログラムにハンドラ作成の1行を追加すると、次の通りになります。
#include <WiFi.h>
Ambient ambient;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
}次に、チャネルキーとライトキーをハンドラに渡して、Ambientに送信し、接続処理を行います。
#include <WiFi.h>
Ambient ambient;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
}ambientへの接続は、以下の形式で記述します。
| Ambientハンドラ名.begin(チャンネルIDが保存されているメモリの領域名,ライトキーが保存されているメモリ領域名,ネットワークハンドラ) |
ネットワークハンドラとは、物に例えると、LANケーブルのようなものです。
H10では1種類のネットワークしか使用しませんが、開発する物によっては複数種類のネットワークを使用することもあります。
そのため、どのネットワークか識別できるようにします。
ここでは、「client」と名付けたネットワークハンドラをambientで指定していますが、これは、ambientというラベルの貼られたLANケーブルを差し込むことと同じことです。
次に、ネットワークハンドラをインターネットに接続します。
LANケーブルに例えるなら、パソコンにLANケーブルを接続しても、もう片方をブロードバンドルーターに差し込まないとインターネットが使用できません。
以下の記述で接続します。
#include <WiFi.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
}WiFiClientは、WiFi.hによって決められた記述で、これがインターネットの接続口のようなものです。
clientの部分はユーザーが自由に決められます。
これを並べて記述することで、「client」がインターネット接続されます。
最後に、以下の1行を最初の部分に追加して、ライブラリを読み込みます。
#include <Ambient.h>上記1行を追加したプログラムは以下の通りです。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
}チャネルIDやライトキーがまだ定義されていない(メモリ領域の確保もしていない)ため、領域確保とチャネルID、ライトキーの定義を行います。
忘れている方のため、念のため繰り返しますが、チャネルIDとライトキーは、Ambient登録時に発行されていますので、思い出してください。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
unsigned int channelId = 1***2;
const char* writeKey = "f**************f";
void setup(){
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
}ここで、電源をONにした直後に行われるWiFi接続とAmbient接続の処理は完成です。
次は、setup()後に延々と繰り返されるloop()処理を作り込んでいきます。
loop()処理では、主MCUから測定データを受け取ってAmbientに送信するという処理を1分ごとに延々と繰り返します。
プロトコル・ガイドのページを振り返ってください。
PCでご覧の方は別ウィンドウまたは新しいタブで開きます。
【動作フロー(通常モード)】のコーナーで、「(1)1秒に1回、スイッチ類の状態を含む全てのセンサーの測定値を主マイコンから副マイコンに対して送信されます。」と書かれています。
送信されてきているわけですから、何らかの方法で受信しなければなりません。
主MCUと副MCUは、UARTというデジタル通信規格で接続されています。(詳しくは「UART」で検索して調べてください。)
そして、データはあらかじめ決められたルールに従って送受信しています。
そのルールが、プロトコル・ガイドページの末尾に書かれた「電文(パケット)のフォーマット」になります。
電文のフォーマットを見ると、一度に送られてくるデータが0から始まり31で終わる計32バイトあることがわかると思います。
それから、聞き慣れない言葉で「上位(下位)バイト」というのがあります。
一つの測定データを上位と下位に分けなければいけない理由を説明します。
主MCU-副MCU間の通信で使用するUARTというデジタル通信は、1秒間に数千〜数百万回、電気が流れたり止まったりを繰り返して情報伝達をしています。
のろしやモールス信号をイメージしていただけるとわかりやすいと思います。
この「流れたり止まったり」は、10回分を1セットとして扱っています。
いきなりデータだけ送れば良いわけではなく、「これからデータを送るよ」「もう終わりです」という合図で各々1回分を使うため、残り8回分(8ビット)が、通信で使用できる回数となります。
ON/OFFという2値の内容を8回送ることができる場合、送ることができる電信のパターンは「2の8乗通り」となるため、上限は255となります。
一方、照度や二酸化炭素濃度などは最大で4000前後の値を扱いますので、上限を振り切ってしまいます。
そのような時、1つの数を2バイト(先程、UARTは「8ビット=1バイト」と説明しました。2バイトの場合それが2つになります。)で表現します。
この時、8ビット×2バイト=16ビットとなります。
16ビットで表現できる数の上限は2の16乗なので、上限が一気に6万5535まで拡張されます。
こうした理由で、上位バイトと下位バイトで分けています。
電文フォーマットが理解できたところで、早速受信処理を行います。
【動作フロー(通常モード)】の説明では、1秒に1回送られてくるデータを受信するところから全てが始まります。
そのため、受信待機をしなければなりません。
受信待機をする時は、以下のプログラムを書きます。
if (Serial.available() >= 1) {
}「Serial.available()」は、受信バッファに入っているデータの数を読み出す処理です。
受信バッファとは、届いたデータを受け取るポストのことです。
Serial.available()を実行すると、ポストを開いて届いたお手紙の数を数えます。
このプログラムでは、「1通以上のお手紙が届いていること」をifの成立条件としています。
これを、ここまで書いてきたコードに追加すると、以下のような形となります。
#include <WiFi.h>
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
void setup(){
Serial.begin(9600);
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
}
void loop(){
if(Serial.available()>=1){
}
}主MCUからのデータ受け取りは、ずっと繰り返されるものなので、
void loop()の中に書きます。
それから、初期化に係る処理が書かれているvoid setup()に以下の1行を追加します。
Serial.begin(9600);Serial.begin(9600)は、「デジタル通信(UART通信)を起動して待機しなさい」という命令です。
この命令を最初に実行しないと、主MCUからデータが送られてきてもデータとして扱われず、ただの高速な電気の点滅になってしまいます。
括弧内の9600は、ON/OFFの速度指定です。
「1秒間に9600ビットの速度でデータの送受信をしてください」という意味です。
文書等で人に伝える時は、9600bps(9600ビット毎秒)と書き、言葉で伝える時は9600ビット・パー・セカンドと発音します。
1秒間に9600回と聞けば、非常に高速でおどろくかもしれませんが、パソコンのUSBは1秒間に200億ビットで通信するため、決して高速ではありません。
H10のような情報量の少ない機器や、おもちゃでも、マイコン間の通信は38400bpsか115200bpsが主流です。
速度を上げれば転送にかかる時間を短縮できる一方で、MCUは常に高速動作をさせて待機させなければなりません。
大規模災害で広域停電が発生しても、乾電池で1週間動くように設計しているため、ここまで速度を下げています。
次に、if(Serial.available()>=1)の条件に合致する状態になった(つまり、主MCUから1バイトのデータが送られてきた)時の処理を記述します。
受信バッファに届けられたデータの取り出しは、以下の命令で行います。
Serial.read();Serial.read()を実行しても、受け取る準備がなければ話を右から左に流すのと同じことになるため、データとして扱えるようにメモリに記録します。
RCV_VALUE00 = Serial.read();これで、RCV_VALUE00と命名されたメモリ領域にSerial.read();で受信したデータが入ります。
Serial.read()で読み出したデータを保存するメモリ領域「RCV_VALUE00」も同じように領域確保する必要があります。
受信データの数は32バイトなので、32個の領域を確保します。
#include <WiFi.h>
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
int RCV_BUFF00,RCV_BUFF01,RCV_BUFF02,RCV_BUFF03,RCV_BUFF04,RCV_BUFF05,RCV_BUFF06,RCV_BUFF07,RCV_BUFF08,RCV_BUFF09,
RCV_BUFF10,RCV_BUFF11,RCV_BUFF12,RCV_BUFF13,RCV_BUFF14,RCV_BUFF15,RCV_BUFF16,RCV_BUFF17,RCV_BUFF18,RCV_BUFF19,
RCV_BUFF20,RCV_BUFF21,RCV_BUFF22,RCV_BUFF23,RCV_BUFF24,RCV_BUFF25,RCV_BUFF26,RCV_BUFF27,RCV_BUFF28,RCV_BUFF29,
RCV_BUFF30,RCV_BUFF31=0;
void setup(){
Serial.begin(9600);
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
}
void loop(){
if(Serial.available()>=1){
RCV_BUFF00=Serial.read();
}
}これで、1バイト分のデータが受信できるようになりました。
でも、受信したいデータは32バイトです。
データフォーマットに従った形(32バイト)でデータ受信をするには、以下のように記述します。
if (Serial.available() >= 1) {
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
RCV_BUFF02 = RCV_BUFF03;
RCV_BUFF03 = RCV_BUFF04;
RCV_BUFF04 = RCV_BUFF05;
RCV_BUFF05 = RCV_BUFF06;
RCV_BUFF06 = RCV_BUFF07;
RCV_BUFF07 = RCV_BUFF08;
RCV_BUFF08 = RCV_BUFF09;
RCV_BUFF09 = RCV_BUFF10;
RCV_BUFF10 = RCV_BUFF11;
RCV_BUFF11 = RCV_BUFF12;
RCV_BUFF12 = RCV_BUFF13;
RCV_BUFF13 = RCV_BUFF14;
RCV_BUFF14 = RCV_BUFF15;
RCV_BUFF15 = RCV_BUFF16;
RCV_BUFF16 = RCV_BUFF17;
RCV_BUFF17 = RCV_BUFF18;
RCV_BUFF18 = RCV_BUFF19;
RCV_BUFF19 = RCV_BUFF20;
RCV_BUFF20 = RCV_BUFF21;
RCV_BUFF21 = RCV_BUFF22;
RCV_BUFF22 = RCV_BUFF23;
RCV_BUFF23 = RCV_BUFF24;
RCV_BUFF24 = RCV_BUFF25;
RCV_BUFF25 = RCV_BUFF26;
RCV_BUFF26 = RCV_BUFF27;
RCV_BUFF27 = RCV_BUFF28;
RCV_BUFF28 = RCV_BUFF29;
RCV_BUFF29 = RCV_BUFF30;
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
}このように書くことで、1バイト受信するたびに最初に受信したデータが順に若い番号に押し出されていくため、32バイト分のデータが全て揃います。
次に、データを取り込む処理を作っていきます。
32バイト受信できるようになったとはいえ、データの区切りがどこかわからないし、データ化けを起こしても検知することができず予期しない動作の原因となってしまいます。
そこで、データ内容が正しいか判断しながら五月雨でデータ受信し、正しいデータが揃ったタイミングで別のメモリーにコピーを取る処理を作っていきます。
if (Serial.available() >= 1) {
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
RCV_BUFF02 = RCV_BUFF03;
RCV_BUFF03 = RCV_BUFF04;
RCV_BUFF04 = RCV_BUFF05;
RCV_BUFF05 = RCV_BUFF06;
RCV_BUFF06 = RCV_BUFF07;
RCV_BUFF07 = RCV_BUFF08;
RCV_BUFF08 = RCV_BUFF09;
RCV_BUFF09 = RCV_BUFF10;
RCV_BUFF10 = RCV_BUFF11;
RCV_BUFF11 = RCV_BUFF12;
RCV_BUFF12 = RCV_BUFF13;
RCV_BUFF13 = RCV_BUFF14;
RCV_BUFF14 = RCV_BUFF15;
RCV_BUFF15 = RCV_BUFF16;
RCV_BUFF16 = RCV_BUFF17;
RCV_BUFF17 = RCV_BUFF18;
RCV_BUFF18 = RCV_BUFF19;
RCV_BUFF19 = RCV_BUFF20;
RCV_BUFF20 = RCV_BUFF21;
RCV_BUFF21 = RCV_BUFF22;
RCV_BUFF22 = RCV_BUFF23;
RCV_BUFF23 = RCV_BUFF24;
RCV_BUFF24 = RCV_BUFF25;
RCV_BUFF25 = RCV_BUFF26;
RCV_BUFF26 = RCV_BUFF27;
RCV_BUFF27 = RCV_BUFF28;
RCV_BUFF28 = RCV_BUFF29;
RCV_BUFF29 = RCV_BUFF30;
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
RX_CHKDIGIT=RCV_BUFF00+RCV_BUFF01+RCV_BUFF02+RCV_BUFF03+RCV_BUFF04+RCV_BUFF05+RCV_BUFF06+RCV_BUFF07+RCV_BUFF08+RCV_BUFF09+RCV_BUFF10+RCV_BUFF11+RCV_BUFF12+RCV_BUFF13+RCV_BUFF14+RCV_BUFF15+RCV_BUFF16+RCV_BUFF17+RCV_BUFF18+RCV_BUFF19+RCV_BUFF20+RCV_BUFF21+RCV_BUFF22+RCV_BUFF23+RCV_BUFF24+RCV_BUFF25+RCV_BUFF26+RCV_BUFF27+RCV_BUFF28+RCV_BUFF29+RCV_BUFF30;
RX_CHKDIGIT=RX_CHKDIGIT%256;
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&((RCV_BUFF30==250)||(RCV_BUFF30==255))&&(RCV_BUFF31==RX_CHKDIGIT)){
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
RCV_VALUE02 = RCV_BUFF02;
RCV_VALUE03 = RCV_BUFF03;
RCV_VALUE04 = RCV_BUFF04;
RCV_VALUE05 = RCV_BUFF05;
RCV_VALUE06 = RCV_BUFF06;
RCV_VALUE07 = RCV_BUFF07;
RCV_VALUE08 = RCV_BUFF08;
RCV_VALUE09 = RCV_BUFF09;
RCV_VALUE10 = RCV_BUFF10;
RCV_VALUE11 = RCV_BUFF11;
RCV_VALUE12 = RCV_BUFF12;
RCV_VALUE13 = RCV_BUFF13;
RCV_VALUE14 = RCV_BUFF14;
RCV_VALUE15 = RCV_BUFF15;
RCV_VALUE16 = RCV_BUFF16;
RCV_VALUE17 = RCV_BUFF17;
RCV_VALUE18 = RCV_BUFF18;
RCV_VALUE19 = RCV_BUFF19;
RCV_VALUE20 = RCV_BUFF20;
RCV_VALUE21 = RCV_BUFF21;
RCV_VALUE22 = RCV_BUFF22;
RCV_VALUE23 = RCV_BUFF23;
RCV_VALUE24 = RCV_BUFF24;
RCV_VALUE25 = RCV_BUFF25;
RCV_VALUE26 = RCV_BUFF26;
RCV_VALUE27 = RCV_BUFF27;
RCV_VALUE28 = RCV_BUFF28;
RCV_VALUE29 = RCV_BUFF29;
RCV_VALUE30 = RCV_BUFF30;
RCV_VALUE31 = RCV_BUFF31;
RxFlg=1;
}else{
}
}else{
}Serial.read()命令で受信が終わったら、
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&((RCV_BUFF30==250)||(RCV_BUFF30==255))&&(RCV_BUFF31==RX_CHKDIGIT)){この行で、受信データがデータフォーマット通りであるかどうかを判断します。
「 || 」や「 && 」のような見慣れない記述があると思います。
「A || B」は、AまたはBいずれか一方の条件が満たされるならば、を意味します。
「A && B」は、AとBいずれの条件も満たされることが条件であることを意味します。
つまり、
・0バイト目、1バイト目が各々255であること。
・30バイト目が250または255であること。
それが、この行に記述されています。
こちらのページの後半に記載しているデータフォーマットをご覧ください。正しいデータであることを満たす条件であることが確認できます。
最後にある以下の記述は、さらに厳しくデータ内容を確認するために必要な条件となります。
&&(RCV_BUFF31==RX_CHKDIGIT)誤ったデータであるにもかかわらず、0バイト目と1バイト目と30バイト目が偶然、255になったことで、正しいデータとして扱われることも考えられます。
これを防ぐため、0〜30バイト目までの値を全て合計した値を256で割った余りの数が一致するかを3つ目の条件に加えています。
これを、チェックデジットといいます。
規則的な数ではないため、偶然が重なる可能性をさらに抑えることができます。
チェックデジットの計算は、RX_CHKDIGITとして、以下の記述で計算されています。
RX_CHKDIGIT=RCV_BUFF00+RCV_BUFF01+RCV_BUFF02+RCV_BUFF03+RCV_BUFF04+RCV_BUFF05+RCV_BUFF06+RCV_BUFF07+RCV_BUFF08+RCV_BUFF09+RCV_BUFF10+RCV_BUFF11+RCV_BUFF12+RCV_BUFF13+RCV_BUFF14+RCV_BUFF15+RCV_BUFF16+RCV_BUFF17+RCV_BUFF18+RCV_BUFF19+RCV_BUFF20+RCV_BUFF21+RCV_BUFF22+RCV_BUFF23+RCV_BUFF24+RCV_BUFF25+RCV_BUFF26+RCV_BUFF27+RCV_BUFF28+RCV_BUFF29+RCV_BUFF30;
RX_CHKDIGIT=RX_CHKDIGIT%256;RX_CHKDIGITと256の間にある%は、割り算の余りを意味します。
次に、正しいフォーマットで届いていることを検知したら、直ちに別の領域にデータをコピーしなければなりません。
これをしないと、せっかく正しくデータが届いても、データが流れていってしまいます。
そのため、
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&((RCV_BUFF30==250)||(RCV_BUFF30==255))&&(RCV_BUFF31==RX_CHKDIGIT)){この記述のカッコの中で、全ての受信バッファの内容をメモリにコピーする処理が書かれています。
こうすることで、次にデータが流れてきても安全にバッファ内容を上書きすることができます。
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
RCV_VALUE02 = RCV_BUFF02;
RCV_VALUE03 = RCV_BUFF03;
RCV_VALUE04 = RCV_BUFF04;
RCV_VALUE05 = RCV_BUFF05;
RCV_VALUE06 = RCV_BUFF06;
RCV_VALUE07 = RCV_BUFF07;
RCV_VALUE08 = RCV_BUFF08;
RCV_VALUE09 = RCV_BUFF09;
RCV_VALUE10 = RCV_BUFF10;
RCV_VALUE11 = RCV_BUFF11;
RCV_VALUE12 = RCV_BUFF12;
RCV_VALUE13 = RCV_BUFF13;
RCV_VALUE14 = RCV_BUFF14;
RCV_VALUE15 = RCV_BUFF15;
RCV_VALUE16 = RCV_BUFF16;
RCV_VALUE17 = RCV_BUFF17;
RCV_VALUE18 = RCV_BUFF18;
RCV_VALUE19 = RCV_BUFF19;
RCV_VALUE20 = RCV_BUFF20;
RCV_VALUE21 = RCV_BUFF21;
RCV_VALUE22 = RCV_BUFF22;
RCV_VALUE23 = RCV_BUFF23;
RCV_VALUE24 = RCV_BUFF24;
RCV_VALUE25 = RCV_BUFF25;
RCV_VALUE26 = RCV_BUFF26;
RCV_VALUE27 = RCV_BUFF27;
RCV_VALUE28 = RCV_BUFF28;
RCV_VALUE29 = RCV_BUFF29;
RCV_VALUE30 = RCV_BUFF30;
RCV_VALUE31 = RCV_BUFF31;この処理の最後に、以下の1行を実行します。
RxFlg=1;これは、データが受信できたことを他の処理ループから認識してもらうために必要なフラグです。
このフラグが1になったことを検出したら、アップロード処理が動くようにプログラミングします。
これまでに作ったプログラムに、受信処理を追加すると以下のようになります。
RCV_BUFFxxに加えて、RCV_VALUExxも領域を確保します。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
unsigned int channelId = 1***2;
const char* writeKey = "f**************f";
int RCV_BUFF00,RCV_BUFF01,RCV_BUFF02,RCV_BUFF03,RCV_BUFF04,RCV_BUFF05,RCV_BUFF06,RCV_BUFF07,RCV_BUFF08,RCV_BUFF09,
RCV_BUFF10,RCV_BUFF11,RCV_BUFF12,RCV_BUFF13,RCV_BUFF14,RCV_BUFF15,RCV_BUFF16,RCV_BUFF17,RCV_BUFF18,RCV_BUFF19,
RCV_BUFF20,RCV_BUFF21,RCV_BUFF22,RCV_BUFF23,RCV_BUFF24,RCV_BUFF25,RCV_BUFF26,RCV_BUFF27,RCV_BUFF28,RCV_BUFF29,
RCV_BUFF30,RCV_BUFF31=0;
int RCV_VALUE00,RCV_VALUE01,RCV_VALUE02,RCV_VALUE03,RCV_VALUE04,RCV_VALUE05,RCV_VALUE06,RCV_VALUE07,RCV_VALUE08,RCV_VALUE09,
RCV_VALUE10,RCV_VALUE11,RCV_VALUE12,RCV_VALUE13,RCV_VALUE14,RCV_VALUE15,RCV_VALUE16,RCV_VALUE17,RCV_VALUE18,RCV_VALUE19,
RCV_VALUE20,RCV_VALUE21,RCV_VALUE22,RCV_VALUE23,RCV_VALUE24,RCV_VALUE25,RCV_VALUE26,RCV_VALUE27,RCV_VALUE28,RCV_VALUE29,
RCV_VALUE30,RCV_VALUE31=0;
void setup(){
Serial.begin(9600);
connectWiFi();
ambient.begin(channelId, writeKey, &client);
}
void loop() {
if (Serial.available() >= 1) { // 受信したデータが存在する
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
RCV_BUFF02 = RCV_BUFF03;
RCV_BUFF03 = RCV_BUFF04;
RCV_BUFF04 = RCV_BUFF05;
RCV_BUFF05 = RCV_BUFF06;
RCV_BUFF06 = RCV_BUFF07;
RCV_BUFF07 = RCV_BUFF08;
RCV_BUFF08 = RCV_BUFF09;
RCV_BUFF09 = RCV_BUFF10;
RCV_BUFF10 = RCV_BUFF11;
RCV_BUFF11 = RCV_BUFF12;
RCV_BUFF12 = RCV_BUFF13;
RCV_BUFF13 = RCV_BUFF14;
RCV_BUFF14 = RCV_BUFF15;
RCV_BUFF15 = RCV_BUFF16;
RCV_BUFF16 = RCV_BUFF17;
RCV_BUFF17 = RCV_BUFF18;
RCV_BUFF18 = RCV_BUFF19;
RCV_BUFF19 = RCV_BUFF20;
RCV_BUFF20 = RCV_BUFF21;
RCV_BUFF21 = RCV_BUFF22;
RCV_BUFF22 = RCV_BUFF23;
RCV_BUFF23 = RCV_BUFF24;
RCV_BUFF24 = RCV_BUFF25;
RCV_BUFF25 = RCV_BUFF26;
RCV_BUFF26 = RCV_BUFF27;
RCV_BUFF27 = RCV_BUFF28;
RCV_BUFF28 = RCV_BUFF29;
RCV_BUFF29 = RCV_BUFF30;
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&(RCV_BUFF29==0)&&(RCV_BUFF30==0)&&((RCV_BUFF31==250)||(RCV_BUFF31==255))){
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
RCV_VALUE02 = RCV_BUFF02;
RCV_VALUE03 = RCV_BUFF03;
RCV_VALUE04 = RCV_BUFF04;
RCV_VALUE05 = RCV_BUFF05;
RCV_VALUE06 = RCV_BUFF06;
RCV_VALUE07 = RCV_BUFF07;
RCV_VALUE08 = RCV_BUFF08;
RCV_VALUE09 = RCV_BUFF09;
RCV_VALUE10 = RCV_BUFF10;
RCV_VALUE11 = RCV_BUFF11;
RCV_VALUE12 = RCV_BUFF12;
RCV_VALUE13 = RCV_BUFF13;
RCV_VALUE14 = RCV_BUFF14;
RCV_VALUE15 = RCV_BUFF15;
RCV_VALUE16 = RCV_BUFF16;
RCV_VALUE17 = RCV_BUFF17;
RCV_VALUE18 = RCV_BUFF18;
RCV_VALUE19 = RCV_BUFF19;
RCV_VALUE20 = RCV_BUFF20;
RCV_VALUE21 = RCV_BUFF21;
RCV_VALUE22 = RCV_BUFF22;
RCV_VALUE23 = RCV_BUFF23;
RCV_VALUE24 = RCV_BUFF24;
RCV_VALUE25 = RCV_BUFF25;
RCV_VALUE26 = RCV_BUFF26;
RCV_VALUE27 = RCV_BUFF27;
RCV_VALUE28 = RCV_BUFF28;
RCV_VALUE29 = RCV_BUFF29;
RCV_VALUE30 = RCV_BUFF30;
RCV_VALUE31 = RCV_BUFF31;
RxFlg=1;
}else{
}
}else{
}
}これで、【電文(パケット)のフォーマット】に書かれたものに添った形で受信が完了しました。
データが正しく届いたことを示すフラグ(RxFlg)が1になったら、データのアップロードなど、受信後の処理を行います。
そのため、以下の処理を作っていきます。
if(RxFlg==1){
}| 「もしRxFlgが1ならば」の表現で、「if(RxFlg==1)」と書いています。 イコールが2つ連続で書かれていますが、打ち間違いではありません。 イコールを1つ(RxFlg=1)にした場合、RxFlgに1を入れる処理となります。 イコールの数が1個の場合は代入、2つの場合は比較と決められています。 詳しく知りたければ「C言語 比較演算子」で検索してみてください。 |
こちらは、受信に成功したとき一度限りの処理ですので、フラグを元に戻します。
このフラグを戻し忘れると、延々とアップロードを続けてしまい、サーバーがパンクしてしまうので注意が必要です。
if(RxFlg==1){
RxFlg=0;
}32個の正しいデータが受信できたことを知らせるフラグが立ったら(RxFlg==1になったら)、受信したデータをプログラムで利用できる形に加工して、Ambientにアップロードできる状態にします。
取得したデータは、そのままでは測定値として使用できません。
副MCUが受け取る値は、温度センサーの値のように実際の測定値に近いものも中にはあるが、多くはセンサーが出力した電圧に比例した値になります。
ここで、以降の理解をスムーズにするため脇道に逸れてADコンバータについて説明しようと思います。
| ADコンバータという言葉を聞いたことありますか? ミュージックプレイヤーなどの音響機器のカタログで見たことがあると思います。 AはAnalogで DはDigitalを意味します。 マイクは、声や楽器などが発する空気の振動を小刻みに変化する電圧変動の波に変換する装置です。 ただ、SDカードのようなデジタル記憶装置に記録する場合、電圧変動の波の状態では記録ができないため、コンピュータで扱えるよう、数値(デジタルデータ)に変換しなくてはなりません。 このような電圧変動をデータに変換する機器をADコンバータといいます。 逆に、デジタルデータをアナログの電気信号(小刻みな電圧変動の波)に変換する機械をDAコンバータといいます。 CDやSDカードの中には、音楽がデジタルデータで記録されています。 一方、スピーカーやイヤホンの中にあるボイスコイルは、小刻みな電圧変化が加わることで磁石と反発する強さが小刻みに変わり、それが空気を震わせることで音が出るので、UART(デジタル通信)で256とか65535などのような数値データをボイスコイルに送っても音は出ません。(鳴っても雑音しか聴こえません。) そこで、デジタル値をアナログの電気信号に変えるため、DAコンバータが使用されます。 ADコンバータは、MCUの電源電圧を基準に、どれくらいの割合の電圧かという値で出力しています。 例えば、H10は主MCUの電源電圧を4096分割した比率で出力しています。 なぜ4096かというと、H10の主MCUに搭載されているADコンバータが12ビット(2の12乗)のADコンバータだからです。 0ボルトからMCUの電源電圧(約3.3ボルト)までの間を10個のON/OFFパターンを並べて表現できる値(2の12乗)でデジタル値にしています。 H10に内蔵されている多くのセンサーは、環境の変化を電圧の変化で出力し、それを主MCUに入力させています。 |
最大で4095までの測定値が送られてくるのに、UARTのデジタルデータは8ビットまでしか対応しないので、上位ビットと下位ビットに分けて送られてきます。
この2つに分かれたデータを1つの測定値に戻す必要があります。
上位バイトと下位バイトを合体させて元の数に戻す時は、上位バイトの値に256を掛けたものに下位バイトを足します。
| 元々の数=(上位バイト×256)+下位バイト |
2進数は、0と1しかなく、2以上の数字が存在しないため、2の二乗ごとに桁が繰り上がります。
上位バイトの最も小さい桁(一桁目)は、元々の数の9桁目にあたります。
元々の数の1桁目は0か1かを表現するため9から1を引いた8・・・・2の8乗=256となるため、上位バイトの1桁目に256を掛けます。
| わかりやすいように10進数で説明します。 10進数は、0に始まり9までが1桁目で次は桁が繰り上がる、みなさんが普段使っている数です。 8桁表示の電卓のように8桁しか表現できない場合、表現できる数の上限は9999万9999までとなります。 その次はどう頑張っても表現できません。 電卓をもう一個並べて9桁目を表現した場合、その電卓の1桁目は億になります。 こうして電卓を2台並べて数を表現する場合、 表現したい数=(後から追加した電卓の値×1億)+最初に用意した電卓の値 となります。 これを2進数に置き換えて理解してください。 |
このような方法で、2つに分かれたデータをAD変換値に戻していきます。
AD変換値とはセンサーが出力した電圧値(に比例した4096を上限とした数)です。
では、【プログラミング・リファレンス】の【電文(パケット)のフォーマット】のページを開きながら、2つに分かれているものを元々の数に戻していきます。
SMELL=(RCV_VALUE02*256)+RCV_VALUE03;
TEMP=(RCV_VALUE04*256)+RCV_VALUE05;
CDS=(RCV_VALUE07*256)+RCV_VALUE08;
NOISE=(RCV_VALUE09*256)+RCV_VALUE10;
CO=(RCV_VALUE12*256)+RCV_VALUE13;
CO2=(RCV_VALUE14*256)+RCV_VALUE15;温度センサーの値は、追加の処理が必要です。
H10は、主MCUから温度データを送る時、10を掛けて送信しています。
例えば現在25度ならば、250が送られてきます。
H10は、小数点以下1位までの精度で温度測定ができます。
しかし、小数点の取り扱いは少々奥が深く、初心者の方には難しいため、10を掛けています。
そのため、温度(TEMP)は以下の通り修正します。
TEMP=((RCV_VALUE04*256)+RCV_VALUE05)/10;人感センサは、反応があるたび1ずつ加算されていき、1分ごとにリセットされます。
主MCUからデータが送られてくるのは1秒に1回のため、60以上になることはないため、1バイトで扱っています。
湿度は小数点以下の測定をしていない(上限が100)ので、1バイトで納まります。
DCINは、DCジャックから電源供給があれば1を、停電していたら0が送られてきます。
押しボタンスイッチが押されていればD_IN1、D_IN2に1が送られてきます。
全てのデータをまとめると以下の通りとなります。
SMELL=(RCV_VALUE02*256)+RCV_VALUE03;
TEMP=((RCV_VALUE04*256)+RCV_VALUE05)/10;
CDS=(RCV_VALUE07*256)+RCV_VALUE08;
NOISE=(RCV_VALUE09*256)+RCV_VALUE10;
CO=(RCV_VALUE12*256)+RCV_VALUE13;
CO2=(RCV_VALUE14*256)+RCV_VALUE15;
HUMI=RCV=VALUE06;
HUMAN=RCV_VALUE11;
DCIN=RCV_VALUE16;
D_IN1=RCV_VALUE17;
D_IN2=RCV_VALUE18;ここで初めて出てきたSMELLやTEMPなどの値を保存する領域の確保も忘れずに行います。
int SMELL,TEMP,CDS,NOISE,CO,CO2,HUMI,HUMAN,DCIN,D_IN1,D_IN2=0;ここではページが長くなるため全部つなげて書いていますが、以下のようにバラバラで書いても大丈夫です。
intはいくつ出てきても大丈夫なので、目的ごとにまとめて初期化すると後から見やすくなります。
int SMELL=0;
int TEMP=0;
int NOISE=0; (以降、略)ここまでをまとめると以下の通りとなります。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
unsigned int channelId = 1***2;
const char* writeKey = "f**************f";
int RCV_BUFF00,RCV_BUFF01,RCV_BUFF02,RCV_BUFF03,RCV_BUFF04,RCV_BUFF05,RCV_BUFF06,RCV_BUFF07,RCV_BUFF08,RCV_BUFF09,
RCV_BUFF10,RCV_BUFF11,RCV_BUFF12,RCV_BUFF13,RCV_BUFF14,RCV_BUFF15,RCV_BUFF16,RCV_BUFF17,RCV_BUFF18,RCV_BUFF19,
RCV_BUFF20,RCV_BUFF21,RCV_BUFF22,RCV_BUFF23,RCV_BUFF24,RCV_BUFF25,RCV_BUFF26,RCV_BUFF27,RCV_BUFF28,RCV_BUFF29,
RCV_BUFF30,RCV_BUFF31=0;
int RCV_VALUE00,RCV_VALUE01,RCV_VALUE02,RCV_VALUE03,RCV_VALUE04,RCV_VALUE05,RCV_VALUE06,RCV_VALUE07,RCV_VALUE08,RCV_VALUE09,
RCV_VALUE10,RCV_VALUE11,RCV_VALUE12,RCV_VALUE13,RCV_VALUE14,RCV_VALUE15,RCV_VALUE16,RCV_VALUE17,RCV_VALUE18,RCV_VALUE19,
RCV_VALUE20,RCV_VALUE21,RCV_VALUE22,RCV_VALUE23,RCV_VALUE24,RCV_VALUE25,RCV_VALUE26,RCV_VALUE27,RCV_VALUE28,RCV_VALUE29,
RCV_VALUE30,RCV_VALUE31=0;
void setup(){
Serial.begin(9600);
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
}
void loop() {
if (Serial.available() >= 1) {
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
(中略)
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&(RCV_BUFF29==0)&&(RCV_BUFF30==0)&&((RCV_BUFF31==250)||(RCV_BUFF31==255))){
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
(中略)
RCV_VALUE31 = RCV_BUFF31;
RxFlg=1;
}else{
}
}
if(RxFlg==1){
RxFlg=0;
SMELL=(RCV_VALUE02256)+RCV_VALUE03; TEMP=((RCV_VALUE04256)+RCV_VALUE05)/10;
CDS=(RCV_VALUE07256)+RCV_VALUE08; NOISE=(RCV_VALUE09256)+RCV_VALUE10;
CO=(RCV_VALUE12256)+RCV_VALUE13; CO2=(RCV_VALUE14256)+RCV_VALUE15;
HUMI=RCV_VALUE06;
HUMAN=RCV_VALUE11;
DCIN=RCV_VALUE16;
D_IN1=RCV_VALUE17;
D_IN2=RCV_VALUE18;
}else{
}
}H10は、1秒ごとに主MCUから測定値データを送信していますが、副MCUから適切な返事をしないと、新たな測定は行いません。
通常、マイコン同士でデジタルデータのやりとりを行う場合、受信側はデータがいつ届いても受信できるよう、待機していなければなりません。
そして、データが届いたら、現在進めている処理を一旦中断して受信処理を行わないと、データを取りこぼしてしまいます。
これを割り込み処理と言います。
主MCU-副MCU間は、9600ビット毎秒という速度でデータ交換をしていますが、主MCUで使用しているデジタル通信はこれだけではありません。
温度・湿度を測定するセンサーはデジタルデータで出力するタイプのセンサを使用していたり、小型の液晶表示装置を接続できるようにしたり、拡張性を持たせるための他の通信もしており、これらは秒間100000ビット毎秒(100kbps)というさらに速い速度で通信しています。
その他、臭気(におい)センサーは温度測定の瞬間に電気を流して良い時間が厳密に決められており、これを超えて通電すると故障の原因となるため、割り込み受信が命取りになることもあります。
H10は、見かけ以上に慌ただしくデータ処理をしています。
これらの処理を行っている最中に9600bpsという低速データ通信を割り込ませてしまうと、他のセンサーとのデータ通信が邪魔されて失敗したり、臭気(におい)センサーを故障させる原因となります。
主MCUのクロック速度を100MHz(メガヘルツ)、200MHzと上げれば全ての処理を両立させることが可能であるものの、クロック速度に比例して消費電力が増えてしまうため、電池で動かなくなってしまいます。
H10は、大規模災害で停電が起きても、単三電池4本で1週間動作することを目標に設計しています。
ここから逆算した消費電力で動作させることを最優先させた時、割り込み受信処理を断念することになりました。
一方、割り込み処理を使用しない場合、副MCUは一体いつ送信すればいいのか迷うことになります。
そこで、主MCUが時報を送信してから0.8秒以内(Webやクラウド上のフラグ確認など、時間の係る処理を行うなどで、副MCU側での処理が0.8秒以内に処理が終わらない場合は、2回目、3回目、さらにそれ以降の時報受信から0.8秒以内でも大丈夫)に返事をすれば良いというルールを設けることで、受信できるようにしています。
ホームセキュリティシステムなど、類似のハードウェアと比較しても高機能で低消費電力を実現していますが、これ以外にも様々な省電力技術を搭載しています。
このように、主MCUから時報を受け取ったら「測定して、結果を返信してください」という返事をしなければなりません。
その方法について説明します。
主MCUから受信するデータは、1秒ごとに受信する時報と、1分ごとに受信する時報の2通りあります。
1秒ごとの時報は、主MCUと副MCUのデータの同期を取るための時報で、1分ごとの時報はアップロードタイミングを通知するために利用されることを想定して開発しています。
そのため、必要な処理が異なるため、どちらの時報が届いたのかを判断しなければなりません。
【プログラミング・リファレンス】の【電文(パケット)のフォーマット】の表をご覧ください。
30バイト目に「種別」があると思います。
1秒ごとの時報である場合、250が届きます。
1分ごとの時報である場合、255が届きます。
例えば、温度をずっと監視して30度を超えたら換気扇を回すためのリレーを動かす・・・などのような処理は、1秒ごとに行った方が使いやすいでしょう。
ただ、クラウドへのアップロードは1分に1度で十分だと思いますので、255が届いた時にやればいいと思います。
届いたデータの内容によって条件分岐する必要がありますが、ここではその方法について説明しようと思います。
最初の方で、「もし1バイト以上のデータを受信したら」という処理を書いたと思います。
思い出してみてください。
if(Serial.available()>=1){
受信処理
}これです。
やり方はこれと同じです。
if(RCV_VALUE30==250){
RCV_VALUE30が250だった時、つまり届いたデータが
1秒ごとの時報だった場合に行う処理を書きます。
}このようになります。
それでは、主MCUに対して返事をする処理を{ }の中に書いていきましょう。
データフォーマットに従った形式で送信されていれば、中身は何であっても構いません。
送信フォーマットは、【プログラミング・リファレンス】の【電文(パケット)のフォーマット】をご覧ください。
今まで見てきたところは「主MCU→副MCU」ですが、今度は逆ですので、もう少しスクロールさせて「副MCU→主MCU」のところを表示させます。
0バイト目、1バイト目が固定値で255であることと、5バイト目にリレーON/OFFがある以外は中身がありませんが、この22バイト(0〜21バイト目までの22バイト)を送り返します。
リレーON/OFFは、H10基板の右端にあるリレーという部品のON/OFFを決めます。
例えばリレーに換気扇をつなぐことで、一定以上の室温になったら換気扇を回すとか、一定以下の照度になったら照明をつけるなどの処理を行う時に使います。
送信は、以下の命令で行います。
Serial.write(送信する値);リレーには、まだ何も接続していないはずですので5バイト目は0とします。
他バイトも、「固定値(10進数)」の部分を参考に送信します。
データフォーマットに添った形で送信するには、以下の通りとなります。
Serial.write(255);
Serial.write(255);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(254);最後のデータ「254」は、チェックデジットです。
チェックデジットを除く全てのフィールドのデータを全て足して256で割った余りが入るため、(255+255)/256の余りとして254が入ります。
この計算を間違えると、主MCUからデータとして扱われませんので注意が必要です。
ここまでで作成したプログラムにこの処理を追加すると以下の通りとなります。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
unsigned int channelId = 1***2;
const char* writeKey = "f**************f";
int RCV_BUFF00,RCV_BUFF01,RCV_BUFF02,RCV_BUFF03,RCV_BUFF04,RCV_BUFF05,RCV_BUFF06,RCV_BUFF07,RCV_BUFF08,RCV_BUFF09,
RCV_BUFF10,RCV_BUFF11,RCV_BUFF12,RCV_BUFF13,RCV_BUFF14,RCV_BUFF15,RCV_BUFF16,RCV_BUFF17,RCV_BUFF18,RCV_BUFF19,
RCV_BUFF20,RCV_BUFF21,RCV_BUFF22,RCV_BUFF23,RCV_BUFF24,RCV_BUFF25,RCV_BUFF26,RCV_BUFF27,RCV_BUFF28,RCV_BUFF29,
RCV_BUFF30,RCV_BUFF31=0;
int RCV_VALUE00,RCV_VALUE01,RCV_VALUE02,RCV_VALUE03,RCV_VALUE04,RCV_VALUE05,RCV_VALUE06,RCV_VALUE07,RCV_VALUE08,RCV_VALUE09,
RCV_VALUE10,RCV_VALUE11,RCV_VALUE12,RCV_VALUE13,RCV_VALUE14,RCV_VALUE15,RCV_VALUE16,RCV_VALUE17,RCV_VALUE18,RCV_VALUE19,
RCV_VALUE20,RCV_VALUE21,RCV_VALUE22,RCV_VALUE23,RCV_VALUE24,RCV_VALUE25,RCV_VALUE26,RCV_VALUE27,RCV_VALUE28,RCV_VALUE29,
RCV_VALUE30,RCV_VALUE31=0;
int senddata[22];
const int httpport=80;
WiFiServer server(80);
void setup() {
delay(2000);
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
delay(2000);
Serial.begin(9600);
while(Serial.available() >= 1){
RCV_VALUE31 = Serial.read( );
}
}
void loop() {
if (Serial.available() >= 1) {
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
(中略)
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&(RCV_BUFF29==0)&&(RCV_BUFF30==0)&&((RCV_BUFF31==250)||(RCV_BUFF31==255))){
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
(中略)
RCV_VALUE30 = RCV_BUFF30;
RCV_VALUE31 = RCV_BUFF31;
RxFlg=1;
}else{
}
}else{
}
if(RxFlg==1){
RxFlg=0;
SMELL=(RCV_VALUE02*256)+RCV_VALUE03;
TEMP=((RCV_VALUE04*256)+RCV_VALUE05)/10;
HUMI=RCV_VALUE06;
CDS=(RCV_VALUE07*256)+RCV_VALUE08;
NOISE=(RCV_VALUE09*256)+RCV_VALUE10;
HUMAN=RCV_VALUE11;
CO=(RCV_VALUE12*256)+RCV_VALUE13;
CO2=(RCV_VALUE14*256)+RCV_VALUE15;
DCIN=RCV_VALUE16;
D_IN1=RCV_VALUE17;
D_IN2=RCV_VALUE18;
if(RCV_VALUE30==250){
Serial.write(255);
Serial.write(255);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(254);
}else{
}
}
}送信する順番にSerial.writeを書いていきます。
これで、主MCUからデータが送られてくる度に返事ができるようになりました。
次に、1分ごとに送信されてくる「255」を受信したらAmbientに測定データをアップロードする処理を追加します。
30バイト目が255だった場合の処理は、以下のように書きます。
if(RCV_VALUE30==255){
RCV_VALUE30が255だった時、つまり届いたデータが
1分ごとの時報だった場合に行う処理を書きます。
}副MCUからの返事を受信したら、主MCUは全センサーの測定処理を行います。
(繰り返しになりますが、1秒ごとに送信しているデータの値は、直前の測定値となります。このようにしている理由は、1秒ごとに測定を行うと、副MCUから返事を受け取るために必要な時間が確保できないからです。)
測定処理が終わったら、副MCUに対して測定値を送信します。
| これまで、時報として1秒ごとにRCV_VALUE31の値として250が、 1分ごとに255が送信されると説明しました。 これは、初心者の方が混乱しないようにするためで、正確には副MCUから主MCUに対して返事を60回行ったら、255を返信します。 サンプルプログラム通りに作った場合は1分ごとに255を送信することになるのですが、例えば副MCU側で2回に1回しか返事をしないようにプログラムした場合は、255が送信される頻度は2分に一回となります。 |
| 概ね1時間に1度、副MCUを再起動させます。 これは、フリーズ防止のためです。 プログラムは、動作確認の時にはきちんと動いていても、しばらく動かしていると不具合が出ることがあります。 主MCUは、弊社開発のため、十分なテストを行なうことで自信を持って提供しておりますが、副MCUは初心者の方が挑戦されることも想定しております。 不慣れな方のプログラミングでは、一見きちんと動いているように見えるプログラムでも、時間が経つことで不具合が出ることがあります。 このような場合でも、1時間ごとにリセットを掛けることで、復帰させることができます。 例えば、H10を遠く離れた別荘の監視に使用するような使い方をしている場合、フリーズする度に別荘まで再起動をするわけにはいきませんし、次回遊びに行くまで放置では買った意味がありません。 こうしたことも考えて、設計しております。 副MCUの電源が切れると困るような処理を行う方は、(上級者向けの内容なので専門用語を使います)変数やテーブルの内容クラウドに保存するか、Arduinoを使用せず、ESP-IDFで開発し、プログラム領域の一部を内蔵フラッシュに割り当てて、1処理ごとに内蔵フラッシュに保存することをおすすめします。 |
取得したデータをAmbientへ送信するプログラムに渡すためのプログラムは、以下の通りです。
ambient.set(通し番号,数字の入っているメモリ領域の名前);通し番号は、1から順番に1、2、3と付けていきます。
数字の入っているメモリ領域の名前は、温度はTEMP、湿度はHUMI、照度はCDS、騒音はNOISE、臭気はSMELL、空間活動量(人感センサ)はHUMAN、一酸化炭素量はCO、二酸化炭素量はCO2と名付けたと思いますので、それをそのまま書きます。
すると、以下のようになります。
ambient.set(1,TEMP);
ambient.set(2,HUMI);
ambient.set(3,CDS);
ambient.set(4,NOISE);
ambient.set(5,SMELL);
ambient.set(6,HUMAN);
ambient.set(7,CO);
ambient.set(8,CO2);これで、取得したデータを送信する準備が整いました。
例えるなら、メールソフトを起動して文書と宛先が入力された状態です。
最後に、「送信」ボタンを押さなければなりません。
送信処理は、以下の通りです。
ambient.send();これまで作成したプログラムに、この処理を追加したものが以下の通りです。
#include <WiFi.h>
#include <Ambient.h>
Ambient ambient;
WiFiClient client;
const char ssid[] = "INETGATE4";
const char passwd[] = "p24zc53b";
unsigned int channelId = 1***2;
const char* writeKey = "f**************f";
const int httpport=80;
WiFiServer server(80);
int RCV_BUFF00,RCV_BUFF01,RCV_BUFF02,RCV_BUFF03,RCV_BUFF04,RCV_BUFF05,RCV_BUFF06,RCV_BUFF07,RCV_BUFF08,RCV_BUFF09,
RCV_BUFF10,RCV_BUFF11,RCV_BUFF12,RCV_BUFF13,RCV_BUFF14,RCV_BUFF15,RCV_BUFF16,RCV_BUFF17,RCV_BUFF18,RCV_BUFF19,
RCV_BUFF20,RCV_BUFF21,RCV_BUFF22,RCV_BUFF23,RCV_BUFF24,RCV_BUFF25,RCV_BUFF26,RCV_BUFF27,RCV_BUFF28,RCV_BUFF29,
RCV_BUFF30,RCV_BUFF31=0;
int RCV_VALUE00,RCV_VALUE01,RCV_VALUE02,RCV_VALUE03,RCV_VALUE04,RCV_VALUE05,RCV_VALUE06,RCV_VALUE07,RCV_VALUE08,RCV_VALUE09,
RCV_VALUE10,RCV_VALUE11,RCV_VALUE12,RCV_VALUE13,RCV_VALUE14,RCV_VALUE15,RCV_VALUE16,RCV_VALUE17,RCV_VALUE18,RCV_VALUE19,
RCV_VALUE20,RCV_VALUE21,RCV_VALUE22,RCV_VALUE23,RCV_VALUE24,RCV_VALUE25,RCV_VALUE26,RCV_VALUE27,RCV_VALUE28,RCV_VALUE29,
RCV_VALUE30,RCV_VALUE31=0;
int TEMP,HUMI,SMELL,CDS,NOISE,SPACE,HUMAN,CO,CO2,D_IN1,D_IN2,infrared,DCIN,CONTACT1,CONTACT2=0;
int RxFlg=0;
int RX_CHKDIGIT=0;
int senddata[22];
void setup() {
delay(2000);
WiFi.begin(ssid, passwd);
while(WiFi.status() != WL_CONNECTED) {
delay(1000);
}
ambient.begin(channelId, writeKey, &client);
delay(2000);
Serial.begin(9600);
while(Serial.available() >= 1){
RCV_VALUE31 = Serial.read( );
}
}
void loop() {
if (Serial.available() >= 1) {
RCV_BUFF00 = RCV_BUFF01;
RCV_BUFF01 = RCV_BUFF02;
(中略)
RCV_BUFF30 = RCV_BUFF31;
RCV_BUFF31 = Serial.read( );
if((RCV_BUFF00==255)&&(RCV_BUFF01==255)&&(RCV_BUFF29==0)&&(RCV_BUFF30==0)&&((RCV_BUFF31==250)||(RCV_BUFF31==255))){
RCV_VALUE00 = RCV_BUFF00;
RCV_VALUE01 = RCV_BUFF01;
(中略)
RCV_VALUE30 = RCV_BUFF30;
RCV_VALUE31 = RCV_BUFF31;
RxFlg=1;
}else{
}
}else{
}
if(RxFlg==1){
RxFlg=0;
SMELL=(RCV_VALUE02*256)+RCV_VALUE03;
TEMP=((RCV_VALUE04*256)+RCV_VALUE05)/10;
HUMI=RCV_VALUE06;
CDS=(RCV_VALUE07*256)+RCV_VALUE08;
NOISE=(RCV_VALUE09*256)+RCV_VALUE10;
HUMAN=RCV_VALUE11;
CO=(RCV_VALUE12*256)+RCV_VALUE13;
CO2=(RCV_VALUE14*256)+RCV_VALUE15;
DCIN=RCV_VALUE16;
D_IN1=RCV_VALUE17;
D_IN2=RCV_VALUE18;
CONTACT1=RCV_VALUE25;
CONTACT2=RCV_VALUE26;
if(RCV_VALUE30==250){
Serial.write(255);
Serial.write(255);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(0);
Serial.write(254);
}else{
}
if(RCV_VALUE30==255){
ambient.set(1,TEMP);
ambient.set(2,HUMI);
ambient.set(3,CDS);
ambient.set(4,NOISE);
ambient.set(5,SMELL);
ambient.set(6,HUMAN);
ambient.set(7,CO);
ambient.set(8,CO2);
ambient.send();
}else{
}
}
}