STM32いじってみた(7) タイマー割り込み編

続いてはマイコンの肝であるタイマーについてやっていきますかね。

STM32F3 シリーズでは結構タイマーがあって、高機能制御タイマーのTIM1/TIM8/TIM20、汎用タイマーのTIM2/TIM3/TIM4、基本タイマーのTIM6/TIM7、汎用タイマーのTIM15/TIM16/TIM17があります。

違いはと言うと、まず基本タイマーは機能がやや不足している印象です。これは一般のタイマーと呼ばれるものにある機能の、コンペアマッチカウントダウン直接の割り込みがありません。カウント方式はカウントアップのみ、割り込みはDMAを介してのみできます。割り込みタイミングですが、これはオーバーフローのみで、カウンタクリア時にロードされる値を指定できますので実質コンペアマッチ的な感じになります。オーバーフローのみということなので、当然 PWM も使うことは出来ません(ARR で全体周期設定はできそうだが)。
ということなので、基本、基本タイマーは使わないと思います。使えなくないですがちょっとやりにくいですね。SysTick タイマーに毛が生えたもの、と考えていて良さそうです。
一方汎用タイマーは十分な機能が揃っています。加えてエンコーダモードも各チャネルにありますから、アクチュエーター制御も手軽にできます。高機能制御タイマーはさらに機能を付したもので、これまた十分すぎる機能です。ですので高機能の方は設定項目が多くやや面倒と言う印象があります。まあ汎用タイマーでも使用する機能によっては設定がめちゃくちゃ面倒くさいんですけどね

とりあえず恒例のLチカを2つのやり方でやっていくということにしましょう。とりあえず TIM2 を使っていきます。

基本の設定

タイマカウントというくらいですから、ひとまず周期とマッチレジスタを設定する必要があります。さらにクロックを分周するためプリスケーラーも使いましょう。

周期は ARR と言うレジスタで管理します。カウントは 0 からはじまり、ARR に一致したらまた 0 に戻ります。つまり、ARR + 1 が周期となるのです。周期が ARR でないという点に注意。
マッチレジスタは CCRx で設定します。こちらもカウントが CCRx になった時に動作するというわけではなく、マッチの次のクロックで動作 となるのでご注意を。つまり 500 カウントで動作させたい時は 500 - 1 と設定してやる必要があります。
プリスケーラーは PSC ですが、こちらもカウントが1つ分多くなるのは同様です。

あ、あとレジスタへのクロック供給をお忘れなく。それと、この記事で示すソースコード中の値はだいたいでええやろということで -1 していません。きっちりカウントしたい場合は 500 等のところを 500 - 1 等と書き換える必要がありますのでご注意。

とりあえずこんな感じで設定しました(ソースコード一部抜粋)。

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Clock supply enable
TIM2->PSC = 8000; // prescaler
TIM2->ARR = 500; // replace and reset value(whole time)
TIM2->CCR1 = 200;
TIM2->CCR2 = 500;

割り込みやってみる

割り込みを行い、割り込み関数内でピンの出力を変えるということをやっていきましょう。とするとコンペアマッチによるタイマー割り込み機能のオン、およびシステムへの割り込みベクタ登録が必要となってきます。それではタイマーの割り込み関連のレジスタを見ていきましょうか。

下位に CCR1 と CCR2 に関係ありそうな箇所がありますね。見てみると…

となっています。なのでこの2つのビットを立てます。

TIM2->DIER |= TIM_DIER_CC1IE | TIM_DIER_CC2IE;

最後に、割り込みベクタ登録をして設定完了です(後述のソースコードにあります)。

割り込み関数を書く

割り込みが起こると決まった名前の関数にジャンプします。それはスタートアップルーチンに書いてあるのですが、見てみると TIM2_IRQHandler とすればいいとわかりますね。つまり void TIM2_IRQHandler(void) { } として、中身を書いていけば良いわけです。

さて、割り込みが起きて内部で処理をするわけですが、単にLEDをチカチカさせる処理を書くだけではその後継続して割り込みが起こってくれなくなります。なのでソフトウェア的に「割り込み処理終わったぜよ」とフラグを操作しなければなりません。データシートを見ると…


ありました。それで下位のビットは…

となっています。ですのでこれでどのマッチによる割り込みか?の判定・割り込み終了処理をしてやります。ざっくりこんな感じにしてみました。

void TIM2_IRQHandler(void) {
    if(TIM2->SR & TIM_SR_CC1IF) {
        GPIOB->ODR |= GPIO_ODR_7;
        TIM2->SR &= ~(TIM_SR_CC1IF);
    }
    if(TIM2->SR & TIM_SR_CC2IF) {
        GPIOB->ODR &= ~GPIO_ODR_7;
        TIM2->ARR = (TIM2->ARR + 100) % 3000;
        if(TIM2->ARR == 0) TIM2->ARR = 500;
        TIM2->SR &= ~(TIM_SR_CC2IF);
    }
}

CCR1 とのマッチで LED の出力を変転、CCR2 とのマッチでまた反転、そして 周期を伸ばすということをやっています。なおこの周期を 500 よりも小さくしてしまうとマッチが一切起こらなくなるので下限は 500 としました。

コード全景

以上のことに +α をした全体のソースコードはこちら。

ARPEUIEUG については後述します。まぁ irq_led() を main 中で呼んで、あとは無限ループさせておけば期待した動作が行われるでしょう。出力は PB7 としました。

ARPE について

ARR は周期を設定するレジスタなのですが、個人的な所感として、タイマ動作中にこれを書き換えるのはあまりよろしくないと思いました。というのは実際に書き換えてきたのですがなんか途中で動作止まるんですよね。理由はよく分かりませんが。
それでは実際にステッピングモーター等の制御で割り込み周期を変えたい時はどうするかというと、この ARPE をセットすれば良いです。これは万が一カウント途中に ARR が変更された場合、タイマ周期を元の ARR にキープしておいて、次にカウンタクリアが起こったらその周期を反映させる、というものです。とりあえずこれを設定しておいたほうが良いでしょう。データシートには以下のようなダイアグラムがあります。図を見れば分かるのですが、実際は ARR のシャドーコピーが作られるんですね。

以下がこの設定がオフの時。

そして以下がオンの時。

UIE について

これは更新割り込みと言われるもので、要はカウンタクリア時に起こる割り込みを有効にするビットです。

UG について

タイマの初期設定で使うビットになります。要は 周期レジスタのバッファコピーやタイマカウンタ値を初期化するためのビットです。周期等の設定が終わったら初期化しておきましょう。

長くなったので PWM 編はまた今度…