はじめに
組み込みソフトでハードウェアを制御する際、使用する言語がC言語ということが多いです。
しかしながら、そればかりではつまらないのでC++も使用しています。
ここではC++でハードウェア制御を書くにあたって便利だと思ったtemplateの活用方法を記載します。
タイマーを制御するClass
例としてSTM32マイコンに内蔵されているタイマーを制御する場面を考えてみます。
STM32にはBasicタイマーとしてTIM6/TIM7が搭載されています。
それぞれ次のようにレジスタマップは同じのため、各機能は同じ制御で実現できます。
作ってみる
ここでTIM6だけを制御するClassを作ってみます。
class TimerDriver
{
public:
static void (*TimeupFunction[TIMER_NUM])(void);
void EnableClocks(){
SetBit(&RCC->APB1ENR, RCC_APB1ENR_TIM6EN);
}
void DisableClocks(){
ClearBit(&RCC->APB1ENR, RCC_APB1ENR_TIM6EN);
}
void EnableTimer(){
SetBit(&TIM6->CR1, TIM_CR1_CEN);
}
void DisableTimer(){
ClearBit(&TIM6->CR1, TIM_CR1_CEN);
}
void EnableInterrupt(){
SetBit(&TIM6->DIER, TIM_DIER_UIE);
}
void DisableInterrupt(){
ClearBit(&TIM6->DIER, TIM_DIER_UIE);
}
static void ClearInterrupt(){
ClearBit(&TIM6->SR, TIM_SR_UIF);
}
void SetTimeout_sec(int timeout_sec){
WriteReg(&TIM6->PSC, 9999);
WriteReg(&TIM6->ARR, 800 * timeout_sec);
WriteReg(&TIM6->CNT, 0);
}
void SetTimeout_msec(int timeout_msec){
WriteReg(&TIM6->PSC, 999);
WriteReg(&TIM6->ARR, 8 * timeout_msec);
WriteReg(&TIM6->CNT, 0);
}
void SetTimeupFunction(void (*function)(void)){
TimeupFunction[TIMER_6] = function;
}
void ClearTimeupFunction(void){
TimeupFunction[TIMER_6] = NULL;
}
};
これはTIM6→TIM7にすればTIM7の制御に使用できるため、なんとかすればClassは共通化できそうに思えます。
templateを用いて作る
templateを用いて共通化します。
そうすると次のようになります。
typedef enum{
TIMER_6,
TIMER_7,
TIMER_NUM,
}TimerId;
template<TimerId id> struct TimerTrait;
template<> struct TimerTrait<TIMER_6>{
constexpr static RCC_TypeDef *rcc = RCC;
constexpr static TIM_TypeDef *timer = TIM6;
constexpr static IRQn_Type irq_num = TIM6_DAC1_IRQn;
constexpr static uint32_t clk_en = RCC_APB1ENR_TIM6EN;
};
template<> struct TimerTrait<TIMER_7>{
constexpr static RCC_TypeDef *rcc = RCC;
constexpr static TIM_TypeDef *timer = TIM7;
constexpr static IRQn_Type irq_num = TIM7_DAC2_IRQn;
constexpr static uint32_t clk_en = RCC_APB1ENR_TIM7EN;
};
template<TimerId id>
class TimerDriver
{
public:
const static TimerId TIMER_ID = id;
typedef TimerTrait<id> Trait;
static RCC_TypeDef *rcc_base_;
static TIM_TypeDef *timer_base_;
static IRQn_Type irq_num_;
static uint32_t clk_en_;
static void (*TimeupFunction[TIMER_NUM])(void);
void SetBase(RCC_TypeDef *rcc, TIM_TypeDef *timer){
rcc_base_ = rcc;
timer_base_ = timer;
}
void EnableClocks(){
SetBit(&rcc_base_->APB1ENR, clk_en_);
}
void DisableClocks(){
ClearBit(&rcc_base_->APB1ENR, clk_en_);
}
void EnableTimer(){
SetBit(&timer_base_->CR1, TIM_CR1_CEN);
}
void DisableTimer(){
ClearBit(&timer_base_->CR1, TIM_CR1_CEN);
}
void EnableInterrupt(){
SetBit(&timer_base_->DIER, TIM_DIER_UIE);
}
void DisableInterrupt(){
ClearBit(&timer_base_->DIER, TIM_DIER_UIE);
}
static void ClearInterrupt(){
ClearBit(&timer_base_->SR, TIM_SR_UIF);
}
void SetTimeout_sec(int timeout_sec){
WriteReg(&timer_base_->PSC, 9999);
WriteReg(&timer_base_->ARR, 800 * timeout_sec);
WriteReg(&timer_base_->CNT, 0);
}
void SetTimeout_msec(int timeout_msec){
WriteReg(&timer_base_->PSC, 999);
WriteReg(&timer_base_->ARR, 8 * timeout_msec);
WriteReg(&timer_base_->CNT, 0);
}
void SetTimeupFunction(void (*function)(void)){
TimeupFunction[TimerDriver<id>::TIMER_ID] = function;
}
void ClearTimeupFunction(void){
TimeupFunction[TimerDriver<id>::TIMER_ID] = NULL;
}
};
template<TimerId id>
void (*TimerDriver<id>::TimeupFunction[TIMER_NUM])(void) = {NULL, NULL};
template<TimerId id> RCC_TypeDef* TimerDriver<id>::rcc_base_ = Trait::rcc;
template<TimerId id> TIM_TypeDef* TimerDriver<id>::timer_base_ = Trait::timer;
template<TimerId id> IRQn_Type TimerDriver<id>::irq_num_ = Trait::irq_num;
template<TimerId id> uint32_t TimerDriver<id>::clk_en_ = Trait::clk_en;
まず、TIM6とTIM7で異なる部分をtemplate構造体として宣言します。
template<TimerId id> struct TimerTrait;
次にTIMER_6とTIMER_7について特殊化します。
template<> struct TimerTrait<TIMER_6>{
constexpr static RCC_TypeDef *rcc = RCC;
constexpr static TIM_TypeDef *timer = TIM6;
constexpr static IRQn_Type irq_num = TIM6_DAC1_IRQn;
constexpr static uint32_t clk_en = RCC_APB1ENR_TIM6EN;
};
template<> struct TimerTrait<TIMER_7>{
constexpr static RCC_TypeDef *rcc = RCC;
constexpr static TIM_TypeDef *timer = TIM7;
constexpr static IRQn_Type irq_num = TIM7_DAC2_IRQn;
constexpr static uint32_t clk_en = RCC_APB1ENR_TIM7EN;
};
TimerDriver内ではstatic変数としてレジスタアドレス変数を持っています。
そのため通常のstatic変数の方法で初期化しています。
template<TimerId id> RCC_TypeDef* TimerDriver<id>::rcc_base_ = Trait::rcc;
template<TimerId id> TIM_TypeDef* TimerDriver<id>::timer_base_ = Trait::timer;
template<TimerId id> IRQn_Type TimerDriver<id>::irq_num_ = Trait::irq_num;
template<TimerId id> uint32_t TimerDriver<id>::clk_en_ = Trait::clk_en;
static const
変数にすればclass内で初期化ができますが、
ここではTestコードでデバイスのアドレス変更したいためこのようにしています。
おわりに
色々と書き方はあると思いますが、一例として記載しました。