2014年3月5日水曜日

STM32F1でタイマ割り込み

今回はタイマ割り込みについて
STM32F103VEではタイマは合計で6本あり、TIM1,TIM2,TIM3,TIM4,TIM5,TIM8という構成です
TIM6とTIM7を飛んでいる理由についてですが
TIM1とTIM8は「高性能タイマ」とされており、他のタイマとは区別されています
対してそれ以外のタイマ(TIMx)は「汎用タイマ」とされています
そしてSTM32F103VEは2x高性能タイマ 4x汎用タイマ という構成なので6と7を飛ばしているわけです

今回は一定期間での割り込みしか使わないので汎用タイマ(TIM2)を使用します

まずはいつもどおりクロックを供給します
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,   ENABLE);
PWM機能等は使用しないため、タイマモジュールにクロックを供給するだけです

次にタイマの初期化です
    TIM_InitStructure.TIM_Period        = 720 - 1;
    TIM_InitStructure.TIM_Prescaler     = 100 - 1;
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStructure.TIM_CounterMode   = TIM_CounterMode_Down;

    TIM_TimeBaseInit(TIM2, &TIM_InitStructure);
タイマの割り込み周期は
周波数(Hz) = CPUの動作周波数(Hz)/TIM_Prescaler/TIM_Period
となりますす
今回は72MHzのマイコンなので、72000000/100/720となり、1000Hzで割り込みが発生します
またカウンターモードダウンの場合はカウンタにPeriodを設定してからデクリメントし、0から変化した瞬間に割り込みが発生します(0になった瞬間ではありません)
そのため、0もカウントすることを考慮に入れ1を減じた値を設定します


USARTの場合はすぐにモジュールを有効にしましたが、今回は割り込み動作があるのでNVIC(ネスト型ベクタ割り込みコントローラ)の設定を行います
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority  = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
Channelに割り込みを発生する条件を指定します
今回はTIM2の割り込みを設定するのでTIM2_IRQnを指定しました

PreemptionPriorityは割り込みの優先度で、0が最高で15が最低です
またSubPriorityは同じ優先度(PreemptionPriority)の割り込みが同時に発生した場合にどちらを優先するかを設定します
これについてはまた別の機会に説明しようと思います


最後に割り込みの許可とモジュールの有効化を行います
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

    TIM_Cmd(TIM2, ENABLE);
念の為にClearITPendingBitでTIM_IT_UpdateをクリアしてからITConfigで割り込みを設定します
その後でTIM_Cmdでタイマモジュールの動作を許可します
これでタイマモジュールの初期化が完了します

TIM_CmdでENABLEを指定しない限り動作は始まらないので、一旦タイマモジュールを初期化して、この時はTIM_Cmdを呼ばずに、他のモジュールやセンサなどをすべて初期化し終わってからTIM_Cmdで割り込みを許可し、その割り込みでセンサを読み込む といったことも可能です


次に割り込みハンドラです
タイマ2の場合は
void TIM2_IRQHandler(void)
という関数を作ることにより、割り込みが発生したらこの関数を呼ぶことができます
また
    if (TIM_GetITStatus(TIM2, TIM_IT_Update))
という条件を設定することにより、タイマ更新割り込みのみを処理しています
(今回はタイマ更新しか有効にしていないのでこの判定は必要ありませんが)

そして
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
で割り込みをクリアしてハンドラを抜けます

なお、多少時間がかかる処理を割り込みハンドラで行う場合は、処理の最後にClearITPendingBitを呼ぶと便利です
これはタイマ割り込みでハンドラ内にいるにもかかわらず次の割り込みでさらにハンドラが呼ばれてハングアップするのを防ぐ意味があります
もっとも、次の割り込みまでに終了できない処理をハンドラ内で行うべきではないのですが…


応用

今回のサンプルを作るにあたって
typedef struct {
    uint32_t Second;
    uint16_t Milli;
} Seconds;
という構造体を作りました
また
volatile Seconds sec;
というグローバル変数を作成しています

そして、割り込みハンドラ内で
        sec.Milli++;
        if (sec.Milli >= 1000) {
            sec.Milli -= 1000;
            sec.Second++;
        }
という処理を行っています

タイマ割り込みの周期は1000Hzで、これは1000回をカウントすると1秒になる ということを意味しています
ということで1000分の1秒は1ミリ秒なので、1回の割り込みでミリ秒をインクリメントし、ミリ秒が1000を超えるとミリ秒をリセットして秒をインクリメント という処理です

そして
        xprintf("now:%d.%03d\n", sec.Second, sec.Milli);
とすれば現在の(割り込みを使ったカウンタの動作が始まってからの)時間を表示することができます

また、ちょっと長いですが
int GetDiff(const Seconds *sec1, const Seconds *sec2, Seconds *sec) {
    sec->Second = sec->Milli = 0;

    if (sec1->Second == sec2->Second) {
        if (sec1->Milli == sec2->Milli) {
            return(0);
        } else if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
            return(1);
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
            return(-1);
        }
    } else if (sec1->Second > sec2->Second) {
        sec->Second = sec1->Second - sec2->Second;
        if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
            sec->Second--;
            sec->Milli = 1000 - sec->Milli;
        }
        return(1);
    } else {
        sec->Second = sec2->Second - sec1->Second;
        if (sec1->Milli > sec2->Milli) {
            sec->Milli = sec1->Milli - sec2->Milli;
            sec->Second--;
            sec->Milli = 1000 - sec->Milli;
        } else {
            sec->Milli = sec2->Milli - sec1->Milli;
        }
        return(-1);
    }
}
という関数を作っておけば
第一引数と第二引数でポインタとして渡したSeconds構造体の時間差を第三引数でポインタとして渡したSeconds構造体に設定し、戻り値でどちらが大きいかを返すという処理になります
第一引数>第二引数の場合は1が 第一引数<第二引数の場合は-1が戻り値として帰ります

これを利用し
        Seconds start = sec;
        /* 時間のかかる処理 */
        Seconds end = sec;

        Seconds diff;
        GetDiff(&start, &end, &diff);

        xprintf("start:%d.%03d\n", start.Second, start.Milli);
        xprintf("end  :%d.%03d\n", end.Second, end.Milli);
        xprintf("diff :%d.%03d\n", diff.Second, diff.Milli);
とすれば処理の実行時間を計測することができます

例えば
        Seconds start = sec;
        xputs(
            "1234567890" // 10
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 50
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 100
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 150
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890"
            "1234567890" // 200
        );
        Seconds end = sec;
        xputc('\n');
とすればUSARTで200文字を出力する時間を計測することができます
結果は
start:0.004
end  :0.021
diff :0.017
となり、17ミリ秒必要だったということがわかります
これは115200bpsで10bit(1バイト+スタートビット+ストップビット)を200個送るための時間(17.36ミリ秒)と正確に一致しています

前回のUSARTで「次回は送信割り込みを使おうと思ったけどタイマ割り込みにします」と書いた理由がこれです
まずはプログラムループで送信するのにどれくらい時間がかかるかを把握しておかないと、割り込みで処理した場合に「たぶん早くなった」としか言えなくなるためです


ということで次回はUSARTの送信割り込みについて書こうと思います

0 件のコメント:

コメントを投稿