ArduinoからMIL-STD-1553っぽい信号を出して遊ぶ

## まえがき

　素人なので温かい目でお願いします。

## MIL-STD-1553とは?

* おおむねF-16以降の時代の西側の軍用機で多く使用されているデータリンク
    * フライ・バイ・ワイヤいうやつ(飛ばすだけじゃなくて、もっといろいろ使える)
    * 初期のF-15は別のデータバスを使っていた(H009)
    * 最近(F-22やF-35)は別のデータバスを使っている(FireWire)
    * 中国やロシアは、自国の軍用機で対応して「西側のミサイルを撃てるよ」とか、自国のミサイルで対応して「西側の飛行機から撃てるよ」とか言って売ろうとしているらしい[要出典]
* 人工衛星でもよく使われている
    * 転送速度がボトルネックになるので、データが大容量化してきた最近は別のバスを採用する傾向(SpaceWire)
* 公称の通信速度は1Mbps
    * マンチェスタ符号なので2Mbaud
    * ヘッダ等を除いたデータレートは0.8Mbps
    * パケットの区切りに適当な待ち時間が必要なので実際の通信レートは更に低い
    * 1本のバスに1つのコントローラと、最大31個のターミナルが接続される
        * コントローラは必要に応じて冗長系が組まれる
        * バス自体も冗長化して使われる
    * コントローラ対ターミナルで通信を行う
        * コントローラがコマンドを送り、ターミナルがレスポンスを返す
        * ターミナル対ターミナルも可能だがオーバーヘッドが増える
        * コントローラをラウンドロビン方式に回すことも可能だが、信頼性が悪いのであまり使われない?
        * 通信はすべてコントローラが制御する(衝突が発生しない)
            * コントローラが故障すると一切の通信が途切れる
            * 冗長化したバスで片側が故障しても通信を続けるとか、コントローラを監視して異常が検出されたら予備を立ち上げるとか
    * ターミナル毎に最大30個(or15個)のサブアドレスを持つ
        * パケットは基本的にサブアドレスに対する書き込みor読み込みという形式
        * サブアドレスの読み書き単位は2-64オクテット(2オクテット単位)
    * ターミナルの数が足りない場合はツリー状に階層が組まれる
    * ターミナルに対して補助的なコマンドを送ることもできる
* 今回はタイミングだけ扱う
    * 送信はArduinoでギリギリどうにかなる、受信は……たぶん無理
    * 電圧とか電線みたいな下位層や、データフォーマットみたいな上位層は扱わない

## ライブラリ

　今回は秋月のPro Mini互換ボード(K-10347)を使用した。ATmega328@16MHz専用のライブラリになる。ビルドオプションとかコンパイラバージョンによっては正常なタイミングが得られない可能性もある。
　このボードはセラミック発振子が載っているので、個体差や環境によっては誤差が大きすぎる可能性もある。
　あくまで、遊びの範疇において動いている気がする、というレベル。

　あと、ステータスワードは実装が面倒なので手抜き。

　8bitのポートに直接出力するので、他の端子の都合とかは一切気にしない。割り込みも止める。

```c++
template <uint16_t buffer_size>
class MILSTD1553
{
public:
  static constexpr bool RX = false;
  static constexpr bool TX = true;
  
  MILSTD1553(volatile uint8_t*const port,
             const uint8_t hot,
             const uint8_t cold,
             const uint8_t reset)
   : port(port), hot(hot), cold(cold), reset(reset), counter() 
   {}

  void send(unsigned int us = 5)
  {
    buff[counter++] = reset;

    const uint8_t*p = buff;
    uint16_t i = counter;
    counter = 0;

    volatile uint8_t*const q = port;

    noInterrupts();
    do {
      *q = *p++;
    } while (--i);
    interrupts();

    delayMicroseconds(us);
  }

  bool command_word(const uint8_t address,
                    const bool transmit,
                    const uint8_t subaddress,
                    const uint8_t datacount)
  {
    return data_word(
      (address & 0x1F) << 11 |
      (transmit ? 0x400 : 0) |
      (subaddress & 0x1F) << 5 |
      (datacount & 0x1F),
      true);
  }

  bool status_word(const uint8_t address, const uint16_t bitfield)
  {
    return data_word(
      (address & 0x1F) << 11 |
      (bitfield & 0x3FFF),
      true);
  }

  bool data_word(const uint16_t data, const bool sync_inverse = false)
  {
    if (buffer_size <= counter + 40 + 1)
    {
      return false;
    }

    uint16_t*const p = reinterpret_cast<uint16_t*>(&buff[counter]);
    counter += 40;

    const uint16_t a = cold | hot  << 8;
    const uint16_t b = hot  | cold << 8;
    const uint16_t c = cold | cold << 8;
    const uint16_t d = hot  | hot  << 8;

    if (!sync_inverse)
    {
      p[0] = c; p[1] = a; p[2] = d;
    } else {
      p[0] = d; p[1] = b; p[2] = c;
    }

    p[ 3] = data & 0x8000 ? b : a;
    p[ 4] = data & 0x4000 ? b : a;
    p[ 5] = data & 0x2000 ? b : a;
    p[ 6] = data & 0x1000 ? b : a;
    p[ 7] = data & 0x0800 ? b : a;
    p[ 8] = data & 0x0400 ? b : a;
    p[ 9] = data & 0x0200 ? b : a;
    p[10] = data & 0x0100 ? b : a;
    p[11] = data & 0x0080 ? b : a;
    p[12] = data & 0x0040 ? b : a;
    p[13] = data & 0x0020 ? b : a;
    p[14] = data & 0x0010 ? b : a;
    p[15] = data & 0x0008 ? b : a;
    p[16] = data & 0x0004 ? b : a;
    p[17] = data & 0x0002 ? b : a;
    p[18] = data & 0x0001 ? b : a;

    p[19] = __builtin_popcount(data) & 1 ? a : b;

    return true;
  }

private:
  volatile uint8_t*const port;
  const uint8_t hot;
  const uint8_t cold;
  const uint8_t reset;
  uint8_t buff[buffer_size];
  uint16_t counter;
};

```

