前回、非常に簡単ではありましたが、テンプレートで 「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 にありますので参照して下さい。