このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
今までのプログラム演習では、一定の時間を待つのに「ビジーウェイト」とい う手法で、何もしないことを何万回と繰り替えして一定の時間を作りました。
この様な手法はどのコンピュータでも使える基本的な手法ですが、何もしてい ないときに電力を抑えたり、あるいは、複数のプログラムを平行して動かした り、外部と協調して動作させるなどには向きません。
そこで、今回はマイコンの機能として、CPU を止めたり、一定の時間をタイマー で計ってプログラムを動かします。
ATmega328P には sleep 命令があります。 この sleep 命令でどのような状態になるかはプログラム内部で 6 通りに指定 することができます(pp.64-65)。 外部からの割り込み信号が来るまでほとんどの機能を停止するモードから、単 に CPU を止めるだけのモードもあります。 今回は、すべての I/O が存続していながら CPU だけを止める Idle モードを選びます。 Idle モードを利用するには次のようにします。
#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に記載されています。
例えば、内蔵LEDを光らせて止まるプログラムは次のように書ける。
;************
;* 例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
外部の刺激などに対して、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秒に一回割り込みがかかるようにします。 そのための設定ですが、以下のようになります。
レジスタ名 | 値 | 意味 |
---|---|---|
TCCR2A | 0b00000000 | normalモードはすべて0 |
TCCR2B | 0b00000111 | クロックの 1024分の1で割り込 み発生(モード設定がTCCR2Aと別れていることに注意) |
TIMSK2 | 0b00000001 | オーバーフロー検知だけ有 効 |
ATMega328P のシステムクロックは 8MHz に設定してあります(p.25)。 また、出荷時のヒューズビット CKDIV8 は ON になっています(p.29)。 従って、CLKPR をデフォルトのままで使用した場合、システムは 8M/8=1MHz で動作します。 そのため、 1024 で分周した後、さらに 256 回に一回の割合で割り込みが発 生するとすると、割り込みは 1024*256/1M ≒ 0.26 秒に一回発生することに なります。
演習 7-5 のプログラムを改造します。 counter という変数を用意し、次のサブルーチンを用意します。
このような準備をした後、次のプログラムを作ります。
また、割り込みの処理は次のようにします。
プログラムは次のようになります
;**********
;* 例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-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
C言語で割り込みを行う場合、ライブラリーを使うのが一般的なようです。
Arduino の「スケッチ→ライブラリをインクルード→ライブラリを管理」
を選び、検索欄に MsTimer2 を入れると、MsTimer2 をインストールできます。
これは、
MsTimer2::set(時間間隔,呼び出し関数)
で関数を登録した後、
MsTimer2::start()
とするだけで自動的に実行できます。
例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();
}
LED の小数点を約 0.5 秒程度で点滅させなさい。 さらに、 スイッチを押す度に LED の数字が増減するようにしな さい。
LED の明るさを変えるには二つの方法があります。 今回は DUTY 比のコントロールについて考えます。 この方法は、早い周期で LED を点けたり消したりすることで、平均出力を下げる ことにより、人間の目には暗く見えるようにすることです。 この時、全体の中で ON になっている比率を DUTY 比と呼びます。
例 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 の明るさが揃ってないことがわかります。
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 比を連続して変化させる時は、比率を連続的に変化させても効 果が薄いことに注意します。
小数点の明るさを変化させることを考えましょう。 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
#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();
}
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
#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-5のプログラムで、 PORTB には counter の値を表示するように改造し なさい。
始めに小数点を約 0.5 秒程度で点滅させることを考えます。 割り込みは Prescaler を 1:256 で使用し、さらに 7 回に一回毎に点滅させ ます。 点滅は点滅パターンを用意して PORTB に対して XOR をかけることで行います。 著者の回路では RB3 が小数点になってますので、 b'00001000' で XOR を行 います。
一方、 RD4 に接続したスイッチを押す度に LED の数字が増えるようにするに は、演習5-6 のプログラムを流用します。 但し、 PORTB にダイレクトに書き込むと小数点の点滅に影響してしまいます。 そこで、ビットパターンを特定の場所だけ上書きすることを考えます。 そのためには、「特定の場所を指定する」情報を考えなくてはいけません。 今、考えるのは PORTB の RA0 を温存したまま RA1 から RA7 までを変更する ことです。
つまり、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-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