MB (メールボックス)レジスタクラスを一応改修
CAN で、厄介なのは、MB レジスタ。
ここだけは、概念がかなり他と違う、一つのパーテーションが 16 バイトなので、通常のレジスタアクセスクラスではサポート出来ない。
そこで、専用の実装を考えた。
※本来は、直接アクセスしなくても、サポートクラスを定義して、丸ごとコピーすれば良い。
※技術的な興味から実装
- 「DLC」はバイトアクセス
- 「DATA」は 8 バイト分のアクセスを行うクラスを追加
- 「TS」は 16 ビットアクセス
- それ以外は 32 ビットアクセス
- SID、EID をまとめて設定、読出し出来るように関数を追加「get_id()、set_id()」
- 全体を初期化する関数を追加「clear()」
- 全体をコピーする関数を追加「copy()」(他のメールボックスから丸ごとコピーする場合)
- 「=」オペレーターを「private」に(代入禁止)
//-----------------------------------------------------------------//
/*!
@brief メールボックスレジスタ j ( MB[j] )( j = 0 ~ 31 )
*/
//-----------------------------------------------------------------//
struct mb_t {
typedef rw32_index_t<base + 0> io0_;
typedef rw8_index_t <base + 4 + 1> io1_;
typedef rw16_index_t<base + 12 + 2> io3_;
bits_rw_t<io0_, bitpos::B0, 18> EID;
bits_rw_t<io0_, bitpos::B18, 11> SID;
bit_rw_t <io0_, bitpos::B30> RTR;
bit_rw_t <io0_, bitpos::B31> IDE;
bits_rw_t<io1_, bitpos::B0, 4> DLC;
struct data_t {
volatile uint8_t& operator [] (uint32_t n) {
return *reinterpret_cast<volatile uint8_t*>(io0_::address() + 6 + n);
}
};
data_t DATA;
io3_ TS;
void set_index(uint32_t j) {
if(j < 32) {
io0_::index = j * 16;
io1_::index = j * 16;
io3_::index = j * 16;
}
}
void clear(uint32_t d = 0) {
auto a = io0_::address();
wr32_(a, d);
wr32_(a + 4, d);
wr32_(a + 8, d);
wr32_(a + 12, d);
}
void copy(uint32_t idx) {
wr32_(io0_::address() + 0, rd32_(base + idx * 16 + 0));
wr32_(io0_::address() + 4, rd32_(base + idx * 16 + 4));
wr32_(io0_::address() + 8, rd32_(base + idx * 16 + 8));
wr32_(io0_::address() + 12, rd32_(base + idx * 16 + 12));
}
uint32_t get_id() {
return SID() | (EID() << 11);
}
void set_id(uint32_t id) {
SID = id & 0x7ff;
EID = id >> 11;
}
mb_t& operator [] (uint32_t idx) {
set_index(idx);
return *this;
}
private:
void operator = (const mb_t& t) {
};
};
typedef mb_t MB_;
static MB_ MB;
これで、CAN 関係で、気になっていた部分は、納得いく範囲で、抽象的にアクセス出来るようになったと思う。
割り込み関係の整備と設計
本格的な上位層を実装する前に、割り込み関係の動作を確認しようと思う。
また、FIFO の機能をどうするか?
ハードウェアーで持ってる FIFO も、中途半端で微妙だよなぁー・・・
FIFO が4つくらいあっても、1パケット(フレーム)が100マイクロ秒前後なので、激しい通信がある場合は足りないと思う。
結局、ソフトで FIFO を作って、突発的に増えた場合の通信量を吸収するしかない。
経験的に、割り込み関数内でガッツリ実装するのは良くないと判っている。(それしか方法が無い場合もあるけど・・)
大抵、そんな事をしなくても、何とかなるし、割り込み内からコールバックを呼んでも、そこから出来る事は限られる。
※本当はコールバックも呼びたくない、もし、重い処理を実装されたら、終わってしまうから・・
RX64M の CAN では、割り込みは、SELB 選択型割り込みになっていて、4つの割り込みがある。
エラー割り込みは、BE0 グループ割り込みになっている。
typedef can_seli_t<0x00090200, peripheral::CAN0,
ICU::VECTOR_SELB::RXF0, ICU::VECTOR_SELB::TXF0,
ICU::VECTOR_SELB::RXM0, ICU::VECTOR_SELB::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;
RX66T では、エラー割り込みの「型」は同一だが、他は通常割り込みになっている。
typedef can_norm_t<0x00090200, peripheral::CAN0,
ICU::VECTOR::RXF0, ICU::VECTOR::TXF0,
ICU::VECTOR::RXM0, ICU::VECTOR::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;
icu_mgr クラスは、「割り込み型」により異なる関数が定義してあるので、C++ コンパイラが自動的に適切な関数を選ぶ、ドライバー側は「型」を意識する必要が無く、プログラムがシンプルになる。
- #ifdef などで、CPU の違い毎に動作を別ける実装をしなくて済む。
- 実際には、can のレジスタ定義では、CPU 毎の定義で「別けて」いるけど、その上位層では、感知しなくて済む。
- つまり、この仕組みは、新規に CPU が追加されても、下位層でそのサポートを行えば、上位層のコードをケアする必要が無い。
static ICU::VECTOR set_interrupt(ICU::VECTOR vec, utils::TASK task, uint8_t lvl) noexcept;
static ICU::VECTOR set_interrupt(ICU::VECTOR_SELB sel, utils::TASK task, uint8_t lvl) noexcept;
※ちょっと不思議なのは、MIER メールボックス割り込み許可レジスタが、RESET 時不定になっている。
他にも不定になっているレジスタがいくつかある・・・
割り込み関係では、厄介な事がある、割り込みが起動した時、どのメールボックスによる要因なのかが判らない。
一応、調べる機構はあるが、送信と受信がシェアされているので、受信にしか使えない・・
送信は、同時に複数のフレームを送信しないので、それでも良いかもしれない・・
とりあえず、メールボックス 4 を送信専用にして、それを使って送信を行った。
問題無く、送信出来ていて、送信割り込みも機能する事を確認した。
※割り込みを使わない場合は物理的に難しいので、割り込みを使う事が前提となる。
CAN での受信は、色々と考える事が多い:
- 固定 ID で来るデータを周期的に観るだけの場合。
- 固定 ID で来るデータ全てに応答する場合。
- どんな ID が来ているかを集計したい場合。(アナライザ的な機能)
- ID 集計後に、無視する ID、観測する ID、応答する ID を決定する場合。
- ID に個別の処理(コールバック)を設定する場合。
アプリケーションを作る場合、柔軟な構成にしたいので、上記の事を考えた設計をしたいが、RX マイコンの CAN 機能だけでは、難しい・・・
ID の範囲が「標準」のみなら、2048個なので、ルックアップするのは現実的だが、「拡張 ID」だと29ビットあるので、少し難しい。
STL の場合、「std::set]、「std::unorderd_set」を使えば、簡単に出来るが、内部で記憶割り当てを使うので、メモリの少ないマイコンの場合は使いたくないのも事実・・・
さてどうするか?
CAN 動作モードの切り替え(注意)
// CAN オペレーションモードに移行
uint8_t idfm = 0b10; // 標準 ID モード、拡張 ID モード、ミックス
uint8_t bom = 0b00; // ISO 11898-1 規格、バスオフ復帰モード
CAN::CTLR = CAN::CTLR.CANM.b(0b00) | CAN::CTLR.SLPM.b(0) | CAN::CTLR.IDFM.b(idfm)
| CAN::CTLR.BOM.b(bom);
// CAN オペレーションモードに移行するまで待機
while(CAN::STR.RSTST() != 0 || CAN::STR.HLTST() != 0) {
sleep_();
}
上記のように、CAN の動作モードを切り替えると、実際にモードが切り替わるまで遅延があるようだ。
オペレーションモードに移行した事を確認してから、次の動作を行う必要がある。
※そうしないと、制御レジスターの書き換えが失敗するなどの問題がある。
※一応、それらしい事が書いてあるが、レジスタの説明に書くべきだろうと思う。
MCTL レジスタの書き換えには注意
ハードウェアーマニュアルに書いてあるが、ビットを OFF にするには、色々と制約と順番などがある。
これは、注意を要する。
RX マイコンの CAN 関係の実装は、落とし穴が沢山あって、簡単では無いなぁーと感じる・・・
要は、あまり完成度が高く無いと感じる、だから RX24T で新しい CAN (RSCAN) になったのかもしれない。
そんな事を言っても仕方無いので、少しづつ機能を確認しつつ追加して、何とか完成させるしかない。
受信割り込みの確認
結局、SCI と同じように FIFO を使って受信動作を実装した。
なので、現状では、ID をマスクしたり、監視する機能は、全てソフトで行う必要がある。
パフォーマンスは犠牲になるが、当面はそれで良い、柔軟性が重要だ、パフォーマンスに問題が起こったら、マスクフィルタの設定などを追加すれば良い。
Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
RX Interrupt level: 1, TX Interrupt level: 1
# send 0x100 0 1 2 3 4 5
#
CAN1:
ID: std 0x100 (256)
DATA(6): 00 01 02 03 04 05
TS: 51713
# ch 1
# send 0x200 10 20 30 99 0x7f
#
CAN0:
ID: std 0x200 (512)
DATA(5): 0A 14 1E 63 7F
TS: 43785
# ch 0
# send 0x450 0
#
CAN1:
ID: std 0x450 (1104)
DATA(1): 00
TS: 12821
#
※ CAN0、CAN1 の送信、受信の確認
今回はここまで。
ソースコードは、github RX/CAN_sample にある。