第 7 回 割り込み

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

第4回のプログラム演習では、プログラムを終了させる際に、CPU を停止させ るのではなく、 rjmp PC により無限ループに入るようにしまし た。 また、一定の時間を待つのに「ビジーウェイト」という手法で、何もしないこ とを何万回と繰り替えして一定の時間を作りました。

この様な手法はどのコンピュータでも使える基本的な手法ですが、何もしてい ないときに電力を抑えたり、あるいは、複数のプログラムを平行して動かした り、外部と協調して動作させるなどには向きません。

そこで、今回はマイコンの機能として、CPU を止めたり、一定の時間をタイマー で計ってプログラムを動かします。

7-1. プログラムの待機

ATtiny2313 には sleep 命令があります。 この sleep 命令でどのような状態になるかはプログラム内部で 3 通りに指定 することができます(p.30)。 外部からの割り込み信号が来るまでほとんどの機能を停止するモードから、単 に CPU を止めるだけのモードもあります。 今回は、第4回の例4-1で使用できる、すべての I/O が存続していながら CPU だけを止める Idle モードを選びます。 Idle モードを利用するには次のようにします。

  1. MCU コントロールレジスタ(MCUCR)の SM1,SM0 をともに 0 にして Idle モードを指定する
  2. MCU コントロールレジスタ(MCUCR)の SE を 1 にして、 Sleep 命令を有 功にする
  3. 必要に応じて sleep 命令を指定し、 Idle モードに入る

これを例4-1に取り入れたのが次のプログラムです。

例7-1


;************
;*  例7-1 *
;************
  .device attiny2313
 .include <tn2313def.inc>
 .cseg
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	ldi	r16,0
	out	ddrd,r16
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idle mode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
	.def	pattern = r16
main:
	ldi   pattern,0b01010101 ; 点灯させるビットパターン
	out   portb,pattern
	sleep	; idle mode へ
	.exit

7-2. 割り込み

外部の刺激などに対して、CPU があらかじめ指定した番地を呼び出すのを 割り込みと言います。 呼び出される番地を割り込みベクタと言います。 AVR では各種の割り込みそれぞれに対して、用意された番地のプログラムを呼 び出します。 これは 1 番地から連続した領域に設定されていますので、直接、割り込みベ クタに処理プログラムを書くことはできません。 そのため、通常はここにはジャンプ命令のみを書いて、割り込み処理プログラ ムへ処理を移します。

ここでは例題として前前回の演習問題4-5 を改造して、タイマ割り込みにより 一定周期でカウンタを増やすプログラムを作ることを考えます。

ATtiny2313 に内蔵されているタイマ Timer0 は 8bit ながら複雑な機能を持っ ています。 今回は、ほぼ 1 秒に一回カウンターを増やして値を表示するだけの処理をす るのが目標です。

これを実現するには、一定時間毎にカウンターを 1 増やして値を表示するプ ログラムを作る一方、初期設定をした後、カウンターを表示してタイマーをス タートした後 sleep するプログラムが必要になります。 なお、sleep中に割り込みが発生すると割り込みを処理した後、 sleep 命令の 次に制御が戻りますので、割り込み以外の処理をしない場合は、 sleep 命令 だけを実行する無限ループを作ります。

割り込みの設定

今回は単純なモードとして normal モードを使用します(pp.66-67)。 各モードの設定は pp.73-79 のレジスタを参照します。

Timer0 は 0x00 から数えて 0xff まで加算した後、0x00に戻る際にオーバフロー 割り込みを発生させます。 つまり、割り込みベクタは OVF0addr = 0x0006 になります。 そのための設定ですが、以下のようになります。

レジスタ名意味
TCCR0A0b00000000normalモードはすべて0
TCCR0B0b00000101クロックの 1024分の1で割り込 み発生
TIMSK0b00000010オーバーフロー検知だけ有 効

ATtiny2313 のシステムクロックは 8MHz に設定してあります(p.25)。 また、出荷時のヒューズビット CKDIV8 は ON になっています(p.29)。 従って、CLKPR をデフォルトのままで使用した場合、システムは 8M/8=1MHz で動作します。 そのため、 1024 で分周した後、さらに 256 回に一回の割合で割り込みが発 生するとすると、割り込みは 1024*256/1M ≒ 0.26 秒に一回発生することに なります。

