TgInitTargetではInitiatorがアクションを起こすまでコマンドレスポンスを返さない

2012-05-16   treby   IDm Snatcher  , このエントリーをはてなブックマークに追加

私がTgInitTargetに関して変なところで詰まっていると猛者さんが助け舟を出してくださった。

それからいろいろ試行錯誤してみて詰まっていた直接の原因はタイトルの通りということが分かった。というか猛者さん、バイナリ見たらコマンド丸分かりですやん。さすが猛者さん、おれたちにできない事を平然とやってのけるッそこにシビれる!あこがれるゥ!

ホストパケットの読み方

猛者さんによるTgInitTargetコマンドまでの道のり

……本題(タイトル)からはちょっと離れるが、猛者さんが提示してくれたファイルを観察してみよう。まずはRC-S620/Sに送ったコマンドが違っていた可能性を疑い「解答例」を見ながら検証することにしたのだ。(もっとも、結果としては送っていたコマンドは間違っていなかったわけであるが)

ちなみにこれらファイルの拡張子.txtとなっているが中身はバイナリである。バイナリエディタでないと中身をみることすらままならないので注意されたい。あと、テキストエディタで開いたときに出てくる警告の「Null文字」とはすなわち「00h」のことである。まあ、どうでもいいか。

それで、実際に開いてみた図が上の画像である。何となく眺めてみても全体的に意味不明だが、コマンドリファレンスの「ホストパケットのフォーマット」の「Normalフレーム」(今回取り上げるパケットは全てNormalフレームである)部分を読んだうえで見ると、以下の構成になっているのが分かる。

  • Preamble(1バイト):00h固定
  • Start Of Packet(2バイト):00h ffh固定
  • LEN(1バイト):データ部の長さ
  • LCS(1バイト):LENのチェックサム
  • データ部(最大256バイト)
  • DCS(1バイト):データ部のチェックサム
  • Postamble(1バイト):00h固定

つまり、複雑そうに見えるホストパケットも読むだけなら最初の5バイトと最後の2バイトは無視して(これらは機械的に求まるため)実際のデータ部だけ確認すればよい。なお、上の画像ではデータ部、すなわちチェックすべき部分を選択した状態にしている。

データ部の最初の2バイトを見ればどのコマンドを実行しているのかが分かる。

  • d4h 32hから始まる …… RFConfiguration コマンド
  • d4h 8chから始まる …… TgInitTarget コマンド

あとはそれぞれのコマンドの説明をたどっていけばよい。

私はこれらの意味を確認し、愚直にArduinoに移植し動作を確認してみた。ところが「ACKが返ってきていない」状況は改善しなかった。

Arduino向けライブラリの実装

実はACKは返ってきていた。それも、どうやら最初に記事を書いた段階からだ。言い換えれば「ACKが返ってこない」としたのは単に私が勘違いしていただけだった。私のいう「ACKが返ってこない」は正確には「RC-S620/S向けライブラリのrwCommandで1が返ってこない」とすべきであった。

問題のrwCommandの実装を見てみよう。

int RCS620S::rwCommand(
    const uint8_t* command,
    uint16_t commandLen,
    uint8_t response[RCS620S_MAX_RW_RESPONSE_LEN],
    uint16_t* responseLen)
{
    int ret;
    uint8_t buf[9];

    flushSerial();

    uint8_t dcs = calcDCS(command, commandLen);

    /* transmit the command */
    buf[0] = 0x00;
    buf[1] = 0x00;
    buf[2] = 0xff;
    if (commandLen <= 255) {
        /* normal frame */
        buf[3] = commandLen;
        buf[4] = (uint8_t)-buf[3];
        writeSerial(buf, 5);
    } else {
        /* extended frame */
        buf[3] = 0xff;
        buf[4] = 0xff;
        buf[5] = (uint8_t)((commandLen >> 8) & 0xff);
        buf[6] = (uint8_t)((commandLen >> 0) & 0xff);
        buf[7] = (uint8_t)-(buf[5] + buf[6]);
        writeSerial(buf, 8);
    }
    writeSerial(command, commandLen);
    buf[0] = dcs;
    buf[1] = 0x00;
    writeSerial(buf, 2);

    /* receive an ACK */
    ret = readSerial(buf, 6);
    if (!ret || (memcmp(buf, "x00x00xffx00xffx00", 6) != 0)) {
        cancel();
        return 0;
    }

    /* receive a response */
    ret = readSerial(buf, 5);
    if (!ret) {
        cancel();
        return 0;
    } else if  (memcmp(buf, "x00x00xff", 3) != 0) {
        return 0;
    }
    if ((buf[3] == 0xff) && (buf[4] == 0xff)) {
        ret = readSerial(buf + 5, 3);
        if (!ret || (((buf[5] + buf[6] + buf[7]) & 0xff) != 0)) {
            return 0;
        }
        *responseLen = (((uint16_t)buf[5] << 8) |
                        ((uint16_t)buf[6] << 0));
    } else {
        if (((buf[3] + buf[4]) & 0xff) != 0) {
            return 0;
        }
        *responseLen = buf[3];
    }
    if (*responseLen > RCS620S_MAX_RW_RESPONSE_LEN) {
        return 0;
    }

    ret = readSerial(response, *responseLen);
    if (!ret) {
        cancel();
        return 0;
    }

    dcs = calcDCS(response, *responseLen);

    ret = readSerial(buf, 2);
    if (!ret || (buf[0] != dcs) || (buf[1] != 0x00)) {
        cancel();
        return 0;
    }

    return 1;
}

注目すべきはreturn文である。確かにACKを受信できない場合も0を返す。しかしコードを読めばその他にも0を返す条件はいろいろあるのが分かるだろう。この部分を私は勘違いしていた。つまり、ACKが返ってこないときのみ0が返ると思い込んでいたのだ。

さて、それが分かると話は早い。私はどのreturn文によって0が返されているのかを調べてみた。

    /* receive a response */
    ret = readSerial(buf, 5);
    if (!ret) {
        cancel();
        return 0;

するとこの部分で引っかかっていることが分かった。この付近はシリアルポートからコマンドレスポンスを取得するコードである。

猛者さんも書かれているように、他のコマンドは、ACKに続けてレスポンスが返ってくるのだが、TgInitTargetはそうではないのだ。その点が理解できていると上に示した部分で0が返るのも納得がいく。