RX66T関係を追加する過程で、RX関連ソースコードを整理した。
今まで、RX24Tに始まり、RX64M、RX71M、RX65Nとサポートデバイスを広げてきたが、RX66Tを加えるにあたり、重複しているファイルや、醜い部分を整理した。
基本はRX64Mとなっている。
・RX64M、RX71Mはほぼ同じ
・RX651/RX65Nは、微妙に異なる
・RX24Tは別品種(割り込み関係が異なる)
・RX66Tは、RX24TとRX64M系の中間的
こんな感じだが、デバイスのレジスター関係は、全てにおいて共通部分が多く、共有出来る。
異なるのは、デバイス(自分のフレームワークでは、ペリフェラルと呼んでいる)の有無。
RX64M、RX71Mは非常に沢山のペリフェラルを持っているが、RX24Tなどは、それに比べて少ないが、中でも「割り込み」ベクターが大きく異なる。
これをどのように表現するかが大きな課題となっていたが、とりあえず、テンプレートにしておけば何とでもなる事が判った。
また、今までは、似ているけど少し異なるような場合は、「#if」などで、分けて凌いでいたが、基本的に各デバイスで、ファイルを分けて対応する事にした。
懸念事項としては、同じコードを全てのデバイス専用ファイルにコピーする必要があり、この場合、一つの修正が、他のデバイスのソースも同じように修正する必要が出てくる・・
一つのファイルで共有して「#if」で細かく分ける書き方だと、かなりトリッキーで読みにくくなる、どちらが良いかは、ケースバイケースで何とも言えないが、保守より読みやすさを優先した。
ペリフェラル:
各デバイスには、「peripheral.hpp」があり、このファイル内で、「enum class」により、「peripheral」型で、利用できるペリフェラルが列挙してある。
この「列挙型」の違いにより、ポートの設定、消費電力設定、割り込み制御など、大まかな動作を分けている、ペリフェラルを直接叩くドライバーは、たとえば割り込みベクター型の違いを考慮する必要が無いように実装してある。
RX24Tのペリフェラルでは、割り込みベクターは、通常ベクターに全て割り当てられているが、RX64Mなどペリフェラルが多いデバイスでは、通常ベクターの数が足りずに、グループベクター、選択型ベクターを新規に設け、それらに割り当てるように工夫されている為、そのままでは同じように扱う事が出来ない。
この違いを吸収して、デバイスドライバーを共通化する為、考えた結果、以下のような実装を行う事でかなりスマートに解決出来た。
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief CMT I/O クラス
@param[in] CMT チャネルクラス
@param[in] TASK タイマー動作クラス
*/ //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class CMT, class TASK = utils::null_task>
class cmt_io {
...
};
上記はCMTの制御クラスの型だが、「CMT」は、CMTペリフェラルクラスで、CMTの制御レジスターを定義したものになっている、通常CMTは0~3まで4チャネルある。
RX64M、RX71M、RX65Nでは、CMT0、CMT1の割り込みは「通常」ベクターだが、CMT2、CMT3は「選択型」ベクターとなっていて、割り込みの設定手順が異なる。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief CMT 定義基底クラス
@param[in] base ベース・アドレス
@param[in] per ペリフェラル
@param[in] INT 割り込みベクター型
@param[in] ivec 割り込みベクター
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <uint32_t base, peripheral per, typename INT, INT ivec>
struct cmt_t {
...
};
#if defined(SIG_RX24T) || defined(SIG_RX66T)
typedef cmt_t<0x00088002, peripheral::CMT0, ICU::VECTOR, ICU::VECTOR::CMI0> CMT0;
typedef cmt_t<0x00088008, peripheral::CMT1, ICU::VECTOR, ICU::VECTOR::CMI1> CMT1;
typedef cmt_t<0x00088012, peripheral::CMT2, ICU::VECTOR, ICU::VECTOR::CMI2> CMT2;
typedef cmt_t<0x00088018, peripheral::CMT3, ICU::VECTOR, ICU::VECTOR::CMI3> CMT3;
#elif defined(SIG_RX64M) || defined(SIG_RX71M) || defined(SIG_RX65N)
typedef cmt_t<0x00088002, peripheral::CMT0, ICU::VECTOR, ICU::VECTOR::CMI0> CMT0;
typedef cmt_t<0x00088008, peripheral::CMT1, ICU::VECTOR, ICU::VECTOR::CMI1> CMT1;
typedef cmt_t<0x00088012, peripheral::CMT2, ICU::VECTOR_SELB, ICU::VECTOR_SELB::CMI2> CMT2;
typedef cmt_t<0x00088018, peripheral::CMT3, ICU::VECTOR_SELB, ICU::VECTOR_SELB::CMI3> CMT3;
#endif
上記のように、テンプレートパラメータで「INT」(割り込みベクター型)を設ける事で、cmt_t クラスは、どの型のベクターなのかを意識しなくて済むようになっていて、「typedef」だけで対応できる。
cmt_io クラスでは、ベクターの型を元に、割り込み設定の方法を分ける事ができ、面倒な「#if文」から開放される。
また、C++ では、ベクター型の違いによる分岐は、コンパイル時に決定できるので、最適化により、余分の分岐一切が無くなるので、速度の面もリソースの面も好都合となる。
このような仕組みは、C言語だと、スマートに実現する事は難しい、C++ が組み込みマイコンのプログラムに向いた言語である事の一例であると思う。
※C言語プログラマは、#define で実現可能と思うかもしれないが、#define では、正確で厳密な「型」チェックは行われないし、構造的や論理的に誤ったコードをエラー無くコンパイルしてしまう可能性があり、テンプレートとは雲泥の差がある。
C++ のテンプレートでは、構造的や論理的に誤った構造は、「ほぼ」エラーになるので、最初の設計が必要十分な要素を詰め込んであれば、自ずと正しい方向に誘導してくれる感じがある。
大雑把に言うと、コンパイルエラーが無くなったら、プログラムも正しく動く事が多いとも言える。
自分が実装したソースでは、「#define」でマクロ的な定義を行う事は一切していない。