SNSへはこちら

Longan NanoでRISC-Vチャレンジ(6) - タイマ割り込み

続いて割り込みを入れてみます。結構簡単にできました。
今回使うペリフェラルは TIMER0 です。この TIMER0 は高機能タイマーですので、いろいろなことができます。例えば カウント、キャプチャ、エンコーダ です。

今回はレジスタ手打ちでアップカウントで更新割り込みを入れてみることにします。

クロック入力

タイマーへのクロック入力は前回の記事のとおり RCU で分周比を設定することができますが、今回はノータッチで行きます。

ペリフェラルレベルでどう調整するかですが、CTL0 レジスタの CKDIV ビットで行います。

とりあえず分周比は1で設定しておきましょう。

プリスケーラ等の設定

どのタイマにもついている機能がプリスケーラです。ここでは 1000 - 1 にします。こうすることで、カウント更新が 108kHz で起こるようになります。
続いてカウントリロードレジスタです。これはカウントの上限およびリセット時のリロード値を設定することができます。この値は 1000 - 1 にすると、カウント更新が 108Hz で起きるようになります。

カウントの繰り返し

次がこのマイコンの特徴ですが、カウンタ繰り返しレジスタ CREP(Counter repetition register)があります。通常だと、1秒ごとに割り込みを入れたい場合は上のプリスケーラ、分周比を設定することによって対処する or 割り込み関数内でカウンタを作っておくということをする必要があります。後者はレジスタのビット幅が 16bit 等、あまりない時に取られる手段ですが、ソフトウェア的な負荷があるのであまり良くはありません。書くならこんなコードですかね。

void TIMER0_UP_IRQHandler(void) {
    static int cnt = 0;
    cnt++;
    if( cnt == 108 ) { // 108以外は無駄な割り込み
        cnt = 0;
        GPIO_OCTL(GPIOC) ^= GPIO_PIN_13;
    }
    TIMER_INTF(TIMER0) = ~TIMER_INTF_UPIF;
}

CREP レジスタを使うとこれは必要ありません。この値を 108 - 1 にすると、108回更新が起きて初めて割り込みが入るようになります。なので割り込み関数内の処理は

void TIMER0_UP_IRQHandler(void) {
    GPIO_OCTL(GPIOC) ^= GPIO_PIN_13;
    TIMER_INTF(TIMER0) = ~TIMER_INTF_UPIF;
}

これだけでいいんですね。

コード全景

上のコードに割り込み有効化をさせればOKです。ボード上の赤色 LED がピコピコ点滅します。

void timer_irq_init(void) {
    rcu_periph_clock_enable(RCU_TIMER0);
    TIMER_PSC(TIMER0) = 1000 - 1; // prescaler
    TIMER_CAR(TIMER0) = 1000 - 1; // maximum value of counting up
    TIMER_CREP(TIMER0) = 108 - 1; // the num of overflows that issues update IRQ.
    TIMER_DMAINTEN(TIMER0) = TIMER_DMAINTEN_UPIE;
    TIMER_CTL0(TIMER0) = TIMER_CTL0_CEN;

    eclic_global_interrupt_enable();
    eclic_enable_interrupt(TIMER0_UP_IRQn);
}

int main(void){
    rcu_periph_clock_enable(RCU_GPIOC);
    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
    timer_irq_init();
    while(1) {
    }
}

void TIMER0_UP_IRQHandler(void) {
    GPIO_OCTL(GPIOC) ^= GPIO_PIN_13;
    TIMER_INTF(TIMER0) = ~TIMER_INTF_UPIF;
}

ここでのハマりどころとして、割り込み関数の属性として __attribute__((interrupt)) は付けては行けないということです。
これを付けても上の例ではうまく動いているように見えますが、もうちょっと頻繁に割り込みが入るようになるとなんか処理が追いつかず途中で動作停止してしまいます。
このマイコンでは ECLIC というモジュールがコアに組み込まれていますので、このような属性指定は要らないということになるのだと思われます。多分。

STM32 と同じくらい簡単に割り込みできました。この程度ならライブラリの初期化用関数を使う必要はないですね。
本来なら構造体を作って、必要なデータを書き込んだものを初期化用関数に渡せば良いのですが、まぁそこまでする必要はないでしょう。

次回はエンコーダ読み取りとPWMをやりたいですね。

おまけ

せっかくなんで全色順番に光らせました。ご査収ください。

#include <gd32vf103.h>

void timer_irq_init(void) {
    rcu_periph_clock_enable(RCU_TIMER0);
    TIMER_PSC(TIMER0) = 1000 - 1; // prescaler
    TIMER_CAR(TIMER0) = 1000 - 1; // maximum value of counting up
    TIMER_CH0CV(TIMER0) = 333 - 1;
    TIMER_CH1CV(TIMER0) = 666 - 1;
    TIMER_DMAINTEN(TIMER0) = TIMER_DMAINTEN_UPIE | TIMER_DMAINTEN_CH0IE | TIMER_DMAINTEN_CH1IE;
    TIMER_CTL0(TIMER0) = TIMER_CTL0_CEN;

    TIMER_INTF(TIMER0) = 0;
    eclic_global_interrupt_enable();
    eclic_enable_interrupt(TIMER0_UP_IRQn);
    eclic_enable_interrupt(TIMER0_Channel_IRQn);
}

void timer_pwm_init(void) {
    rcu_periph_clock_enable(RCU_TIMER0);
    TIMER_PSC(TIMER0) = 1000 - 1; // prescaler
    TIMER_CAR(TIMER0) = 1000 - 1; // maximum value of counting up
    TIMER_CREP(TIMER0) = 108 - 1; // the num of overflows that issues update IRQ.
    TIMER_DMAINTEN(TIMER0) = TIMER_DMAINTEN_UPIE;
    TIMER_CTL0(TIMER0) = TIMER_CTL0_CEN;
}

void TIMER0_UP_IRQHandler(void) {
    static int cnt_r = 0;
    if( ++cnt_r >= 108 ) {
        cnt_r = 0;
        GPIO_OCTL(GPIOC) ^= GPIO_PIN_13;
    }
    TIMER_INTF(TIMER0) = ~TIMER_INTF_UPIF;
}

void TIMER0_Channel_IRQHandler(void) {
    if( TIMER_INTF(TIMER0) & TIMER_INTF_CH0IF ) {
        static int cnt_g = 0;
        if( ++cnt_g == 108 ) {
            cnt_g = 0;
            GPIO_OCTL(GPIOA) ^= GPIO_PIN_1;
        }
        TIMER_INTF(TIMER0) = ~TIMER_INTF_CH0IF;
    }
    if( TIMER_INTF(TIMER0) & TIMER_INTF_CH1IF ) {
        static int cnt_b = 0;
        if( ++cnt_b == 108 ) {
            cnt_b = 0;
            GPIO_OCTL(GPIOA) ^= GPIO_PIN_2;
        }
        TIMER_INTF(TIMER0) = ~TIMER_INTF_CH1IF;
    }
}

int main(void){
    rcu_periph_clock_enable(RCU_GPIOA);
    rcu_periph_clock_enable(RCU_GPIOC);
    gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_13);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1 | GPIO_PIN_2);
    GPIO_OCTL(GPIOC) |= GPIO_PIN_13;
    GPIO_OCTL(GPIOA) |= GPIO_PIN_1 | GPIO_PIN_2;

    timer_irq_init();
    while(1) {
    }
}

こんな感じに動きます。