So-net無料ブログ作成
検索選択

AVRアセンブラで配列的なもの [AVR]

アセンブラではレジスタに値を入れていろいろ計算することはわかった。
しかしレジスタだけでは足りない。高級言語で言うところの配列に相当する
のはなんだろう?

数値をバッファに格納していって平均値を計算するなどの処理はどうやるのか。
いろいろ調べてたどり着いたのがここ8 POINT MOVING AVERAGE (AVR 222)

SRAMに格納していけばよさそうな感じだ。SRAMへのアクセスはYレジスタを使っているので
まねしてみよう。上記サイトではSRAMのアドレスはハードコーディングだが、incファイル
見ると、開始アドレスはSRAM_STARTと定義されているようなので、そう書いておこう。

1.カウンターのr17をインクリメントしつつ4バイトリングバッファに格納
2.インクリメントするたびにリングバッファの平均を計算しr16に入れる
3.カウンターが9まで言ったら0に戻る

というプログラムを書いてみた。

.include "m168def.inc"

.org	0x0000	jmp reset   ;initialize
.equ	SIZE = 4
.def	acc = r16

;=============================================================
; 16bit divide by 2
; @0 : high address
; @1 : low address
;=============================================================
.macro Div16By2		; Div16By2 @0 @1
			mov		acc, @0
			lsr		@0
			andi	acc, 1
			lsr		@1
			sbrc	acc, 0
			ori		@1, 0b1000_0000
			nop
.endmacro

;=============================================================
; main
;=============================================================
reset:		ldi		r16, low(ramend)
			out		spl, r16
			ldi		r16, high(ramend)
			out		sph, r16
 
			; set y register sram start address
			ldi		yl, low(SRAM_START)
			ldi		yh, high(SRAM_START)

			clr		r17		; counter
			clr		acc
			rcall	store_acc	; reset buffer
			rcall	store_acc
			rcall	store_acc
			rcall	store_acc

loop:		mov		acc, r17
			rcall	store_acc
			rcall	average
			inc		r17
			cpi		r17, 10
			brlt	loop		; if r17 != 10, goto loop
			clr		r17			; else r17=0
			rjmp	loop

;=============================================================
; store_acc
; store acc valu into ring buffer
;=============================================================
store_acc:	st		y+, acc
			cpi		yl, low(SRAM_START)+SIZE
			breq	store_acc_resetpos
			rjmp	store_acc_ext
store_acc_resetpos:
			ldi		yl, low(SRAM_START)
			ldi		yh, high(SRAM_START)
store_acc_ext:
			ret
;=============================================================
; average
; take summation of ring buffer and divide by 4
;=============================================================
.def suml = r17
.def sumh = r18
average:
			push	yl
			push	yh
			push	suml
			push	sumh

			ldi yl, low(SRAM_START)
			ldi yh, high(SRAM_START)
			clr		suml
			clr		sumh

average_loop:
			ld		acc, y+
			add		suml, acc
			ldi		acc, 0
			adc		sumh, acc		; addition with carry
			cpi		yl, low(SRAM_START)+SIZE
			breq	avrage_div
			rjmp	average_loop
avrage_div:
			Div16By2	sumh, suml
			Div16By2	sumh, suml
			mov		acc, suml

			pop		sumh
			pop		suml
			pop		yh
			pop		yl
			ret



ループのたび(rcall average の後)に
(0+0+0+0)/4=0,
(0+1+0+0)/4=0,
(0+1+2+0)/4=0,
(0+1+2+3)/4=1
(4+1+2+3)/4=2,
(4+5+2+3)/4=3,
(4+5+6+3)/4=4,
(4+5+6+7)/4=5,
(8+5+6+7)/4=6,
(8+9+6+7)/4=7,
(8+9+0+7)/4=6,
(8+9+0+1)/4=4,
(2+9+0+1)/4=3
...
という計算結果がaccに入っているので一応動いているようだ。

AD 変換に挑戦 [AVR]

AD 変換に挑戦してみました。

PC0 に入力された電圧を計測して PB の LED に表示します。
基準電圧はAVccです。
精度は8bit で計測完了割り込みで計測しています。

ADCの設定もタイマーのときと同じく(データシートや解説書などにある)表を見て、
ADCSRAとADMUXというレジスタにビットを立ててセットすることで設定を有効にします。

割り込みの中でもう一回計測要求して、次の割り込みを発生させることで
連続して値を読みます。

.include "m168def.inc"	;

