LoginSignup
0
0

More than 3 years have passed since last update.

TWILITE の「超簡単!標準アプリ(App_Twelite)」の 送信処理を調べる

Last updated at Posted at 2020-04-09

「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) {
内で、シリアル送信を呼び出しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0