CAN の実験で気がついた
GR-CITRUS はピンの制限が非道なのだが、CAN0 で利用するピン CRX0(P33), CTX0(P32) はとりあえず使える。
そこで、CAN_sample を動かしてみようと思い、プロジェクトに RX631 を追加してコンパイル。
とりあえず、ターミナル表示を確認した。
Start CAN sample for 'RX631 GR-CITRUS' 96[MHz]
CAN command version: 0.89
CAN0: SPEED: 1000000 [bps], BRP: 2, TSEG1: 15, TSEG2: 8, SJW: 4
RX Interrupt level: 1, TX Interrupt level: 0
#
何故か、TX の割り込みレベルが0と表示されている・・
can_io クラスは、ポーリングでの動作は出来ない、必ず、1以上の割り込みレベルが必須となっていて、何も指定しないと、レベル1割り込みを設定する。
おかしいな・・、色々調べて、icu.hpp の CAN 関係の割り込みベクターに間違いがあった・・・
それを直して、もう一度。
Start CAN sample for 'RX631 GR-CITRUS' 96[MHz]
CAN command version: 0.89
CAN0: SPEED: 1000000 [bps], BRP: 2, TSEG1: 15, TSEG2: 8, SJW: 4
RX Interrupt level: 0, TX Interrupt level: 0
#
今度は、両方「0」に・・・
調べると、RX631/RX63N の割り込みレベル設定(IPR レジスタ)は、かなりの部分が共有されており、この部分のケアが抜けていた。
RX621、RX62N や、RX63T、RX24T などでも同じだが、そちらはケアされていた。
RX621/RX62N に比べると、規則性があるが、全般に渡ってかなりの分量だったが、何とか修正した。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief IPR レジスタ・クラス @n
全て、下位4ビットが有効
@param[in] base ベースアドレス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t base>
struct ipr_t {
//-------------------------------------------------------------//
/*!
@brief []オペレータ
@param[in] vec 標準割り込みベクター型
@return IPR レジスターの参照
*/
//-------------------------------------------------------------//
volatile uint8_t& operator [] (VECTOR vec) {
uint32_t idx = 0;
switch(vec) {
case VECTOR::BUSERR: idx = 0; break;
case VECTOR::FIFERR: idx = 1; break;
case VECTOR::FRDYI: idx = 2; break;
case VECTOR::SWINT: idx = 3; break;
case VECTOR::CMI0: idx = 4; break;
case VECTOR::CMI1: idx = 5; break;
case VECTOR::CMI2: idx = 6; break;
case VECTOR::CMI3: idx = 7; break;
case VECTOR::SPRI0:
case VECTOR::SPTI0:
case VECTOR::SPII0:
idx = static_cast<uint32_t>(VECTOR::SPRI0);
break;
case VECTOR::SPRI1:
case VECTOR::SPTI1:
case VECTOR::SPII1:
idx = static_cast<uint32_t>(VECTOR::SPRI1);
break;
case VECTOR::SPRI2:
case VECTOR::SPTI2:
case VECTOR::SPII2:
idx = static_cast<uint32_t>(VECTOR::SPRI2);
break;
case VECTOR::RXF0:
case VECTOR::TXF0:
case VECTOR::RXM0:
case VECTOR::TXM0:
idx = static_cast<uint32_t>(VECTOR::RXF0);
break;
case VECTOR::RXF1:
case VECTOR::TXF1:
case VECTOR::RXM1:
case VECTOR::TXM1:
idx = static_cast<uint32_t>(VECTOR::RXF1);
break;
...
default: idx = static_cast<uint32_t>(vec); break;
}
return *reinterpret_cast<volatile uint8_t*>(base + idx);
}
};
typedef ipr_t<0x0008'7300> IPR_;
static IPR_ IPR;
※最近の RX マイコンは、ほぼ、割り込み要因毎に割り込みレベルが設定出来る仕様になっているのでスルーしていた。
※古い、RxV1 コアなどのマイコンは注意しなければならない・・
未定義ビットの扱い
システムクロックコントロールレジスタ(SCKCR)の予約ビットの扱いを見直した。
このレジスタ、B0~B7 には書き込む場合に特定のビットを立てなくてはならない。
素のままだと、特定のビットだけを変更する場合に問題が発生する。
device::system::SCKCR.ICK = 10;
このような実装では、最終的に SCKCR レジスタ、B0~B7 には、読み出した値(全て0)が上書きされる。
そこで、以下のように、書き込み時に、特定のビットフィールドを追加するように改修した。
//-----------------------------------------------------------------//
/*!
@brief システムクロックコントロールレジスタ(SCKCR)
@param[in] base ベースアドレス
*/
//-----------------------------------------------------------------//
template <uint32_t base>
struct sckcr_t : public rw32_t<base, 0b0001'0001> {
typedef rw32_t<base, 0b0001'0001> io_;
using io_::operator =;
using io_::operator ();
using io_::operator |=;
using io_::operator &=;
bits_rw_t<io_, bitpos::B8, 4> PCKB;
bits_rw_t<io_, bitpos::B12, 4> PCKA;
bits_rw_t<io_, bitpos::B16, 4> BCK;
bit_rw_t <io_, bitpos::B22> PSTOP0;
bit_rw_t <io_, bitpos::B23> PSTOP1;
bits_rw_t<io_, bitpos::B24, 4> ICK;
bits_rw_t<io_, bitpos::B28, 4> FCK;
};
typedef sckcr_t<0x0008'0020> SCKCR_;
static SCKCR_ SCKCR;
rw32_t クラスは、以下のように変更。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief Read/Write 32 bits アクセス・テンプレート
@param[in] adr アドレス
@param[in] wov 書き込み OR 値(通常0)
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <address_type adr, uint32_t wov = 0>
struct rw32_t {
typedef uint32_t value_type;
static constexpr auto address = adr; ///< アドレス定義
static constexpr auto write_or_value = wov; ///< 書き込み OR 値
static constexpr uint8_t BUS = 32; ///< バス幅
static constexpr bool RD = true; ///< 読出し
static constexpr bool WR = true; ///< 書き込み
//-----------------------------------------------------------------//
/*!
@brief 書き込み
@param[in] data 書き込み値
*/
//-----------------------------------------------------------------//
static void write(value_type data) noexcept { wr32_(adr, data | wov); }
...
};
テンプレートパラメーター、wov は、何も指定しない場合、「0」なので、影響を与えない。
書き込み時に「0」を OR するのが余計だと思うかもしれないが、最適化されると、その操作は削除されてて無くなるので通常動作で余分なマシンサイクルを消費しない。
最適化しない場合、余分なコードが大量に生成されてしまうので、専用関数とするべきかもしれない・・
まとめ
実装時、かなり注意しているが、間違いを避ける事は難しい、集中している時は、それも少ないとは言え、何かの別作業が間に入ったりすると、凡ミスも多くなる。
少し前から、ペリフェラルの定義を検証する為の仕組みを入れ始めている。
重要な部分はユニットテストなどを行うしかないが、テストコードを生成する、良い方法を考えないと現実的では無いとも思う。
これは、すこし考えたい・・
ハードウェアーマニュアルから必要な部分を抽出して、テストコードを自動生成するような事が必要かもしれない・・