環境
「超簡単!標準アプリ(App_Twelite)」(以下簡単アプリ)
簡単アプリ通信内容
簡単アプリでは、5種類の通信がなされる。
この通信は受信側のUARTでモニターできる(I2C関係の通信は出力されないかな?)。
以下のプロトコルに合致しないパケットは、受信はされるが受信側の簡単アプリ中のパケット処理で廃棄される。
送信側端末からの状態通知:ステータス0x81
データはポートの値が変化した場合と定期的(約1秒周期)~~ とあるが、実験した限りでは定期通知は確認できなかった~~ (2020/04/12修正 実験に使った親機が不良品だったようで、交換したら確認できました。)。
例
:788115017581000038002899000C04230000FFFFFFFFFFD4
- 78 子機から親機への通知
- 81 ステータスコード
- 15 パケット識別子 (アプリケーションIDより生成される)だそうです。
- 01 プロトコルバージョン
- 75 LQI 受信時に受信側でLQI値がここに上書きされる
- 81000038 送信元個体識別番号
- 00 宛先の論理デバイスID 00だと親機
- 2899 タイムスタンプ (秒64カウント)
- 00 中継回数(0~3)
- 0C04 電源電圧[mV]
- 23 未使用
- 00 DI1〜DI4 の状態ビット列。アクティブロー。
- 00 DI1〜DI4 の状態変更ビット列。 1が変更。
- FFFFFFFF AD1~AD4の変換値。
- FF AD1~AD4補正値ビット列。1チャンネルに2ビットずつ使用。
D4 チェックサム LRC 形式
・・・
受信側端末のIO出力変更:コマンド0x80
送信側から入力された、UARTシリアルからのコマンドです。受信側でこのコマンドに従ってIOの出力が制御されます。
例
:7880010F0F0380030002800200DF
- 78 宛先 (0x00 ~ 0x7F:受信側アドレス 0x00の場合は親機, 0x78の場合は全子機)
- 80 コマンドコード
- 01 プロトコルバージョン
- 0F IO設定ビット
- 0F IO設定ビットマスク
- 0380 PWM1設定値
- 0300 PWM2設定値
- 0280 PWM3設定値
- 0200 PWM4設定値
- DF チェックサム
I2Cへのアクセス:コマンド0x88
例
:DB88010123230055
- DB 宛先(0xDB: 自分自身, 0x00 ~ 0x7F:受信側アドレス 0x00の場合は親機, 0x78の場合は全子機)
- 88 コマンドコード
- ・・・
I2Cの応答:ステータス0x89
I2Cへのアクセス:コマンド0x88 によって指定された操作の返答
例
:7889AB0401013A14
- DB 宛先 (0xDB: 自分自身, 0x00 ~ 0x7F:受信側アドレス 0x00の場合は親機, 0x78の場合は全子機
- 89 ステータスコード
- ・・・
任意データの送受信:コマンド0x01
送信側のUARTから下記のように入力された場合、同じ内容のパケットが送信されます。
例
:780100112233AABBCCDD13
- 78 宛先 (0x00 ~ 0x7F:受信側アドレス 0x00の場合は親機, 0x78の場合は全子機)
- 01 コマンドコード
- 00112233AABBCCDD ペイロード(任意長)
- 13 チェックサム
受信処理をソースコードから読み解く
簡単アプリソース master.c 1079行目付近
/** @ingroup MASTER
* パケットの受信完了時に呼び出されるコールバック関数。\n
* パケットの種別によって具体的な処理関数にディスパッチしている。
* データ種別は psRx->u8Cmd (ToCoNet のパケットヘッダに含まれます) により識別される。
*
* - パケット種別
* - TOCONET_PACKET_CMD_APP_DATA : シリアル電文パケット
* - TOCONET_PACKET_CMD_APP_USER_IO_DATA : IO状態の伝送
* - TOCONET_PACKET_CMD_APP_USER_IO_DATA_EXT : シリアル電文による IO 状態の伝送
*
* @param psRx 受信パケット
*/
void cbToCoNet_vRxEvent(tsRxDataApp *psRx) {
//uint8 *p = pRx->auData;
・・・
ここで、
* - TOCONET_PACKET_CMD_APP_DATA : シリアル電文パケット
* - TOCONET_PACKET_CMD_APP_USER_IO_DATA : IO状態の伝送
* - TOCONET_PACKET_CMD_APP_USER_IO_DATA_EXT : シリアル電文による IO 状態の伝送
*
はそれぞれ
0x00,0x02,0x03 となっていた。
また、受信パケットは最初のコロンやチェックサムは付いていない。シリアル出力の場合、出力時
vSerOutput_ModbusAscii
によって付加される。
その後の、振り分け処理部分。
・・・
switch (psRx->u8Cmd) {
case TOCONET_PACKET_CMD_APP_DATA: // シリアルメッセージのパケット
vReceiveSerMsg(psRx);
break;
case TOCONET_PACKET_CMD_APP_USER_IO_DATA: // IO状態の伝送
if (PRSEV_eGetStateH(sAppData.u8Hnd_vProcessEvCore) == E_STATE_RUNNING) { // 稼動状態でパケット処理をする
#ifdef USE_MONOSTICK
//bColdStart = FALSE;
#endif
vReceiveIoData(psRx);
}
break;
case TOCONET_PACKET_CMD_APP_USER_IO_DATA_EXT: // IO状態の伝送(UART経由)
#ifdef USE_MONOSTICK
//bColdStart = FALSE;
#endif
if (PRSEV_eGetStateH(sAppData.u8Hnd_vProcessEvCore) == E_STATE_RUNNING) { // 稼動状態でパケット処理をする
vReceiveIoSettingRequest(psRx);
}
break;
}
TOCONET_PACKET_CMD_APP_DATA : シリアル電文パケット
以下の箇所で I2C と シリアル出力処理がなされている。
コマンド0x88, ステータス0x89,コマンド0x01の処理っぽい
static void vReceiveSerMsg(tsRxDataApp *pRx) 内 4086行目付近
if (sAppData.u8Mode != E_IO_MODE_ROUTER) { // 中継機は処理しない
if (au8SerBuffRx[1] == SERCMD_ID_I2C_COMMAND) {
// I2C の処理
#ifndef USE_I2C_PORT_AS_OTHER_FUNCTION
if (!IS_APPCONF_OPT_PWM_MOVE_PORTS()) {
vProcessI2CCommand(au8SerBuffRx, sSerSeqRx.u16DataLen, sSerSeqRx.u8IdSender);
}
#endif
} else {
// 受信データの出力
vSerOutput_ModbusAscii(&sSerStream,
sSerSeqRx.u8IdSender, // v1.2.1 1バイト目は送り主に変更。他のコマンドなどとの整合性のため。
au8SerBuffRx[1], au8SerBuffRx + 2,
sSerSeqRx.u16DataLen - 2);
}
}
TOCONET_PACKET_CMD_APP_USER_IO_DATA : IO状態の伝送
コマンド 0x80 の処理っぽい
static void vReceiveIoData(tsRxDataApp *pRx) {
内3648行目付近
if (sAppData.prPrsEv == vProcessEvCoreSlpRecv) {
// 間欠受信モードでは、無条件に全ポートを書き換える /////////////////////////////
// 以下の動作パターンを想定
// 子機が間欠受信で DO1=LO に設定
// 親機が電源OFF
// その後、親機のDI1=HI に戻る
// 親機が電源ON
// この場合、親機のボタン変更マスク DI1=未使用 と設定され、子機DO1 が HI に戻る
// 事が無いため。
//
// 通常モードでも同様の現象は発生するが、用途要件によってどちらが良いかは一意に
// 決められないため、そのままとする。
u8ButtonChanged = 0x0F;
}
// ポートの値を設定する(変更フラグのあるものだけ)
for (i = 0, j = 1; i < 4; i++, j <<= 1) {
if (u8ButtonChanged & j) {
vDoSet_TrueAsLo(au8PortTbl_DOut[i], u8ButtonState & j);
・・・
/* UART 出力 */
if (!sSerCmd.bverbose) {
if (IS_APPCONF_OPT_REGULAR_PACKET_NO_DISP() && bRegular) {
; // 通常パケットの場合の出力抑制設定
} else {
// 以下のようにペイロードを書き換えて UART 出力
pRx->auData[0] = pRx->u8Len; // 1バイト目はバイト数
pRx->auData[2] = pRx->u8Lqi; // 3バイト目(もともとは送信元の LogicalID) は LQI
vSerOutput_ModbusAscii(&sSerStream, u8AppLogicalId,
SERCMD_ID_INFORM_IO_DATA, pRx->auData, pRx->u8Len);
}
}
TOCONET_PACKET_CMD_APP_USER_IO_DATA_EXT : シリアル電文による IO 状態の伝送
ステータス 0x81 の処理っぽい
static void vReceiveIoSettingRequest(tsRxDataApp *pRx) {
内3883行付近
if (u8Format == 1) {
/* BUTTON */
uint8 u8ButtonState = G_OCTET();
uint8 u8ButtonChanged = G_OCTET();
// ポートの値を設定する(変更フラグのあるものだけ)
for (i = 0, j = 1; i < 4; i++, j <<= 1) {
if (u8ButtonChanged & j) {
vDoSet_TrueAsLo(au8PortTbl_DOut[i], u8ButtonState & j);
sAppData.sIOData_now.au8Output[i] = u8ButtonState & j;
}
}
for (i = 0; i < 4; i++) {
uint16 u16Duty = G_BE_WORD();
if (u16Duty <= 1024) {
sAppData.sIOData_now.au16OutputPWMDuty[i] = u16Duty;
} else {
sAppData.sIOData_now.au16OutputPWMDuty[i] = 0xFFFF;
}
}
DBGOUT(1, "RECV:IO REQ: %02x %02x %04x:%04x:%04x:%04x"LB, u8ButtonState,
u8ButtonChanged, sAppData.sIOData_now.au16OutputPWMDuty[0],
sAppData.sIOData_now.au16OutputPWMDuty[1],
sAppData.sIOData_now.au16OutputPWMDuty[2],
sAppData.sIOData_now.au16OutputPWMDuty[3]);
// PWM の再設定
for (i = 0; i < 4; i++) {
if (sAppData.sIOData_now.au16OutputPWMDuty[i] != 0xFFFF) {
sTimerPWM[i].u16duty =
_PWM(sAppData.sIOData_now.au16OutputPWMDuty[i]);
if (sTimerPWM[i].bStarted)
vTimerStart(&sTimerPWM[i]); // DUTY比だけ変更する
}
}
}
/* UART 出力 */
#if 0
if (!sSerCmd.bverbose) {
// 以下のようにペイロードを書き換えて UART 出力
pRx->auData[0] = pRx->u8Len;// 1バイト目はバイト数
pRx->auData[2] = pRx->u8Lqi;// 3バイト目(もともとは送信元の LogicalID) は LQI
vSerOutput_ModbusAscii(&sSerStream, u8AppLogicalId, SERCMD_ID_INFORM_IO_DATA, pRx->auData, pRx->u8Len);
}
#endif
}