2018年3月1日木曜日

CANのフィルタ

 STM32のCANのフィルタがやっと理解できたので、メモしておきます。
 昔、フィルタの動作が理解できなくてちょっとトラウマ気味でした。

 なお、CANのフィルタにはマスクモードとリストモードがありますが、今回はマスクモードだけを試しました。


 さて、CANのフィルタですが、「何に対してのフィルタなのか」というのが重要です。
 結論から言うと、リファレンスマニュアルで「CAN受信FIFOメールボックス識別子レジスタ(CAN_RIxR)」と呼ばれるレジスタに対してのフィルタになります。決して、メッセージIDに直接フィルタリングを行っているわけではありません(理解してからリファレンスマニュアルを読んでみると、そのように読めるのですが、知らずに読むと難解でした)。
 このレジスタは32bit幅で、bit0が予約、bit1がRTR、bit2がIDE、bit3-15がExId0-12、bit16-20がExId13-17、bit21-31がExId18-28/StId0-10が割り当てられています。


 一方で、CAN_FilterConfTypeDefにはFilterIdHigh, FilterIdLow, FilterMaskIdHigh, FilterMaskIdLowという、32bitのレジスタが4本ありますが、この4本はすべて下位16bitのみ使用されます。
 IdLowはRIxRの下位16bitに割り当てられ、IdHighはRIxRの上位16bitに割り当てられ、それぞれのIdとMaskでビット演算を行い、その結果に応じてFIFOに入れる、という動作になります。具体的には、それぞれ32bitに結合したIdとMaskがあるとして、RIxR & Mask == Id & Maskが真ならFIFOに入れる、という動作です。


 とりあえず動作確認のために以下のようなコマンドを追加して確認しました。

if (!strncmp(str, "filt ", 5))
{
    int flag = 1;

    uint32_t FIFO_number = 0;
    uint32_t filter_number = 0;
    uint32_t mask_low = 0;
    uint32_t mask_high = 0;
    uint32_t id_low = 0;
    uint32_t id_high = 0;

    if (flag)
    {
        flag = 6 == sscanf(str + 5, "%lu %lu %lu %lu %lu %lu", &FIFO_number, &filter_number, &id_low, &id_high, &mask_low,
                            &mask_high);
    }

    if (flag)
    {
        CAN_FilterConfTypeDef filter = { 0 };

        filter.FilterIdHigh = id_high;
        filter.FilterIdLow = id_low;
        filter.FilterMaskIdHigh = mask_high;
        filter.FilterMaskIdLow = mask_low;
        filter.FilterFIFOAssignment = FIFO_number;
        filter.FilterNumber = filter_number;
        filter.FilterMode = CAN_FILTERMODE_IDMASK;
        filter.FilterScale = CAN_FILTERSCALE_32BIT;
        filter.FilterActivation = ENABLE;

        flag = HAL_OK == HAL_CAN_ConfigFilter(&hcan1, &filter);
    }

    printf("%s\n", flag ? "ok" : "error");
}

 filtにつづいて6個の引数を渡して、それをフィルタに設定する、というものです(使っていて思ったけど、こういうときの引数は10進より16進のほうが楽)。

filt 0 0 2 0 2 0 リモートフレームだけを受信する
filt 0 0 0 0 2 0 データフレームだけを受信する
filt 0 0 4 0 4 0 拡張フレームだけを受信する
filt 0 0 0 0 4 0 標準フレームだけを受信する
filt 0 0 0 32 4 65504 標準フレームでIdが1のフレームだけを受信する
filt 0 0 4 0 4 61440 拡張フレームで上位4bitがドミナントのフレームだけを受信する

 とりあえず、引数と結果はこのような感じになります。
 例えば、RTRだけをマスクし、RTRが0という条件にすればデータフレームのみを受信しますし、IDEだけをマスクし、IDEが1という条件にすれば拡張フレームだけを受信します。
 一番最後の例は拡張フレームの上位4bitがドミナント、という条件ですが、CANバスはその特性上、上位ビットの連続ドミナントが長いほど優先度が高く判定されますから、例えばメッセージの重要度を上位4bitで表現し、15なら最低優先度、0なら最高優先度、として、優先度0(最優先)のメッセージだけをFIFO0に入れ、その他はFIFO1に入れる、とすれば、最優先のメッセージを取り逃がす危険性が減る、みたいな用途に使えるはずです。


 フィルタには32bitモードと16bitモードがありますが、16bitモードの時は拡張IDの一部をチェックできません。試していませんが、16bit時はIDE/RTRと標準IDのすべてのビット、および拡張IDの上位14bit分をチェックできるようです。


 ちなみに、STM32のCANはフィルタで弾いたメッセージでも、ACKの送信を行うようです。ACKはCRCの後なので、IDが一致しないならACKを返さない、といった動作もできるはずですが、そもそもCANのACKはバスの健全性を確認するための手段なので、メッセージの中身には関係なくACKを送信する、という動作なのだと思います。


 ということで、だいぶ前にかなり悩みこんだフィルタの動作がわかって、ちょっとスッキリしました。

 とはいえ、HALのCANの割り込み処理はとても不思議な実装で、次はこれを使いこなす方法を考える必要があります。割り込みハンドラの中で以降の割り込みをDISABLEしていたり、受信先のFIFOがわからなかったり、想像を絶する感じです。


 CANバスは本来CANドライバICが必要ですが、極めて短距離の場合は、TXをOpenDrainで駆動し、1.5kくらいでプルアップして、すべてのデバイスのTXとRXを短絡させれば、ちゃんとCANバスとして使えます。250kbaudで全長30cm程度なら立ち上がり時間も問題無いようです。低コスト(&低放射ノイズ)な小型ロボットで3-5個程度のマイコンを接続したい、という用途ならこの程度でも十分かもしれません。I2Cのバスと似たような構成なので、信頼性もその程度で、せいぜい50cm未満が限界でしょうが。

0 件のコメント:

コメントを投稿