第 8 回 割り込み

本日の内容


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

今までのプログラム演習では、一定の時間を待つのに「ビジーウェイト」とい う手法で、何もしないことを何万回と繰り替えして一定の時間を作りました。

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

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

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

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

C言語

  1. avr/sleep.h をインクルードする
  2. set_sleep_mode で スリープモードを指定する。 Idle モードにするには set_sleep_mode(SLEEP_MODE_IDLE) を呼び出す。
  3. sleep_enable() を呼ぶ
  4. sleep_cpu()呼び出しで実際に Sleep する
例8-1

#include <avr/sleep.h>
const byte out7seg[] = {6,7,8,9,10,12,13,0xff};
void setup(){
  for(byte i=0; out7seg[i]!=0xff; i++){
    pinMode(out7seg[i], OUTPUT);
  }
  set_sleep_mode(SLEEP_MODE_IDLE);
  sleep_enable();
  digitalWrite(8,HIGH);
  digitalWrite(10,HIGH);
  sleep_cpu();
  digitalWrite(9,HIGH);
}
void loop(){
}

マシン語

AtMega328P の 14章 Power Management and Sleep Modesに記載されています。

  1. Sleep Mode Control Register(SMCR)の SM2,SM1,SM0 をともに 0 にして Idle モードを指定する
  2. 同じく SMCRの SE を 1 にして、 Sleep 命令を有 功にする
  3. 必要に応じて sleep 命令を指定し、 Idle モードに入る

例えば、内蔵LEDを光らせて止まるプログラムは次のように書ける。

例8-2


;************
;*  例8-2 *
;************
 .cseg
 .org 0x0000
	rjmp reset
 .org INT_VECTORS_SIZE
reset:
	ldi	r16,high(RAMEND)
	out	SPH,r16
	ldi	r16,low(RAMEND)
	out	SPL,r16

.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16

	in	r16,smcr
	cbr	r16,<<sm0
	cbr	r16,<<sm1
	cbr	r16,<<sm2
	sbr	r16,<<se

main:
	ldi	r16,0b110100
	out	portb,r16
	sleep
	ldi	r16,0b00111111
	out	portb,r16
.exit

8-2. 割り込み

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

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

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

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

割り込みの設定

今回は単純なモードとして normal モードを使用します(19.7.2 p.134)。 各モードの設定は pp.141-151 のレジスタを参照します。

TC2 は 0x00 から数えて 0xff まで加算した後、0x00に戻る際にオーバフロー 割り込みを発生させます。 つまり、割り込みベクタは TOV2=0x0009 になります。 またPrescalar を使用して、ほぼ1秒に一回割り込みがかかるようにします。 そのための設定ですが、以下のようになります。

レジスタ名意味
TCCR2A0b00000000normalモードはすべて0
TCCR2B0b00000111クロックの 1024分の1で割り込 み発生(モード設定がTCCR2Aと別れていることに注意)
TIMSK20b00000001オーバーフロー検知だけ有 効

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

例8-3

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

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

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

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

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

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

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


;**********
;*  例8-3 *
;**********
.cseg
.org 0x0000
	rjmp reset
.org	OVF2addr
	rjmp	timer2vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,high(RAMEND)
	out	SPH,r16
	ldi	r16,low(RAMEND)
	out	SPL,r16

.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16

initsleep:
	in	r16,smcr
	cbr	r16,1<<sm0
	cbr	r16,1<<sm1
	cbr	r16,1<<sm2
	sbr	r16,1<<se
	out	smcr,r16

inittimer2:
	ldi	r16,0b00000000	;normal mode
	sts	tccr2a,r16
	ldi	r16,0b00000111	; normal mode, prescaler=1024
	sts	tccr2b, r16
	ldi	r16, 0b00000001 ; enable interruption for overflow
	sts	timsk2, r16

.def	counter = r18
.def	pattern = r17

	clr	counter	
	rcall	dispcounter
	sei
main:
	sleep	
	rjmp	main

