「TWILITE の「超簡単!標準アプリ(App_Twelite)」の通信と受信処理を調べてみる」
https://qiita.com/nanbuwks/items/cf6647ef648efc80a106
では、「超簡単!標準アプリ(App_Twelite)」(以下「簡単アプリ」)の5種類の通信について調べました。ここではその5種類の送信がソースでどう記述されているか調べます。
ステータス0x81:IO状態の送信
変化割り込みおよび、定期送信に使われている。
/** @ingroup MASTER
* IO 情報を送信します。
*
* - IO状態の変化、および1秒置きの定期送時に呼び出されます。
*
* - Packet 構造
* - OCTET: 識別ヘッダ(APP ID より生成)
* - OCTET: プロトコルバージョン(バージョン間干渉しないための識別子)
* - OCTET: 送信元論理ID
* - BE_DWORD: 送信元のシリアル番号
* - OCTET: 宛先論理ID
* - BE_WORD: 送信タイムスタンプ (64fps カウンタの値の下16ビット, 約1000秒で1周する)
* - OCTET: 中継フラグ(中継したパケットは1、最初の送信なら0を格納)
* - BE_WORD: 電圧
* - OCTET: 温度 (int8型) ※ TODO: 値が不正確。ADC 値から温度への変換式がメーカより開示されないため。
* - OCTET: ボタン (LSB から順に SW1 ... SW4, 1=Lo), 0x80ビットは通常送信の識別用フラグ
* - OCTET: ボタン変化 (LSB から順に SW1 ... SW4, 1=変化)
* - OCTET: ADC1 (MSB から 8bit)
* - OCTET: ADC2 (MSB から 8bit)
* - OCTET: ADC3 (MSB から 8bit)
* - OCTET: ADC4 (MSB から 8bit)
* - OCTET: ADC 詳細部 (MSB b8b7b6b5b4b3b2b1 LSB とすると b2b1 が ADC1 の LSB 2bit, 以下同様)
*
* - ADC 値の復元方法
* - 8bit ADC値の場合 16倍すると mV 値になります。
* - ADC詳細部の2bitを追加し 10bit ADC 値の場合 4 倍すると mV 値なります。
*
* @returns -1:ERROR, 0..255 CBID
*/
static int16 i16TransmitIoData(uint8 u8Quick, bool_t bRegular) {
if (IS_APPCONF_ROLE_SILENT_MODE())
return -1;
int16 i16Ret = -1;
tsTxDataApp sTx;
memset(&sTx, 0, sizeof(sTx));
uint8 *q = sTx.auData;
int i;
// ペイロードを構成
S_OCTET(sAppData.u8AppIdentifier);
S_OCTET(APP_PROTOCOL_VERSION);
S_OCTET(sAppData.u8AppLogicalId); // アプリケーション論理アドレス
S_BE_DWORD(ToCoNet_u32GetSerial()); // シリアル番号
S_OCTET(IS_LOGICAL_ID_PARENT(sAppData.u8AppLogicalId) ? LOGICAL_ID_CHILDREN : LOGICAL_ID_PARENT); // 宛先
S_BE_WORD((sAppData.u32CtTimer0 & 0x7FFF) + (u8Quick == TX_NODELAY_AND_QUICK_BIT ? 0x8000 : 0)); // タイムスタンプ
// bQuick 転送する場合は MSB をセットし、優先パケットである処理を行う
S_OCTET(0); // 中継フラグ
S_BE_WORD(sAppData.sIOData_now.u16Volt); // 電圧
// チップ内温度センサーの予定だったが・・・
・・・
// ボタンのビットマップ使用フラグ (1度でもLoになったポートは1になる)
{
uint8 i, c = 0x0;
#ifdef USE_MONOSTICK
for (i = 0; i < 4; i++) {
if( i != 2 ){
c |= (sAppData.sIOData_now.u32BtmUsed & (1UL << au8PortTbl_DIn[i])) ? (1 << i) : 0;
}
}
#else
for (i = 0; i < 4; i++) {
c |= (sAppData.sIOData_now.u32BtmUsed & (1UL << au8PortTbl_DIn[i])) ? (1 << i) : 0;
}
#endif
if (u8Quick == TX_NODELAY_AND_RESP_BIT) c |= 0x80;
S_OCTET(c);
}
// ADC 部のエンコード
uint8 u8LSBs = 0;
for (i = 0; i < 4; i++) {
// MSB 部分 (10bit目~3bit目まで)
uint16 u16v = sAppData.sIOData_now.au16InputADC[i];
u16v >>= 2; // ADC 値は 0...2400mV
uint8 u8MSB = (u16v >> 2) & 0xFF;
S_OCTET(u8MSB);
// 下2bitを u8LSBs に詰める
u8LSBs >>= 2;
u8LSBs |= ((u16v << 6) & 0xC0); //
}
S_OCTET(u8LSBs); // 詳細ビット部分を記録
sTx.u8Len = q - sTx.auData; // パケット長
sTx.u8Cmd = TOCONET_PACKET_CMD_APP_USER_IO_DATA; // パケット種別
// 送信する
sTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST; // ブロードキャスト
sTx.u8Retry = sAppData.u8StandardTxRetry; // 再送
// フレームカウントとコールバック識別子の指定
sAppData.u16TxFrame++;
sTx.u8Seq = (sAppData.u16TxFrame & 0xFF);
sTx.u8CbId = sTx.u8Seq;
{
/* MAC モードでは細かい指定が可能 */
sTx.bAckReq = FALSE;
sTx.u32SrcAddr = sToCoNet_AppContext.u16ShortAddress;
// 送信タイミングの調整
switch (u8Quick) {
case TX_NODELAY:
case TX_NODELAY_AND_QUICK_BIT:
case TX_NODELAY_AND_RESP_BIT:
sTx.u16RetryDur = 0; // 再送間隔
sTx.u16DelayMin = 0; // すみやかに送信する
sTx.u16DelayMax = 0; // すみやかに送信する
break;
case TX_SMALLDELAY:
case TX_REPONDING:
sTx.u16RetryDur = 0; // 再送間隔
sTx.u16DelayMin = 4; // 衝突を抑制するため送信タイミングを遅らせる
sTx.u16DelayMax = 8; // 衝突を抑制するため送信タイミングを遅らせる
break;
default:
sTx.u16RetryDur = 4; // 再送間隔
sTx.u16DelayMin = 0; // 衝突を抑制するため送信タイミングを遅らせる
sTx.u16DelayMax = 16; // 衝突を抑制するため送信タイミングにブレを作る(最大16ms)
break;
}
#ifdef USE_SLOW_TX
//ここから
if (u8Quick == 0x10) {
sTx.u8Retry = sAppData.u8StandardTxRetry; // 再送回数を3回とする
sTx.u16DelayMax = 100; // 初回送信は送信要求発行時~100ms の間(ランダムで決まる)
sTx.u16RetryDur = 20; // 20ms おきに再送する
}
//ここまで
#endif
// 送信API
if (ToCoNet_bMacTxReq(&sTx)) {
if (sTx.u16DelayMax == 0) {
// すぐに送る場合
ToCoNet_Tx_vProcessQueue();
}
i16Ret = sTx.u8CbId;
sAppData.sIOData_now.u32TxLastTick = u32TickCount_ms;
}
}
return i16Ret;
}
定期送信
ステータス0x81:IO状態の送信 のうち定期送信は
void vProcessEvCorePwr(tsEvent *pEv, teEvent eEvent, uint32 u32evarg) {
のうちの402行付近で処理されている
// レギュラー送信
if (!bCond && (sAppData.u16CtRndCt == 0)) {
if (!IS_APPCONF_OPT_REGULAR_PACKET_NO_TRANSMIT()) {
bCond = TRUE;
bRegular = TRUE;
}
}
// 送信
if (bCond) {
// デバッグ出力
DBGOUT(5,
"A(%02d/%04d)%d%d: v=%04d A1=%04d/%04d A2=%04d/%04d B=%d%d%d%d %08x"LB,
sAppData.u32CtTimer0, u32TickCount_ms & 8191,
sAppData.bUpdatedAdc ? 1 : 0,
sAppData.sIOData_now.u32BtmChanged ? 1 : 0,
sAppData.sIOData_now.u16Volt,
sAppData.sIOData_now.au16InputADC[0],
sAppData.sIOData_now.au16InputPWMDuty[0] >> 2,
sAppData.sIOData_now.au16InputADC[1],
sAppData.sIOData_now.au16InputPWMDuty[1] >> 2,
sAppData.sIOData_now.au8Input[0] & 1,
sAppData.sIOData_now.au8Input[1] & 1,
sAppData.sIOData_now.au8Input[2] & 1,
sAppData.sIOData_now.au8Input[3] & 1,
sAppData.sIOData_now.u32BtmBitmap);
// 低遅延送信が必要かどうかの判定
bool_t bQuick = FALSE;
if (sAppData.sIOData_now.u32BtmChanged
&& (sAppData.sFlash.sData.u32Opt
& E_APPCONF_OPT_LOW_LATENCY_INPUT)) {
bQuick = TX_NODELAY_AND_QUICK_BIT; // 低レイテンシ専用
}
// 送信要求
sAppData.sIOData_now.i16TxCbId = i16TransmitIoData(bQuick, bRegular);
IO状態の送信を任意に実行してみる
以下で送信できる
bool bQuick=TX_NODELAY;
bool bRegular = TRUE;
sAppData.sIOData_now.i16TxCbId = i16TransmitIoData(bQuick, bRegular);
コマンド0x80,コマンド0x01,コマンド0x88:シリアル送信部分
シリアルUARTから入力された電文の送信を行います。
また、I2Cの操作コマンド0x88の返答である、ステータス0x89にも使われています。
/** @ingroup MASTER
* シリアルメッセージの送信要求を行います。1パケットを分割して送信します。
*
* - Packet 構造
* - OCTET : 識別ヘッダ(APP ID より生成)
* - OCTET : プロトコルバージョン(バージョン間干渉しないための識別子)
* - OCTET : 送信元個体識別論理ID
* - BE_DWORD : 送信元シリアル番号
* - OCTET : 送信先シリアル番号
* - OCTET : 送信タイムスタンプ (64fps カウンタの値の下16ビット, 約1000秒で1周する)
* - OCTET : 送信フラグ(リピータ用)
* - OCTET : 要求番号
* - OCTET : パケット数(total)
* - OCTET : パケット番号 (0...total-1)
* - BE_WORD : 本パケットのバッファ位置
* - OCTET : 本パケットのデータ長
*
* @param p ペイロードのデータへのポインタ
* @param u16len ペイロード長
* @param bRelay 中継フラグ TRUE:中継する
* @return -1:失敗, 0:成功
*/
static int16 i16TransmitSerMsg(uint8 *p, uint16 u16len, uint32 u32AddrSrc,
uint8 u8AddrSrc, uint8 u8AddrDst, uint8 u8RelayLv, uint8 u8Req) {
if (IS_APPCONF_ROLE_SILENT_MODE())
return -1;
// パケットを分割して送信する。
tsTxDataApp sTx;
memset(&sTx, 0, sizeof(sTx));
uint8 *q; // for S_??? macros
// 処理中のチェック(処理中なら送信せず失敗)
if (sSerSeqTx.bWaitComplete) {
return -1;
}
// sSerSeqTx は分割パケットの管理構造体
sSerSeqTx.u8IdSender = sAppData.u8AppLogicalId;
sSerSeqTx.u8IdReceiver = u8AddrDst;
sSerSeqTx.u8PktNum = (u16len - 1) / SERCMD_SER_PKTLEN + 1;
sSerSeqTx.u16DataLen = u16len;
sSerSeqTx.u8Seq = sSerSeqTx.u8SeqNext; // パケットのシーケンス番号(アプリでは使用しない)
sSerSeqTx.u8SeqNext = sSerSeqTx.u8Seq + sSerSeqTx.u8PktNum; // 次のシーケンス番号(予め計算しておく)
sSerSeqTx.u8ReqNum = u8Req; // パケットの要求番号(この番号で送信系列を弁別する)
sSerSeqTx.bWaitComplete = TRUE;
sSerSeqTx.u32Tick = u32TickCount_ms;
memset(sSerSeqTx.bPktStatus, 0, sizeof(sSerSeqTx.bPktStatus));
DBGOUT(3, "* >>> Transmit(req=%d) Tick=%d <<<" LB, sSerSeqTx.u8ReqNum,
u32TickCount_ms & 65535);
sTx.u8Cmd = TOCONET_PACKET_CMD_APP_DATA; // data packet.
sTx.u8Retry = sAppData.u8StandardTxRetry;
sTx.u16RetryDur = sSerSeqTx.u8PktNum * 10; // application retry
int i;
for (i = 0; i < sSerSeqTx.u8PktNum; i++) {
q = sTx.auData;
sTx.u8Seq = sSerSeqTx.u8Seq + i;
sTx.u8CbId = sTx.u8Seq; // callback will reported with this ID
// ペイロードを構成
S_OCTET(sAppData.u8AppIdentifier);
S_OCTET(APP_PROTOCOL_VERSION);
S_OCTET(u8AddrSrc); // アプリケーション論理アドレス
S_BE_DWORD(u32AddrSrc); // シリアル番号
S_OCTET(sSerSeqTx.u8IdReceiver); // 宛先
S_BE_WORD(sAppData.u32CtTimer0 & 0xFFFF); // タイムスタンプ
S_OCTET(u8RelayLv); // 中継レベル
S_OCTET(sSerSeqTx.u8ReqNum); // request number
S_OCTET(sSerSeqTx.u8PktNum); // total packets
S_OCTET(i); // packet number
S_BE_WORD(i * SERCMD_SER_PKTLEN); // offset
uint8 u8len_data =
(u16len >= SERCMD_SER_PKTLEN) ? SERCMD_SER_PKTLEN : u16len;
S_OCTET(u8len_data);
memcpy(q, p, u8len_data);
q += u8len_data;
sTx.u8Len = q - sTx.auData;
// あて先など
sTx.u32DstAddr = TOCONET_MAC_ADDR_BROADCAST; // ブロードキャスト
if (sAppData.eNwkMode == E_NWKMODE_MAC_DIRECT) {
sTx.u32SrcAddr = sToCoNet_AppContext.u16ShortAddress;
sTx.bAckReq = FALSE;
sTx.u8Retry = sAppData.u8StandardTxRetry;
ToCoNet_bMacTxReq(&sTx);
}
p += u8len_data;
u16len -= SERCMD_SER_PKTLEN;
}
return 0;
}
任意の電文をコマンド0x01で送信する
以下で送信できる。
tsModbusCmd a;
a.au8data = au8SerBuffTx;
a.u16len=5;
au8SerBuffTx[0]=0;
au8SerBuffTx[1]=1;
au8SerBuffTx[2]=2;
au8SerBuffTx[3]=3;
au8SerBuffTx[4]=4;
vProcessSerialCmd(&a);
これを子機から送信すると、親機で :0001020304F6 と表示される。
シリアルから入力の際はコロン、F6、CR、LFが必要だが、 vProcessSerialCmdに渡すデータには必要ない。
I2Cの応答:ステータス0x89
static void vProcessI2CCommand(uint8 *p, uint16 u16len, uint8 u8AddrSrc) {
内で、シリアル送信を呼び出しています。