割り込みテクニックでタイマを使おう:イチから作って丸ごと学ぶ! H8マイコン道(10)(3/3 ページ)
制御プログラミングで欠かせない割り込み処理。今回は、C言語によるタイマA割り込みプログラムについて詳しく解説する。
それではC言語によるタイマA割り込みプログラムを考えてみます。
C言語による割り込み処理ルーチンは、関数と同じ形式でコーディングします。割り込み処理ルーチンは、引数も戻り値もないので、
void 割り込み処理ルーチン名(void) { …… }
となります。
しかし、割り込み処理ルーチンは特別なコードが必要なので、
void int_tima(void) __attribute__((interrupt_handler));
のように、「__attribute__((interrupt_handler))」を付けてプロトタイプ宣言します。するとCコンパイラは普通の関数と区別して、割り込み処理ルーチンとしてのコンパイルを行います。割り込み処理ルーチンの宣言は、Cコンパイラによって異なります。この方法はH8マイコン用GCC特有のものなので注意してください。
また、割り込み要求を0クリアするのは、割り込み処理ルーチンの仕事です。
IRR1 &= ~IRRTA;
で、割り込み要求を解除してください。
次に、割り込み許可について説明します。
図6にH8マイコンの割り込みの仕組みを示します。
H8マイコンの周辺機能には、「割り込み要求フラグ」と「割り込みイネーブルビット」があります。割り込みイネーブルビットが「1」のとき、割り込み要求ビットが「1」にセットされると、CPUに対して割り込みが要求されます。割り込みイネーブルビットが「0」ならば、割り込み禁止です。つまり、割り込みイネーブルビットを「1」にセットしないと、割り込みは発生しません。
周辺機能からの割り込み要求は、割り込みコントローラで制御され、CPUに通知されます。
CPUの「コンディションコードレジスタ(CCR)」のIビットが「0」のとき、CPUは割り込みを受け付けます。Iビットが「1」のときは割り込みが保留されます。つまり、割り込み処理を実行するには、Iビットが「0」でなければなりません。
それでは、タイマAの割り込みを許可しましょう。
IENTAビットは、「割り込みイネーブルレジスタ1(IENR1)」の第6ビットにあります(図7)。
IENR1を、
#define IENR1 (*(volatile unsigned char *)0xfff4)
と定義し、IENTAビットを、
#define IENTA 0x40
と定義します。
IENTAビットに「1」をセットするには、
IENR1 |= IENTA;
とします。これで、タイマA割り込みが発生するようになります。
次に、CCRのIビットを操作します。
メモリ空間に配置されたレジスタは、ポインタによりC言語で操作できますが、CPUのレジスタはC言語で操作できません。このような場合、アセンブリ命令を直接記述します。
多くのマイコン用Cコンパイラでは、C言語の中にアセンブリ命令を組み込めるように言語仕様が拡張されています。
CPUのIビットを0クリアし、CPUが割り込みを受け取るようにするには、
asm("andc.b #0x3f,ccr");
とアセンブリ命令を実行させます。
同じように、Iビットをセットし割り込みを保留させるには、
asm("orc.b #0xc0,ccr");
とします。
プログラムは次のようになります(ソースコード3)。
/* 1秒間隔でカウント表示する。 * */ #define PCR5 (*((volatile unsigned char *)0xffe8)) #define PDR5 (*((volatile unsigned char *)0xffd8)) #define PCR8 (*((volatile unsigned char *)0xffeb)) #define PDR8 (*((volatile unsigned char *)0xffdb)) #define TMA (*(volatile unsigned char *)0xffa6) #define IRR1 (*(volatile unsigned char *)0xfff6) #define IENR1 (*(volatile unsigned char *)0xfff4) #define IRRTA 0x40 #define IENTA 0x40 void int_tima(void) __attribute__((interrupt_handler)); unsigned char LED[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; int count; int main(void) { asm("orc.b #0xc0,ccr"); /* 割り込み禁止 */ PDR5 = 0xfe; PCR5 = 0x03; PCR8 = 0xff; TMA = 0x18; IRR1 &= ~IRRTA; IENR1 |= IENTA; asm("andc.b #0x3f,ccr"); /* 割り込み許可 */ count = 0; PDR8 = LED[count]; for (;;) ; return 0; } void int_tima(void) { IRR1 &= ~IRRTA; if (count < 9) count++; else count = 0; PDR8 = LED[count]; }
ソースコード3 |
- ソースコード3のダウンロード(seccnt2.lzh)
コラム(1):割り込みベクタテーブル
H8マイコンは、割り込み要求に応じた処理を呼び出すために、「割り込みベクタテーブル(Interrupt Vector Table)」でアドレス管理しています。H8マイコンの割り込みベクタテーブルは0番地に配置されています。
割り込みベクタテーブルには、割り込み処理ルーチンのスタートアドレスが格納されています。その内容を表2に示します。
もし、タイマA割り込みが発生したならば、CPUは「0x0026」番地のデータをスタートアドレスとして、タイマA割り込み処理ルーチンに飛んでいきます。H8マイコンがリセットした場合も同様で、CPUは0番地のデータを「プログラムカウンタ(PC)」にセットし、そこから最初のプログラムの実行がはじまります。つまり、0番地にはスタートアップルーチンのアドレスが設定されているのです。
ベクタ番号 | 割り込み要因 | ベクタアドレス |
---|---|---|
0 | リセット | 0x0000 |
1〜6 | システム予約 | 0x0002〜 |
7 | 外部割り込み(NMI) | 0x000E |
8〜11 | トラップ命令 | 0x0010〜 |
12 | アドレスブレーク | 0x0018 |
13 | スリープ命令の実行による直接遷移 | 0x001A |
14〜18 | 外部割り込み(IRQ0〜IRQ3,WKP) | 0x001C〜 |
19 | タイマA割り込み | 0x0026 |
20 | システム予約 | 0x0028 |
21 | タイマW割り込み | 0x002A |
22 | タイマV割り込み | 0x002C |
23 | SCI割り込み | 0x002E |
24 | IIC割り込み | 0x0030 |
25 | A/D変換器割り込み | 0x0032 |
表2 H8/3664の割り込みベクタテーブル |
コラム(2):リンカスクリプトとC言語の割り込み処理ルーチン
H8用GCCにおいても、割り込みベクタテーブルは、きちんと管理されています。
リンカスクリプト「3664.x」ファイルをテキストエディタで開くと、
SECTIONS { .vectors 0 : { SHORT(ABSOLUTE(_start)) /* 0 : Reset */ SHORT(ABSOLUTE(_start)) /* 1 : */ SHORT(ABSOLUTE(_start)) /* 2 : */ SHORT(ABSOLUTE(_start)) /* 3 : */ SHORT(ABSOLUTE(_start)) /* 4 : */ SHORT(ABSOLUTE(_start)) /* 5 : */ SHORT(ABSOLUTE(_start)) /* 6 : */ SHORT(DEFINED(_int_nmi)?ABSOLUTE(_int_nmi): ABSOLUTE(_start)) /* 7 : NMI */ SHORT(DEFINED(_int_trap0)?ABSOLUTE(_int_trap0): ABSOLUTE(_start)) /* 8 : trap0 */ SHORT(DEFINED(_int_trap1)?ABSOLUTE(_int_trap1): ABSOLUTE(_start)) /* 9 : trap1 */ SHORT(DEFINED(_int_trap2)?ABSOLUTE(_int_trap2): ABSOLUTE(_start)) /* 10 : trap2 */ SHORT(DEFINED(_int_trap3)?ABSOLUTE(_int_trap3): ABSOLUTE(_start)) /* 11 : trap3 */ SHORT(ABSOLUTE(_start)) /* 12 : */ SHORT(ABSOLUTE(_start)) /* 13 : */ SHORT(DEFINED(_int_irq0)?ABSOLUTE(_int_irq0): ABSOLUTE(_start)) /* 14 : IRQ0 */ SHORT(DEFINED(_int_irq1)?ABSOLUTE(_int_irq0): ABSOLUTE(_start)) /* 15 : IRQ1 */ SHORT(DEFINED(_int_irq2)?ABSOLUTE(_int_irq0): ABSOLUTE(_start)) /* 16 : IRQ2 */ SHORT(DEFINED(_int_irq3)?ABSOLUTE(_int_irq0): ABSOLUTE(_start)) /* 17 : IRQ3 */ SHORT(ABSOLUTE(_start)) /* 18 : */ SHORT(DEFINED(_int_tima)?ABSOLUTE(_int_tima): ABSOLUTE(_start)) /* 19 : Timer A */ SHORT(ABSOLUTE(_start)) /* 20 : */ SHORT(DEFINED(_int_timw)?ABSOLUTE(_int_timw): ABSOLUTE(_start)) /* 21 : Timer W */ SHORT(DEFINED(_int_timv)?ABSOLUTE(_int_timv): ABSOLUTE(_start)) /* 22 : Timer V */ SHORT(DEFINED(_int_sci3)?ABSOLUTE(_int_sci3): ABSOLUTE(_start)) /* 23 : SCI3 */ SHORT(DEFINED(_int_iic)?ABSOLUTE(_int_iic): ABSOLUTE(_start)) /* 24 : IIC */ SHORT(DEFINED(_int_adi)?ABSOLUTE(_int_adi): ABSOLUTE(_start)) /* 25 : A/D */ FILL(0xff) } > rom
と記述された部分があります。実は、これが割り込みベクタテーブルの記述です。
例えばタイマA割り込みの場合、そのベクタ番号は「19」なので、その19行目を見ます。すると、
SHORT(DEFINED(_int_tima)?ABSOLUTE(_int_tima): ABSOLUTE(_start)) /* 19 : Timer A */
と記述されているので、タイマAの割り込み処理ルーチンの名前は「int_tima」であると分かるのです。そこに、もしint_tima関数があるならば、ベクタアドレスにint_tima関数を設定し、int_tima関数がなければ、スタートアップルーチンを設定すると記述されています。
健一君。割り込みについて理解できた?
はい!
割り込みもC言語で記述できるなんてすごいですね。
そうでしょ!
健一君のやる気も出てきたみたいだし……。
それじゃー、いつもの宿題いくわよ〜。
おっと、携帯から割り込みが……。
で、で、で、では。
お、お、お、お先に失礼しまーす。
何よ!! ワタシの方が優先順位、高いはずでしょ!
頑張って勉強してるからご褒美にデートしてあげてもいいかなって考えていたのに!
ドッキーン!!
はいはーい。今回は何でしょうか。
宿題カモーン!
まったくもう!
晴子さんからの宿題(4)
0秒から59秒までカウントするプログラムを作ってね。左右2つの7セグメントLEDを使って、2けた同時に表示できないとダメよ!!
今回はH8マイコンの周辺機能の1つ、タイマAを使ってみました。タイマAはとてもシンプルで使い方も簡単です。マイコンのタイマは種類も豊富で、もっと多くの機能を持っています。ですから、実際にはさらに複雑なプログラムになることでしょう。
また今回は、割り込み処理についても解説しました。制御プログラミングで、割り込み処理は必修といえます。リアルタイムOSのように、割り込み処理を隠蔽(いんぺい)したり、処理プログラム間の協調動作をサポートするソフトウェアもありますが、今回はその基礎となる技術です。
次回のテーマは「7セグメントLEDのダイナミック点灯」です。お楽しみに!(次回に続く)
Copyright © ITmedia, Inc. All Rights Reserved.