timer2vec:
	rcall	inccounter
	rcall	dispcounter
	reti
.macro	outport
	ldi	r20,@0
	and	r20,r16	
	in	r21,@1
	andi	r21,~@0
	or	r21,r20
	out	@1,r21
.endmacro

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

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

startdata:
	.db	0b10110111,0b00100100
	.db 0b01110011,0b01110110
	.db 0b11100100,0b11010110
	.db 0b11010111,0b00110100
	.db 0b11110111,0b11110110
	.db 0b11110101,0b11000111
	.db 0b10010011,0b01100111
	.db 0b11010011,0b11010001
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)

.exit

例8-4

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

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


;**********
;*  例8-4 *
;**********

.cseg
.org 0x0000
	rjmp reset
.org	OVF2addr
	rjmp	timer2vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,high(RAMEND)
	out	SPH,r16
	ldi	r16,low(RAMEND)
	out	SPL,r16

.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16

initsleep:
	in	r16,smcr
	cbr	r16,1<<sm0
	cbr	r16,1<<sm1
	cbr	r16,1<<sm2
	sbr	r16,1<<se
	out	smcr,r16

inittimer2:
	ldi	r16,0b00000000	;normal mode
	sts	tccr2a,r16
	ldi	r16,0b00000111	; normal mode, prescaler=1024
	sts	tccr2b, r16
	ldi	r16, 0b00000001 ; enable interruption for overflow
	sts	timsk2, r16

.def	counter = r19
.def	pattern = r17
.def	pcounter = r18
.equ	ptime = 20
	ldi	pcounter,ptime
	clr	counter	
	rcall	dispcounter
	sei
main:
	sleep	
	rjmp	main

timer2vec:
	rcall	postscaler
	reti
postscaler:
	dec		pcounter
	breq	postscaler1
	ret
postscaler1:
	ldi		pcounter,ptime
	rcall	inccounter
	rcall	dispcounter
	reti

.macro	outport
	ldi	r20,@0
	and	r20,r16	
	in	r21,@1
	andi	r21,~@0
	or	r21,r20
	out	@1,r21
.endmacro

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

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

startdata:
	.db	0b10110111,0b00100100
	.db 0b01110011,0b01110110
	.db 0b11100100,0b11010110
	.db 0b11010111,0b00110100
	.db 0b11110111,0b11110110
	.db 0b11110101,0b11000111
	.db 0b10010011,0b01100111
	.db 0b11010011,0b11010001
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)

.exit

8-3. C言語でのタイマー割り込み

C言語で割り込みを行う場合、ライブラリーを使うのが一般的なようです。 Arduino の「スケッチ→ライブラリをインクルード→ライブラリを管理」 を選び、検索欄に MsTimer2 を入れると、MsTimer2 をインストールできます。 これは、 MsTimer2::set(時間間隔,呼び出し関数) で関数を登録した後、 MsTimer2::start() とするだけで自動的に実行できます。

例8-5

例8-7と同様に一定時間毎に別のパターンを表示するプログラムを、割り込み と sleep を使用して書いたのが以下のプログラムです。


/************
*  例8-5 *
************/
#include <avr/sleep.h>
#include <MsTimer2.h>
  // put your setup code here, to run once:
const byte out7seg[]={6,7,8,9,10,11,12,13,0xff};
const byte pattern[]={
		     0b11011110, //0
                      0b10010000, //1
                      0b11001101, //2
                      0b11011001, //3
                      0b10010011, //4
                      0b01011011, //5
                      0b01011111, //6
                      0b11010000, //7
                      0b11011111, //8
                      0b11011011, //9
                      0b11010111, //A
                      0b00011111, //b
                      0b01001110, //C
                      0b10011101, //d
                      0b01001111, //E
			0b01000111 //F

};
byte counter;
void setup() {
  for(byte i=0;out7seg[i]!=0xff;i++){
    pinMode(out7seg[i], OUTPUT);
  }
  counter=0;
  MsTimer2::set(1000,vect);
  dispcounter();
  MsTimer2::start();
}