;=============================================================
; Constants
;=============================================================
.equ ADCSRAVAL	= (1<<ADEN)|(1<<ADSC)|(1<<ADIF)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
.equ ADMUXVAL	= (1<<REFS0)|(1<<ADLAR)

;=============================================================
; variables
;=============================================================
.def acc		= r16	; accumulator


;=============================================================
; program
;=============================================================
.cseg					   ; Code segment

;=============================================================
; vectors
;=============================================================
.org 	0x0000		jmp	main	 		; reset handler
.org	0x002A		jmp adc_complete	; adc complete

;=============================================================
; main
;=============================================================
main:
	cli

	; initialize port
	ldi		acc, 0xff
	out		ddrb, acc
	out		portb, acc

	; initialize stack pointer
	ldi		acc, low(ramend)	; get lower byte of end of ram address
	out		spl, acc			; init stack lower pointer
	ldi		acc, high(ramend)	; get higher byte of end of ram address

	; initialize adc
	ldi		acc, ADMUXVAL
	sts		admux, acc
	ldi		acc, ADCSRAVAL
	sts		adcsra, acc
	ldi		acc, 0x00
	sts		adcsrb, acc

	sei						; allow all interruption

main_loop:
	rjmp	main_loop		; loop

;=============================================================
; adc_complete
;=============================================================
adc_complete:
	rcall s_vchk
	reti

;=============================================================
; s_vchk
;=============================================================
s_vchk:
	; output value to portb
	lds		acc, adch
	out		portb, acc

	; setadc interruption again	
	ldi		acc, ADCSRAVAL
	sts		adcsra, acc

	ret



メロディーを鳴らす [AVR]

現時点でできたことの集大成で、メロディーを鳴らす
プログラムを作ってみました。

接続は以下
- PD0 と GRD の間に音圧ブザー
- PB 全部に LED(なくても動く)

デジットのトレーニングボード(ATMega168)でやってますんで、
ポート全部にLEDがついていて PB に出力中の音を表す変数を
表示させていますが、なくてもスピーカーだけつければ音は鳴ります。

コードはちょっと長いので GitHub に AvrMelodyPlayerという名前でアップしました。

いやー、電子工作たのしいわー。

データの最後を判定 [AVR]

フラッシュROMにデータを置いて読むでは変数をインクリメントしていって
あらかじめわかっているデータの長さになれば最後だと判断してました。

データの長さがわかっているならzレジスタの指すアドレス
データの最後のアドレスを比較して同じなら最後だと判定すれば
よいと思ったのですが、方法がわからずモヤモヤしていました。

そこで発見したのがこのサイト
とても勉強になります。そしてなんといってもコードが美しい!!(と初心者の私にもわかるほど)
その中のLessonA0でまさに
データの最後のアドレス比較をしていました。なるほど!そうやるのか!

まずはこの様にデータの後にラベルをつける
データの個数は偶数個
LEDDATA: .db データ...
LEDDATAEND:

これまで同様にzレジスタにデータの先頭アドレスセット
ldi zl,low(LEDDATA<<1)
ldi zh,high(LEDDATA<<1)
ldi r16,low(LEDDATA)

読み出し方も同じ
lpm r18, z+

判定はデータの最後のラベルの下位と上位アドレスを上でやったのと
同じ方法でビットシフトしてzl,zhと比較しします。
同じならデータの最後を読んだと判定しています。

cpi zl, low(LEDDATAEND<<1) ; 下位アドレス比較
brne somewhere ; どこかへジャンプ
cpi zh, high(LEDDATAEND<<1) ; 上位アドレス比較
brne somewhere ; どこかへジャンプ
ここまできたら終わり


これにより、カウンタ用のレジスタを使わなくて済みますが、
これはスペース(レジスタ)と時間(判定のための4行分の処理時間)のトレードオフです。
このご時勢にスペースたくさんあるのでケチケチすべきではありません(たぶん)。

それよりも何がうれしいかは、データの長さを変えてもコードが同じになること
だと思います。あと可読性もよい気がします。

"フラッシュROMにデータを置いて読む"を書き直したら次のようになりました。

.include "m168def.inc"
                                
.org 	0x0000		jmp	reset 	;各種リセット

reset:		ldi	r16, low(ramend)
			out	spl, r16
			ldi	r16, high(ramend)
			out	sph, r16

			ldi r16, 0x00
			out ddrd, r16	; d input
			ldi r16, 0xff
			out ddrb, r16	; b output
			out portb, r16