例7-2

演習 4-5 のプログラムを改造します。 counter という変数を用意し、次のサブルーチンを用意します。

  1. counter の値を表示するサブルーチン
  2. counter を 1 増やし、 counter の値を表示するサブルーチン

このような準備をした後、次のプログラムを作ります。

  1. sleepの設定をします
  2. 割り込みの設定をします。
  3. counterを 0 にし、LED を光らせます。
  4. sleep を含む無限ループに入ります。

また、割り込みの処理は次のようにします。

  1. まず OVF0addr 番地から割り込み処理のプログラムに飛ぶようにします。
  2. counter を1増やし、 LEDを光らせます
  3. reti 命令により割り込みを終了します。

なお、割り込みの設定は OPTION_REG に Timer0 の仕様を入れます。 今回は内部クロックを使い、 Prescaler を 1:256 で使用します。 (デフォルト値は全て 1 にセットされています(データシート TABLE 6-1 p. 47)。 そして、 INTCON レジスタの割り込み許可 T0IE をセットし、割り込みフラグ T0IF をクリアし、全体の割り込み許可 GIE をセットします。

プログラムは次のようになります


;**********
;*  例7-2 *
;**********
.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000101	; normal mode, prescaler=1024
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16
.def	pattern = r16
.def	counter = r17	

	clr	counter	
	rcall	dispcounter
	sei
main:
	sleep	
	rjmp	main

timer0vec:
	rcall	inccounter
	rcall	dispcounter
	reti

dispcounter:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcounter1
	inc	ZH
dispcounter1:
	lpm	pattern,Z
	out	portb,pattern
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)

.exit

例7-3

例7-2ではカウンタの動きが速すぎます。 Prescaler を使用しても最大で 1/256 にしかなりません。 内部クロック 4MHz では 1 マシンサイクルが 1μ秒なので、カウンタ一つ 増やす間隔は 256×256μ秒 ≒ 0.066 秒に一回になってます。 これを大体 1 秒の間隔にするにはさらに 15 倍程度遅くする必要があります。

そこで大体 1 秒毎に増えるカウンタを作りましょう。 そのためには割り込みがかかっても 15 回に一回しか反応しないようにします。 その機能を持つサブルーチンの名前を Postscalerと呼ぶ事にしま す。 これは新たにレジスタを使用して、 15 回中 14 回まではレジスタを加算する だけで戻り、15回目にレジスタを 0 にして、指定のサブルーチンを呼ぶもの です。


;**********
;*  例7-3 *
;**********
.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000101	; normal mode, prescaler=1024
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16
.def	pattern = r16
.def	counter = r17	
.def	pcounter = r18
.equ	ptime	= 4
	ldi	pcounter,ptime
	clr	counter	
	rcall	dispcounter
	sei
main:
	sleep	
	rjmp	main

timer0vec:
	rcall	postscaler
	reti

postscaler:
	dec	pcounter
	breq	postscaler1
	ret
postscaler1:
	ldi	pcounter,ptime
	rjmp	target

target:
	rcall	inccounter
	rcall	dispcounter
	ret

dispcounter:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcounter1
	inc	ZH
dispcounter1:
	lpm	pattern,Z
	out	portb,pattern
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)

.exit

演習7-1

LED の小数点を約 0.5 秒程度で点滅させなさい。 さらに、 PORTD4 に接続したスイッチを押す度に LED の数字が増えるようにしな さい。

なお、レジスタ中の複数のビットを On, Off するには AND と OR 演算が有効で す。bit X を bit Y に変えるには (( X and 0) or Y) (C 言語だと X&=0; X|=Y;)とします。

7-3. LED の明るさを変える(DUTY 比)

LED の明るさを変えるには二つの方法があります。 今回は DUTY 比のコントロールについて考えます。 この方法は、早い周期で LED を点けたり消したりすることで、平均出力を下げる ことにより、人間の目には暗く見えるようにすることです。 この時、全体の中で ON になっている比率を DUTY 比と呼びます。

例7-4

