SNSへはこちら

AVR32マイコンやってみよう(8) - 割り込み・タイマ

続いて、割り込みを使用していきます。しかし、データシートやリファレンスを読んでもいまいち理解できないので、とりあえず割り込み関連のライブラリを使ってしまいます。理解は動いてから。そうしよう。

ASFを導入

ASF とは旧 Atmel のライブラリ名称です。もともとは Atmel Software Framework の略称だったのですが、今では Advanced Software Framework になっています。
某ルネサス社SH(Super Hitachi -> SuperH)や、HEW(Hitachi Embedded Workshop -> High-performance Embedded Workshop) のようですね。

このライブラリは STM32 LL のようなもので、関数呼び出しでペリフェラルの設定・操作ができるようになっています。ですが、不必要なライブラリ関数を用いるなんて甘えですから、本シリーズでも割り込みに限って使用させてもらいます。

さて、この ASF、8bit の AVR や、ARM の SAM では Atmel Start というプロジェクトテンプレートを生成する Web ツールがあるのですが、この 32bit AVR では見捨てられたのか、使用できません。

なので、自力で ASF をダウンロードしてきて導入する必要があります。

ASFをダウンロード

例によって、アーカイブサイトからダウンロードしてきます。ASF の欄を見つけてください。最新版がいいでしょう。

そうしたら展開します。以下の説明では ~/Downloads に展開したとして説明を進めていきます。

プロジェクトの準備

ではまず、前回までの記事で用意したプロジェクトディレクトリに、専用のサブディレクトリを用意します。
とりあえず、プロジェクトルートに include/intc を用意しておきます。

$ mkdir -p include/intc

必要ファイルのコピー・加工

実際に作業に入っていきましょう。必要なファイルが複数あるので、抜けがないようにしてください。
また、コマンド操作では、現在はプロジェクトディレクトリのルートにいるとします。

まずは、割り込みコントローラである INTC に直接関係のあるソースファイルとヘッダを持ってきます。ASF 内の avr32/drivers/intc/ にある intc.cintc.hexception.S をコピーします。

$ cp ~/Downloads/xdk-asf-3.47.0/avr32/drivers/intc/intc.h include/intc
$ cp ~/Downloads/xdk-asf-3.47.0/avr32/drivers/intc/intc.c .
$ cp ~/Downloads/xdk-asf-3.47.0/avr32/drivers/intc/exception.S .

続いて、avr32/utils/ にある compiler.hpreprocessor ディレクトリをコピーします。

$ cp ~/Downloads/xdk-asf-3.47.0/avr32/utils/compiler.h include/intc
$ cp -r ~/Downloads/xdk-asf-3.47.0/avr32/utils/preprocessor include/intc

最後に、common/utils にある interrupt.hinterrupt ディレクトリparts.h をコピーします。

$ cp ~/Downloads/xdk-asf-3.47.0/common/utils/interrupt.h include/intc
$ cp -r ~/Downloads/xdk-asf-3.47.0/common/utils/interrupt include/intc
$ cp ~/Downloads/xdk-asf-3.47.0/common/utils/parts.h include/intc

続いてここまで来たら、一部ファイルを修正します。include/intc/compiler.h を開いて、#include "header_files/uc3d_defines_fix.h"#include "header_files/uc3l3_l4_defines_fix.h" の行をコメントアウトします。ここではスッキリとコマンドを打ち込んで修正してしまいます。

$ sed -i 's/^.*header_files.*$/\/\/ &/' include/intc/compiler.h

ここまで来たら、コピーしたファイル以外を加工します。まずは Makefile。こうやって INCLUDES 変数を新設して...

INCLUDES = -Iinclude -Iinclude/intc -Iinclude/intc/preprocessor

そうして CFLAGS に追加してやってください。

CFLAGS = -march=ucr1 -c $(INCLUDES) -std=gnu99 $(OPTFLAGS) -D__AVR32_UC3B064__

続いて、linker.ld です。.text セクション内に .exception の記述を追加します。

  .text           :
  {
    KEEP(*(.exception)) /* ADD ME */
    *(.text .stub .text.* .gnu.linkonce.t.*)
    KEEP (*(.text.*personality*))
    /* .gnu.warning sections are handled specially by elf32.em.  */
    *(.gnu.warning)
  } >FLASH AT>FLASH =0xd703d703

これで準備万端です!あとは make を叩けば無事にコンパイルできていることでしょう。

タイマーやってみよう

ということで、早速割り込みを用いるペリフェラルを用いていきたいと思います。
このマイコンには、16bit タイマである、TC が存在します。それがいいので使っていきたいと思います。注意点として、若干データシートがわかりにくいです。

TCの動作モード

データシートを参考に要約しています。主にモードは大きく2つに分けることができて、

  • キャプチャ(Capture)モード
  • 波形生成(Wave Generation)モード

です。このうち前者のキャプチャモードでは、波形入力動作(よく分かっていない)や、周期タイマーとしての動作ができます。後者の波形生成モードでは、対応するピンを出力にして、PWM 波形生成等ができます。
今回はとりあえず割り込みを入れればいいので、キャプチャモードで行きます。

ペリフェラルへのクロック供給

32bit マイコンと言ったら、大体が無駄に省電力を意識しているので、ペリフェラルへのクロック供給を統括する回路ブロックがあります。このマイコンもご多分に漏れず PM(Power Management) で管理しています。
が、AVR32 ではデフォルトで全てのペリフェラルが ON なので、現状は触れる必要性はありません。