void dispcounter(){
  for(byte i=0; i<8;i++){
    digitalWrite(out7seg[i],pattern[counter]&1<<i);
  }
}
void inccounter(){
  counter++;
  if(counter>=sizeof(pattern)){
    counter=0;
  }
}
void vect(){
  inccounter();
  dispcounter();
}
void loop() {
  sleep_cpu();
}

演習8-1

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

8-4. LED の明るさを変える(DUTY 比)

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

例8-6

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


;**********
;*  例8-6 *
;**********
.cseg
.org	0x0000
		rjmp	reset
.org	INT_VECTORS_SIZE
reset:
	ldi     r16,high(RAMEND)
	out     SPH,r16 
	ldi		r16,low(RAMEND)
	out		SPL,r16
.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
.equ	pullup	= 0b00110000
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		
.macro	clrport
		in	r16,@0
		andi	r16,~@1
		out	@0,r16
.endmacro
.macro setport
		in	r16,@0
		andi	r16,~@1
		ori	r16,@2
		out	@0,r16
.endmacro
main:
		setport portb,onb,0b00000001
		setport portb,onb,0b00000011
		setport portb,onb,0b00000111
		setport portb,onb,0b00001111
		setport portb,onb,0b00011111
		setport portb,onb,0b00111111
		setport portd,ond,0b01000000
		setport portd,ond,0b11000000
		clrport	portd,ond
		rjmp	main
.exit

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

C言語版

const byte out[]={6,7,8,9,10,11,12,13,0xff};
void setup() {
  for(byte i=0;out[i]!=0xff;i++){
    pinMode(out[i], OUTPUT);
  }
}
void clear(){
  for(byte i=0;out[i]!=0xff;i++){
    digitalWrite(out[i], LOW);
  }
}
byte i=0;
void loop() {
  digitalWrite(out[i++],HIGH);
  delay(1);
  if(out[i]==0xff){
    i=0;
    clear();
  }
}

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

例8-7

小数点の明るさを変化させることを考えましょう。 DUTY比を変えるには小数点だけを素早く ON, OFF させます。 つまり小数点は常に同じ値ではなく、割り込みにより On/OFF を繰り返します。 つまり、タイマ割り込みを使用し、 duty 比が 25% になるように 4 回に 1 回だけ set し、残りは clear するようにします。 なお比較のために、 7SEG LED は小数点以外は全て点灯させておきます。

プログラムにおいて、レジスタ 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 とします。


;**********
;*  例8-7 *
;**********
.cseg
.org 0x0000
	rjmp reset
.org	OVF2addr
	rjmp	timer2vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,high(RAMEND)
	out	SPH,r16
	ldi	r16,low(RAMEND)
	out	SPL,r16

.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16

initsleep:
	in	r16,smcr
	cbr	r16,1<<sm0
	cbr	r16,1<<sm1
	cbr	r16,1<<sm2
	sbr	r16,1<<se
	out	smcr,r16

inittimer2:
	ldi	r16,0b00000000	;normal mode
	sts	tccr2a,r16
	ldi	r16,0b00000001	; normal mode, prescaler=1
	sts	tccr2b, r16
	ldi	r16, 0b00000001 ; enable interruption for overflow
	sts	timsk2, r16


.macro	outport
	ldi	r20,@0
	and	r20,r16	
	in	r21,@1
	andi	r21,~@0
	or	r21,r20
	out	@1,r21
.endmacro

	ldi	r16,0b11110111
	outport	onb,portb
	outport	ond,portd
	sei
main:
	sleep	
	rjmp	main

timer2vec:
	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

C言語版

#include <avr/sleep.h>
#include <MsTimer2.h>
  // put your setup code here, to run once:
const byte out7seg[]={6,7,8,9,10,11,12,13,0xff};

