周期割り込みなんて、簡単ですよ [PSoC]
本日の話題は、PSoC3を使って、周期割り込みを実現する方法です。 今回の例を見ていただけると、PSoCって、普通言われるようなマイコンじゃあないんだなと理解していただけると思います。
周期割り込みって、何だ?
唐突に周期割り込みという言葉が出てきました。 周期割り込みは、周期的に発生する割り込みのことです。 たとえば、1m秒ごとに周期的に割り込みを発生させてとある処理ルーチンを実行させます。 すると、割り込みの回数を数えるだけで、ミリ秒単位の時間を知ることができます。 また、割り込み周期が一定なので、処理ルーチンの処理時間に関係なく、各処理の開始間隔を一定にすることできます。
一定時間ごとに処理ができるようになると、A/D変換のサンプリングやD/A変換の出力タイミングも精確になるので、より性能が高くなります。
周期割り込みといえば、タイマを使うでしょう
従来のマイコンで、周期割り込みを実現しようとすると、通常はタイマモジュールを使用します。 今どきのタイマには、周期が設定できて、タイマカウンタが周期レジスタの値に達したらタイマカウンタを0に戻します。 このときに、出力を変化させたり、割り込みを発生させたりすることができます。 PSoC3にも、タイマがあり、「周期レジスタ」で周期的に割り込みを発生することができます。
Parameter | Value |
---|---|
Resolution | 8-bit |
Period | 250 |
Trigger Mode | None |
Capture Mode | None |
Enable Mode | Software Only |
Run Mode | Continuous |
Interrupts | On TC |
タイマには、1kHzのILO (Internal Low-frequency oscillator)が接続されています。
このクロック・パルスを250個数えることによって、250m秒ごとにinterrupt
出力をアサートしています。
interrupt
出力の信号は、isr_1インスタンスに伝わり、割り込み処理ルーチンを起動します。
Pin_1は、ハードウェア接続の無い、出力専用端子として定義されています。
使用したリソースは、ざっとこんなところです。
Resource Type : Used : Free : Max : % Used ============================================================ Macrocells : 1 : 191 : 192 : 0.52% Unique Pterms : 1 : 383 : 384 : 0.26% Total Pterms : 1 : : : Datapath Cells : 1 : 23 : 24 : 4.17% Status Cells : 1 : 23 : 24 : 4.17% Control/Count7 Cells : 1 : 23 : 24 : 4.17% Sync Cells : 1 : 91 : 92 : 1.09% Interrupts : 1 : 31 : 32 : 3.13%
main.c
の中核は、このようになっています。
CY_ISR(isr_1_isr) { (void)Timer_1_ReadStatusRegister(); // to clear flag Pin_1_Write(Pin_1_Read()?(0):(1)); } void main() { Timer_1_Start(); isr_1_StartEx(isr_1_isr); CYGlobalIntEnable; for(;;) { // Nothing to do } }
isr_1インスタンスとTimer_1インスタンスは、回路図上で接続されていますが、それぞれ独立した存在です。
しかしながら、Timer_1のinterrupt
出力が自動的にクリアされないため、割り込み処理ルーチンから割り込みフラグをクリアする関数Timer_1_ReadStatusRegister()
を呼び出さざるを得ませんでした。
う~ん、美しくない。
処理ルーチンの中では、LEDが接続されたPin_1の出力を反転させています。 250m秒ごとに反転するので、LEDが2Hzで点滅する様子が観測できます。
無理にinterrupt出力を使うことないじゃん
上の例は、interrupt
出力を使ったために、割り込み処理ルーチンでフラグをクリアしなくてはなりませんでした。
isr_1インスタンスは、エッジをつかまえて割り込みを発生させることができるので、隣にあるtc
(Terminal Count) 出力を接続しても、同じ動作をするはずです。
しかも、フラグをクリアする必要がありません。
Parameter | Value |
---|---|
Resolution | 8-bit |
Period | 250 |
Trigger Mode | None |
Capture Mode | None |
Enable Mode | Software Only |
Run Mode | Continuous |
Interrupts | - |
すでに、interrupt
出力は、まったくアサートされなくなりました。
Resource Type : Used : Free : Max : % Used ============================================================ Macrocells : 2 : 190 : 192 : 1.04% Unique Pterms : 1 : 383 : 384 : 0.26% Total Pterms : 2 : : : Datapath Cells : 1 : 23 : 24 : 4.17% Status Cells : 1 : 23 : 24 : 4.17% Control/Count7 Cells : 1 : 23 : 24 : 4.17% Sync Cells : 1 : 91 : 92 : 1.09% Interrupts : 1 : 31 : 32 : 3.13%
消費したリソースは、マクロセルがひとつ増えました。 割り込み出力が必要なくなったので、減るかと期待していたんですけどね。
CY_ISR(isr_1_isr) { Pin_1_Write(Pin_1_Read()?(0):(1)); }
プログラムは、割り込み処理ルーチンからフラグのクリアが消えました。 このプログラムも、2Hzで出力を反転させます。
周期的な信号とそれを受ける割り込み機能があれば十分
これまで、周期割り込みにタイマを使ってきましたが、タイマの機能を十分に使っているとは言えません。 単なる分周器でも十分です。 いっそのこと、クロックを直接つないでしまったら、どうでしょう。 というわけで、4Hzのクロックをつないでみました。
Resource Type : Used : Free : Max : % Used ============================================================ Digital domain clock dividers : 1 : 7 : 8 : 12.50% Interrupts : 1 : 31 : 32 : 3.13%
必要なリソースも、4Hzのクロックを生成するための分周器と割り込みモジュールだけです。
CY_ISR(isr_1_isr) { Pin_1_Write(Pin_1_Read()?(0):(1)); } void main() { isr_1_StartEx(isr_1_isr); CYGlobalIntEnable; for(;;) { // Nothing to do } }
プログラムも、さらに簡単になりました。
Timer_1は、Start()
を呼び出して起動する必要がありますが、Clock_1は、最初から起動しているからです。
このプログラムでも、2HzでLEDが点滅します。
余談1:割り込みコストって、高すぎる
で、よせばいいのに、コンパイル結果を見てしまいました。
; FUNCTION isr_1_isr (BEGIN) 0000 C0E0 PUSH ACC 0002 C0F0 PUSH B 0004 C083 PUSH DPH 0006 C082 PUSH DPL 0008 C085 PUSH DPH1 000A C084 PUSH DPL1 000C C086 PUSH DPS 000E 758600 MOV DPS,#00H 0011 C000 E PUSH ?C?XPAGE1SFR 0013 750000 E MOV ?C?XPAGE1SFR,#?C?XPAGE1RST 0016 C0D0 PUSH PSW 0018 75D000 MOV PSW,#00H 001B C000 PUSH AR0 001D C001 PUSH AR1 001F C002 PUSH AR2 0021 C003 PUSH AR3 0023 C004 PUSH AR4 0025 C005 PUSH AR5 0027 C006 PUSH AR6 0029 C007 PUSH AR7 ; SOURCE LINE # 14 ; SOURCE LINE # 15 002B 120000 E LCALL Pin_1_Read
なんですか?これは。 割り込み処理ルーチンの入口に、これでもかっていうぐらいにレジスタをスタックに押し込む命令が並んでいます。 もちろん、出口にも同じだけスタックからレジスタの値を復帰させる命令が並んでいます。
これは、レジスタの本数が増えたからにほかならないのですが、理由はもう一つあります。 それは、割り込み処理ルーチンの中で関数を呼び出したことです。 このコンパイラは、割り込み処理ルーチンの中で関数を呼び出した場合には、無条件で全レジスタを退避させるコードを生成するらしく、関数の処理内容にかかわらず、このように大げさなスタック操作が行われるようになります。
余談2:オーバヘッドを減らすためにポーリングを使う?
これに対する解決策が、サイプレス社のアプリケーションノートAN60630 - Optimizing 8051 Code in PSoC® 3に書かれています。 解決方法は、割り込み処理ルーチンの中では、フラグをセットするだけにして、実際の作業は、メインルーチンの中で行わせることだそうです。 えっと、つまり、それは、ポーリングってことですか?
uint8 isr_1_flag = 0; CY_ISR(isr_1_isr) { isr_1_flag = 1; } void main() { isr_1_StartEx(isr_1_isr); CYGlobalIntEnable; for(;;) { if (isr_1_flag) { isr_1_flag = 0; Pin_1_Write(Pin_1_Read()?(0):(1)); } } }
コンパイル後のコードは、こうなりました。
; FUNCTION isr_1_isr (BEGIN) 0000 C0E0 PUSH ACC 0002 C083 PUSH DPH 0004 C082 PUSH DPL 0006 C085 PUSH DPH1 0008 C084 PUSH DPL1 000A C086 PUSH DPS 000C 758600 MOV DPS,#00H 000F C000 E PUSH ?C?XPAGE1SFR 0011 750000 E MOV ?C?XPAGE1SFR,#?C?XPAGE1RST ; SOURCE LINE # 16 ; SOURCE LINE # 17 0014 900000 R MOV DPTR,#isr_1_flag 0017 7401 MOV A,#01H
確かにスタッキングの数は減りましたが、まだまだ多いようです。
余談3:フラグに最適な変数クラスがある
アプリケーションノートAN60630 - Optimizing 8051 Code in PSoC® 3には、続きがあります。 フラグには、bitクラスを使え。
PSoC3に使われているCPU 8051 は、ある限られたメモリ空間で、低コストにビット操作を行うことができます。 そのときに使用される変数クラスが、bitです。
bit isr_1_flag = 0;
変更したのは、ここだけです。 コンパイルしてみると、こんなコードが生成されていました。
; FUNCTION isr_1_isr (BEGIN) ; SOURCE LINE # 16 ; SOURCE LINE # 17 0000 D200 R SETB isr_1_flag ; SOURCE LINE # 18 0002 32 RETI ; FUNCTION isr_1_isr (END)
今までの嵐のようなスタッキングは、何だったんだろうという簡単さです。 スタッキングを一切必要としていません。 フラグをセットしたら、割り込み処理ルーチンから戻ります。 ただ、それだけです。
これで、割り込み発生からの遅延が少なくなりますが、ポーリングを使っている時点で、遅延が発生するようにも思います。 はてさて、どっちがお得なんでしょうね。
新しいコンパイラを入手すると、コンパイル結果(アセンブルリスト)を見たくなっちゃいますよね。
最適化したときと、しないときの違いとかw
by Ponta (2011-02-22 23:42)