LoginSignup
7
8

More than 5 years have passed since last update.

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

Posted at

はじめに

組み込みソフトでハードウェアを制御する際、使用する言語がC言語ということが多いです。
しかしながら、そればかりではつまらないのでC++も使用しています。
ここではC++でハードウェア制御を書くにあたって便利だと思ったtemplateの活用方法を記載します。

タイマーを制御するClass

例としてSTM32マイコンに内蔵されているタイマーを制御する場面を考えてみます。
STM32にはBasicタイマーとしてTIM6/TIM7が搭載されています。
それぞれ次のようにレジスタマップは同じのため、各機能は同じ制御で実現できます。
temp.png

作ってみる

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

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

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構造体として宣言します。

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

おわりに

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

7
8
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8