ハードウェア制御でtemplateを活用する

#はじめに
組み込みソフトでハードウェアを制御する際、使用する言語がC言語ということが多いです。
しかしながら、そればかりではつまらないのでC++も使用しています。
ここではC++でハードウェア制御を書くにあたって便利だと思ったtemplateの活用方法を記載します。
#タイマーを制御するClass
例としてSTM32マイコンに内蔵されているタイマーを制御する場面を考えてみます。
STM32にはBasicタイマーとしてTIM6/TIM7が搭載されています。
それぞれ次のようにレジスタマップは同じのため、各機能は同じ制御で実現できます。
![temp.png](https://qiita-image-store.s3.amazonaws.com/0/140055/95f48b20-fcfd-ecdb-5315-aa93a53c5ee3.png)

##作ってみる
ここでTIM6だけを制御するClassを作ってみます。

```cpp:timer_driver.h
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を用いて共通化します。
そうすると次のようになります。

```cpp:timer_driver.h
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構造体として宣言します。

```cpp
template<TimerId id> struct TimerTrait;
```

次にTIMER_6とTIMER_7について特殊化します。

```cpp

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変数の方法で初期化しています。

```cpp
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コードでデバイスのアドレス変更したいためこのようにしています。

#おわりに
色々と書き方はあると思いますが、一例として記載しました。