byte counter;
void setup() {
  for(byte i=0;out7seg[i]!=0xff;i++){
    pinMode(out7seg[i], OUTPUT);
  }
  counter=0;
  MsTimer2::set(1,vect);
  write7seg(0b11011111);
  MsTimer2::start();
}
void write7seg(byte x){
  for(byte i=0; i<8;i++){
    digitalWrite(out7seg[i],x&1<<i);
  }
}

#define dutymax 16
#define drate 1
byte dutycounter;
void duty(){
  dutycounter++;
  if(dutycounter==dutymax){
    dutycounter=0;
  }
  if(dutycounter<drate){
    digitalWrite(11,HIGH);
  }else{
    digitalWrite(11,LOW);
  }
}

void vect(){
  duty();
}
void loop() {
  sleep_cpu();
}

例8-5

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 となる相対値を返します。

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

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

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


;************
;*  例8-5 *
;************
.cseg
.org 0x0000
	rjmp reset
.org	OVF2addr
	rjmp	timer2vec
.org INT_VECTORS_SIZE
reset:
	ldi	r16,high(RAMEND)
	out	SPH,r16
	ldi	r16,low(RAMEND)
	out	SPL,r16

.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
.equ	pullup	= 0b00110000

		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16

initsleep:
	in	r16,smcr
	cbr	r16,1<<sm0
	cbr	r16,1<<sm1
	cbr	r16,1<<sm2
	sbr	r16,1<<se
	out	smcr,r16

inittimer2:
	ldi	r16,0b00000000	;normal mode
	sts	tccr2a,r16
	ldi	r16,0b00000001	; normal mode, prescaler=1
	sts	tccr2b, r16
	ldi	r16, 0b00000001 ; enable interruption for overflow
	sts	timsk2, r16


.macro	outport
	ldi	r20,@0
	and	r20,r16	
	in	r21,@1
	andi	r21,~@0
	or	r21,r20
	out	@1,r21
.endmacro

	ldi	r16,0b11110111
	outport	onb,portb
	outport	ond,portd
	sei


.def	counter=r17
		clr		counter


.def	last = r18
.def	current = r19

main:
	rcall	setduty
	in		current,PIND

	sbrc	last,PIND4
	rjmp	lpressed
	sbrs	current,PIND4
	rjmp	lnotpressed
lpressednow:
	rcall	inccounter
lpressed:
lnotpressed:

	sbrc	last,PIND5
	rjmp	rpressed
	sbrs	current,PIND5
	rjmp	rnotpressed
rpressednow:
	rcall	deccounter
rpressed:
rnotpressed:

	mov	last,current
	rjmp	main



timer2vec:
	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
	
deccounter:
	cpi	counter,0
	breq	deccounter1
	dec		counter
	ret
deccounter1:
	ldi	counter,dutymax-1
	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
C言語版

#include <avr/sleep.h>
#include <MsTimer2.h>
  // put your setup code here, to run once:
const byte out7seg[]={6,7,8,9,10,11,12,13,0xff};
const byte dutydata[]={1,2,4,8,16,32,64,128,0};
const byte button1=4;
const byte button2=5;
byte counter;
byte dcycle;
void setup() {
  for(byte i=0;out7seg[i]!=0xff;i++){
    pinMode(out7seg[i], OUTPUT);
  }
  pinMode(button1,INPUT_PULLUP);
  pinMode(button2,INPUT_PULLUP);
  counter=0;
  MsTimer2::set(1,vect);
  write7seg(0b11011111);
  MsTimer2::start();
}
void write7seg(byte x){
  for(byte i=0; i<8;i++){
    digitalWrite(out7seg[i],x&1<<i);
  }
}


#define drate 1
byte dutycounter;
void duty(){
  dutycounter++;
  if(dutycounter==dcycle){
    dutycounter=0;
  }
  if(dutycounter<drate){
    digitalWrite(11,HIGH);
  }else{
    digitalWrite(11,LOW);
  }
}

void vect(){
  duty();
}

