fibril_can CAN ルータ (uplink 1 + downlink N)
uplink CAN 1 本と downlink CAN N 本を束ねる同梱ランタイム。bridge は単一バス前提のまま動く。std_id → busmask で分配、node → bus で単一バス転送
要点: fibril_can_runtime には uplink 1 + downlink N を束ねるルータが同梱される。FCAN_ROUTER_ENABLE でガードされて通常スレーブビルドでは .text も RAM も増えない。master からは「1 本のバス」として透過的に見え、fibril_can_bridge は単一バス前提のまま動く。
デプロイ形態 3 択
| 形態 | FCAN_ROUTER_ENABLE | fcan_router_attach_self |
|---|---|---|
| 通常スレーブ | 0 (既定) | ルータコード自体なし |
| 透過 router | 1 | NULL (自分の node_id なし、中継専用) |
| ノード兼 router | 1 | 初期化済み fcan_node_t * (自分もスレーブとして動く) |
有効化
cmake -DFCAN_ROUTER_ENABLE=ON ...FCAN_ROUTER_MAX_BUSES (既定 8) でマスク型幅がコンパイル時決定:
| MAX_BUSES | mask 型 | 最大 downlink 数 |
|---|---|---|
| ≤ 8 | uint8_t | 6 |
| ≤ 16 | uint16_t | 14 |
| ≤ 32 | uint32_t | 30 |
分配の中核ロジック
分配先ビット = stdid_to_busmask[std_id] & ~source_busmaster が「downlink A の publisher (S2M)」と「downlink B の subscriber (M2S)」に同じ std_can_id を FRAME_DEFINE すると、内部 busmask は UPLINK | A | B の 3 ビット。A から来たフレームは & ~A = UPLINK | B に自動分配。bridge は単一バス前提で同じ ID を振るだけ、cross-downlink Topic 中継はルータが勝手に成立させる。
バス識別子
| bus | 意味 |
|---|---|
FCAN_ROUTER_BUS_UPLINK (0xFE センチネル) | uplink |
0 .. downlink_count-1 | 各 downlink の生インデックス |
| self | 内部擬似バス。caller から名指ししない (uplink 経由の ext_id.node_id で自動判別) |
初期化 (self ノード使う場合、3 段)
// 1) ルータ本体
fcan_router_config_t rcfg = {
.uplink_hal = uplink_hal,
.downlink_hals = downlink_hals,
.limits = {
.downlink_count = 2,
.max_stdids = 1024,
.max_node_ids = 128,
.pending_fd_slots = 4,
},
.allocator = my_allocator,
};
fcan_router_init(router, &rcfg);
// 2) self 用ラッパ HAL を取って fcan_config_t に詰める
fcan_config_t ncfg = make_self_config();
ncfg.hal = fcan_router_get_self_hal(router);
fcan_init(self_node, &ncfg);
// 3) self を attach
fcan_router_attach_self(router, self_node);fcan_router_get_self_hal を init 前に呼ぶと全 NULL の HAL が返る (誤用検知)。self を後から detach するには fcan_router_attach_self(router, NULL) で透過モードに戻せる。
動作中 API
void fcan_router_on_rx(router, bus, id, ext, data, len); // ISR / RX スレッド
void fcan_router_poll(router); // メインループ
void fcan_router_get_diag(router, &diag); // 任意poll は self が attach されていれば fcan_poll(self) も駆動する。
学習ルール (永続化なし)
- node_to_bus: downlink から届く HEARTBEAT / ANNOUNCE で学習
- 同
node_idが異なる downlink から異なるboot_idで観測 → cross-bus 衝突として先着優先、後発は転送抑制 - 同
boot_idの再観測 (ループバック等) は良性扱いで最初の割当保持
- 同
- stdid_to_busmask: FRAME_DEFINE の payload を peek し、対象 slave の ACK が届いた瞬間にビットを立てる。NAK は学習しない
- DISCOVER 受信で
stdid_to_busmaskは全クリア (SPEC §5.11 の slave 側挙動と同期) - pending FRAME_DEFINE: ACK 待ちの一時表、既定 4 スロット。溢れると diag に計上
診断カウンタ (monotonic)
fwd_up_to_down[k] fwd_down_to_up[k] fwd_cross_down[k]
drop_unknown_stdid drop_unknown_node drop_cross_bus_conflict
drop_table_overflow drop_send_failed
learned_nodes learned_stdidsルータ自身は schema を持たないので、診断を Topic で吐きたければ self ノードの通常 Topic で fcan_router_get_diag の結果を publish する。
既知の制約
- Service は単一バス転送のみ: downlink 間サービスは master 経由でラップ
rx_queue_depth経由の ISR ファストパス未実装: 同期転送のみ- self の S→M Topic は uplink にしか流れない: downlink にも届けたいなら master 側 bridge の FRAME_DEFINE 構成で表現