タイマー割り込み(再)


概要

以前にタイマー割り込みを実現したが,割り込み前,割り込み処理,割り込み後,でコンテキストの情報は全く考慮していなかった.つまり,割り込みが入ったあと,割り込み処理でレジスタが破壊されてしまう可能性があった.以前の例では,ほぼタイマー割り込みの処理だけしかなかったので壊されることもないし,壊されたところでどうってことなかったが,今後このタイマー割り込みでスレッドを切り替えることを考えると,コンテキストを保存しておく必要がある.
そこで,コンテキストを保存するようにタイマー割り込みを改良する.さらに,割り込み処理は別のスタックを使う,という改良も行う.

コンテキストの保存

コンテキストの保存とは,つまりレジスタの値を保存することを指す.atmega328pは,r0~r31という,32個のレジスタを持つ.また,SREGというフラグレジスタで状態を管理しているので,いかのようにすれば良い,

  1. 割り込み時にスタックにこれらの値をPUSH
  2. 割り込み処理関数を呼び出す
  3. 割り込み復帰後,スタックに積んであったレジスタの値を戻す.

この処理は,割り込みベクタに登録した関数で行う.

割り込みベクタの改良1

コンテキストを保存するように割り込みベクタの関数を変更する.以前はstart.sに関数を定義していたが,割り込み専用のファイル(intr.s)を用意する

intr.s
.global intr_time
.type   intr_time, @function

intr_time:
    cli
    push r31
    push r30
    push r29
    push r28
    push r27
    push r26
    push r25
    push r24
    push r23
    push r22
    push r21
    push r20
    push r19
    push r18
    push r17
    push r16
    push r15
    push r14
    push r13
    push r12
    push r11
    push r10
    push r9
    push r8
    push r7
    push r6
    push r5
    push r4
    push r3
    push r2
    push r1
    push r0
    in r31, 0x3f ; save SREG to r31
    push r31     ; push SREG
    rcall t0a    ; t0aの呼び出し
    pop r31        ; pop SREG to r31
    out 0x3f, r31  ; set SREG
    pop r0
    pop r1
    pop r2
    pop r3
    pop r4
    pop r5
    pop r6
    pop r7
    pop r8
    pop r9
    pop r10
    pop r11
    pop r12
    pop r13
    pop r14
    pop r15
    pop r16
    pop r17
    pop r18
    pop r19
    pop r20
    pop r21
    pop r22
    pop r23
    pop r24
    pop r25
    pop r26
    pop r27
    pop r28
    pop r29
    pop r30
    pop r31
    reti

先程の説明通り,r31からr0までと,SREG(フラグレジスタ)スタックに積んだあとt0aを呼び出し,戻ってきたら積んだ順番と逆にスタックからPOPする.なお,SREGはメモリの0x3fに存在している(仕様書より).

この時のスタックの様子は以下の通り

このように,割り込み処理をするのに,直前まで実行していたmainのスタックをそのまま利用している.

割り込みベクタの改良2

上記のようにしても動作はするが,今後スレッドを実装することを考えると,割り込み処理は常にどこかのスレッドのスタックを使うことになる.実害はないものの,割り込み処理は専用のスタックを使いたい.具体的には,以下のようにスタックを切り替えて使いたい.

この例では,t0aの呼び出し前にスタックを切り替え(intstack),t0aの処理をしたあと,再び元のスタックに戻して割り込み処理を終える.こうするには,intrstackをリンカスクリプト定義しする.

ld.scr

MEMORY{ 
(略)
    ram(rwx)        : o = 0x800100, l = 0x800600 - 0x800100
    userstack(rw)   : o = 0x800600, l = 0x000000
    intrstack(rw)   : o = 0x800700, l = 0x000000 ; 割り込み用スタック
    bootstack(rw)   : o = 0x8007fc, l = 0x000000
}

SECTIONS
{
(略)

    . = ALIGN(4);
    _end = . ;

    .userstack : {
        _userstack = .;
    } > userstack

    .intrstack : {
        _intrstack = .;
    } > intrstack

  .bootstack : {
        _bootstack = .;
    } > bootstack
}

こうした上で,図のような処理を行うよう,intr_timeを書き換える.

intr.s
intr_time:
        cli
        push r31
(略)
        push r0
        in   r31, 0x3f ; SREG
        push r31
        in   r24, 0x3d ; save current sp low  to r22
        in   r25, 0x3e ; save current sp high to r23
        ldi  r28, lo8(_intrstack) ; save intrrstack lo byte to r28
        ldi  r29, hi8(_intrstack) ; save intrstack hi byte to r29
        out  0x3d, r28 ; save intrstack low   ; change sp to intrstack
        out  0x3e, r29 ; save intrstack high
        push r24       ; 旧SPのlowをintrstackに積む
        push r25       ; 旧SPのhiをintrstackに積む
        eor  r1, r1

        rcall t0a

        pop r29        ; pop old sp hi  from intrstack
        pop r28        ; pop old sp low from intrstack
        out 0x3d, r28  ; set sp low
        out 0x3e, r29  ; set sp high   -> change sp to original
        pop r31        ; restore SREG to r31
        out 0x3f, r31  ; set SREG
        pop r0
(略)
        pop r31
        reti

この実装は,以下のコマンドで試すことができる.

>git clone https://github.com/hiro4669/iosv.git
>cd iosv
>git branch interpt_ver1 origin/interpt_ver1
>git checkout interpt_ver1
>cd interpt
>make
>make write