SSブログ

周期割り込みなんて、簡単ですよ [PSoC]このエントリーを含むはてなブックマーク#

本日の話題は、PSoC3を使って、周期割り込みを実現する方法です。 今回の例を見ていただけると、PSoCって、普通言われるようなマイコンじゃあないんだなと理解していただけると思います。

周期割り込みって、何だ?

唐突に周期割り込みという言葉が出てきました。 周期割り込みは、周期的に発生する割り込みのことです。 たとえば、1m秒ごとに周期的に割り込みを発生させてとある処理ルーチンを実行させます。 すると、割り込みの回数を数えるだけで、ミリ秒単位の時間を知ることができます。 また、割り込み周期が一定なので、処理ルーチンの処理時間に関係なく、各処理の開始間隔を一定にすることできます。

一定時間ごとに処理ができるようになると、A/D変換のサンプリングやD/A変換の出力タイミングも精確になるので、より性能が高くなります。

周期割り込みといえば、タイマを使うでしょう

WS000047.png

従来のマイコンで、周期割り込みを実現しようとすると、通常はタイマモジュールを使用します。 今どきのタイマには、周期が設定できて、タイマカウンタが周期レジスタの値に達したらタイマカウンタを0に戻します。 このときに、出力を変化させたり、割り込みを発生させたりすることができます。 PSoC3にも、タイマがあり、「周期レジスタ」で周期的に割り込みを発生することができます。


Timer_1の設定
ParameterValue
Resolution8-bit
Period250
Trigger ModeNone
Capture ModeNone
Enable ModeSoftware Only
Run ModeContinuous
InterruptsOn 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_1interrupt出力が自動的にクリアされないため、割り込み処理ルーチンから割り込みフラグをクリアする関数Timer_1_ReadStatusRegister()を呼び出さざるを得ませんでした。 う~ん、美しくない。

処理ルーチンの中では、LEDが接続されたPin_1の出力を反転させています。 250m秒ごとに反転するので、LEDが2Hzで点滅する様子が観測できます。

無理にinterrupt出力を使うことないじゃん

WS000048.png

上の例は、interrupt出力を使ったために、割り込み処理ルーチンでフラグをクリアしなくてはなりませんでした。 isr_1インスタンスは、エッジをつかまえて割り込みを発生させることができるので、隣にあるtc (Terminal Count) 出力を接続しても、同じ動作をするはずです。 しかも、フラグをクリアする必要がありません。


Timer_1の設定
ParameterValue
Resolution8-bit
Period250
Trigger ModeNone
Capture ModeNone
Enable ModeSoftware Only
Run ModeContinuous
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で出力を反転させます。

周期的な信号とそれを受ける割り込み機能があれば十分

WS000049.png

これまで、周期割り込みにタイマを使ってきましたが、タイマの機能を十分に使っているとは言えません。 単なる分周器でも十分です。 いっそのこと、クロックを直接つないでしまったら、どうでしょう。 というわけで、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)

今までの嵐のようなスタッキングは、何だったんだろうという簡単さです。 スタッキングを一切必要としていません。 フラグをセットしたら、割り込み処理ルーチンから戻ります。 ただ、それだけです。

これで、割り込み発生からの遅延が少なくなりますが、ポーリングを使っている時点で、遅延が発生するようにも思います。 はてさて、どっちがお得なんでしょうね。


nice!(1)  コメント(1)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 1

コメント 1

Ponta

新しいコンパイラを入手すると、コンパイル結果(アセンブルリスト)を見たくなっちゃいますよね。

最適化したときと、しないときの違いとかw
by Ponta (2011-02-22 23:42) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。