main:		ldi	zl,low(LEDDATA<<1)
			ldi	zh,high(LEDDATA<<1)

main1:		lpm	r18, z+

main2:		rcall delay1ms
			in	r16,pind
			cpi	r16,0b1111_1110
			brne main2
			out	portb,r18
main3:		rcall delay1ms
			in	r16,pind
			cpi	r16,0b1111_1111
			brne main3

			; check more data left
			cpi zl, low(LEDDATAEND<<1)
			brne main1
			cpi zh, high(LEDDATAEND<<1)
			brne main1
			rjmp main

LEDDATA:	.db 0x00,0x01
			.db 0x02,0x04
			.db 0x08,0x10
			.db 0x20,0x40
			.db 0x80,0xFF
LEDDATAEND:

delay1ms:	push r16
			in r16,sreg
			push r16
			push r17
;			100000-10=99990
			ldi r17,198
delay2:		ldi r16,100
delay1:		dec r16
			nop
			cpi r16,0
			brne delay1
			dec r17
			cpi r17,0
			brne delay2
;			999990-(100*5+4)*198=198
			ldi r16,46
delay3:		dec r16
			cpi r16,0
			brne delay3
;			198-46*4=14
			nop
			nop
			nop
			pop r17
			pop r16
			out sreg,r16
			pop r16
			ret

16bit タイマー1 のオーバーフロー割り込み [AVR]

16bit タイマーでは256秒までカウントできるそうな。
これも動いたのがうれしいのでアップ。

なお、クロックは1MHzです。
.include "m168def.inc"

.def	STACK	= r16
.def	R_TEMP1	= r17
.def	R_TEMP2	= r18

.equ    P_LED   =   portb
.equ    B_LED   =   0

.org 	0x0000		jmp	reset 		;各種リセット
.org	0x001A		jmp tim1_ovf	;タイマ/カウンタ1オーバーフロー


tim1_ovf:	; evaculate status register
			in	STACK, sreg

			; stop timer
			ldi	R_TEMP1, 0x00		; stop timer
			sts	tccr1b, R_TEMP1

			; toggle led
        	in  R_TEMP1, P_LED
        	ldi R_TEMP2, 0x00
        	sbr R_TEMP2, (1<<B_LED)
        	eor R_TEMP1, R_TEMP2	; take 1 xor x toggle the bit of x
        	out P_LED, R_TEMP1

			rcall strtcnt

			; restore status register
			out	sreg, STACK

			reti

reset:		cli

			ldi R_TEMP1, low(ramend)
			out spl,R_TEMP1
			ldi R_TEMP1, high(ramend)
			out sph,R_TEMP1

        	ldi R_TEMP1, 0xff
        	ldi R_TEMP2, (1<<B_LED)
        	out ddrb, R_TEMP1
        	out P_LED, R_TEMP2

			rcall strtcnt

			lds	R_TEMP1, timsk1
			sbr R_TEMP1, (1<<toie1)
			sts timsk1, R_TEMP1

			sei

main:		
			rjmp main

strtcnt:
			; set counter
			ldi	R_TEMP1, 0x1b
			ldi	R_TEMP2, 0x17
			sts	tcnt1h, R_TEMP1
			sts tcnt1l, R_TEMP2

			; start counter
			ldi	R_TEMP1, 0x01
			sts	tccr1b, R_TEMP1

			ret


CTC の PWM 動いた! [AVR]

最初はチンプンカンプンだっがけど tccr0a と tccr0b のビットを
表を見ながら設定すればよいことがわかった。
com0a1,com0a0 のビットを 01にする必要があったのが
わからずにハマってしまったが動いたので感無量。

.include "m168def.inc"
                                
.org 	0x0000		JMP	RESET 	;各種リセット

reset:		ldi r16, low(ramend)
			out spl, r16
			ldi r16, high(ramend)
			out sph, r16

			ldi r16, 0xff
			out ddrd, r16

main:		cli

			; set tccr0a
			; standard port, CTC mode
			ldi r16, 0<<com0a1 | 1<<com0a0 | 1<<wgm01 | 0<<wgm00

			out tccr0a, r16

			; set tccr0b
			; CTC mode, prescaler(010)=clk/1
			ldi r16, 0<<wgm02 | 0<<cs02 | 1<<cs01 | 0<<cs00
			out tccr0b, r16
			
			ldi r16, 0x00
			out tcnt0 ,r16	; counter init val = 0
			
			ldi r16, 0x30	; top value
			out ocr0a, r16

			sei

