このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
あらかじめ与えられた特定のパターン(例えば 2 進数で与える)の値を PORTB に出力し、 LED を光らせたままにするプログラムを作りなさい。 (ヒント: プログラムを止めるには sleep ではなく、単なる無限ループを作ります。 )
;************
;* 例5-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
.def pattern = r16
main:
ldi pattern,0b01010101 ; 点灯させるビットパターン
out portb,pattern
end:
rjmp end
.exit
あらかじめ与えられたふたつの特定のパターン(例えば 2 進数で与える)の値 のうちのどちらかが on になっている部分の LED を光らせるプログラムを作 りなさい。 (ヒント: or, ori 命令)
どちらのビットが on の時、on にするには両方のビットパターンに対して OR を行います。 x or 0 = x, x or 1 = 1 より、ビットパターンが 0 の部分はそのままの値に なり、ビットパターンが 1 の部分は 1 になります。
;************
;* 例5-2 *
;************
.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
.def pattern = r16
main:
ldi pattern,0b01010101 ; 点灯させるビットパターン
ori pattern,0b00001111
out portb,pattern
end:
rjmp end
.exit
あらかじめ与えられたふたつの特定のパターン(例えば 2 進数で与える)の値 のうちの両方が on になっている部分の LED を光らせるプログラムを作 りなさい。 (ヒント: and 命令)
あらかじめ与えられた特定のパターン(例えば 2 進数で与える)の値に対して、 2 進数で指定される特定の部分だけ反転させて LED を光らせるプログラムを作 りなさい。 (ヒント: eor 命令はレジスタ同士しか無いので、レジスタを二つ使う必要が あります)
PORTB に接続した LED になにかパターンを表示し、 PD4 に接続した sw を押 した時に On , Off が反転するプログラムを作りなさい。
PORTB に接続した LED が PD4 に接続した sw を押す度に On , Off が反転す るプログラムを作りなさい。
LED のつながっている PORT B に対して、 PB0 のみ、 PB0 と PB1、 PB0 から PB2 まで、と順番に一つずつビットを 1 にしていき、全て 1 になったら全て消灯するプログラムを作りなさい。
第 2 回の flash プログラムを流用 します。
各パターンを書き込んで、wait を呼び出します。
;************
;* 例5-3 *
;************
.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
.def pattern = r16
main:
ldi pattern,0b00000000
out portb,pattern
rcall wait
ldi pattern,0b00000001
out portb,pattern
rcall wait
ldi pattern,0b00000011
out portb,pattern
rcall wait
ldi pattern,0b00000111
out portb,pattern
rcall wait
ldi pattern,0b00001111
out portb,pattern
rcall wait
ldi pattern,0b00011111
out portb,pattern
rcall wait
ldi pattern,0b00111111
out portb,pattern
rcall wait
ldi pattern,0b01111111
out portb,pattern
rcall wait
ldi pattern,0b11111111
out portb,pattern
rcall wait
rjmp main
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
これで美しくないと思った場合の別解を示します。 まず、考えられるのは似たようなパターンが出てきますので、これを一つのマ クロ命令で置き換えることです。
;**************
;* 例5-3 2 *
;**************
.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
.equ time=3
.def pattern = r16
.macro display
ldi pattern,@0
out portb,pattern
rcall wait
.endmacro
main:
display 0b00000000
display 0b00000001
display 0b00000011
display 0b00000111
display 0b00001111
display 0b00011111
display 0b00111111
display 0b01111111
display 0b11111111
rjmp main
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
プログラミングのコツとして、入出力部分を一箇所にまとめて、最小限にする ことが考えられます。 ループ変数を用意して、出力パターンを計算して出力することを考えます。 C 言語で書くと次のようになります。
for(;;){
pattern=0;
for(counter=1; counter<256; counter*=2){
pattern = counter | i;
pattern の出力;
}
}
counter *= 2 を実現するには rotateあるいは shift
と呼ばれる命令を使います。
これはレジスタの内容をビット毎に右又は左に移動する命令です。
単なる移動は shift で、溢れた bit を反対側の bit に回すのが rotate です。
PIC では STATUS の C bit を含めた rotate 命令のみがあります。
bit 毎に移動するということは、二進数で桁をずらすことになるので、 2 倍、
あるいは 1/2 にするということになります。
つまり、値を 2 倍するには左に rotate すれば良いことになります。
また、rotate した値が256 を越えると STATUS レジスタの C が 1 になりま
す。
なお、C 言語にも shift 演算 << と >> があります。
例えば、 count*=2
(2倍する) は
count<<=1
(左に 1 bit シフトする) と書けます。
但し、 counter に 1 が入ってから、 rotate で C=1 となる まで 8 回の繰り返しになるので、このプログラムのままでは全消灯を含めた 9 パターンは表示できません。
;**************
;* 例5-3 3 *
;**************
.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
.def pattern = r16
main:
clr pattern
loop:
out portb,pattern
rcall wait
sec
rol pattern
brcs main
rjmp loop
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
前の解法はエレガントですが、パターンを計算して出力する点で応用が効きま せん。 そこで、パターンはデータとして列挙し、パラメータで呼び出すことにします。 そして、出力はやはり一箇所で行うようにします。
データを列挙する場所はプログラム領域になります。 データを列挙するディレクティヴは .db や .dw になります。 .db は 8bit ずつ、 .dw は 16bit ずつデータを列挙できます。 一方、プログラム領域の値をレジスタに読み込む命令は lpm です。 これは定数の値のほか、インデックスレジスタ Z だけは間接読み出し(レジス タの指す番地の値を読む)が可能です。 特に、アドレッシングとして事前に Z を一つ減算したり、読み出した後で一 つ加算することもできます。
但し、 AVR はハーバードアーキテクチャなので、ここで単純に話は終わりま せん。 AVR ではレジスタ、データメモリは 8bit ですが、プログラムは 16bit です。 そのため、次のような注意点があります。
このよう自明でないな事情があることから、プログラム中で Z レジスタの値 と実アドレスを関連付ける(代入、比較など)をする場合に表現が複雑になりま す。 これを避けるために、あらかじめデータの入っている領域のそばでアドレス計 算をしておくようにします。 パターンの最大数はパターンの終了アドレスからパターンの開始アドレスを引 いて求めると、あとでパターンの変更をしてもパターンを出力するプログラム を変更する必要が無くなります。
インデックスレジスタは16bitなので、16bit の処理ができる命令がいろいろ あると便利ですが、AVR にはあまりありません。 つぎのようなものだけです
したがって、比較をする際も 8bit ごとに分ける必要があります。 大小比較するしようとするとかなり複雑です。 但し、等しいかどうかを判定するのは cp 命令二つでできます。
また、 16bit のデータを分けて処理する場合、読み込みは下位から、書き込 みは上位から行うようにします。
;**************
;* 例5-3 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
.def pattern = r16
main:
ldi ZH,high(startdataadr)
ldi ZL,low(startdataadr)
loop:
lpm pattern,Z+
out portb,pattern
rcall wait
cpi ZL,low(enddataadr)
brne loop
cpi ZH,high(enddataadr)
brne loop
rjmp main
.equ padding = 0
startdata:
.db 0b00000001, 0b00000011
.db 0b00000111, 0b00001111
.db 0b00011111, 0b00111111
.db 0b01111111, 0b11111111
.db 0b00000000, padding
enddata:
.equ startdataadr = startdata<<1
.equ enddataadr = (enddata<<1)-1
.equ time=3
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
PORTB につながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを作りなさい。
PD4 に接続した sw を押す度に PORT B に つながっている LED が 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F を順番に表示するプログラムを作りなさい。
ビットを off にするには off にしたいビットパターンを作り、AND を行います。 x and 0 = 0, x and 1 = x より、ビットパターンが 0 の部分は 0 になり、 ビットパターンが 1 の部分はそのままの値になります。
;************
;* 演習5-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
.def pattern = r16
main:
ldi pattern,0b01010101 ; 点灯させるビットパターン
andi pattern,0b00001111
out portb,pattern
end:
rjmp end
.exit
ビットを反転させるには反転させたいビットを指定するビットパターンを作り、 XOR を行います。 x xor 0 = x, x xor 1 = x より、ビットパ ターンが 0 の部分はそのままになり、 ビットパターンが 1 の部分は値が反転します。
;************
;* 演習5-2 *
;************
.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
.def pattern = r16
.def work = r17
main:
ldi pattern,0b01010101 ; 点灯させるビットパターン
ldi work,0b00001111
eor pattern,work
out portb,pattern
end:
rjmp end
.exit
押してない時と押した時で PORTB の値を変化させます。
あらかじめ ON の時のパターンを pattern レジスタに入れておき、反転した
パターンを invpattern にいれておきます。
その後、sbic pind, 4
で PD4 のビットを検査します。
もし、OFF だったら invpattern を portb に出力し、 ON だったら pattern
を portb に出力します。
そのため、 ON の時のパターンから OFF の時のパターンをあらかじめ作ってお きます。 com 命令でレジスタのビット反転ができます。
また、あらかじめ PORTD の 4 bit 目を入力ポートになるように初期化します。
;************
;* 演習5-3 *
;************
.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
ldi r16,1<<4
out ddrd,r16
.def pattern = r16
.def invpattern = r17
ldi pattern,0b01010101 ; 点灯させるビットパターン
mov invpattern,pattern
com invpattern
main:
sbic pind, 4
rjmp swon
swoff:
out portb,invpattern
rjmp main
swon:
out portb,pattern
rjmp main
.exit
演習5-3 と同様の準備が必要です。
sw が押される度というのはどうやって考えれば良いでしょうか? sw は押している間 1 で、離すと 0 になります。 「1 なら反転」とすると、押している間反転を繰り返してしまうことになりま す。
押す度に切替えるには、押した瞬間に切替え、その後、幾ら押し続けても反転 しないようにする必要があります。 つまり、sw が押されてない状態から押された状態への変化を捉え、そこで反 転させます。 そのためには、直前の動作をファイルレジスタに取っておき、直前が 0 でス イッチが 1 の時だけ反転するようにします。
;************
;* 演習5-4 *
;************
.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
ldi r16,1<<PIND4
out ddrd,r16
.def pattern = r16
ldi pattern,0b01010101 ; 点灯させるビットパターン
.def last = r17
.def current = r18
main:
in current,PIND
sbrc last,PIND4
rjmp pressed
sbrs current,PIND4
rjmp notpressed
pressednow:
com pattern
pressed:
notpressed:
out portb,pattern
mov last,current
rjmp main
.exit
なお、このように入力に対して動作が変化するようなプログラムをデバッグす る時は、シミュレータを使用します。 Atmel Studio の上部にある play や pause ボタンはシミュレータの実行や 一時停止を意味します。 止まっている時は、右側のペインでI/Oや CPU の状態などを見ることができま す。 さらに、特定の命令で右クリックしてブレークポイントを設定す ると、 playボタンを押しても、ブレークポイントの設定された命令を実行す ると自動的に pause 状態になります。 さらに、外部ピンを ON にするには、当該ポート(例えば PORTD の 4 bit目) を指す四角をクリックして色を反転させると、次のタイミングで PIN のビッ トが追従します。
例5-3 4 のプログラムにおいて出力パターンを変更したものを作ります。 出力パターンは例5-3 のプログラムを動かして、どの LED がどのビットに 対応しているのかを調べると簡単です。
;************
;* 演習5-5 *
;************
.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
.def pattern = r16
.equ time=3
main:
ldi ZH,high(startdata<<1)
ldi ZL,low(startdata<<1)
loop:
lpm pattern,Z+
out portb,pattern
rcall wait
cpi ZL,low(enddataadr)
brne loop
cpi ZH,high(enddataadr)
brne loop
rjmp main
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 startdataadr=startdata<<1
.equ enddataadr=enddata<<1
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
前述の演習5-5のプログラムには欠点があります。 それは、まず、表示している文字と対応するデータをプログラム内で保持して いないこと。 つまり、例えば、 1 を表示している時に、どのレジスタも 1 を持っていない ということです。 これは、このプログラムを再利用して他のプログラムから特定の数字を表示 しようとする場合へ応用が効かなくなります。
さらに、もう一つ欠点があります。 前述のプログラムでは Z レジスタをループカウンタとして利用して、プログ ラム領域のデータをうまく取り出して表示していました。 しかし、このような使い方のできる便利なレジスタは Z レジスタ一本しかあ りません。 そのため、このように利用度の高いレジスタを占有してしまうようなプログラ ムは、他のプログラムと結合し辛くなります。
以上のことより、ループカウンタとして counter という汎用レジスタを用意 し、次のサブルーチンを用意します。
このような準備をした後、次のプログラムを作ります。
このように改良したのが以下のプログラムです。
dispcounter ではデータの先頭番地を Z レジスタに入れた後、 8bit の counter を足します。 そのため、下位 8bit である ZL に counter を足し込んだ後、オーバーフロー した時だけ ZH を 1 増やします。
;*************
;* 演習5-52 *
;*************
.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
.def pattern = r16
.def counter = r17
.equ time=3
clr counter
main:
rcall dispcount
rcall wait
rcall inccounter
rjmp main
dispcount: ;counter の値を表示
ldi ZH,high(startdata<<1)
ldi ZL,low(startdata<<1)
add ZL,counter
brcc dispcount1
inc ZH
dispcount1:
lpm pattern,Z
out portb,pattern
ret
inccounter:
inc counter
cpi counter,countmax
breq inccounter1
ret
inccounter1:
clr counter
ret
.equ padding = 0
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)
.def wreg0 = r20
.def wreg1 = r21
.def wreg2 = r22
wait:
ldi wreg0,time
wait0:
ldi wreg1,0
wait1:
ldi wreg2,0
wait2:
nop
dec wreg2
brne wait2
dec wreg1
brne wait1
dec wreg0
brne wait0
ret
.exit
演習5-6 のプログラムと 演習5-4 のプログラムを組み合わせます。 但し、表示する dispcount と、値を増やす inccount を分けて呼び出すこと にして、メインからは常に dispcount を呼び出し、ボタンが押されたこと 検知すると inccount を呼ぶようにします。
;************
;* 演習5-6 *
;************
.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,1<<PIND4
out ddrd,r16
.def pattern = r16
.def last = r17
.def current = r18
.def counter = r19
clr counter
main:
rcall dispcounter
in current,PIND
sbrc last,PIND4
rjmp pressed
sbrs current,PIND4
rjmp notpressed
pressednow:
rcall inccounter
pressed:
notpressed:
out portb,pattern
mov last,current
rjmp main
dispcount: ;counter の値を表示
ldi ZH,high(startdata<<1)
ldi ZL,low(startdata<<1)
add ZL,counter
brcc dispcount1
inc ZH
dispcount1:
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