## 使用例

```c++
using My_MILSTD1553 = MILSTD1553<40 * 10 + 1>;

My_MILSTD1553 ms1553_A(&PORTD, 0x04, 0x08, 0);
My_MILSTD1553 ms1553_B(&PORTD, 0x04, 0x08, 0);
My_MILSTD1553 ms1553_C(&PORTD, 0x04, 0x08, 0);

void setup() {
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
}

void loop() {
  delay(47);

  { // BC to RT
    ms1553_A.command_word(3, My_MILSTD1553::RX, 1, 1);
    ms1553_A.data_word(0x1234);

    ms1553_B.status_word(3, 0);

    ms1553_A.send();
    ms1553_B.send();
  }
  { // RT to BC
    ms1553_A.command_word(3, My_MILSTD1553::TX, 1, 1);

    ms1553_B.status_word(3, 0);
    ms1553_B.data_word(0x1234);

    ms1553_A.send();
    ms1553_B.send();
  }
  { // RT to RT
    ms1553_A.command_word(3, My_MILSTD1553::RX, 1, 1);
    ms1553_A.command_word(4, My_MILSTD1553::TX, 1, 1);

    ms1553_B.status_word(4, 0);
    ms1553_B.data_word(0x1234);

    ms1553_C.status_word(3, 0);
    
    ms1553_A.send();
    ms1553_B.send();
    ms1553_C.send();
  }
  { // BC to RT(s)
    ms1553_A.command_word(31, My_MILSTD1553::RX, 1, 1);
    ms1553_A.data_word(0x1234);

    ms1553_A.send();
  }
  { // RT to RT(s)
    ms1553_A.command_word(31, My_MILSTD1553::RX, 1, 1);
    ms1553_A.command_word(4, My_MILSTD1553::TX, 1, 1);

    ms1553_B.status_word(4, 0);
    ms1553_B.data_word(0x1234);

    ms1553_A.send();
    ms1553_B.send();
  }
  { // BC to RT(s) mode without data
    ms1553_A.command_word(31, My_MILSTD1553::RX, 0, 1);

    ms1553_A.send();
  }
  { // BC to RT(s) mode with data
    ms1553_A.command_word(31, My_MILSTD1553::RX, 0, 17);
    ms1553_A.data_word(0x1234);

    ms1553_A.send();
  }
  {
    ms1553_A.command_word(3, My_MILSTD1553::RX, 1, 2);
    ms1553_A.data_word(0);
    ms1553_A.data_word(-1);

    ms1553_B.status_word(3, 0);

    ms1553_A.send();
    ms1553_B.send();
  }
}
```

　ブロードキャストやモードコマンドも含めて一通り。

***

　オシロで見るとこんな感じ。

![20221223T083001 MIL-STD-1553 ATmega328-16MHz - コピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264452/0f9d7de3-de3f-20a0-c7e4-2084b7fb72dc.png)

　パラレルバスから出力しているので、平衡で出力してみた。

***

　ZEROPLUSのLAP-CはMIL-STD-1553バスのプロトコル解析にも対応している。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264452/a258966a-2aff-f775-0557-2c7b252c8dd6.png)

　ただしmode command without data wordがフレームエラーになり、MSBが1のデータワードも正常に処理できない(バグ?)。

***

　感覚としては、1Mbpsはちょっと早いけど、出力側から見た通信プロトコルはかなりシンプル。例えばCANバスみたいにビットスタッフィングやCRCは必要ない。UARTよりちょっと難しい程度。ただし通信速度故に受信側を作る難易度が高く、プロトコルに則った通信を行うのは困難だと思う。