2017-01-13 追記:
TWE-LITE(旧表記「TWE-Lite」) のメーカーが変わり同社からのリクエストに基づいて関連箇所の改訂を行いました。結果として、2015年4月に公開した元の記事に2017年1月現在の新しい情報やキーワードが部分的に反映された内容となっています。念のため要所に注釈を加えており混乱につながる要素は特に含まれていないと判断していますが、記事参照の際にはご注意下さい。
モノワイヤレス株式会社 様の ZigBee 無線マイコンモジュール「TWE-LITE DIP 」はコストパフォーマンスが高く人気のある製品です。手軽にノード間の無線通信を実現できるため IoT を構成する要素としても所定の圏内に閉じた機器間ネットワークのための道具立てとしても有用でしょう。
標準のものに加え複数の典型的な用途に即したファームウェア群とそのソースコードが開発環境とともに無償で公開されていることも興味深く、製品はホスト PC から専用の USB アダプタ (TWE-LITE R ) または市販の USB シリアル変換モジュール/ケーブル 経由で自由にファームを書き換えられるようにデザインされています。
このように単なる通信モジュールではなくプログラマブルなマイコンとしての側面を持ち合わせながら価格が低廉 であることが TWE-LITE DIP の大きな魅力です。また、「超簡単!」の惹句の通り出荷時の標準ファームでは扱いやすさが特に重視されており、そういった間口の広さと奥の深さがこの製品の特長と言ってよいでしょう。
試みのきっかけ
TWE-LITE ユーザはメーカーの提示するソフトウェア使用許諾契約書に基づいて TWE-NET SDK を利用することができます。また、規約の認める範囲で公式ファームのソースコードを改変したりその内容を公開することも可能です。
ネットを参照すると現時点では実際に公式ファームに手を加えて TWE-LITE DIP を使っている利用者は製品の人気に比べまだあまり多くないように見受けられます(注:2015年04月時点の記述) 。その背景には、まず公式のファームウェア群が充実しているため改変の不要なケースの多いことが想像されますが、それに加えて、精密に記述されている各ファームのボリュームのあるソースコードはその内容を適切に理解しなければ手を触れにくいという事情もあるように思われます。メーカーが公開しているこれらのファームは基本的に実用を目的とするものであって教育用ではありませんから、利用する側はコードの世界をじっくり楽しみながらノウハウを覚えるスタンスに立つことが好ましいでしょう。
ただ、ここしばらく TWE-LITE に触れた印象では、そこに「ちょっとした足がかり」があれば公式ファームのコードをより見渡しやすくなり、そのことがこの優れた製品を手元で活用する機会を拡げることにもつながるように感じました。思い浮かべたのは "Hello, world!" のようにもっとも単純で簡潔な内容からはじまり徐々に処理が肉付けされていくイメージの小さなサンプルファームコード群です。でも残念ながら今のところそういうものは見当たりません(注:2015年04月時点の記述) 。そこで、実験と勉強をかねて現時点での自分の到達点なりにそういうコードを書いてみることにしました。一連の過程での疑問点・不明点の解決には言うまでもなく公式ファームのソースと上記の SDK マニュアルが非常に参考になりました。この試みは今後も継続するかもしれませんが、まずはここまでのソースコードと動作の様子を公開します。興味のある方は覗いてみて下さい。
※本記事に掲載のソースコードには公式ファームウェアのソースからの
引用が含まれます。取り扱いに際しては著作権表記を確認の上、前掲の
「モノワイヤレスソフトウェア使用許諾契約書」の内容を遵守して下さい。
なお、本記事での各ソースコードの掲載ならびにメーカー公式サイト上の
個々のページへの直接のリンク・公式文書からの情報の抜粋については
いずれも事前にモノワイヤレス様より承諾を得ています。
※本記事の本文および本記事に掲載のソースコードには誤りが含まれて
いる可能性があります。そのことがいかなる損害に繋がったとしても
筆者および KLab は一切の責任を負いません。あらかじめご了承下さい。
技術情報について
TWE-LITE 用プログラミングに必要な情報は多岐に渡り、メーカー公式サイトの情報 や前掲の TWE-NET SDK マニュアル 、また、NXP 社製 JN5164 用のペリフェラル (TWE-LITE の I/O ポートまわり) API のマニュアル 等が主な情報源となります。後続のソースコードを参照する上で最小限必要なもっとも基本的な資料三点を以下に抜粋します。
※図はクリックで大きく表示されます
TWE-LITE DIP のピン配置
(メーカー公式サイトの「超簡単!TWE標準アプリ」 ページより)
TWE-LITE ファームウェアコードの動作フロー
(前掲の SDK マニュアル より)
Test01: LED を点滅させる
まずはマイコン・電子工作の世界での「Hello, world!」にあたる LED の点滅、いわゆる「L チカ」を行います。ネットワーク通信は行いません。
動作の様子
装置の写真と構成図(クリックで大きく表示)
ソースコード GitHub
ユーザ定義のイベントハンドラ内で 1秒周期の E_EVENT_TICK_SECOND 通知に呼応し出力ポートの Lo, Hi をトグルする
Test02: スイッチ操作に LED の状態を連動させる
上の Test01 は全自動かつエンドレスですが、今度は人間の操作に反応させてみます。タクトスイッチの ON/OFF に LED の点灯/消灯 が連動します。ネットワーク通信は行いません。
動作の様子
装置の写真と構成図(クリックで大きく表示)
ソースコード GitHub
既定のイベントハンドラ cbToCoNet_vMain() 内でタクトスイッチの状態に応じて LED の状態を変化させる
/*
* Test02.c
*
* TWE-LITE のデジタル入力 1 (DI1) の状態にデジタル出力 4 (DO4) を連動させる
*
* DI1: Lo -> DO4: Hi
* DI1: Hi -> DO4: Lo
*
* DI1 にタクトスイッチ、DO4 に LED を接続して動作を観察する
* ネットワーク通信は行わない
*
* 2015 KLab Inc.
*
*/
#include <AppHardwareApi.h> // NXP ペリフェラル API 用
#include "utils.h" // ペリフェラル API のラッパなど
#include "ToCoNet.h"
#define DI1 12 // デジタル入力 1
#define DO4 9 // デジタル出力 4
// ハードウェア初期化
static void vInitHardware()
{
// 使用ポートの設定
vPortAsInput(DI1);
vPortAsOutput(DO4);
vPortSetLo(DO4);
}
// ユーザ定義のイベントハンドラ
static void vProcessEvCore(tsEvent *pEv, teEvent eEvent, uint32 u32evarg)
{
return;
}
//
// 以下 ToCoNet 既定のイベントハンドラ群
//
// 割り込み発生後に随時呼び出される
void cbToCoNet_vMain(void)
{
// DI1: Lo -> DO4: Hi, DI1: Hi -> DO4: Lo
vPortSet_TrueAsLo(DO4, !bPortRead(DI1));
return;
}
// パケット受信時
void cbToCoNet_vRxEvent(tsRxDataApp *pRx)
{
return;
}
// パケット送信完了時
void cbToCoNet_vTxEvent(uint8 u8CbId, uint8 bStatus)
{
return;
}
// ネットワークイベント発生時
void cbToCoNet_vNwkEvent(teEvent eEvent, uint32 u32arg)
{
return;
}
// ハードウェア割り込み発生後(遅延呼び出し)
void cbToCoNet_vHwEvent(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return;
}
// ハードウェア割り込み発生時
uint8 cbToCoNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return FALSE;
}
// コールドスタート時
void cbAppColdStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
} else {
// ユーザ定義のイベントハンドラを登録
ToCoNet_Event_Register_State_Machine(vProcessEvCore);
vInitHardware();
}
}
// ウォームスタート時
void cbAppWarmStart(bool_t bAfterAhiInit)
{
return;
}
Test03: シリアル経由でデバッグメッセージを出力
プログラミングにはデバッグのための手段が不可欠です。Test02 のコードに、シリアル接続経由で PC 上のターミナルアプリへトレース文を出力する処理を加えてみます。PC との接続には専用の USB アダプタ 「TWE-LITE R」 を使用しています。 (市販の USB シリアル変換モジュール/ケーブルも利用できます ) ネットワーク通信は行いません。
動作の様子
装置の写真と構成図(クリックで大きく表示)
ソースコード GitHub
起動時に UART とデバッグ出力用の初期化を行い vfPrintf() 関数を使ってシリアルへトレース文を出力
/* Copyright (C) 2016 Mono Wireless Inc. All Rights Reserved. *
* Released under MW-SLA-1J/1E (MONO WIRELESS SOFTWARE LICENSE *
* AGREEMENT VERSION 1). */
/*
* Test03.c
*
* Test02.c の内容にシリアルポート経由でのデバッグメッセージ出力処理を追加
* ネットワーク通信は行わない
*
* 2015 KLab Inc.
*
*/
#include <AppHardwareApi.h> // NXP ペリフェラル API 用
#include "utils.h" // ペリフェラル API のラッパなど
#include "serial.h" // シリアル用
#include "sprintf.h" // SPRINTF 用
#include "ToCoNet.h"
#define DI1 12 // デジタル入力 1
#define DO4 9 // デジタル出力 4
#define UART_BAUD 115200 // シリアルのボーレート
static tsFILE sSerStream; // シリアル用ストリーム
static tsSerialPortSetup sSerPort; // シリアルポートデスクリプタ
// デバッグメッセージ出力用
#define DBG
#ifdef DBG
#define dbg(...) vfPrintf(&sSerStream, LB __VA_ARGS__)
#else
#define dbg(...)
#endif
// デバッグ出力用に UART を初期化
static void vSerialInit() {
static uint8 au8SerialTxBuffer[96];
static uint8 au8SerialRxBuffer[32];
sSerPort.pu8SerialRxQueueBuffer = au8SerialRxBuffer;
sSerPort.pu8SerialTxQueueBuffer = au8SerialTxBuffer;
sSerPort.u32BaudRate = UART_BAUD;
sSerPort.u16AHI_UART_RTS_LOW = 0xffff;
sSerPort.u16AHI_UART_RTS_HIGH = 0xffff;
sSerPort.u16SerialRxQueueSize = sizeof(au8SerialRxBuffer);
sSerPort.u16SerialTxQueueSize = sizeof(au8SerialTxBuffer);
sSerPort.u8SerialPort = E_AHI_UART_0;
sSerPort.u8RX_FIFO_LEVEL = E_AHI_UART_FIFO_LEVEL_1;
SERIAL_vInit(&sSerPort);
sSerStream.bPutChar = SERIAL_bTxChar;
sSerStream.u8Device = E_AHI_UART_0;
}
// ハードウェア初期化
static void vInitHardware()
{
// デバッグ出力用
vSerialInit();
ToCoNet_vDebugInit(&sSerStream);
ToCoNet_vDebugLevel(0);
// 使用ポートの設定
vPortAsInput(DI1);
vPortAsOutput(DO4);
vPortSetLo(DO4);
}
// ユーザ定義のイベントハンドラ
static void vProcessEvCore(tsEvent *pEv, teEvent eEvent, uint32 u32evarg)
{
// 起動時にデバッグメッセージを出力
if (eEvent == E_EVENT_START_UP) {
dbg("** Test03 **");
}
return;
}
//
// 以下 ToCoNet 既定のイベントハンドラ群
//
// 割り込み発生後に随時呼び出される
void cbToCoNet_vMain(void)
{
static bool_t stsPrev = TRUE;
// DI1: Lo -> DO4: Hi, DI1: Hi -> DO4: Lo
bool_t sts = bPortRead(DI1);
vPortSet_TrueAsLo(DO4, !sts);
// DI1 の状態が変化していればデバッグメッセージを出力
if (sts != stsPrev) {
stsPrev = sts;
dbg("DI1 is [%s]", (sts) ? "Lo" : "Hi");
}
return;
}
// パケット受信時
void cbToCoNet_vRxEvent(tsRxDataApp *pRx)
{
return;
}
// パケット送信完了時
void cbToCoNet_vTxEvent(uint8 u8CbId, uint8 bStatus)
{
return;
}
// ネットワークイベント発生時
void cbToCoNet_vNwkEvent(teEvent eEvent, uint32 u32arg)
{
return;
}
// ハードウェア割り込み発生後(遅延呼び出し)
void cbToCoNet_vHwEvent(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return;
}
// ハードウェア割り込み発生時
uint8 cbToCoNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return FALSE;
}
// コールドスタート時
void cbAppColdStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
} else {
// ユーザ定義のイベントハンドラを登録
ToCoNet_Event_Register_State_Machine(vProcessEvCore);
vInitHardware();
}
}
// ウォームスタート時
void cbAppWarmStart(bool_t bAfterAhiInit)
{
return;
}
Test04: スイッチ押下でメッセージを送信 〜 受信側は LED で反応
無線送受信を行います。Test02 の装置と同じものをもうひとつ用意します。タクトスイッチが押下されるとメッセージをブロードキャストし、受信した側は LED を一定時間点灯させます。
動作の様子
装置の写真と構成図(クリックで大きく表示)
ソースコード GitHub
スイッチ押下時に ToCoNet_bMacTxReq() によりブロードキャストを実行。既定のイベントハンドラ cbToCoNet_vTxEvent() および cbToCoNet_vRxEvent() 内で送受信通知への対応を行う
/* Copyright (C) 2016 Mono Wireless Inc. All Rights Reserved. *
* Released under MW-SLA-1J/1E (MONO WIRELESS SOFTWARE LICENSE *
* AGREEMENT VERSION 1). */
/*
* Test04.c
*
* TWE-LITE のデジタル入力 1 (DI1) が Lo に変化したらブロードキャストを行い
* パケットを受信したら一定時間 デジタル出力 4 (DO4) を Hi にする
*
* ふたつの TWE-LITE モジュールを用意してそれぞれ DI1 にタクトスイッチ、
* DO4 に LED を接続して動作を観察する
*
* 2015 KLab Inc.
*
*/
#include <string.h> // C 標準ライブラリ用
#include <AppHardwareApi.h> // NXP ペリフェラル API 用
#include "utils.h" // ペリフェラル API のラッパなど
#include "serial.h" // シリアル用
#include "sprintf.h" // SPRINTF 用
#include "ToCoNet.h"
#include "ToCoNet_mod_prototype.h" // ToCoNet モジュール定義
#define DI1 12 // デジタル入力 1
#define DO4 9 // デジタル出力 4
#define UART_BAUD 115200 // シリアルのボーレート
// ToCoNet 用パラメータ
#define APP_ID 0x33333333
#define CHANNEL 18
static tsFILE sSerStream; // シリアル用ストリーム
static tsSerialPortSetup sSerPort; // シリアルポートデスクリプタ
static uint32 u32Seq; // 送信パケットのシーケンス番号
// デバッグメッセージ出力用
#define DBG
#ifdef DBG
#define dbg(...) vfPrintf(&sSerStream, LB __VA_ARGS__)
#else
#define dbg(...)
#endif
// デバッグ出力用に UART を初期化
static void vSerialInit() {
static uint8 au8SerialTxBuffer[96];
static uint8 au8SerialRxBuffer[32];
sSerPort.pu8SerialRxQueueBuffer = au8SerialRxBuffer;
sSerPort.pu8SerialTxQueueBuffer = au8SerialTxBuffer;
sSerPort.u32BaudRate = UART_BAUD;
sSerPort.u16AHI_UART_RTS_LOW = 0xffff;
sSerPort.u16AHI_UART_RTS_HIGH = 0xffff;
sSerPort.u16SerialRxQueueSize = sizeof(au8SerialRxBuffer);
sSerPort.u16SerialTxQueueSize = sizeof(au8SerialTxBuffer);
sSerPort.u8SerialPort = E_AHI_UART_0;
sSerPort.u8RX_FIFO_LEVEL = E_AHI_UART_FIFO_LEVEL_1;
SERIAL_vInit(&sSerPort);
sSerStream.bPutChar = SERIAL_bTxChar;
sSerStream.u8Device = E_AHI_UART_0;
}
// ハードウェア初期化
static void vInitHardware()
{
// デバッグ出力用
vSerialInit();
ToCoNet_vDebugInit(&sSerStream);
ToCoNet_vDebugLevel(0);
// 使用ポートの設定
vPortAsInput(DI1);
vPortAsOutput(DO4);
vPortSetLo(DO4);
}
// ブロードキャスト実行
static bool_t sendBroadcast ()
{
tsTxDataApp tsTx;
memset(&tsTx, 0, sizeof(tsTxDataApp));
tsTx.u32SrcAddr = ToCoNet_u32GetSerial();
tsTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST;
tsTx.bAckReq = FALSE;
tsTx.u8Retry = 0x02; // 送信失敗時は 2回再送
tsTx.u8CbId = u32Seq & 0xFF;
tsTx.u8Seq = u32Seq & 0xFF;
tsTx.u8Cmd = TOCONET_PACKET_CMD_APP_DATA;
// SPRINTF でペイロードを作成
SPRINTF_vRewind();
vfPrintf(SPRINTF_Stream, "Hello! from %08X seq=%u", ToCoNet_u32GetSerial(), u32Seq);
memcpy(tsTx.auData, SPRINTF_pu8GetBuff(), SPRINTF_u16Length());
tsTx.u8Len = SPRINTF_u16Length();
u32Seq++;
// 送信
return ToCoNet_bMacTxReq(&tsTx);
}
// ユーザ定義のイベントハンドラ
static void vProcessEvCore(tsEvent *pEv, teEvent eEvent, uint32 u32evarg)
{
static bool_t count = 0;
// 起動時
if (eEvent == E_EVENT_START_UP) {
dbg("** Test04 **");
}
// データ受信時に DO4 を Hi に
if (eEvent == E_ORDER_KICK) {
count = 100;
vPortSetHi(DO4);
}
// 4ms 周期のシステムタイマ通知
if (eEvent == E_EVENT_TICK_TIMER) {
if (count > 0) {
count--;
} else {
// DO4 が Hi なら Lo に
if (!bPortRead(DO4)) {
vPortSetLo(DO4);
}
}
}
return;
}
//
// 以下 ToCoNet 既定のイベントハンドラ群
//
// 割り込み発生後に随時呼び出される
void cbToCoNet_vMain(void)
{
static bool_t stsPrev = TRUE;
bool_t sts = bPortRead(DI1);
if (sts != stsPrev) {
// DI1 が Hi -> Lo に変化したら送信を行う
if (sts) {
sendBroadcast();
}
stsPrev = sts;
dbg("DI1 is [%s]", (sts) ? "Lo" : "Hi");
}
return;
}
// パケット受信時
void cbToCoNet_vRxEvent(tsRxDataApp *pRx)
{
static uint32 u32SrcAddrPrev = 0;
static uint8 u8seqPrev = 0xFF;
// 前回と同一の送信元+シーケンス番号のパケットなら受け流す
if (pRx->u32SrcAddr == u32SrcAddrPrev && pRx->u8Seq == u8seqPrev) {
return;
}
// ペイロードを切り出してデバッグ出力
char buf[64];
int len = (pRx->u8Len < sizeof(buf)) ? pRx->u8Len : sizeof(buf)-1;
memcpy(buf, pRx->auData, len);
buf[len] = '\0';
dbg("RECV << [%s]", buf);
u32SrcAddrPrev = pRx->u32SrcAddr;
u8seqPrev = pRx->u8Seq;
// DO4 を一定時間 Hi にする
ToCoNet_Event_Process(E_ORDER_KICK, 0, vProcessEvCore);
return;
}
// パケット送信完了時
void cbToCoNet_vTxEvent(uint8 u8CbId, uint8 bStatus)
{
dbg(">> SEND %s seq=%u", bStatus ? "OK" : "NG", u32Seq);
return;
}
// ネットワークイベント発生時
void cbToCoNet_vNwkEvent(teEvent eEvent, uint32 u32arg)
{
return;
}
// ハードウェア割り込み発生後(遅延呼び出し)
void cbToCoNet_vHwEvent(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return;
}
// ハードウェア割り込み発生時
uint8 cbToCoNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return FALSE;
}
// コールドスタート時
void cbAppColdStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
// 必要モジュール登録手続き
ToCoNet_REG_MOD_ALL();
} else {
// SPRINTF 初期化
SPRINTF_vInit128();
// ToCoNet パラメータ
sToCoNet_AppContext.u32AppId = APP_ID;
sToCoNet_AppContext.u8Channel = CHANNEL;
sToCoNet_AppContext.bRxOnIdle = TRUE; // アイドル時にも受信
u32Seq = 0;
// ユーザ定義のイベントハンドラを登録
ToCoNet_Event_Register_State_Machine(vProcessEvCore);
// ハードウェア初期化
vInitHardware();
// MAC 層開始
ToCoNet_vMacStart();
}
}
// ウォームスタート時
void cbAppWarmStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
} else {
vInitHardware();
ToCoNet_vMacStart();
}
}
Test05: 電力消費を抑制した送信専用コードと装置
上記 Test04 での送信処理を独立させ、消費電力を抑えることを目的にメーカー公式の「無線タグアプリ(App_Tag)」 (注:2015年04月時点での名称は「Samp_Monitor」) の押しボタン・磁気スイッチ対応機能 における子機処理(EndDevice_Input)での以下の要所を取り入れた内容です。
TWE-LITE のモード設定ビット 1 (M1) が GND に接続されていれば(すなわち M1 が Lo であれば)、デジタル入力 1 (DI1) の立ち上がり (Lo -> Hi) を送信のトリガーとする
(典型的には平時が導通状態の磁気リードスイッチが磁界から離れ切断された状況)
立ち上がりトリガーの場合 節電のため DI1 の内部プルアップを無効化する。そのためこの場合は外部プルアップ抵抗を設置する。(公式サイト上の記事 を参考に 1MΩ 抵抗を使用)
M1 が GND に接続されていなければ DI1 の立ち下がり (Hi -> Lo) をトリガーとする
(典型的には平時が非導通状態のタクトスイッチが押下された状況)
送信を終えたらすみやかに Sleep 状態へ移行し DI1 の状態が変化すると Sleep から復帰する
受信側は Test04 のものをそのまま使います。以下の動画・写真では送信側の装置は磁気リードスイッチを使っており立ち上がり検出を行っています。
ちなみにこの装置の待機(Sleep)状態の消費電流を測ったところ 3μA(0.003mA)でした。一般に CR2032 の放電容量はおよそ 225mAh であることから、下記のサイトを利用して単純計算すると待機継続可能期間は 225mAh / 0.003mA * 0.7 = 52500時間 = 2187.5日 ≒ 5.99年となります。実際の電池寿命は送信頻度によって大きく変わるでしょう。
電池寿命カリキュレータ - www.digikey.jp
動作の様子
装置の写真と構成図(クリックで大きく表示)
ソースコード GitHub
電源投入直後やリセット後はまずそのまま Sleep 状態へ移行。DI1 の状態が変化すると起床して送信を行いふたたび Sleep へ。DI1 の立ち上がり/立ち下がりのどちらで送信を行うかは M1 の Lo / Hi 状態で決定する
/* Copyright (C) 2016 Mono Wireless Inc. All Rights Reserved. *
* Released under MW-SLA-1J/1E (MONO WIRELESS SOFTWARE LICENSE *
* AGREEMENT VERSION 1). */
/*
* Test05.c
*
* Test04.c の送信処理を独立させ 電池稼働を想定して以下の機能を加えたもの
*
* - TWE-LITE のモード設定ビット 1 (M1) が GND に接続されていれば
* デジタル入力 1 (DI1) の立ち上がり (Lo -> Hi) を送信のトリガーとする
* -> 典型的には平時導通状態の磁気リードスイッチが切断された状況
*
* ※立ち上がりトリガーの場合 節電のため DI1 の内部プルアップは無効化
* される。メーカーは外部プルアップとして 1MΩ 抵抗の設置を推奨。
* http://mono-wireless.com/jp/products/TWE-APPS/App_Tag/mode_push.html
*
* M1 が GND に接続されていなければ DI1 の立ち下がりをトリガーとする
* -> 典型的には平時非導通状態のタクトスイッチが押下された状況
*
* - 節電のため送信を終えたらすみやかに Sleep 状態へ移行し DI1 の状態が
* 変化すると Sleep から復帰する
*
* 受信側は Test04 のバイナリと回路をそのまま使用する
*
* 2015 KLab Inc.
*
*/
#include <string.h> // C 標準ライブラリ用
#include <AppHardwareApi.h> // NXP ペリフェラル API 用
#include "utils.h" // ペリフェラル API のラッパなど
#include "serial.h" // シリアル用
#include "sprintf.h" // SPRINTF 用
#include "ToCoNet.h"
#include "ToCoNet_mod_prototype.h" // ToCoNet モジュール定義
#define DI1 12 // デジタル入力 1
#define M1 10 // モード設定ビット 1
#define UART_BAUD 115200 // シリアルのボーレート
// Sleep からの復帰に DI1 の状態変化を利用するための bitmap
#define PORT_BITMAP_DI1 (1UL << DI1)
// ToCoNet 用パラメータ
#define APP_ID 0x33333333
#define CHANNEL 18
// 状態定義(ユーザ)
typedef enum
{
E_STATE_APP_BASE = ToCoNet_STATE_APP_BASE,
E_STATE_APP_WAIT_TX,
E_STATE_APP_SLEEP,
} teStateApp;
static tsFILE sSerStream; // シリアル用ストリーム
static tsSerialPortSetup sSerPort; // シリアルポートデスクリプタ
static uint32 u32Seq; // 送信パケットのシーケンス番号
static bool_t triggerEdgeRising_DI1; // TRUE: 送信トリガーは DI1 の立ち上がり
// FALSE: 〃 DI1 の立ち下がり
// デバッグメッセージ出力用
#define DBG
#ifdef DBG
#define dbg(...) vfPrintf(&sSerStream, LB __VA_ARGS__)
#else
#define dbg(...)
#endif
// デバッグ出力用に UART を初期化
static void vSerialInit() {
static uint8 au8SerialTxBuffer[96];
static uint8 au8SerialRxBuffer[32];
sSerPort.pu8SerialRxQueueBuffer = au8SerialRxBuffer;
sSerPort.pu8SerialTxQueueBuffer = au8SerialTxBuffer;
sSerPort.u32BaudRate = UART_BAUD;
sSerPort.u16AHI_UART_RTS_LOW = 0xffff;
sSerPort.u16AHI_UART_RTS_HIGH = 0xffff;
sSerPort.u16SerialRxQueueSize = sizeof(au8SerialRxBuffer);
sSerPort.u16SerialTxQueueSize = sizeof(au8SerialTxBuffer);
sSerPort.u8SerialPort = E_AHI_UART_0;
sSerPort.u8RX_FIFO_LEVEL = E_AHI_UART_FIFO_LEVEL_1;
SERIAL_vInit(&sSerPort);
sSerStream.bPutChar = SERIAL_bTxChar;
sSerStream.u8Device = E_AHI_UART_0;
}
// ハードウェア初期化
static void vInitHardware()
{
// デバッグ出力用
vSerialInit();
ToCoNet_vDebugInit(&sSerStream);
ToCoNet_vDebugLevel(0);
// 使用ポートの設定
vPortAsInput(DI1);
vPortAsInput(M1);
}
// ブロードキャスト実行
static bool_t sendBroadcast ()
{
tsTxDataApp tsTx;
memset(&tsTx, 0, sizeof(tsTxDataApp));
tsTx.u32SrcAddr = ToCoNet_u32GetSerial();
tsTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST;
tsTx.bAckReq = FALSE;
tsTx.u8Retry = 0x02; // 送信失敗時は 2回再送
tsTx.u8CbId = u32Seq & 0xFF;
tsTx.u8Seq = u32Seq & 0xFF;
tsTx.u8Cmd = TOCONET_PACKET_CMD_APP_DATA;
// SPRINTF でペイロードを作成
SPRINTF_vRewind();
vfPrintf(SPRINTF_Stream, "Hello! from %08X seq=%u", ToCoNet_u32GetSerial(), u32Seq);
memcpy(tsTx.auData, SPRINTF_pu8GetBuff(), SPRINTF_u16Length());
tsTx.u8Len = SPRINTF_u16Length();
u32Seq++;
// 送信
return ToCoNet_bMacTxReq(&tsTx);
}
// ユーザ定義のイベントハンドラ
static void vProcessEvCore(tsEvent *pEv, teEvent eEvent, uint32 u32evarg)
{
switch (pEv->eState) {
// アイドル状態
case E_STATE_IDLE:
if (eEvent == E_EVENT_START_UP) { // 起動時
dbg("** Test05 **");
dbg("triggerEdgeRising_DI1=%d", triggerEdgeRising_DI1);
// Sleep からの復帰なら稼働状態へ
// 電源投入直後やリセット後ならそのまま Sleep へ
if (u32evarg & EVARG_START_UP_WAKEUP_RAMHOLD_MASK) {
ToCoNet_Event_SetState(pEv, E_STATE_RUNNING);
} else {
ToCoNet_Event_SetState(pEv, E_STATE_APP_SLEEP);
}
}
break;
// 稼働状態
case E_STATE_RUNNING:
if (eEvent == E_ORDER_KICK) {
if (u32evarg == TRUE) {
// state を送信完了待ちにして送信を実行
ToCoNet_Event_SetState(pEv, E_STATE_APP_WAIT_TX);
sendBroadcast();
} else {
// Sleep へ
ToCoNet_Event_SetState(pEv, E_STATE_APP_SLEEP);
}
}
break;
// 送信完了待ち状態
case E_STATE_APP_WAIT_TX:
if (eEvent == E_ORDER_KICK) {
// 送信完了につき Sleep 状態へ移行
ToCoNet_Event_SetState(pEv, E_STATE_APP_SLEEP);
}
break;
// Sleep への移行状態
case E_STATE_APP_SLEEP:
// vSleep は必ず E_EVENT_NEW_STATE 内など1回のみ呼び出される場所で呼び出す
if (eEvent == E_EVENT_NEW_STATE) {
dbg("Sleeping...\r\n");
// UART 出力の完了を待つ
WAIT_UART_OUTPUT(E_AHI_UART_0);
// DI1 の状態変化を Sleep から復帰のトリガーとする
vAHI_DioWakeEnable(PORT_BITMAP_DI1, 0);
/*
if (triggerEdgeRising_DI1) {
vAHI_DioWakeEdge(PORT_BITMAP_DI1, 0); // Lo -> Hi に反応
} else {
vAHI_DioWakeEdge(0, PORT_BITMAP_DI1); // Hi -> Lo に反応
}
*/
// RAM 状態保持のまま Sleep
ToCoNet_vSleep(E_AHI_WAKE_TIMER_0, 0, FALSE, FALSE);
}
break;
default:
break;
}
return;
}
//
// 以下 ToCoNet 既定のイベントハンドラ群
//
// 割り込み発生後に随時呼び出される
void cbToCoNet_vMain(void)
{
static unsigned char stsPrev = 0xFF;
bool_t sts = bPortRead(DI1);
// DI1 の状態が変化したら処理
if (sts != stsPrev) {
stsPrev = sts;
dbg("DI1 is [%s]", (sts) ? "Lo" : "Hi");
// DI1 の状態と 立ち上がり or 立ち下がりトリガーの設定が
// 符合すれば送信処理へ そうでなければ Sleep へ
if ((sts && !triggerEdgeRising_DI1) ||
(!sts && triggerEdgeRising_DI1)) {
// E_ORDER_KICK イベントを引数 TRUE で通知 -> 送信
ToCoNet_Event_Process(E_ORDER_KICK, TRUE, vProcessEvCore);
} else {
// E_ORDER_KICK イベントを引数 FALSE で通知 -> Sleep
ToCoNet_Event_Process(E_ORDER_KICK, FALSE, vProcessEvCore);
}
}
return;
}
// パケット受信時
void cbToCoNet_vRxEvent(tsRxDataApp *pRx)
{
return;
}
// パケット送信完了時
void cbToCoNet_vTxEvent(uint8 u8CbId, uint8 bStatus)
{
dbg(">> SEND %s seq=%u", bStatus ? "OK" : "NG", u32Seq);
// E_ORDER_KICK イベントを通知
ToCoNet_Event_Process(E_ORDER_KICK, 0, vProcessEvCore);
return;
}
// ネットワークイベント発生時
void cbToCoNet_vNwkEvent(teEvent eEvent, uint32 u32arg)
{
return;
}
// ハードウェア割り込み発生後(遅延呼び出し)
void cbToCoNet_vHwEvent(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return;
}
// ハードウェア割り込み発生時
uint8 cbToCoNet_u8HwInt(uint32 u32DeviceId, uint32 u32ItemBitmap)
{
return FALSE;
}
// コールドスタート時
void cbAppColdStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
// 必要モジュール登録手続き
ToCoNet_REG_MOD_ALL();
} else {
// 電源電圧が 2.0V まで下降しても稼働を継続
vAHI_BrownOutConfigure(0, FALSE, FALSE, FALSE, FALSE);
// SPRINTF 初期化
SPRINTF_vInit128();
// ToCoNet パラメータ
sToCoNet_AppContext.u32AppId = APP_ID;
sToCoNet_AppContext.u8Channel = CHANNEL;
u32Seq = 0;
// ユーザ定義のイベントハンドラを登録
ToCoNet_Event_Register_State_Machine(vProcessEvCore);
// ハードウェア初期化
vInitHardware();
// M1 が GND に接続 (= Lo) されていれば
// 送信のトリガーは DI1 の立ち上がり (Lo -> Hi) とする
// そうでなければトリガーは DI1 の立ち下がり (Hi -> Lo) とする
triggerEdgeRising_DI1 = bPortRead(M1);
if (triggerEdgeRising_DI1) {
// 立ち上がりトリガーの場合は節電のため内部プルアップを無効化
vPortDisablePullup(DI1);
}
// MAC 層開始
ToCoNet_vMacStart();
}
}
// ウォームスタート時
void cbAppWarmStart(bool_t bAfterAhiInit)
{
if (!bAfterAhiInit) {
} else {
vAHI_BrownOutConfigure(0, FALSE, FALSE, FALSE, FALSE);
vInitHardware(bAfterAhiInit);
ToCoNet_vMacStart();
}
}
付録:「無線タグアプリ」カスタマイズの記録
2015年4月、手元の実験的要件への対応のために前掲の公式アプリ「無線タグアプリ(App_Tag)」 (当時の名称は「Samp_Monitor」) の親機(Parent/)および子機(EndDevice_Input/)コードのカスタマイズを試みました。以下はその記録です。
変更を加えたソース・ヘッダ
※ベースのバージョンは 2015年4月20日当時の最新版「Samp_Monitor v1.4.1 β」です(2017年1月現在は既に公開終了)
※変更箇所は識別子「MODIFIED_BY_KLAB」で区別しています
変更内容
EndDevice_Input:
センサモードが PKT_ID_BUTTON(押しボタン・磁気スイッチ) の場合、一度の送信完了でただちにスリープ状態へ移行せず所定の回数繰り返し送信を行ってから移行する (diff )
Parent:
電子ブザーの接続を想定し DO4 の使用を追加。センサモードが PKT_ID_BUTTON の子機からパケットを受信した場合、DO1 の LED トグルに合わせ DO4 の状態もトグルする
PKT_ID_BUTTON の子機からの受信発生後は親機がリセットされるまで DO1 の LED を点滅させる(受信有無を事後に目で確認するための便宜) (diff )
以下の動画・写真では子機側の装置は磁気リードスイッチを使用し立ち上がり検出を行っています。したがってこの子機のパラメータ設定 において「m:センサ種別の設定」には 0xFE、「p:センサ固有パラメータの設定」には 1 を指定しています。
動作の様子
装置の写真と構成図 上:親機 下:子機 (クリックで大きく表示)
(備考)
2017年1月13日時点での最新版 SDK 「2014/8月号」 に収録されている旧 Samp_Monitor v1.3.2 の子機側ソースコードには本来の意図とは異なるものと考えられるロジックが含まれています。
子機のパラメータ設定内容をセーブ領域から読み出すタイミング
(/TWESDK/Wks_ToCoNet/Samp_Monitor/EndDevice_Input/Source/EndDevice_Input.c)
※ 旧 Samp_Monitor v1.4.1β で改修されました
子機のセンサモードが PKT_ID_BUTTON (0xFE) の場合の固有パラメータ 立ち上がり (1) / 立ち下がり (0) 指定に対する処理
(/TWESDK/Wks_ToCoNet/Samp_Monitor/EndDevice_Input/Source/ProcessEv_Button.c)
※ 旧 Samp_Monitor v1.3.3 で改修されました
そのため、今の時点で「無線タグアプリ」を使う場合は単体で配布されている版 を利用するほうが良いでしょう。
(tanabe)