試したアクトプログラム
======================================
twelite子機0x01 -> 0x02への送信をテストして通信距離を調べました。
通信の送信、受信のサンプルにもなっています。
======================================
TWELITのメリットは無線モジュールが付いている事です。
通信距離のテストをする為にLEDSend、LEDReceiveの2つのプログラムを作ってみます。
LEDSendは3秒毎に電波を送信します。
LEDReceiveは電波を受け取るとLEDが0.5秒間だけ点灯します。
LEDSendを実行するDIPを置いておき、LEDReceiveを実行するDIPを持って移動すれば、LEDが点滅している間は電波が届いている事が分かります。
私のテストでは見通しが良い所では200mは確実に届き、250mで不安定になりました。手に持って移動したので1.2m程の高さにアンテナがあります。
DIPの到達距離1kmのテストは約3mの高さで実験したと書かれています。
actサンプルから「act0」のフォルダをコピーしてフォルダ名を「LEDSend」に変えます。
LEDSendフォルダを開いてact0.cppファイルをLEDSend.cppに名前を変え、LEDSend.cppをエディタで開きます。
以下のコードをコピーして貼り付け、上書き保存します。
LEDSendのソース
-----
#include <TWELITE>
#include <NWK_SIMPLE>
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t u8devid = 0x01; // このDIPのIDは0x01
uint16_t count16; // カウント用16bit
bool b_trans; // 送信時のフラグ
uint8_t datax; // 送信するデータ用
void setup() {
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL);
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk
<< NWK_SIMPLE::logical_id(u8devid); // 0x01
the_twelite.begin();
// 変数の初期化
count16 = 0;
b_trans = false;
datax = 0x00;
}
void loop() {
// 3秒経過したらb_transフラグをtrueにする
if (TickTimer.available()) { // 1m秒毎に実行される
count16 += 1; // カウントアップ
if (count16 >= 3000) { // カウントが3000になったら
b_trans = true; // フラグを送信にセット
count16 = 0; // カウントをリセット
}
}
// 送信実行
if (b_trans) { // フラグが送信なら
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
pkt
<< tx_addr(0x02) // 送信先は0x02
<< tx_retry(0x1) // リトライ回数 送信回数は合計で2回
<< tx_packet_delay(0,0,2);
// 送信するデータをセット
pack_bytes(
pkt.get_payload(), // 格納先
uint8_t(datax) // 送信するデータ
);
// 送信実行
pkt.transmit();
b_trans = false; // 送信フラグをfalseにする
}
}
}
-----
再び、actサンプルから「act0」のフォルダをコピーしてフォルダ名を「LEDReceive」に変えます。
LEDReceiveフォルダを開いてact0.cppファイルをLEDReceive.cppに名前を変え、LEDReceive.cppをエディタで開きます。
以下のコードをコピーして貼り付け、上書き保存します。
LEDReceiveのソース
-----
#include <TWELITE>
#include <NWK_SIMPLE>
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t PIN_LED = 18; // LED接続用のピンを定義
const uint8_t u8devid = 0x02; // このDIPのIDは0x02
uint8_t datax; // 受信するデータ用
void setup() {
pinMode(PIN_LED,OUTPUT); // LED点灯用のピン設定
digitalWrite(PIN_LED,HIGH); // LOWでLED点灯
datax = 0;
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL)
<< TWENET::rx_when_idle(); // 受信準備
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk
<< NWK_SIMPLE::logical_id(u8devid); // このDIPのID 0x02 をセット
the_twelite.begin();
}
void loop() {
if (the_twelite.receiver.available()) {
auto&& rx = the_twelite.receiver.read();
expand_bytes(
rx.get_payload().begin(), // コンテナの先頭のポインタ
rx.get_payload().end(), // コンテナの末尾のポインタ
datax // 受信データ(今回は使っていない)
);
// 受信の度にLED点灯
digitalWrite(PIN_LED,LOW); // LED点灯
delay(500);
digitalWrite(PIN_LED,HIGH); // 0.5秒後消す
}
}
-----
TWELITE DIPのピン配置のカードの裏の接続を参照
DO1ピン18 <-- (カソード)LED(アノード) <-- 680Ω <-- VCCに接続すると3秒毎に点滅します。
LEDの長い足がアノードです。
======================================
押しボタンを感知するサンプル
======================================
DI1 シルク12ピン -> 押しボタン -> GNDに接続しています。
押しボタンの状態で分岐するサンプルですが、
結果がSerialなのでTWELITE R2(ライター)に接続した状態で12ピンの接続をする必要があります。
ButtonTest.cpp
-----
#include <TWELITE>
const uint8_t PIN_BTN = 12; // DI1 シルク12ピン
/*** the setup procedure (called on boot) */
void setup() {
Buttons.setup(5); // ボタンのチャタリング処理用メモリ確保
Buttons.begin(pack_bits(PIN_BTN),5,10); // チャタリング処理設定
}
/*** the loop procedure (called every event) */
void loop() {
// ボタンの状態を調べる
if (Buttons.available()) { // ボタンが変化した(押した・離した)
uint32_t btn_state, change_mask; // 変数定義
Buttons.read(btn_state, change_mask); // ボタンの状態を読み込む
// btn_state // 現在のボタンの状態
// change_mask // どのボタンが変化したか
if (!(change_mask & 0x80000000) && (btn_state & (1UL << PIN_BTN))) {
Serial << "PIN_BTN 離した" << crlf; // L -> H
}
else
{
Serial << "PIN_BTN 押した" << crlf; // H -> L
}
}
}
-----
// PIN_BTNのピンはプルアップされている
// 押しボタンスイッチは押すと通電、離すと切断する
======================================
制御用DIP 0x09から2つのDIP(DIP 0x01,DIP 0x02)に無線で個別に信号を送るテスト
3台のDIPが必要です。(区別できるように数字を書くと良いです)
======================================
制御用DIPに押しボタンスイッチをつけます。
押しボタンスイッチは押している間は端子間が接続され、離すと端子間が切れます。
ボタンを押している間はDIP 0x01に3秒毎に信号を送信します。
ボタンを離している間はDIP 0x02に3秒毎に信号を送信します。
DIP 0x02は「通信距離を調べるテスト」で使用したDIPがそのまま使えます。
DIP 0x01は「LEDReceive.cpp」の1行を修正してDIPに書き込んでください。
変更箇所は以下の部分です。
LEDReceive.cpp
-----
7行目
const uint8_t u8devid = 0x02; <-- 0x02 を 0x01 に変更
-----
送信する制御用DIPのプログラムはLEDSelectDIP.cppとします。
LEDSelectDIP.cpp
-----
#include <TWELITE>
#include <NWK_SIMPLE>
const uint32_t APP_ID = 0x1234abcd;
const uint8_t CHANNEL = 13;
const uint8_t u8devid = 0x09; // このDIPのIDは0x09
const uint8_t PIN_BTN = 12; // ボタンのピン
uint8_t sendid; // 送り先のDIPのID
uint16_t count16; // カウント用16bit
bool b_trans; // 送信時のフラグ
uint8_t datax; // 送信するデータ用
void setup() {
Buttons.setup(5); // ボタン
the_twelite
<< TWENET::appid(APP_ID)
<< TWENET::channel(CHANNEL);
auto&& nwk = the_twelite.network.use<NWK_SIMPLE>();
nwk
<< NWK_SIMPLE::logical_id(u8devid); // 0x09
Buttons.begin(pack_bits(PIN_BTN),5,10); // ボタン
the_twelite.begin();
// 変数の初期化
count16 = 0;
b_trans = false;
datax = 0x00;
sendid = 0x01; // 初期の送信先は0x01とする
}
void loop() {
// 3秒経過したらb_transフラグをtrueにする
if (TickTimer.available()) { // 1m秒毎に実行される
count16 += 1; // カウントアップ
if (count16 >= 3000) { // カウントが3000になったら
b_trans = true; // フラグを送信にセット
count16 = 0; // カウントをリセット
}
}
// 押しボタンの状態を調べる
if (Buttons.available()) { // ボタンが変化した(押した・離した)
uint32_t btn_state, change_mask;
Buttons.read(btn_state, change_mask);
if (!(change_mask & 0x80000000) && (btn_state & (1UL << PIN_BTN))) {
// PIN_BTN 離した
sendid = 0x01; // 送り先を 0x01 にセット
}
else // PIN_BTN 押した
{
sendid = 0x02; // 送り先を 0x02 にセット
}
}
// 送信実行
if (b_trans) { // フラグが送信なら
if (auto&& pkt = the_twelite.network.use<NWK_SIMPLE>().prepare_tx_packet()) {
pkt
<< tx_addr(sendid) // 送信先を設定
<< tx_retry(0x1) // リトライ回数
<< tx_packet_delay(0,0,2);
// 送信するデータをセット
pack_bytes(
pkt.get_payload(), // 格納先
uint8_t(datax) // 送信するデータ
);
// 送信実行
pkt.transmit();
b_trans = false; // 送信が終わったので送信フラグをfalseにする
}
}
}
-----
======================================
アナログ入力のサンプル
======================================
測定する電圧は抵抗10kΩ×2で分圧した。
AnalogueTEST.cpp
-----
#include <TWELITE>
uint16_t au16AI[2]; // アナログ値保存用配列
/*** the setup procedure (called on boot) */
void setup() {
Analogue.setup(true);
// 引数の内容(ADCの初期化、Tick_timer、終了時のコールバック関数なし)
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC),100);
// ADCするピンはA1とVCC、他にA2も選べる。
// ADCの開始のタイマーの割込み回数 -> 100m秒間隔
// ピンA1,A2はアナログ専用ピンなのでそのまま使えるが、アナログ/デジタル共有のAI2,AI4ピンは
// スリープ時に利用制限があるので今回は使わない。
// VCCは電池の電圧を測定する。電池の消耗具合が分かる。
}
/*** the loop procedure (called every event) */
void loop() {
// A1 22ピンに電圧をかけておく
if (Analogue.available()) { // アナログ・デジタル変換が完了した
// ADCから値を読み込む
au16AI[0] = uint16_t(analogRead_mv(PIN_ANALOGUE::A1)); // 22ピンの電圧
au16AI[1] = uint16_t(analogRead_mv(PIN_ANALOGUE::VCC)); // 電源電圧
Analogue.end(); // ADCを一旦止める
Serial << format("A1=%d, VCC=%d",au16AI[0] ,au16AI[1]) << crlf;
// Serial << "A1= " << int(au16AI[0] << " VCC= " << int(au16AI[1] << crlf; // これでもOK
delay(1000);
// ADC再開
Analogue.begin(pack_bits(PIN_ANALOGUE::A1, PIN_ANALOGUE::VCC),100);
}
delay(20); // 負荷軽減?
}
-----
MWXライブラリのADCサンプルコードにはAnalogue.available()とAnalogue.end()が使用されていません。
通常、アナログ・デジタル変換には変換時間が必要です。
しかし、十分に長い時間経った後ではAnalogue.available()で確認する必要が無いのかもしれません。
このサンプルコードではAnalogue.end()でADC回路を停止していますが、
常時AD変換する場合は、Analogue.end()とdelay(1000)後の//ADC再開Analogue.begin()は不要かもしれません。
ADC回路の開始・停止を繰り返す事が良いのか悪いのかは分かりません。自己責任でお願いします。
少しだけC++の文法
変数の定義の例
uint8_t 変数名 = 0; // unsigned-integer-type 8ビットの符号なし整数型 8,16,32,64ビットがある
int8_t 変数名 = 0; // signed-integer-type 8ビットの符号あり整数型 8,16,32,64ビットがある
int 変数名 = 0; // サイズは4バイトで、最大値は2147483647、最小値は-2147483648
bool 変数名 = false; // trueかfalseの値を持つ。
char 変数名 = 'P'; // アルファベット1文字を記憶
char 変数名[] = 'PING'; // 文字の配列
// 定義と同時に初期化(= 0;)をするのが望ましい。必須ではない。
// 変更できない変数の例
const uint8_t 変数名 = 18; // const + 変数の定義 + 値 <- 値は必須
// 後から代入できないから。
// 変数の種類と記載位置
// グローバル変数(プログラム全体で参照できる)
// #include <・・・>のすぐ下の行に定義
// 局所変数(ローカル変数)
if (Buttons.available()) { // {
uint32_t btn_state, change_mask; // 定義した btn_state, change_mask の変数は
Buttons.read(btn_state, change_mask); // このブロック中だけで使用できる。
} // }
スコープ
TWENET::appid()とかTWENET::channel()とかは
TWENETクラスのメソッドを明示する書き方。
::はスコープ解決演算子(Scope Resolution Operator)
スリープについて説明します。
温室の室温を計測して送信するシステムで考えてみましょう。
室温は1分毎とか10分毎にデータを蓄積できれば十分です。
計測処理に0.1秒かかるとして、残りは待機時間になります。
delay()でも処理を待つことができますが、電力の消費は変わりません。
スリープは省エネの待機状態になります。
delay()の代わりにsleep()を使うと省エネになります。
乾電池などでの駆動を考える時には必ず使う機能です。
======================================
スリープのサンプル
======================================
SleepTEST.cpp
-----
#include <TWELITE>
uint16_t count16 = 0;
void setup() {
Serial << "setup " << int(count16) << crlf;
}
void wakeup() {
Serial << "wakeup " << int(count16) << crlf;
}
void loop() {
count16 += 1;
Serial << "loop " << int(count16) << crlf;
Serial.flush();
the_twelite.sleep(600000); // 10分設定でもOK
}
-----
sleep処理の流れ
setup()
loop()
sleep() <- sleep()の開始。loop()中で呼び出します。
sleep中…
wakeup() <- 時間がくると起床します。
loop() <- その後loop()に処理が移ります。
sleep() <- sleep()の開始
sleep中…
wakeup()
・・・
を繰り返します。
DIPの普段の消費電流は約21mA(CPUクロック:16MHz:規定値)だが、
sleepの時は消費電流は0.0015mAになります。
sleep関数の引数[デフォルト値]
sleep(
ミリ秒, <- 必須
前回の起床時間をもとに次の起床時間を再計算する。[true],
RAMを保持しないでスリープになる。[false],
スリープ用に用いるウェイクアップタイマー [SLEEP_WAKETIMER_PRIMARY]
)
「前回の起床時間をもとに次の起床時間を再計算する」の意味
ChatGPTの説明によると、sleepの時間を1000msとし、処理時間が50msかかるとすると
trueだと起床時刻が0,1000,2000,3000…となる。(先に起床時刻を決めてから処理をする)
falseだと起床時刻が0,1050,2100,3150…となる。(処理が済んでから1000ms間スリープする)
「RAMを保持しないでスリープになる」の意味
通常は変数の値を保持するべきなので、デフォルト値のfalseを使います。
もし、trueにすると、setup(),loop(),sleep中,setup(),loop(),sleep中…という流れになるそうです。多分。