コンペアマッチタイマ・テンプレートの使い方(cmt_mgr.hpp)
前回の SCI 編で既に使っていますが、CMT を使う場合を、詳しく説明したいと思います。
CMT は一定間隔でイベントを発生するタイマーで、割り込みを発生させる事が出来ます。
通常、マスタークロックは、PCLKB が使われます。
PCLKB が 60MHz の場合、1.7Hz~7.5MHz 程度の時間間隔をプログラム出来ます。
※分周比は制限があり、自由な周波数を設定出来るわけではありません。
このテンプレートクラス(common/cmt_mgr.hpp)も、RX マイコンの C++ フレームワークの中では初期の段階から改修されてきているクラスです。
通常行いたい事を、最低限の手順で出来るように工夫してあります。
FreeRTOS のようなマルチタスクOSを使わない場合で、平行処理的な制御を扱う場合には欠かせない物です。
比較的簡単に平行処理的な概念を利用する事が出来るので、シンプルなアプリケーションには、逆に使いやすいものです。
RX マイコンでは、CMT は標準装備なので、どんな RX マイコンでも利用可能です。
通常 CMT0 から CMT3 までの4チャネルを使う事が出来ます。
※ICU の解説では、CMT0 は、OS 用で予約してあるのですが、OS(FreeRTOS と思われる)を使わない場合、どれを使っても問題無いと思えます。
FreeRTOS では、以下のように初期化しています。
extern void vTickISR(void);
extern void vSoftwareInterruptISR(void);
void vApplicationSetupTimerInterrupt(void)
{
uint8_t intr = configKERNEL_INTERRUPT_PRIORITY;
cmt_.start(configTICK_RATE_HZ, intr, vTickISR);
device::icu_mgr::set_task(device::ICU::VECTOR::SWINT, vSoftwareInterruptISR);
device::icu_mgr::set_level(device::ICU::VECTOR::SWINT, configKERNEL_INTERRUPT_PRIORITY);
}
通常、タイマーの割り込み処理から呼ばれる関数をアタッチ出来ます。
C++ では、「ファンクタ」と呼ばれる手法を良く使います。
cmt_mgr も、割り込み内から呼ぶ関数を、ファンクタとして扱うようにしています。
cmt_mgr のプロトタイプは以下のようになっています。
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief CMT マネージャー・クラス
@param[in] CMT チャネルクラス
@param[in] TASK タイマー動作クラス
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
template <class CMT, class TASK = utils::null_task>
class cmt_mgr {
ここで、「TASK」はファンクタとして機能し、以下のように、() オペレータを使って動かす関数を定義しておきます。
class my_cmt_task {
public:
void operator() () {
// 処理
}
};
CMT1 を使い、cmt_mgr の定義は、上記クラスを使い、以下のようにします。
typedef utils::cmt_mgr<device::CMT1, my_cmt_task> CMT;
CMT cmt_;
これで、割り込み処理で、この部分が処理されます。
※デフォルトでは、「utils::null_task」クラスが指定されます、これは、何もしないファンクタです。
※何もしないファンクタは、最適化で、完全に無くなります。
C++ のテンプレートでは、このような仕組みにする事で、処理する関数を呼び出すオーバーヘッドを節約出来ます。
CMT の起動
CMT は、間隔(周波数)と、割り込みレベルだけです。
※割り込みレベルが「0」の場合、割り込みを使いません。
{
uint8_t intr_lvl = 3;
uint32_t freq = 1000; // 1000Hz
cmt_.start(freq, intr_lvl);
}
割り込みとの同期
割り込み処理と、メイン側で同期を取りたい場合は、「sync()」を使います。
while(1) {
cmt_.sync();
}
上記のループは、1000Hzの間隔でループする事になります。
割り込み回数の取得
また、割り込み回数が欲しいなら、「get_counter()」を使います。
※このカウンターは、外部割込み関数を登録した場合には無効になります。
auto n = cmt_.get_counter();
※カウンターは32ビットで1周します。
設定周波数の取得(リアルな値)
「cmt_mgr」クラスでは、設定した周波数になるべく近い周期を生成しようと努力します。
ですが、マスタークロックに対して、割り切れない場合、実際の周期には誤差があります。
以下の関数で、設定した周波数と、実際に運用されている周波数を表示します。
uint32_t freq = 850;
cmt_.start(freq, 4);
utils::format("Freq (set): %u Hz\n") % cmt_.get_rate();
utils::format("Freq (real): %u Hz\n") % cmt_.get_rate(true);
Freq (set): 850 Hz
Freq (real): 849 Hz
C++ でのインスタンスの考え方
よく、例題などで、クラスのインスタンスを「new」を使って作成して、「delete」で廃棄する例を見ます。
また、C++ をあまり良く判っていない人は、インスタンスを作るコストが大きいので、C++ は小規模システム向きでは無いと思っている人が多いようです。
でも実際は、動的に作らなければ良いだけの事で、テンプレートなどを利用する事で、柔軟性があり、「動的な割り当て」をほぼ「0」に出来ます。
このような勘違いや思い込みは、C++ の場合非常に沢山あり、それが、C++ を敬遠する理由にもなっているようです。
※公開している RX マイコンフレームワークには「new、delete、malloc、free」は一切ありません。
※外部ライブラリで使われている場合があり、その対応としては多少あります。
MyClass* my_class = new MyClass();
my_class->xxx();
delete my_class;
「new」すれば、必ず「delete」しなければならず、管理が面倒です、常に使う物は、単に宣言すれば良く、コンパイラが、メモリを自動で割り当てます。
※シェアードポインターで包む事で、廃棄忘れを防止出来ますが、「動的」に確保する必要が無い場合は、実態を書けば良いだけです。
※関数内の場合は、割り当てはスタック上ですが、関数外に置けば、ワークメモリに割り当ててくれます。
{
MyClass my_class;
my_class.xxx();
}
※上記の場合、MyClass の実態「my_class」は、スコープを抜ければ、自動で廃棄されます。
このように、C++ では、「割り当て」は極力減らし、それに伴うリスクを避ける事が出来ます。