前回、非常に簡単ではありましたが、テンプレートで 「C 言語よりお得な C++ その5」FIFO のサイズを可変する方法を書きました。
今度は、もう少し複雑ですが、もっと実用的な実装のアイディアを示します。
以前から主に RX マイコン用の I/O 定義を C++ で実装して来ました、今回それを活用して実際に I/O の操作を行うクラスをテンプレートで実装してみます。
組み込みマイコンでは、ハードウェアーリソースは、大抵複数のチャネルがあります。
例えば、RX63T には CMT(コンペアマッチタイマー)は4チャネルあります、チャネル毎に操作する I/O の対象は微妙に違い、C のプログラムでは、チャネル毎に別々に実装するしかありませんでした(define のマクロで解決する方法は論外です)、このような微妙な違いは、大抵は、I/O のアドレスが違うとか、ビットの位置が違うなどでした、ですから、自分がどのチャネルに対して操作しているか判れば、柔軟性のあるドライバーを書く事は出来るのですが、テンプレートを使うと、非常にシンプルに判りやすく書けます、また最適化によって余分な部分は自動的に消してくれますので、リソースのダイエットや速度の向上が望めます。
※少し長いですが、cmt_io.hpp を示します。
#pragma once //=====================================================================// /*! @file @brief RX62N, RX621, RX63T グループ・CMT I/O 制御 @n Copyright 2013 Kunihito Hiramatsu @author 平松邦仁 (hira@rvf-rc45.net) */ //=====================================================================// #include "cmt.hpp" #include "rx63x/system.hpp" #include "rx63x/icu.hpp" #include "vect.h" namespace device { //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// /*! @brief CMT I/O クラス @param[in] CMTx CMT チャネルクラス */ //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// template <class CMTx> class cmt_io { uint32_t clock_; void sleep_() { } static volatile uint32_t counter_; static void (*task_)(); static INTERRUPT_FUNC void cmt_task_() { ++counter_; if(task_) (*task_)(); switch(CMTx::get_chanel()) { case 0: ICU::IR.CMI0 = 0; break; case 1: ICU::IR.CMI1 = 0; break; case 2: ICU::IR.CMI2 = 0; break; case 3: ICU::IR.CMI3 = 0; break; } } public: //-----------------------------------------------------------------// /*! @brief コンストラクター */ //-----------------------------------------------------------------// cmt_io() : clock_(0) { } //-----------------------------------------------------------------// /*! @brief ベースクロックの設定 @param[in] clock ベース周波数 */ //-----------------------------------------------------------------// void set_clock(uint32_t clock) { clock_ = clock; } //-----------------------------------------------------------------// /*! @brief 初期化 @param[in] freq タイマー周波数 @param[in] level 割り込みレベル @return レンジオーバーなら「false」を返す */ //-----------------------------------------------------------------// bool initialize(uint32_t freq, uint8_t level) const { if(freq == 0 || clock_ == 0) return false; uint32_t cmcor = clock_ / freq / 8; uint8_t cks = 0; while(cmcor > 65536) { cmcor >>= 2; ++cks; } if(cks > 3 || cmcor == 0) { return false; } uint32_t chanel = CMTx::get_chanel(); task_ = 0; switch(chanel) { case 0: set_interrupt_task(cmt_task_, ICU::VECTOR::CMI0); CMTx::CMSTR0.STR0 = 0; SYSTEM::MSTPCRA.MSTPA15 = 0; ICU::IPR.CMI0 = level; ICU::IER.CMI0 = true; ICU::IR.CMI0 = 0; break; case 1: CMTx::CMSTR0.STR1 = 0; SYSTEM::MSTPCRA.MSTPA15 = 0; set_interrupt_task(cmt_task_, ICU::VECTOR::CMI1); ICU::IPR.CMI1 = level; ICU::IER.CMI1 = true; ICU::IR.CMI1 = 0; break; case 2: CMTx::CMSTR1.STR2 = 0; SYSTEM::MSTPCRA.MSTPA14 = 0; set_interrupt_task(cmt_task_, ICU::VECTOR::CMI2); ICU::IPR.CMI2 = level; ICU::IER.CMI2 = true; ICU::IR.CMI2 = 0; break; case 3: CMTx::CMSTR1.STR3 = 0; SYSTEM::MSTPCRA.MSTPA14 = 0; set_interrupt_task(cmt_task_, ICU::VECTOR::CMI3); ICU::IPR.CMI3 = level; ICU::IER.CMI3 = true; ICU::IR.CMI3 = 0; break; } CMTx::CMCR = CMTx::CMCR.CMIE.b() | CMTx::CMCR.CKS.b(cks); CMTx::CMCOR = cmcor - 1; switch(chanel) { case 0: CMTx::CMSTR0.STR0 = 1; break; case 1: CMTx::CMSTR0.STR1 = 1; break; case 2: CMTx::CMSTR1.STR2 = 1; break; case 3: CMTx::CMSTR1.STR3 = 1; break; } return true; } //-----------------------------------------------------------------// /*! @brief 割り込みタスクを設定 @param[in] task 設定タスク */ //-----------------------------------------------------------------// void set_task(void (*task)()) const { cmt_task_ = task; } //-----------------------------------------------------------------// /*! @brief 割り込みと同期 */ //-----------------------------------------------------------------// void sync() { volatile uint32_t cnt = counter_; while(cnt == counter_) ; } //-----------------------------------------------------------------// /*! @brief 割り込みカウンターの値を取得 */ //-----------------------------------------------------------------// uint32_t get_count() const { return counter_; } //-----------------------------------------------------------------// /*! @brief CMT カウンターの値を取得 */ //-----------------------------------------------------------------// uint16_t get_cmt_count() const { return CMTx::CMCNT(); } }; template <class CMTx> volatile uint32_t cmt_io<CMTx>::counter_; template <class CMTx> void (*cmt_io::task_)(); }
ここで、重要なしくみは、「static」で宣言された関数と変数です、これは、割り込みルーチンと、クラスとのデータをやり取りする部分ですが、static に宣言されている為、クラスのインスタンスを取得する事が必要では無く、割り込み関数から簡単にアクセス出来ます。
※クラス外で、static 宣言されたリソースの実態を記述しておく必要がありますが、それは、コードの最後の方で行っています。
template <class CMTx> volatile uint32_t cmt_io<CMTx>::counter_; // カウンター変数の実態 template <class CMTx> void (*cmt_io::task_)(); // 関数アドレスの実態
クラス内で static 宣言された変数やクラスは、そのクラスで共有されるのですが、このクラスはテンプレートクラスなので、CMT のチャネルが違えば、変数や、関数は、別々にインスタンス化されますので、好都合です。
このテンプレートクラスでは、個々の処理で、チャネル番号を取得して、switch 文により、処理を分けていますが、ここが、テンプレートの賢いところで、チャネル番号は、インスタンス化の時に定数なので、余分なケースは取り除かれてチャネル固有の実装だけが、取り込まれます。
どうでしょうか?、組み込みでも C++ を使う効果が理解できたのでは無いでしょうか?
※このソースコードや、I/O 定義ヘッダーは github にありますので参照して下さい。