例 5-3 を思い出して下さい。 これは、PORTB0 から PORTB7 まで順番に点灯してくというプログラムでした。 その演習では、一つのポートの LED を点灯してから、次のポートを点灯させ るまで時間待ちをしました。 そこで、この waitを外したものを動かしてみましょう。 単純で改造がしやすい 5-3 2 のプログラムを使用します。 改造した物が次になります。 なお、全部消灯するパターンも取り除いています。


;**********
;*  例7-4 *
;**********
  .include <tn2313def.inc>
 .cseg
 
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	.equ	time=3
	.def	pattern = r16

	.macro	display
	ldi	pattern,@0
	out portb,pattern
	.endmacro
main:
	display	0b00000001
	display	0b00000011
	display	0b00000111
	display	0b00001111
	display	0b00011111
	display	0b00111111
	display	0b01111111
	display	0b11111111
	rjmp	main
.exit

実行すると、 LED の明るさが揃ってないことがわかります。

この場合、PORTB0 の duty 比は 100% です。一方、 PORTB7 の duty 比は1/8 = 12.5% になります。 但し、実験してみれば分かるように duty 比の高い LED の明るさの違いはほ とんど分かりません。 人間の目は対数的な特性があるため、指数関数的な変化でないとはっきり把握 できません。 つまり、 duty 比を連続して変化させる時は、比率を連続的に変化させても効 果が薄いことに注意します。

例7-5

小数点の明るさを変化させることを考えましょう。 DUTY比を変えるには小数点だけを素早く ON, OFF させます。 つまり小数点は常に同じ値ではなく、割り込みにより On/OFF を繰り返します。 つまり、タイマ割り込みを使用し、 duty 比が 25% になるように 4 回に 1 回だけ set し、残りは clear するようにします。 なお比較のために、 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 の時に、 小数点を 光らせ、それ以外の時に小数点を消灯することで、 DUTY 比 = (dutyrate - 1)/countrate を実現します。 25% にするには countrate = 4, dutyrate = 2 とします。


;**********
;*  例7-5 *
;**********

.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000001	; normal mode, prescaler=off
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16
.def	pattern = r16

	ldi	pattern,0b11110111	; pattern = 8
	out portb,pattern
	sei
main:
	sleep	
	rjmp	main

timer0vec:
	push	r16
	in		r16,SREG
	push	r16
	rcall	duty
	pop		r16
	out		SREG,r16
	pop		r16
	reti
	.def	dutycounter = r17
	.equ	dcycle = 8
	.equ	drate = 1
duty:
	inc	dutycounter
	cpi	dutycounter,dcycle
	brcs	duty1
	clr	dutycounter
duty1:
	cpi	dutycounter,drate
	brcs	onstate
	rjmp	offstate
onstate:
	sbi	portb,3
	ret
offstate:
	cbi	portb,3
	ret

.exit

例7-6

duty 比 1, 1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128 という階調を考えます。 これを RD4 に接続した sw により切替えることを考えます。 但し、これらは計算で求めるのではなく、以前に LED に数字を出した時のよ うにテーブルで参照するようにします。 W レジスタに求める duty 比を入れて、 getduty とすると必要な比 が W レジスタに得られるようにします。 但し、 0 の時、 128 を示し、 1 の時 64, 2 の時 32となる値、つまり最小 が 1 となる相対値を返します。

例7-5同様に PORTB は小数点以外は全て点灯させ、小数点の明るさを変えます。

プログラムは基本的には 例7-5 と演習5-6 のプログラムを流用します。 但し duty 比はプログラムの中で 1 を加え、 duty 比 = (dutyrate - 1)/maxcount ではなく、 duty 比 = dutyrate/maxcount となるようにします。

このプログラムを実現するには、 counter の値により duty_ratio を変化させ る必要があります。 つまり、 sw が押されるたびに、 counter の値を変化させ、その値により duty 比を変化させる必要があります。


;************
;*  例7-6 *
;************

.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000001	; normal mode, prescaler=off
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16
.def	pattern = r16

	ldi	pattern,0b11110111	; pattern = 8
	out portb,pattern
.def	counter = r17
.def current = r18
.def	last = r19
 	clr	counter	
	sei
