Timer (STM32)

(2014.6.23 作成)

(2014.7.31 更新)

(2015.4.25 更新)

(2015.8.30 更新)

(2019.6.16 Update)

 タイマー割り込みを使用して指定した時間間隔でカウンタ値を更新します。

 たとえば1[us]間隔でカウントした場合、65535でTIMペリフェラルのカウンタがオーバーフローしますが、このオーバーフローした回数も32bit整数で数えています。何がいいたいかというと、1[us]間隔でカウントしたとしても 1[us] * 65536*(2^32-1) = 8年ぐらいはカウントできます。

割り込み処理

 STM32の場合使用するタイマー毎に割り込みで呼び出される関数名が固定されています。

 Arduinoのようにユーザーから具体的なその関数名を隠蔽してクラスを作成することも可能でしたが、コードを無用に冗長にするだけなのであえてユーザー側に一手間かけてもらうことでシンプルにしています。

 具体的には各マイコン種類ごとに定義されている以下の関数をユーザーコードとして作成し、その中でタイマのカウントアップルーチンを書き込んで使用してください。

 具体的な関数名は以下の通りです。

 

STM32F303xC

(STM32 F3Discovery)

STM32F401xE

(NucleoF401RE)

TIM1 TIM1_UP_TIM16_IRQHandler TIM1_UP_TIM10_IRQHandler
TIM2 TIM2_IRQHandler TIM2_IRQHandler
TIM3 TIM3_IRQHandler TIM3_IRQHandler
TIM4 TIM4_IRQHandler TIM4_IRQHandler
TIM5 - TIM5_IRQHandler
TIM6 TIM6_DAC_IRQHandler -
TIM7 TIM7_IRQHandler -
TIM8 TIM8_UP_IRQHandler -
TIM9 - TIM1_BRK_TIM9_IRQHandler 
TIM10 - TIM1_UP_TIM10_IRQHandler
TIM11 - TIM1_TRG_COM_TIM11_IRQHandler
TIM15 TIM1_BRK_TIM15_IRQHandler -
TIM16 TIM1_UP_TIM16_IRQHandler -
TIM17 TIM1_TRG_COM_TIM17_IRQHandler  -

 注意点としては一つの関数名に複数のタイマが関連付けられている場合があること、割り込みフラグをクリアすること、関数作成時に extern "C"を付け忘れないことぐらいでしょうか。

 下にサンプルコードがあるので参考にしてください。

関数

コンストラクタ

プロトタイプ

Timer(

    TIM_TypeDef *TIM,

    const DKS::TimeUint &timeUnit =  

                                        DKS::TimeUnit_MilliSec);

戻り値

なし

引数 

TIM_TypeDef *TIM

使用するタイマ

DKS::TimeUint &timeUnit

タイマの動作単位

usまたはms

TimeUnit型

備考


カウンタの値を増やす

プロトタイプ CountUp(void)
戻り値

なし

引数

なし

備考

カウンタを更新する。

ユーザーが定義する割り込みルーチン内に記載してください

少し技術的な解説

 カウンタ値を読みに行く(readCounter)際にタイマーのレジスタ値と保存していたカウンタ値を足して返すのですが、ちょうどこのタイミングでタイマーの更新イベントが発生するとおかしな値を返します。このためこのプログラムでは以下のような対策を行っています。(読みやすくするため若干修正しています)

uint64_t DKS::Timer::readCounter()const
{
        const uint32_t counter1 = m_counter;
        const uint32_t CNT1 = LL_TIM_GetCounter(m_TIM);
        const uint32_t counter2 = m_counter;
        const uint8_t m_counter_shift(16);// Update events occurs every 65536 counts. (1<<16)

        if (counter1 == counter2)       //割込みが発生しなかった。
                return ((counter1 << m_counter_shift) + CNT1) >> m_shift;
        else    // counter1とcounter2の計測の間に割り込みが発生した
        return ((counter2 << m_counter_shift) + LL_TIM_GetCounter(m_TIM)) >> m_shift;
}

 すなわちカウンタ値を2回確認し、変化がなければその間で割り込みが発生しなかったと判断できるので、2回目のカウント値を取得する前のレジスタ値を加える。一方変化していればこの間で割り込みが発生したと考えられるので再度読み直したレジスタ値を加えています。

 一旦割り込みを停止するなど、もう少しスマートなやり方があるのかもしれませんが、これよりよい方法が思いつきませんでした。

サンプルコード

以下のコードをSTM32F103C8T6基板に記入するとで100us周期でLED4が点滅します。

#include "DKS_Common_F103xB.h"
#include "DKS_Timer_F103xB.h"
#include "DKS_F103C8T6.h"

extern "C"
{

        DKS::Timer tim;

        int main(void)
        {
                DKS::InitSystem();
                DKS::STM32F103C8T6 board(DKS::BluePill);
                board.Init();

                tim.Init(TIM3, DKS::TimeUnit_MicroSec); //引数なしだと1msでカウントアップ

                tim.Start();
                volatile uint32_t countValueNew, countValueOld;
                while (1)
                {
                        countValueNew = tim.ReadCounter();
                        if ((countValueNew - countValueOld) >= 100)
                        {
                                board.led.toggle();
                                countValueOld = countValueNew;
                        }
                }

        }

        void TIM3_IRQHandler()
        {
                if (LL_TIM_IsActiveFlag_UPDATE(tim.m_TIM) == 1)
                {
                        LL_TIM_ClearFlag_UPDATE(tim.m_TIM);
                        tim.CountUp();
                }
        }
}