SNSへはこちら

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

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

STM32F3 シリーズでは結構タイマーがあって、高機能制御タイマーのTIM1/TIM8/TIM20、汎用タイマーのTIM2/TIM3/TIM4、基本タイマーのTIM6/TIM7、汎用タイマーのTIM15/TIM16/TIM17があります。型番や Flash 容量によって使える個数が異なるんですがね。

違いはと言うと、まず基本タイマーはシンプルなタイマーです。これは他のタイマーにある機能の、コンペアマッチカウントダウンPWM出力エンコーダモードがありません。カウント方式はカウントアップのみ、割り込みはカウンタが周期レジスタの値と一致したときのみで、その際にカウントがクリアされ、再ロードされる値を指定できますので実質コンペアマッチ的な感じになります。
ですので、単に周期的な割り込みをしたい時等、単純な作業でしたら基本タイマーで十分事足りると思います。
一方汎用タイマーは十分な機能が揃っています。加えてエンコーダモードも各チャネルにありますから、アクチュエーター制御も手軽にできます。高機能制御タイマーはさらに機能を付したもので、これまた十分すぎる機能です。ですので高機能の方は設定項目が多くやや面倒と言う印象があります。まあ汎用タイマーでも使用する機能によっては設定がめちゃくちゃ面倒くさいんですけどね

とりあえず恒例の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;

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

割り込み関数を書く

割り込みが起こると決まった名前の関数にジャンプします。それはスタートアップルーチンに書いてあります。ちょっと見てみましょう。該当ファイルは startup/startup_stm32f303x8.s にあります。

; 一部抜粋...
    .word   ADC1_2_IRQHandler
    .word   CAN_TX_IRQHandler
    .word   CAN_RX0_IRQHandler
    .word   CAN_RX1_IRQHandler
    .word   CAN_SCE_IRQHandler
    .word   EXTI9_5_IRQHandler
    .word   TIM1_BRK_TIM15_IRQHandler
    .word   TIM1_UP_TIM16_IRQHandler
    .word   TIM1_TRG_COM_TIM17_IRQHandler
    .word   TIM1_CC_IRQHandler
    .word   TIM2_IRQHandler
    .word   TIM3_IRQHandler
    .word   0
    .word   I2C1_EV_IRQHandler
    .word   I2C1_ER_IRQHandler

という訳で、これを見ると名前的に 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 編はまた今度...