main:
	rcall	setduty

	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	rcall	inccounter
pressed:
notpressed:
	mov	last,current
	rjmp	main


timer0vec:
	push	r16
	in		r16,SREG
	push	r16
	rcall	duty
	pop		r16
	out		SREG,r16
	pop		r16
	reti
	.def	dutycounter = r20
	.def	dcycle = r21
	.equ	drate = 1
duty:
	inc	dutycounter
	cp	dutycounter,dcycle
	brcs	duty1
	clr	dutycounter
duty1:
	cpi	dutycounter,drate
	brcs	onstate
	rjmp	offstate
onstate:
	sbi	portb,3
	ret
offstate:
	cbi	portb,3
	ret

setduty:	;counterの値に対応するduty比を設定
	ldi	ZH,high(startdutydata<<1)
	ldi	ZL,low(startdutydata<<1)
	add	ZL,counter
	brcc	setduty1
	inc	ZH
setduty1:
.equ	mask = 0b00001000
	lpm	dcycle,Z
	ret

inccounter:
	inc	counter
	cpi	counter,dutymax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdutydata:
	.db	1,2,4,8,16,32,64,128
enddutydata:
.equ	duty_start_adr = startdutydata <<1
.equ	duty_end_adr = enddutydata << 1
.equ	dutymax = low(duty_end_adr - duty_start_adr)

.exit

演習7-2

例7-6のプログラムで、 PORTB には counter の値を表示するように改造し なさい。

7-4. 複数の LED のコントロール

DUTY 比 50% の LED はそれほど暗くなく、これを標準の明るさとして用いて も問題ない明るさです。 したがって、二つの別の LED を高速に交互に点灯させると、人間の目には二 つの別々の LED が同時に光っているように見えます。

そこで、7 セグメント LED の二桁表示をすることを考えます。 カソードコモン LED の場合、カソードを GND に繋ぎますが、ここにスイッチ を付けることで、 LED の点灯、消灯をコントロールできます。 スイッチは高速で、 PIC の出力により ON, OFF が出来なければなりません。 そのためトランジスタのスイッチング回路を使います。 回路図を示します。

7-5. 演習の解説

演習7-1

始めに小数点を約 0.5 秒程度で点滅させることを考えます。 割り込みは Prescaler を 1:256 で使用し、さらに 7 回に一回毎に点滅させ ます。 点滅は点滅パターンを用意して PORTB に対して XOR をかけることで行います。 著者の回路では RB3 が小数点になってますので、 b'00001000' で XOR を行 います。

一方、 RD4 に接続したスイッチを押す度に LED の数字が増えるようにするに は、演習5-6 のプログラムを流用します。 但し、 PORTB にダイレクトに書き込むと小数点の点滅に影響してしまいます。 そこで、ビットパターンを特定の場所だけ上書きすることを考えます。 そのためには、「特定の場所を指定する」情報を考えなくてはいけません。 今、考えるのは PORTB の RA0 を温存したまま RA1 から RA7 までを変更する ことです。

  1. 特定のビットを 1 にする論理演算は or で行います。つまり前のビットが X だった時、 X or 1 = 1 となります。 したがって、パターン中の 1 は or により書き込むことが出来ます。
  2. 特定のビットを 0 にするには and を使います。前のビットが X だった時、 X and 0 = 0 となります。
  3. それでは特定のビットを Y(=0,1) にするにはどうすれば良いでしょうか? 0 と or を取れば Y になりますので、 X と 0 を and をとってから Y と or を取ればよいです。 つまり、 ( X and 0 ) or Y とすることで、 X を Y にすることが出来ます。

つまり、PORTB に pattern を書き込むには、書き込みたい部分を 0 にした マスクと and を取り、それから pattern と or を取れば良いで す。


;************
;*  演習7-1 *
;************
.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
	
	ldi	r16,1<<PIND4
	out	ddrd,r16

initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000101	; normal mode, prescaler=1024
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16



.def	pattern = r16
.def	counter = r17	
.def	pcounter = r18
.equ	ptime	= 2
	.def	last = r19
	.def	current = r20
	.def	work = r21

	clr	counter	
	sei
