このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
なお、この章で定義したマクロを含むマクロ集は gs25.inc とまとめてあります。
外部の刺激などに対して、CPU があらかじめ指定した番地を call するのを 割り込みと言います。 call される番地を割り込みベクタと言います。PIC では 4 番地 が割り込みベクタです。
ここでは前前回の演習問題4-5 を改造して、タイマ割り込みにより一定周期でカ ウンタを増やすプログラムを作ることを考えます。
PIC に内蔵されているタイマ Timer0 は ffh から 00h に変わる時に割り込み が発生します。 この時呼び出される、counter を 1 増やし、 counter の値を表示するサブルー チンを呼ぶプログラムを作ります。
一方、PIC が起動する時に実行するのは初期設定のみ、つまり、counter を 0 にし、それを表示することだけです。 これを終えて、主プログラムは無限ループに入り、全ての処理を割り込みに委 ねます。
PIC の内部クロックは 4MHz です(データシート 14.2.4 p.98)。 4 クロックで1 命令実行しますので(データシート 3.1, 3.2 p.13)、PIC は内部クロックで一秒間に 100万命令実行します。 一つの命令が動作する最小時間を マシンサイクルと言います。 Timer0 を内部クロックで動作させると、 1 マシンサイクル毎に 1 つずつ 8bit のカウンタが増えて行きます。そして、オーバフローした時に割り込みが かかります。
Timer0 にはプリスケーラがあります(データシート 6.3 Timer0 Prescaler p.46)。 これは割り込み間隔を調整するため、Timer0 のカウンタを増やす間隔を 2 マ シンサイクル毎から 256 マシンサイクル毎までに調整するものです。
演習 4-5 のプログラムを改造します。 counter という変数を用意し、次のサブルーチンを用意します。
このような準備をした後、次のプログラムを作ります。
また、割り込みの処理は次のようにします。
なお、割り込みの設定は OPTION_REG に Timer0 の仕様を入れます。 今回は内部クロックを使い、 Prescaler を 1:256 で使用します。 (デフォルト値は全て 1 にセットされています(データシート TABLE 6-1 p. 47)。 そして、 INTCON レジスタの割り込み許可 T0IE をセットし、割り込みフラグ T0IF をクリアし、全体の割り込み許可 GIE をセットします。
プログラムは次のようになります
;**********
;* 例6-1 *
;**********
#include "gs25.inc"
cblock
savew
savest
endc
org 0x0000
goto start
org 0x0004
goto intvec
org 0x0008
start
enable_PB
clrwdt
banksel OPTION_REG
; movlw b'11010000' ; 内部クロック、 PS=1:2
movlw b'11010111' ; 内部クロック、 PS=1:256
movwf OPTION_REG
banksel INTCON
bsf INTCON,T0IE
bcf INTCON,T0IF
bsf INTCON,GIE
counter_init
main
goto main
intvec
movwf savew
movf STATUS,0
movwf savest
btfss INTCON,T0IF
goto nottimer0
bcf INTCON,T0IF
call inccounter
nottimer0
movf savest,0
movwf STATUS
movf savew,0
retfie
counter_prg pat_end-pat_start
getpat_num_prg
end
ここで、この割り込みに関する部分をマクロにしましょう。
timer0_init macro psmode
cblock
savew
savest
endc
clrwdt
banksel OPTION_REG
movlw psmode
movwf OPTION_REG
banksel INTCON
bsf INTCON,T0IE
bcf INTCON,T0IF
bsf INTCON,GIE
endm
timer0_prg macro func
timer0
movwf savew
movf STATUS,0
movwf savest
btfss INTCON,T0IF
goto nottimer0
bcf INTCON,T0IF
call func
nottimer0
movf savest,0
movwf STATUS
movf savew,0
retfie
endm
マクロ化したプログラムは次のようになります。
;************
;* 例6-1 2 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PB
timer0_init b'11010111' ; 内部クロック、 PS=1:256
counter_init
main
goto main
timer0_prg inccounter
counter_prg pat_end-pat_start
getpat_num_prg
end
例6-1ではカウンタの動きが速すぎます。 Prescaler を使用しても最大で 1/256 にしかなりません。 内部クロック 4MHz では 1 マシンサイクルが 1μ秒なので、カウンタ一つ 増やす間隔は 256×256μ秒 ≒ 0.066 秒に一回になってます。 これを大体 1 秒の間隔にするにはさらに 15 倍程度遅くする必要があります。
そこで大体 1 秒毎に増えるカウンタを作りましょう。 そのためには割り込みがかかっても 15 回に一回しか反応しないようにします。 その機能を持つサブルーチンの名前を Postscalerと呼ぶ事にしま す。
;**********
;* 例6-2 *
;**********
#include "gs25.inc"
#define post_rate d'15'-1
cblock
post_count
endc
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PB
movlw post_rate
movwf post_count
timer0_init b'11010111' ; 内部クロック、 PS=1:256
counter_init
main
goto main
timer0_prg postscaler
postscaler
decfsz post_count,1
return
movlw post_rate
movwf post_count
call inccounter
return
counter_prg pat_end-pat_start
getpat_num_prg
end
この postscaler もマクロ化しましょう。
postscaler_init macro rate
#define post_rate rate
cblock
post_count
endc
movlw post_rate
movwf post_count
endm
postscaler_prg macro func
postscaler
decfsz post_count,1
return
movlw post_rate
movwf post_count
call func
return
endm
マクロ化すると次のようになります。
;************
;* 例6-2 2 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PB
postscaler_init d'15'-1
timer0_init b'11010111' ; 内部クロック、 PS=1:256
counter_init
main
goto main
timer0_prg postscaler
postscaler_prg inccounter
counter_prg pat_end-pat_start
getpat_num_prg
end
LED の小数点を約 0.5 秒程度で点滅させなさい。 さらに、 RA5 に接続したスイッチを押す度に LED の数字が増えるようにしな さい。
なお、レジスタ中の複数のビットを On, Off するには AND と OR 演算が有効で す。bit X を bit Y に変えるには (( X and 0) or Y) (C 言語だと X&=0; X|=Y;)とします。
LED の明るさを変えるには二つの方法があります。 今回は DUTY 比のコントロールについて考えます。 この方法は、早い周期で LED を点けたり消したりすることで、平均出力を下げる ことにより、人間の目には暗く見えるようにすることです。 この時、全体の中で ON になっている比率を DUTY 比と呼びます。
例 4-3 を思い出して下さい。 これは、RB0 から RB7 まで順番に点灯してくというプログラムでした。 その演習では、一つのポートの LED を点灯してから、次のポートを点灯させ るまで時間待ちをしました。 そこで、この waitを外したものを動かしてみましょう。 単純で改造がしやすい 4-3 2 のプログラムを使用します。 改造した物が次になります。 なお、全部消灯するパターンも取り除いています。
;**********
;* 例6-3 *
;**********
#include "gs2.inc"
display macro pattern
movlw pattern
movwf PORTB
endm
org 0x0000
goto start
org 0x0008
start
enable_PB
main
display b'00000001'
display b'00000011'
display b'00000111'
display b'00001111'
display b'00011111'
display b'00111111'
display b'01111111'
display b'11111111'
goto main
end
実行すると、 LED の明るさが揃ってないことがわかります。
この場合、RB0 の duty 比は 100% です。一方、 RB7 の duty 比は1/8 = 12.5% になります。 但し、実験してみれば分かるように duty 比の高い LED の明るさの違いはほ とんど分かりません。 人間の目は対数的な特性があるため、指数関数的な変化でないとはっきり把握 できません。 つまり、 duty 比を連続して変化させる時は、比率を連続的に変化させても効 果が薄いことに注意します。
PORTA の表示パターンの明るさを変化させることを考えましょう。 DUTY比を変えるには PORTA の値を素早く ON, OFF させます。 つまり PORTA は常に同じ値ではなくなります。 そのため、 PORTA の表示パターンを A には書かず、別の領域に書くことにし ます。 そして、タイマ割り込みを使用し、 duty 比が 25% になるように 4 回に 1 回だけ表示するようにします。 なお比較のために、 PORTB は全て点灯させておきます。
プログラムにおいて、レジスタ crate の値は割り込みがかかる度に countrate, countrate-1, ... ,3,2,1, countrate, countrate-1, ... ,3,2,1, と周期的に変化して行きます。 そして、その度に あらかじめ W レジスタに入れた dutyrate の値から引き算します。 すると、crate - dutyrate < 0 となる時は C フラグが 0 になり、そうでなければ 1 になります。 ここで、 C フラグが 0 の時、つまり 1 ≤ crate < dutyrate の時に、PORTA を 光らせ、それ以外の時に消灯することで、 DUTY 比 = (dutyrate - 1)/countrate を実現します。 25% にするには countrate = 4, dutyrate = 2 とします。
;**********
;* 例6-4 *
;**********
#include "gs25.inc"
#define countrate d'4'
#define dutyrate d'1'
cblock
crate
counter
patterna
endc
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PAPB
timer0_init b'11010000' ; 内部クロック、 PS=1:2
movlw b'11111111'
movwf PORTB
movwf patterna
movlw countrate
movwf counter
movlw dutyrate
movwf crate
main
goto main
timer0_prg duty
duty
movf counter,0
subwf crate,0
btfsc STATUS,C
goto raon
clrf PORTA
goto dend
raon
movf patterna,0
movwf PORTA
dend
decfsz counter,1
return
movlw countrate
movwf counter
return
end
なお、ここで duty もマクロ化しておきます。 on にする時と、 off にする時をサブルーチンで指定するようにします。 すると次のようにマクロ化できます。
duty_init macro
cblock
duty_ratio,duty_period,duty_counter
endc
endm
duty_prg macro func_off, func_on
duty
movf duty_counter,0
subwf duty_ratio,0
btfsc STATUS,C
goto duty_on
call func_off
goto duty_end
duty_on
call func_on
duty_end
decfsz duty_counter,1
return
movf duty_period,0
movwf duty_counter
return
endm
これを使用すると、上記のプログラムは次のようになります。
;***********
;* 例6-4 2*
;***********
#include "gs25.inc"
cblock
patterna
endc
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PAPB
timer0_init b'11010000' ; 内部クロック、 PS=1:2
duty_init
movlw d'4'
movwf duty_period
movwf duty_counter
movlw d'1'
movwf duty_ratio
movlw b'11111111'
movwf PORTB
movwf patterna
main
goto main
timer0_prg duty
duty_prg porta_off, porta_on
porta_off
clrf PORTA
return
porta_on
movf patterna,0
movwf PORTA
return
end
duty 比 1, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 という階調を考えます。 これを RA5 に接続した sw により切替えることを考えます。 但し、これらは計算で求めるのではなく、以前に LED に数字を出した時のよ うにテーブルで参照するようにします。 W レジスタに求める duty 比を入れて、 getduty とすると必要な比 が W レジスタに得られるようにします。 但し、 0 の時、 128 を示し、 1 の時 64, 2 の時 32となる値、つまり最小 が 1 となる相対値を返します。
例6-4同様に PORTB は全て点灯させ、PORTA のみの明るさを変えます。
プログラムは基本的には 例6-4 と演習4-6 のプログラムを流用します。 但し duty 比はプログラムの中で 1 を加え、 duty 比 = (dutyrate - 1)/maxcount ではなく、 duty 比 = dutyrate/maxcount となるようにします。
このプログラムを実現するには、 counter の値により duty_ratio を変化させ る必要があります。 つまり、 sw が押されるたびに、 counter の値を変化させ、その値により duty_ratio を与える必要があります。 このサブルーチンを set_rate_by_counter という名前にしましょう。
;************
;* 例6-5 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PAPB
timer0_init b'11010000' ; 内部クロック、 PS=1:2
cblock
patterna
endc
movlw b'11111111'
movwf PORTB
movwf patterna
duty_init
cblock
counter
endc
clrw
movwf counter
call getduty ; duty 0 を獲得
movwf duty_period
movwf duty_counter
call set_rate_by_counter
sw_init
sw_prg swfunc
swfunc
call inccounter
call set_rate_by_counter
return
inccounter
incf counter,1
movlw endduty-startduty
subwf counter,0
btfsc STATUS,Z
clrf counter
return
timer0_prg duty
set_rate_by_counter
movf counter,0
call getduty
movwf duty_ratio
return
duty_prg porta_off, porta_on
porta_on
movf patterna,0
movwf PORTA
return
porta_off
clrf PORTA
return
getduty
addwf PCL,1
startduty
retlw d'128'
retlw d'64'
retlw d'32'
retlw d'16'
retlw d'8'
retlw d'4'
retlw d'2'
retlw d'1'
endduty
end
なお、このプログラムだと、PORTA がちらついて見えます。 そのため、割り込みの周期を上げる必要があります。 一つは 1:2 である Prescaler を使わない(OPTION_REG<PSA> を 0 にす る)こと。もう一つは 128 をあきらめ、 64 や 32 にまでにするということが 考えられます。 ちらつきがなくなるよう、調整してみて下さい。
ちなみに筆者は下記のように PSA を on にして、 128 のみを削った状態で ちらつきがきにならなくなりました(個人差あり)。
;************
;* 例6-5-2 *
;************
timer0_init b'11011000' ; 内部クロック、 PS 不使用
getduty
addwf PCL,1
startduty
; retlw d'128'
retlw d'64'
retlw d'32'
retlw d'16'
retlw d'8'
retlw d'4'
retlw d'2'
retlw d'1'
endduty
end
さて、この set_duty_by_counter, getduty, porta_on, porta_off もマクロ化しましょう。
set_rate_by_counter_prg macro
set_rate_by_counter
movf counter,0
call getduty
movwf duty_ratio
return
endm
getduty_prg macro
getduty
addwf PCL,1
startduty
; retlw d'128'
retlw d'64'
retlw d'32'
retlw d'16'
retlw d'8'
retlw d'4'
retlw d'2'
retlw d'1'
endduty
endm
porta_init macro
cblock
patterna
endc
movlw b'11111111'
movwf patterna
endm
porta_prg macro
porta_on
movf patterna,0
movwf PORTA
return
porta_off
clrf PORTA
return
endm
マクロを使った例は以下の通り。
;************
;* 例6-5-2 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PAPB
timer0_init b'11011000' ; 内部クロック、 PS 不使用
porta_init
movlw b'11111111'
movwf PORTB
duty_init
cblock
counter
endc
clrw
movwf counter
call getduty ; duty 0 を獲得
movwf duty_period
movwf duty_counter
call set_rate_by_counter
sw_init
sw_prg swfunc
swfunc
call inccounter
call set_rate_by_counter
return
inccounter
incf counter,1
movlw endduty-startduty
subwf counter,0
btfsc STATUS,Z
clrf counter
return
set_rate_by_counter_prg
timer0_prg duty
duty_prg porta_off, porta_on
porta_prg
getduty_prg
end
例6-5のプログラムで、 PORTB には counter の値を表示するように改造し なさい。
DUTY 比 50% の LED はそれほど暗くなく、これを標準の明るさとして用いて も問題ない明るさです。 したがって、二つの別の LED を高速に交互に点灯させると、人間の目には二 つの別々の LED が同時に光っているように見えます。
そこで、7 セグメント LED の二桁表示をすることを考えます。 カソードコモン LED の場合、カソードを GND に繋ぎますが、ここにスイッチ を付けることで、 LED の点灯、消灯をコントロールできます。 スイッチは高速で、 PIC の出力により ON, OFF が出来なければなりません。 そのためトランジスタのスイッチング回路を使います。 回路図を示します。
始めに小数点を約 0.5 秒程度で点滅させることを考えます。 割り込みは Prescaler を 1:256 で使用し、さらに 7 回に一回毎に点滅させ ます。 点滅は点滅パターンを用意して PORTB に対して XOR をかけることで行います。 著者の回路では RB0 が小数点になってますので、 b'00000001' で XOR を行 います。
一方、 RA5 に接続したスイッチを押す度に LED の数字が増えるようにするに は、演習4-6 のプログラムを流用します。 但し、 PORTB にダイレクトに書き込むと小数点の点滅に影響してしまいます。 そこで、ビットパターンを特定の場所だけ上書きすることを考えます。 そのためには、「特定の場所を指定する」情報を考えなくてはいけません。 今、考えるのは PORTB の RA0 を温存したまま RA1 から RA7 までを変更する ことです。
つまり、PORTB に pattern を書き込むには、書き込みたい部分を 0 にした マスクと and を取り、それから pattern と or を取れば良いで す。
;************
;* 演習6-1 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
enable_PAPB
timer0_init b'11010111' ; 内部クロック、 PS=1:256
postscaler_init d'7'-1
counter_init
sw_init
sw_prg inccounter
timer0_prg postscaler
postscaler_prg flashdot
flashdot
movlw b'00000001' ; 反転パターン
xorwf PORTB,1
return
inccounter
incf counter,1
movlw pat_end-pat_start
subwf counter,0
btfsc STATUS,Z
clrf counter
dispcounter
movf counter,0
call getpat
movwf bout ; 出力パターン仮置き
movlw b'00000001' ; マスク
andwf PORTB,0
iorwf bout,0
movwf PORTB
return
getpat_num_prg
end
なお、この改造された dispcounter は従来のプログラムに対して互換性があ りますので、マクロを差し替えます。
例6-5のプログラムに、従来のプログラム中にある inccount, dispcount を 組み込むだけです。
;************
;* 演習6-2 *
;************
#include "gs25.inc"
org 0x0000
goto start
org 0x0004
goto timer0
org 0x0008
start
timer0_init b'11011000' ; 内部クロック、 PS 不使用
enable_PAPB
duty_init
porta_init
counter_init
clrw
call getduty ; duty 0 を獲得
movwf duty_period
movwf duty_counter
sw_init
main
sw_prg func
func
call inccounter
call set_rate_by_counter
return
counter_prg endduty-startduty
set_rate_by_counter_prg
timer0_prg duty
duty_prg porta_off, porta_on
porta_prg
getduty_prg
getpat_num_prg
end