void increase(){
  if(dutydata[counter]==0xff){
    counter=0;
  }else{
    counter++;
  }
}
void decrease(){
  if(counter==0){
    counter=sizeof(dutydata)/sizeof(dutydata[0])-1;
  }else{
    counter--;
  }
}

void setduty(){
  dcycle = dutydata[counter];
}
void loop() {
  static bool prev1=HIGH;
  static bool prev2=HIGH;
  static bool now1=HIGH;
  static bool now2=HIGH;
  setduty();
  now1=digitalRead(button1);
  if(now1==HIGH && prev1==LOW){
    decrease();
  }
  prev1=now1;
  now2=digitalRead(button2);
  if(now2==HIGH && prev2==LOW){
    increase();
  }
  prev2=now2;
}

演習8-2

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

8-5. 演習の解説

演習8-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 を取れば良いで す。


;************
;*  演習8-1 *
;************
.cseg


.org	0x0000
		rjmp	reset
.org	OVF2addr
	rjmp	timer2vec
.org	INT_VECTORS_SIZE
reset:
	ldi     r16,high(RAMEND)
	out     SPH,r16 
	ldi		r16,low(RAMEND)
	out		SPL,r16
.equ	onb	= 0b00111111
.equ	ond	= 0b11000000
.equ	dispb = 0b00110111
.equ	dispd = 0b11000000
.equ	pullup	= 0b00110000

		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
inittimer2:
	ldi	r16,0b00000000	;normal mode
	sts	tccr2a,r16
	ldi	r16,0b00000111	; normal mode, prescaler=1024
	sts	tccr2b, r16
	ldi	r16, 0b00000001 ; enable interruption for overflow
	sts	timsk2, r16


.def	counter=r17
.def	pcounter = r18
.equ	ptime = 20
	ldi	pcounter,ptime
		clr		counter


.def	last = r14
.def	current = r15
		sei
main:
	rcall	dispcount
	in		current,PIND

	sbrc	last,PIND4
	rjmp	lpressed
	sbrs	current,PIND4
	rjmp	lnotpressed
lpressednow:
	rcall	inccounter
lpressed:
lnotpressed:

	sbrc	last,PIND5
	rjmp	rpressed
	sbrs	current,PIND5
	rjmp	rnotpressed
rpressednow:
	rcall	deccounter
rpressed:
rnotpressed:

	mov	last,current
	rjmp	main

timer2vec:
	rcall	postscaler
	reti
postscaler:
	dec		pcounter
	breq	postscaler1
	ret
postscaler1:
	ldi		pcounter,ptime
	rcall	invertdot
	reti

invertdot:
	push	r17
	push	r20
	in	r17,portb
	ldi	r20,0b00001000
	eor	r17,r20
	out	portb,r17
	pop		r20
	pop	r17
	ret

.macro	outport
	ldi	r20,@0
	and	r20,r16	
	in	r21,@1
	andi	r21,~@0
	or	r21,r20
	out	@1,r21
.endmacro

dispcount:	;counter	の値を表示
	ldi	ZH,high(startdata<<1)
	ldi	ZL,low(startdata<<1)
	add	ZL,counter
	brcc	dispcount1
	inc	ZH
dispcount1:
	lpm	r16,Z
	outport	dispb,portb
	outport	dispd,portd
	ret

inccounter:
	inc	counter
	cpi	counter,countmax
	breq	inccounter1
	ret
inccounter1:
	clr	counter
	ret
	
deccounter:
	cpi		counter,0
	breq	deccounter1
	dec		counter
	ret
deccounter1:
	ldi	counter,countmax-1
	ret

startdata:
	.db	0b10110111,0b00100100
	.db 0b01110011,0b01110110
	.db 0b11100100,0b11010110
	.db 0b11010111,0b00110100
	.db 0b11110111,0b11110110
	.db 0b11110101,0b11000111
	.db 0b10010011,0b01100111
	.db 0b11010011,0b11010001
enddata:

.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)
.exit

演習8-2

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


;************
;*  演習8-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

坂本直志 <[email protected]>
東京電機大学工学部情報通信工学科