まえがき
素人なので温かい目でお願いします。
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のポートに直接出力するので、他の端子の都合とかは一切気にしない。割り込みも止める。
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;
};
使用例
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();
}
}
ブロードキャストやモードコマンドも含めて一通り。
オシロで見るとこんな感じ。
パラレルバスから出力しているので、平衡で出力してみた。
ZEROPLUSのLAP-CはMIL-STD-1553バスのプロトコル解析にも対応している。
ただしmode command without data wordがフレームエラーになり、MSBが1のデータワードも正常に処理できない(バグ?)。
感覚としては、1Mbpsはちょっと早いけど、出力側から見た通信プロトコルはかなりシンプル。例えばCANバスみたいにビットスタッフィングやCRCは必要ない。UARTよりちょっと難しい程度。ただし通信速度故に受信側を作る難易度が高く、プロトコルに則った通信を行うのは困難だと思う。