main:
	rcall	dispcounter

	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	rcall	inccounter
pressed:
notpressed:
	mov	last,current
	rjmp	main


timer0vec:
	push	r16
	in		r16,SREG
	push	r16
	rcall	postscaler
	pop		r16
	out		SREG,r16
	pop		r16
	reti

postscaler:
	inc	pcounter
	cpi	pcounter,ptime
	breq	postscaler1
	ret
postscaler1:
	clr	pcounter
	rjmp	target

target:
	rcall	blinkdot
	ret

blinkdot:
	sbis	portb,3	; dot は 3bit目
	rjmp	dotoff
doton:
	cbi	portb,3
	ret
dotoff:
	sbi	portb,3
	ret

dispcounter:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcounter1
	inc	ZH
dispcounter1:
.equ	mask = 0b00001000
	cli
	in	work,portb
	andi	work,mask
	lpm	pattern,Z
	or	work,pattern
	out	portb,work
	sei
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)

.exit

演習7-2

例7-6のプログラムに、従来のプログラム中にある inccount, dispcount を 組み込むだけです。


;************
;*  演習7-2 *
;************
.include <tn2313def.inc>

.cseg
.org 0x0000
	rjmp reset
.org	OVF0addr
	rjmp	timer0vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,low(RAMEND)
	out	SPL,r16
	ldi	r16,0xff
	out	ddrb, r16
initsleep:
	in	r16,mcucr
	cbr	r16, 1<<sm0 | 1<<sm1	;idlemode
	sbr	r16, 1<<se	; enable sleep
	out	mcucr, r16
inittimer0:
	ldi	r16,0b00000000	;normal mode
	out tccr0a,r16
	ldi	r16,0b00000001	; normal mode, prescaler=off
	out	tccr0b, r16
	ldi	r16, 0b00000010 ; enable interruption for overflow
	out	timsk, r16
.def	pattern = r16

	ldi	pattern,0b11110111	; pattern = 8
	out portb,pattern
.def	counter = r17
.def current = r18
.def	last = r19
 	clr	counter	
	sei
main:
	rcall	setduty
	rcall	dispcounter

	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	rcall	inccounter
pressed:
notpressed:
	mov	last,current
	rjmp	main


timer0vec:
	push	r16
	in		r16,SREG
	push	r16
	rcall	duty
	pop		r16
	out		SREG,r16
	pop		r16
	reti
	.def	dutycounter = r20
	.def	dcycle = r21
	.equ	drate = 1
duty:
	inc	dutycounter
	cp	dutycounter,dcycle
	brcs	duty1
	clr	dutycounter
duty1:
	cpi	dutycounter,drate
	brcs	onstate
	rjmp	offstate
onstate:
	sbi	portb,3
	ret
offstate:
	cbi	portb,3
	ret





setduty:	;counterの値に対応するduty比を設定
	ldi	ZH,high(startdutydata<<1)
	ldi	ZL,low(startdutydata<<1)
	add	ZL,counter
	brcc	setduty1
	inc	ZH
setduty1:
	lpm	dcycle,Z
	ret

inccounter:
	inc	counter
	cpi	counter,dutymax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret

startdutydata:
	.db	1,2,4,8,16,32,64,128
enddutydata:
.equ	duty_start_adr = startdutydata <<1
.equ	duty_end_adr = enddutydata << 1
.equ	dutymax = low(duty_end_adr - duty_start_adr)



.def work = r22
dispcounter:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcounter1
	inc	ZH
dispcounter1:
.equ	mask = 0b00001000
	cli
	in	work,portb
	andi	work,mask
	lpm	pattern,Z
	or	work,pattern
	out	portb,work
	sei
	ret


startdata:
	.db	0b01110111,	0b00010100	;0,1
	.db	0b10110011,	0b10110110	;2,3
	.db	0b11010100,	0b11100110	;4,5
	.db	0b11100111,	0b00110100	;6,7
	.db	0b11110111,	0b11110110	;8,9
	.db	0b11110101,	0b11000111	;A,b
	.db	0b01100011,	0b10010111	;C,d
	.db	0b11100011,	0b11100001	;E,F
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1


.exit

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科