概要
以前にタイマー割り込みを実現したが,割り込み前,割り込み処理,割り込み後,でコンテキストの情報は全く考慮していなかった.つまり,割り込みが入ったあと,割り込み処理でレジスタが破壊されてしまう可能性があった.以前の例では,ほぼタイマー割り込みの処理だけしかなかったので壊されることもないし,壊されたところでどうってことなかったが,今後このタイマー割り込みでスレッドを切り替えることを考えると,コンテキストを保存しておく必要がある.
そこで,コンテキストを保存するようにタイマー割り込みを改良する.さらに,割り込み処理は別のスタックを使う,という改良も行う.
コンテキストの保存
コンテキストの保存とは,つまりレジスタの値を保存することを指す.atmega328pは,r0
~r31
という,32個のレジスタを持つ.また,SREG
というフラグレジスタで状態を管理しているので,いかのようにすれば良い,
- 割り込み時にスタックにこれらの値をPUSH
- 割り込み処理関数を呼び出す
- 割り込み復帰後,スタックに積んであったレジスタの値を戻す.
この処理は,割り込みベクタに登録した関数で行う.
割り込みベクタの改良1
コンテキストを保存するように割り込みベクタの関数を変更する.以前はstart.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
をリンカスクリプト定義しする.
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_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