ここでは勉強も兼ねて、当該のレジスタ説明を見ていきましょう。

TC を利用することを考えると、PBAMASK の 12bit 目を入れればいいですね。すなわち

    AVR32_PM.pbamask |= 1 << 12;

とすればいい。これを入力してやっと TC ペリフェラルをいじることができます。

ペリフェラルの設定

ここまで来て面倒くさくなってしまったので(すみません...)、ざっくりとした解説にします。
このモードでは、カウントを比較する RA, RB, RC のうち、RC のみが利用可能です。デフォルトではカウントオーバーフローでやっとカウンタがリセットされますが、レジスタの設定によって RC レジスタにカウンタがマッチした次のクロックでカウンタクリアをすることができます。これでカウント周期を司れるというわけです。今回は 30000 - 1 カウントでカウントリセット & 割り込みを入れるようにします。

割り込み関数では割り込み1回につき専用の volatile 変数をインクリメントします。

割り込みコントローラINTC

ARM でいう NVIC のように、このアーキテクチャでも INTC が用意されています。詳細は省きますが、とりあえずグローバル割り込みの設定が絶対に必要なのは分かりますかね。

このマイコンに独特なものとしては、割り込みベクタテーブルが事前に決まっておらず、各ペリフェラルごとに関数ポインタを登録する必要があるということです。
「必要がある」と記述するのは若干語弊がありまして、むしろ割り込み関数を決まった名前で記述する必要があるわけではなく、自分で決められるということでもあります。
実際、割り込み関数を登録する関数である INTC_register_interrupt では第一引数の型が __int_handler と定義されており、GNU gcc では

typedef void (*__int_handler)(void);

とエイリアスが貼られています。すなわち、__int_handler の型というのは、返り値無しであり、引数もない関数のことである とされているのです。
関数ポインタは特に* をつけることなく関数の名前を書くだけで表現することができるので、単純です。
また、これは重要なことですが、割り込み関数では __attribute__((__interrupt__)) という、何やらアンダーバーがたくさんある属性を付けねばなりません。これは 8bit AVR で、割り込み関数でのリターン命令は ret ではなく reti でなくてはならなかったように、特殊な事情があるのです(今回は省略)。

コード

ということで、今回の説明をすべて反映したコードを示します。これを書き込んで動かした場合、1秒ごとにLEDがチカチカしていたら成功です。

#include <avr32/io.h>
#include <intc.h>

void clock_osc0_start(void) {
    AVR32_PM.OSCCTRL0.startup = AVR32_PM_OSCCTRL0_STARTUP_64_RCOSC;
    AVR32_PM.OSCCTRL0.mode = AVR32_PM_OSCCTRL0_MODE_CRYSTAL_G3;
    AVR32_PM.MCCTRL.osc0en = 1;
    while( !AVR32_PM.POSCSR.osc0rdy ); // wait for OSC0 to start
}

void clock_pll_start(void) {
    // (OSC0)12MHz * 10 / 1 / 2 == 60MHz
    // set MUL & DIV & PLL divider and then start PLL0
    AVR32_PM.PLL[0].pllmul = 10 - 1;
    AVR32_PM.PLL[0].plldiv = 1;
    AVR32_PM.PLL[0].pllopt = 1 << 1;
    AVR32_PM.PLL[0].pllen = 1;
    while( !AVR32_PM.POSCSR.lock0 ); // wait for PLL0 to be locked
}

typedef enum {
    SLOW, OSC, PLL
} ClockType;

void set_clock(ClockType clk) {
    switch(clk) {
        case SLOW:
            AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_SLOW;
            break;
        case OSC:
            AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_OSC0;
            break;
        case PLL:
            AVR32_PM.MCCTRL.mcsel = AVR32_PM_MCCTRL_MCSEL_PLL0;
            break;
        default:
            break;
    }
}

void led_init(void) {
    AVR32_GPIO.port[0].gper |= 1 << 14;
    AVR32_GPIO.port[0].oder |= 1 << 14;
}

volatile int cnt;
__attribute__((__interrupt__))
void tc_isr(void) {
    if( AVR32_TC.channel[0].SR.cpcs ) {
        cnt++;
    }
}
void tc_init(void) {
    AVR32_PM.pbamask |= 1 << 12;

    AVR32_TC.channel[0].CMR.capture.cpctrg = 1;
    AVR32_TC.channel[0].CMR.capture.tcclks = AVR32_TC_CMR0_TCCLKS_TIMER_CLOCK2;
    AVR32_TC.channel[0].rc = 30000 - 1; // match occurs per 1usec.

    AVR32_TC.channel[0].IER.cpcs = 1; // enable CPCS
    AVR32_TC.channel[0].CCR.clken = 1;
    AVR32_TC.channel[0].CCR.swtrg = 1; // clock enabled

    INTC_register_interrupt(tc_isr, AVR32_TC_IRQ0, AVR32_INTC_INT0);
}


int main(void) {
    Disable_global_interrupt();
    INTC_init_interrupts();

    clock_osc0_start();
    clock_pll_start();
    set_clock(PLL);
    led_init();
    tc_init();
    Enable_global_interrupt();

    while(1) {
        if( cnt == 1000 ) {
            AVR32_GPIO.port[0].ovrt = 1 << 14;
            cnt = 0;
        }
    }
    return 0;
}

懸念していた割り込みがすんなりと入ってよかったです。まあそりゃそうか。ライブラリ使えば誰でもできるよねこんなの

ということで、次回は多分 PWM です。