main01:
			rjmp main01

フラッシュROMにデータを置いて読む [AVR]

フラッシュROM
.db と書くとフラッシュromに保存されるそうな。
それをPD0ピンのスイッチを押すごとにlpmで呼んでいくサンプル

ちなみにこれもデジット
マイコン製作会
で懇切丁寧に教えてもらったものです。

.include "m168def.inc"
                                
.org 	0x0000		jmp	reset 	;initialize

reset:		ldi	r16, low(ramend)
			out	spl, r16
			ldi	r16, high(ramend)
			out	sph, r16

			ldi r16, 0x00
			out ddrd, r16	; d input
			ldi r16, 0xff
			out ddrb, r16	; b output
			out portb, r16

main:		ldi	zl,low(LEDDATA<<1)
			ldi	zh,high(LEDDATA<<1)
			ldi r16,low(LEDDATA)
			clr r19

main1:		inc r19
			lpm	r18,z+
			cpi r19, 6
			breq main
main2:		rcall delay1ms
			in	r16,pind
			cpi	r16,0b1111_1110
			brne main2
			out	portb,r18
main3:		rcall delay1ms
			in	r16,pind
			cpi	r16,0b1111_1111
			breq main1
			rjmp main3

LEDDATA:	.db 0x00,0x01
			.db 0x02,0x04
			.db 0x08,0x0F


delay1ms:	push r16
			in r16,sreg
			push r16
			push r17
;			100000-10=99990
			ldi r17,198
delay2:		ldi r16,100
delay1:		dec r16
			nop
			cpi r16,0
			brne delay1
			dec r17
			cpi r17,0
			brne delay2
;			999990-(100*5+4)*198=198
			ldi r16,46
delay3:		dec r16
			cpi r16,0
			brne delay3
;			198-46*4=14
			nop
			nop
			nop
			pop r17
			pop r16
			out sreg,r16
			pop r16
			ret



AVRアセンブラでCTCタイマー [AVR]

AVRマイコンプログラミング入門(廣田修一)
をみながら勉強中。
ほぼコピペですけど動いたのがうれしいので掲載。

CTC割り込み
1MHzで動かしていたなら、その1/1024が100回で割り込み発生
するので、(1/1M)*1024*100=100ms おきにLチカが発生。

.include "m168def.inc"

.def	STACK	=	r16
.def	R_TEMP1	=	r17
.def	R_TEMP2	=	r18

.equ	P_LED	=	portb
.equ	B_LED	=	0

.org	0x00		jmp	reset			;initialize
.org	0x1c		jmp tim0_compa		;タイマ/カウンタ0比較A一致

tim0_compa:
		in	STACK,	sreg				; evaculate registers
		
		in	R_TEMP1, P_LED
		ldi	R_TEMP2, 0x00
		sbr	R_TEMP2, (1<<B_LED)
		eor	R_TEMP1, R_TEMP2			; take 1 xor x toggle the bit of x
		out	P_LED, R_TEMP1

		out	sreg, STACK					; restore registers
		
		reti

reset:
		cli								; prevent interrupt

		ldi	R_TEMP1, 0xff
		ldi R_TEMP2, 0x01
		out	ddrb, R_TEMP1
		out portb, R_TEMP2

		ldi	R_TEMP1, 0x02
		out	tccr0a, R_TEMP1				; set timer 0 ctc mode

		ldi	R_TEMP1, 0x05
		out	tccr0b, R_TEMP1				; prescaler 1024 (means interrupt every 1024 clocks)

		ldi	R_TEMP1, 0x62				; count up to 100 til interrupt
		out	ocr0a, R_TEMP1

		lds	R_TEMP1, timsk0				; set timer mask
		sbr	R_TEMP1, (1<<ocie0a)
		sts timsk0, R_TEMP1				; start timer

		sei								; allow interrupt

main:
		rjmp main



AVRアセンブラはじめました [AVR]

デジットマイコン製作会で習ってきました。
とりあえずLチカ
.include "m168def.inc"

.org	0x00	jmp	reset

reset:							; initialization
		ldi	r16, low(ramend)
		out	spl, r16
		ldi	r16, high(ramend)
		out	sph, r16
		
		ldi	r16, 0xff
		out	ddrb, r16			; set direction on portb

		ldi	r16, 2
		out	portb, r16			; turn off portb pin2, others are on 

main:
		rjmp main


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。