第 7 回 アセンブラ演習

本日の内容


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

7-1. アセンブラ演習

例7-1

特定のLEDをONにしなさい。

解説

I/Oポートなどで一つのビットだけを On にするにはいくつかの方法が あります。

  1. 特定のbitをonにする命令を使う
  2. レジスタに値を入れ、論理演算の組み合わせを使う

例えば、 PORTB の 1番ポートだけを On にするには、AVR なら sbi portb,1 とすれば On になります。

レジスタの値に従って出力を変化させる場合、単純にレジスタの値を出 力してしまうと、既に書き込んである値を消してしまったりする場合が ありますので、手順を追う必要があります。 これは一命令ではないので、後で学ぶ割り込みの影響を受けないように するため、割り込みを適宜禁止する必要があります。

  1. (割り込み禁止)
  2. I/Oポートの値をレジスタに読み込む
  3. レジスタの値を論理演算により加工する
  4. I/Oポートに出力
  5. (割り込み許可

PORTBの1番ポートをOnにするために、 r17レジスタに 1<<1 =0b00000010 が入っている時、次のようにすると On になります。

    
	cli
	in	r16,portb
	or	r16,r17
	out	portb,r16
	sei

例7-2

I/Oポートの下位4bitだけ r17 の下位4ビットの値にしたい。 但し、I/Oポート、r17とも上位4bitの値は任意とし、値を変えてはいけ ない。

解説

この場合、上位4bit と下位4bit の扱いが異なるので、それを取り扱う ための情報が必要となります。 そこで、 mask = 0b11110000 となる値を使用します。 r16 に I/O ポートの値を読み込んだ跡、 mask を使用して r16 の下位4bit を 0 にします。 一方、 r17 の値に mask の反転パターンを使用して r18 に r17 の上 位 4bit を 0 にしたものを入れます。 そして、 r16 に r16とr18のorを取ったものを入れます。 最後に r16 を出力します。

    
.equ	mask = 0x11110000
	cli
	in	r16,portb
	andi	r16,mask
	ldi	r18,~mask
	and	r18,r17
	or	r16,r18
	out	portb,r16
	sei

演習7-1

すべてのLED が一個ずつ順番に1秒ずつ点灯するプログラムを書きなさ い。

演習7-2

最初、LEDが一つだけ点灯し、その後、一秒ごとに一つずつ光るLED増え ていき、全点灯したら、最初に戻るプログラムを書きなさい。

演習7-3

すべてのLEDが一個ずつ順番に1秒ずつ点灯するが、ボタンを押している 間は消灯するプログラムを書きなさい

演習7-4

最初、LEDが一つだけ点灯し、その後、押ボタンを押すたびに一つずつ光るLED増え ていき、全点灯したら、最初に戻るプログラムを書きなさい。

例7-3

特定のパターン 0b00000001, 0b00000011, 0b00000111, 0b00001111 を順にレジスタr16に入れて rcall sub をしなさい

解説

ベタ打ち

	ldi	r16,0b00000001	
	rcall	sub
	ldi	r16,0b00000011	
	rcall	sub
	ldi	r16,0b00000111	
	rcall	sub
	ldi	r16,0b00001111	
	rcall	sub
一命令でやる方法

定形のコマンドを繰り返し使用する時、マクロが便利です。


.macro	mac
	ldi	r16,@0
	rcall	sub
.endmacro

	mac	0b00000001
	mac	0b00000011
	mac	0b00000111
	mac	0b00001111

ループを使い、表示部分を最小にする

プログラミングのコツとして、入出力部分を一箇所にまとめて、最小限にする ことが考えられます。 ループ変数を用意して、出力パターンを計算して出力することを考えます。 C 言語で書くと次のようになります。


for(;;){
  pattern=0;
  for(counter=1; counter<256; counter*=2){
    pattern = counter | i;
    pattern の出力;
  }
}

counter *= 2 を実現するには rotateあるいは shift と呼ばれる命令を使います。 これはレジスタの内容をビット毎に右又は左に移動する命令です。 単なる移動は shift で、溢れた bit を反対側の bit に回すのが rotate です。 avr では STATUS レジスタの C を含めた rotate 命令のみがあります。 bit 毎に移動するということは、二進数で桁をずらすことになるので、 2 倍、 あるいは 1/2 にするということになります。 つまり、値を 2 倍するには左に rotate すれば良いことになります。 また、rotate した値が 256 を越えると STATUS レジスタの C が 1 になりま す。 なお、C 言語にも shift 演算 << と >> があります。 例えば、 count*=2(2倍する) は count<<=1(左に 1 bit シフトする) とも書けます。


	clr	r16
	sec
	rol	r16
	rcall	sub	
	sec
	rol	r16
	rcall	sub	
	sec
	rol	r16
	rcall	sub	
	sec
	rol	r16
	rcall	sub	

またはマクロを使って


.macro	next
	sec
	rol	r16
	rcall	sub	
.endmacro

	clr	r16
	next
	next
	next
	next

パターンをパラメータで呼び出す

前の解法はエレガントですが、パターンを計算して出力する点で応用が効きま せん。 そこで、パターンはデータとして列挙し、パラメータで呼び出すことにします。 そして、出力はやはり一箇所で行うようにします。

データを列挙する場所はプログラム領域になります。 データを列挙するディレクティヴは .db.dw になります。 .db は 8bit ずつ、 .dw は 16bit ずつデータを列挙できます。 一方、プログラム領域の値をレジスタに読み込む命令は lpm です。 これは定数の値のほか、インデックスレジスタ Z だけは間接読み出し(レジス タの指す番地の値を読む)が可能です。 特に、アドレッシングとして事前に Z を一つ減算したり、読み出した後で一 つ加算することもできます。

但し、 AVR はハーバードアーキテクチャなので、ここで単純に話は終わりま せん。 AVR ではレジスタ、データメモリは 8bit ですが、プログラムは 16bit です。 そのため、次のような注意点があります。

  1. .db ディレクティヴでは、二つで16bit のデータ一組として、一つのア ドレスに入る。 奇数個のデータを指定すると、アセンブル時に警告が出て、最後に8bit の 0 が埋められる。
  2. Z レジスタの最下位ビットは 0 で 16bit のデータの上位 8bit を読み、 1 で下位 8bit を読む。そのため、Zレジスタへのアドレスの指定方法はプ ログラム領域の アドレスを左に一つシフトして、最下位 bit には上位か 下位のどちらを読むかを指定する。

このよう自明でないな事情があることから、プログラム中で Z レジスタの値 と実アドレスを関連付ける(代入、比較など)をする場合に表現が複雑になりま す。 これを避けるために、あらかじめデータの入っている領域のそばでアドレス計 算をしておくようにします。 パターンの最大数はパターンの終了アドレスからパターンの開始アドレスを引 いて求めると、あとでパターンの変更をしてもパターンを出力するプログラム を変更する必要が無くなります。

インデックスレジスタは16bitなので、16bit の処理ができる命令がいろいろ あると便利ですが、AVR にはあまりありません。 つぎのようなものだけです

movw
レジスタ同士の値の移動(r1:r0からr31:r30まで)
adiw
レジスタ(r25:r24からr31:30まで)と定数(0から63まで)の加算
sbiw
レジスタ(r25:r24からr31:30まで)と定数(0から63まで)の減算

したがって、比較をする際も 8bit ごとに分ける必要があります。 大小比較するしようとするとかなり複雑です。 但し、等しいかどうかを判定するのは cp 命令二つでできます。

また、 16bit のデータを分けて処理する場合、読み込みは下位から、書き込 みは上位から行うようにします。


main:
	ldi	ZH,high(startdataadr)
	ldi	ZL,low(startdataadr)
loop:
	lpm	r16,Z+
	rcall	sub
	lpm	r16,Z+
	rcall	sub
	lpm	r16,Z+
	rcall	sub
	lpm	r16,Z+
	rcall	sub

startdata:
	.db	0b00000001,	0b00000011
	.db	0b00000111,	0b00001111
enddata:
.equ	startdataadr = startdata<<1
.equ	enddataadr = enddata<<1

演習7-5

7SEG LED に 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F が一秒ごとに光り、 また最初に戻るプログラムを書きなさい

演習7-6

7SEG LED に 0 を表示し、右のボタンを押したら数が 1,2,3,4,5,6,7,8,9,A,b,C,d,E,F,0,1 ... と一つずつ増え、左のボタンを押した ら F,E,d,C,b,A,9,8,7,6,5,4,3,2,1,0,F,E ... と減るようなプログラムを書きなさい

7-2. 解答

演習7-1

すべてのLED が一個ずつ順番に1秒ずつ点灯するプログラムを書きなさ い。


; 演習7-1.asm
;
; Created: 2018/10/25 15:00:00
; Author : sakamoto
;

.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
.equ	onc	= 0b00001111
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		ldi		r16,onc
		out		ddrc, 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
	rcall	wait
	setport portb,onb,0b00000010
	rcall	wait
        setport portb,onb,0b00000100
	rcall	wait
	setport portb,onb,0b00001000
	rcall	wait
        setport portb,onb,0b00010000
	rcall	wait
	setport portb,onb,0b00100000
	rcall	wait
	clrport	portb,onb
        setport portd,ond,0b01000000
	rcall	wait
	setport portd,ond,0b10000000
	rcall	wait
	clrport	portd,ond
        setport portc,onc,0b00000001
	rcall	wait
	setport portc,onc,0b00000010
	rcall	wait
        setport portc,onc,0b00000100
	rcall	wait
	setport portc,onc,0b00001000
	rcall	wait
	clrport	portc,onc
	rjmp	main

.equ	time = 10

.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

演習7-2

最初、LEDが一つだけ点灯し、その後、一秒ごとに一つずつ光るLED増え ていき、全点灯したら、最初に戻るプログラムを書きなさい。


;
; ex72.asm
;
; Created: 2018/10/18 17:34:30
; Author : sakamoto
;
;

.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
.equ	onc	= 0b00001111
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		ldi		r16,onc
		out		ddrc, 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

.equ	time = 10

main:
		setport portb,onb,0b00000001
		rcall	wait
		setport portb,onb,0b00000011
		rcall	wait
		setport portb,onb,0b00000111
		rcall	wait
		setport portb,onb,0b00001111
		rcall	wait
		setport portb,onb,0b00011111
		rcall	wait
		setport portb,onb,0b00111111
		rcall	wait
		setport portd,ond,0b01000000
		rcall	wait
		setport portd,ond,0b11000000
		rcall	wait
		setport portc,onc,0b00000001
		rcall	wait
		setport portc,onc,0b00000011
		rcall	wait
		setport portc,onc,0b00000111
		rcall	wait
		setport portc,onc,0b00001111
		rcall	wait
		clrport	portb,onb
		clrport	portd,ond
		clrport	portc,onc
		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

演習7-3

すべてのLEDが一個ずつ順番に1秒ずつ点灯するが、ボタンを押している 間は消灯するプログラムを書きなさい


; 演習7-3.asm
;
; Created: 2018/10/25 15:00:00
; Author : sakamoto
;

.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
.equ	onc	= 0b00001111
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		ldi		r16,onc
		out		ddrc, r16

.def	prtb	= r17
.def	prtd	= r18
.def	prtc	= r19
	in	prtb,portb
	in	prtd,portd
	in	prtc,portc

.macro	clrport
		in	r16,@0
		andi	r16,~@1
		out	@0,r16
.endmacro
.macro	clrreg
		andi	@0,~@1
.endmacro
.macro setport
		andi	@0,~@1
		ori	@0,@2
.endmacro
.macro	setreg
		andi	@0,~@1
		ori	@0,@2
.endmacro

main:
        setreg prtb,onb,0b00000001
	rcall	wait
	setreg	prtb,onb,0b00000010
	rcall	wait
        setreg prtb,onb,0b00000100
	rcall	wait
	setreg prtb,onb,0b00001000
	rcall	wait
        setreg prtb,onb,0b00010000
	rcall	wait
	setreg prtb,onb,0b00100000
	rcall	wait
	clrreg	prtb,onb
	andi	prtb,~onb
        setreg prtd,ond,0b01000000
	rcall	wait
	setreg prtd,ond,0b10000000
	rcall	wait
	clrreg	prtd,ond
	andi	prtd,~ond
        setreg prtc,onc,0b00000001
	rcall	wait
	setreg prtc,onc,0b00000010
	rcall	wait
        setreg prtc,onc,0b00000100
	rcall	wait
	setreg prtc,onc,0b00001000
	rcall	wait
	clrreg	prtc,onc
	andi	prtc,~onc
	rjmp	main

.equ	time = 5

.def	wreg0 = r20
.def	wreg1 = r21
.def	wreg2 = r22
wait:
	ldi	wreg0,time
wait0:
	ldi	wreg1,0
wait1:
	ldi	wreg2,0
wait2:
	sbis	pind,pind5
	rjmp	swoff
	sbis	pind,pind4
	rjmp	swoff
swon:
	out	portb,prtb
	out	portd,prtd
	out	portc,prtc
	rjmp	sw
swoff:
	clrport	portb,onb
	clrport	portd,ond
	clrport	portc,onc
sw:
	dec	wreg2
	brne	wait2
	dec	wreg1
	brne	wait1
	dec	wreg0
	brne	wait0
	ret
.exit

演習7-4

最初、LEDが一つだけ点灯し、その後、押ボタンを押すたびに一つずつ光るLED増え ていき、全点灯したら、最初に戻るプログラムを書きなさい。



;
; Created: 2018/10/25 15:00:00
; Author : sakamoto
;

.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
.equ	onc	= 0b00001111
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		ldi		r16,onc
		out		ddrc, 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

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


.def	counter=r17
.def	last = r18
.def	current = r19
main:
	rcall	dispcount
	in	current,PIND
	sbrc	last,PIND4
	rjmp	pressed
	sbrs	current,PIND4
	rjmp	notpressed
pressednow:
	rcall	inccounter
pressed:
notpressed:
	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	r16,Z
	outport	onb,portb
	outport	ond,portd
	ret

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

	.equ	padding = 0
startdata:
	.db	0b00000000, 0b00000001
	.db	0b00000011,	0b00000111
	.db	0b00000111,	0b00001111
	.db	0b00011111,	0b00111111
	.db	0b01111111,	0b11111111
enddata:
.equ	num_start_adr = startdata <<1
.equ	num_end_adr = enddata << 1
.equ	countmax = low(num_end_adr - num_start_adr)
.exit

演習7-5

7SEG LED に 0,1,2,3,4,5,6,7,8,9,A,b,C,d,E,F が一秒ごとに光り、 また最初に戻るプログラムを書きなさい


;
; ex75.asm
;
; Created: 2018/11/29 16:56:24
; Author : sakamoto
;

;
; Created: 2018/10/25 15:00:00
; Author : sakamoto
;

.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
.equ	onc	= 0b00001111
		ldi		r16,onb
		out		ddrb, r16
		ldi		r16,ond
		out		ddrd, r16
		ldi		r16,pullup
		out		portd, r16
		ldi		r16,onc
		out		ddrc, r16

.def	counter=r17
		ldi		r17,0

.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

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


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	r16,Z
	outport	onb,portb
	outport	ond,portd
	ret

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

	.equ	padding = 0
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)

.equ	time = 20

.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

演習7-6

7SEG LED に 0 を表示し、右のボタンを押したら数が 1,2,3,4,5,6,7,8,9,A,b,C,d,E,F,0,1 ... と一つずつ増え、左のボタンを押した ら F,E,d,C,b,A,9,8,7,6,5,4,3,2,1,0,F,E ... と減るようなプログラムを書きなさい


;
; ex76.asm
;
; Created: 2018/11/29 18:43:55
; Author : sakamoto
;

.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


.def	counter=r17
		clr		counter


.def	last = r18
.def	current = r19

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


.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	onb,portb
	outport	ond,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

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