Assembler - Spiel

From XennisWiki
Jump to: navigation, search

Programmierung des Mikrocontrollers Atmel AVR - ATmega 16.

Zum besseren Verständnis des Programms der Programmablaufplan: File:Assembler - Spiel PAP.pdf

; TGI - Versuch 12
; Spiel (Interrupts, Timer und A/D-Wandlung)

.include "m16def.inc"

; ##### Benutzerdefinierte Konstanten #####
.equ	vglWert0	= 156 				; Vergleichswert für Timer/Counter0
.equ	vglWert1 	= 3906				; Vergleichswert für Timer/Counter1
; ##### Ende benutzerdefinierte Konstanten #####

.dseg
.org	0x0060

; #### Benutzerdefinierte Variablen ####
figure_pos:	.byte	1				; (0-11) Position der Spielfigur auf dem Display (linke seite)
block_pos_x:	.byte	1				; (4-15) x-Position der rechten unteren Ecke der Box
block_pos_y:	.byte	1				; (0-78) y-Position der rechten unteren Ecke der Box
gameover:	.byte	1				; 1: Spielfigur getroffen, 0: nicht getroffen
seed:		.byte	1				; Wert des Zufallszahlengenerators
start_pos:	.byte	1				; Anfangszeile der Box (kleiner = leichter, da weiter oben)
; #### Ende benutzerdefinierte Variablen ####

; #### Interupt Vektoren Tabelle ####
.cseg
.org	0x0000						; Reset Address
	jmp		init				; Springe zu "init"
.org	0x000C						; Timer 1 Compare Match A
	jmp		t1comp_isr			; Springe zur Steinposition- und Kollisionsberechnung
.org	0x0026						; Timer 0 Compare
	jmp		t0comp_isr			; Springe zur poti-abtastung 
.org	0x002a						
; #### Ende Interupt Vektoren Tabelle ####

; #### Systeminitialisierung ####
init:
	; Stackpointer initialisieren
	ldi 	R16,		high(RAMEND)
	out 	SPH,		R16
	ldi 	R16,		low(RAMEND)
	out 	SPL,		R16
	; Initialisierung des Textmatrixdisplays
	call	lcd_init
	; Konfiguration der Interuptquellen
	call	init_timer0
	call	init_timer1
	; Konfiguration des A/D Wandlers
	call 	init_adc
	; Setzen des PORTC zum Einlesen des Tasters
	ldi	R16,		0x00
	out	DDRC,		R16
	ldi	R16,		0xFF
	out	PORTC,		R16

; #### Ende Systeminitialisierung ####

; #### Hauptprogramm ####
main:
	ldi	R16,		0
	sts	gameover,	R16			; gameover=0 setzen

	call	lcd_startdisplay

	; Triggern auf Wechsel des Pegels des Tastenfeldes
	ldi	R17,		1<<PC0			; Maske für Pin C0
	in	R16,		PINC			; Pin C0 einlesen
	and	R16,		R17
	brne	m_trigger0				; Ist PC0=1 (d.h. Z=0) => springe
m_trigger1:						; Warte auf PC0=1
	in	R16,		PINC
	and	R16,		R17
	breq	m_trigger1				; Ist PC0=0 (d.h. Z=1) => warte bzw. springe
	rjmp	m_setSeed
m_trigger0:						; Warte auf PC0=0
	in	R16,		PINC
	and	R16,		R17
	brne	m_trigger0				; Ist PC0=1 (d.h. Z=0) => warte bzw. springe

	; Setze seed Wert
m_setSeed:
	in	R16,		TCNT1L			; Lade das Low-Byte des akutellen Timerwertes des Timer/Counter1
	sts	seed,		R16			; Speicher das Low-Byte den low(Timerwert) in "seed"
	cpi	R16,		0
	brne	m_setPos				; Ist low(Timerwert)!=0 => springe

	ldi	R17,		42
	sts	seed,		R17			; Ist low(Timerwert)=0 => setze "seed" mit beliebigen Wert

	; Setze zufällige Position
m_setPos:
	call	lfsr
	sts	block_pos_x,	R16
	sts	start_pos,	R16

	call	lcd_clear
	sei						; Globale Unterbrechungen freigeben

	; Warte solange gameover!=1
m_waitGameover:
	lds	R16,		gameover
	cpi	R16,		1
	brne	m_waitGameover				; Ist gameover!=1 => warte in Schleife

m_end:	
	cli						; Globale Unterbrechungen speeren

	call	lcd_gameover
	; Startvergleichswert für Timer/Counter1 wiederherstellen
	ldi	R16,		high(vglWert1)		; Vergleichswert (Hi) (zuerst High-Byte schreiben!)
	out	OCR1AH,		R16
	ldi	R16,		low(vglWert1)		; Vergleichswert (Lo) ( = 3906)
	out	OCR1AL,		R16

	jmp	main


; #### Ende Hauptprogramm ####


; ###########################################################################
; #### Benutzerdefinierte Unterprogramme ####


; # INIT_ADC
; # ---------
; # Initialisierung des A/D-Wandlers
; # I: -
; # O: -
init_adc:
	push	R16

	; Multiplexer Selection Register
	in 	R16,		ADMUX
	sbr	R16,		0<<REFS1	
	sbr	R16,		1<<REFS0		; Referenzspannung: Versorgungsspannung
	sbr	R16,		1<<ADLAR		; linksbuendiges Speichern
	out	ADMUX,		R16
		
	; Control and Status Register A
	in	R16,		ADCSRA
	sbr	R16,		1<<ADEN			; ADC anschalten
	sbr	R16,		1<<ADPS2
	sbr	R16,		0<<ADPS1
	sbr	R16,		1<<ADPS0		; Taktung: clk/32
	out	ADCSRA,		R16

	pop	R16
	ret


; # READ_ADC
; # ---------
; # Liest den A/D-Wandler aus
; # I: -
; # O: R16 = Gewandelter Wert
read_adc:

	; Control and Status Register A
	in	R16,		ADCSRA
	sbr	R16,		1<<ADSC			; Konvertierung starten
	out	ADCSRA,		R16

ra_waitConversation:					; Benötigt 13 Takte zum Wandeln der analogen Größe, darum warte solange
	in	R16,		ADCSRA
	andi	R16,		1<<ADSC			; Maske fuer ADSC
	brne	ra_waitConversation			; Ist Konvertierung noch nicht beendet (d.h. Z=0) => warte in Schleife

	in	R16,		ADCH			; Gewandelten Wert auslesen

	ret


; # INIT_TIMER0
; # -----------
; # Initialisierung von Timer/Counter0
; # I: -
; # O: -
init_timer0:
	push	R16

	; Control Register
	in	R16,		TCCR0
	sbr	R16,		1<<CS02
	sbr	R16,		0<<CS01
	sbr	R16,		0<<CS00			; Zählertakt = 1/256 * Systemtakt
	sbr	R16,		0<<WGM00
	sbr	R16,		1<<WGM01		; Rücksetzen nach Erreichen des Vergleichswertes
	sbr	R16,		0<<COM00
	sbr	R16,		0<<COM01		; OC0 getrennt ( Nur Interupt ?! )
	out	TCCR0,		R16

	; Output Compare Register 0
	ldi	R16,		vglWert0		; Vergleichswert für Zähler ...
	out	OCR0,		R16			; in Vergleichswertregister speichern

	; Mask Register
	in	R16,		TIMSK
	sbr	R16,		1<<OCIE0		; Interupt bei Erreichen des Vergleichswert auf Adr. 0C0addr
	sbr	R16,		0<<TOIE0		; Kein Interupt bei Ueberlauf
	out	TIMSK,		R16

	pop	R16
	ret


; # INIT_TIMER1
; # ------------
; Initialisierung von Timer/Counter1
; I: -
; O: -
init_timer1:
	push	R16

	; Control Register A (TCCR1A)
	in	R16,		TCCR1A
	sbr	R16,		0<<COM1A1	
	sbr	R16,		0<<COM1A0		; OC1A getrennt
	sbr	R16,		0<<COM1B1
	sbr	R16,		0<<COM1B0		; OC1B getrennt
	sbr	R16,		0<<WGM11	
	sbr	R16,		0<<WGM10		; CTC Rücksetzen bei OCR1A
	out	TCCR1A,		R16

	; Control Register B (TCCR1B)
	in	R16,		TCCR1B
	sbr	R16,		0<<WGM13
	sbr	R16,		1<<WGM12		; CTC Rücksetzen bei OCR1A
	sbr	R16,		0<<CS12
	sbr	R16,		1<<CS11
	sbr	R16,		1<<CS10			; CLK / 64
	out	TCCR1B,		R16

	; Output Compare Register 1 A
	ldi	R16,		high(vglWert1)		; Vergleichswert (Hi)
	out	OCR1AH,		R16
	ldi	R16,		low(vglWert1)		; Vergleichswert (Lo) ( = 3906)
	out	OCR1AL,		R16

	; Mask Register (TIMSK)
	in	R16,		TIMSK
	sbr	R16,		1<<OCIE1A		; Interupt bei Vergleichswert auf OC1Aaddr
	sbr	R16,		0<<OCIE1B		; Kein Interupt bei Vergleichswert B
	sbr	R16,		0<<TOIE1		; Kein Interupt bei Ueberlauf
	out	TIMSK,		R16	

	pop	R16
	ret


; # CHECK_COLLISION
; # ----------------
; # Ueberprueft, ob Box und Figur kollidieren und setzt ggf. gameover auf 1
; # I: -
; # O: -
check_collision:
	push	R16
	push	R20
	push	R21

	lds	R16,		block_pos_y		; y-Position (Box) laden
check73:
	cpi	R16,		73			; Block oberhalb von Figur Arm ?
	brlo	check71					; ... dann springe

	ldi	R20,		0			; y-Position (Box) >= 73
	ldi	R21,		4
	rjmp	cc_end

check71:
	cpi	R16,		71			; Block oberhalb von Figur Hals
	brlo	check70					; ... dann springe

	ldi	R20,		1			; y-Position (Box) >= 71
	ldi	R21,		3
	rjmp	cc_end

check70:						; y-Position (Box) = 70
	ldi	R20,		2
	ldi	R21,		2

cc_end:
	call	check_collision_corner

	pop	R21
	pop	R20
	pop	R16
	ret


; # check_collision_corner
; # ----------------
; # Ueberprueft, ob Box und Figur kollidieren
; I: R20 = Versatz der Stelle (z.B. linker Arm) zum Ursprung der Figur
;    R21 = Versatz der Stelle (z.B. rechter Arm) zum Ursprung der Figur
; O: 
check_collision_corner:
	push	R16
	push	R17
	push	R20
	push	R21

	lds	R16,		figure_pos		; linke x-Position der Figur
	lds	R17,		block_pos_x		; rechte x-Position der Box

	add	R20,		R16			; figur_pos + Versatz = Linke x-Position der Stelle

	cp	R17,		R20			; Box links neben Figur? (Rechte Ecke der Box < linke Ecke der Stelle)
	brlo	ib_end

	ldi	R20,		4
	sub	R17,		R20			; = linke x-Position der Box = block_pos_x - 4
	add	R21,		R16			; Rechte x-Position der Stelle

	cp	R21,		R17			; Box rechts neben Figur (Rechte Ecke der Stelle < linke Ecke der Box)
	brlo	ib_end

	ldi	R16,		1			; Gameover auf 1 setzen
	sts	gameover,	R16

ib_end:
	pop	R21
	pop	R20
	pop	R17
	pop	R16
	ret


; #### Ende benutzerdefinierte Unterprogramme ####


; ###########################################################################
; ##### Benutzerdefinierte Interrupt-Service-Routinen #####


; # T0COMP_ISR
; # -----------
; # (Interupt-Service-Routine)
t0comp_isr:
	push	R15					; Divisionsrest div8u
	push	R16					; Poti-Wert
	push	R17					; Skalierungsgroesse
	in	R16,		SREG			; Sicher SREG
	push	R16

	call	read_adc				; Poti-Wert => R16
	ldi	R17,		0x16			; Skalierungsgroesse = 22
	call	div8u					; Skalieren, Erg => R16
	call	draw_man				; Position aktualisieren (I: R16)

	pop	R16
	out	SREG,		R16
	pop	R17
	pop	R16
	pop	R15
	reti


; # T1COMP_ISR
; # -----------
; # (Interupt-Service-Routine)
t1comp_isr:
	push	R16
	push	R17
	push	R18
	push	R19
	in	R16,		SREG
	push	R16
		
	lds	R16,		block_pos_y		; y-Position laden ...
	inc	R16					; inkrementieren ...
	sts	block_pos_y,	R16			; und speichern

	cpi	R16,		70			; Überprüfen, ob die Box und die Spielfigur kollidieren könnten (d.h. Y-Koordinate>=70)
	brlo	check_bodenkontakt			; Ist y-Postion<70 => springe
	call	check_collision				; y-Postion>=70 => rufe Unterprogramm "check_collision" auf

	lds	R17,		gameover
	cpi	R17,		1
	breq	t1i_end

check_bodenkontakt:
	cpi	R16,		78			; Überprüfen, ob die Box auf dem Boden angekommen ist (d.h. Y-Koordinate=78)
	brlo	redraw_box

reset_box:
	lds	R16,		start_pos		; Box-Startposition laden
	sts	block_pos_y,	R16			; Box-Startposition setzen

	call	lfsr					; "zufaelligen" x-Wert ermitteln
	sts	block_pos_x,	R16			; "zufaelligen" x-Wert schreiben		

redraw_box:
	lds	R16,		block_pos_x		; x-Position uebergeben
	lds	R17,		block_pos_y		; y-Position uebergeben
	call	draw_box

	; Steine beschleunigen:
	in	R17,		OCR1AL			; Vergleichswert fuer Timer 1 laden (Lo)
	in	R16,		OCR1AH			; Vergleichswert fuer Timer 1 laden (Hi)
	ldi	R18,		0x0A			; Dekrementierungswert
	clr	R19					; Temp fuer Dekrement		

	sub	R17,		R18			; Dekrementwert von Lo abziehen  ! ggf. Unterlauf
	sbc	R16,		R19			; = (R16 - 0) - Carry  (bei Unterlauf Hi erniedrigen)
		
	cpi	R16,		0x04			; entspricht 1024 (0x 04.00) (15Hz); kleiner als 1024 bzw. schneller als 15Hz?
	brmi	set_max_speed				; Branch if Minus d.h. Kleiner als 1024 => set_max_speed
	rjmp	save_speed				; Sonst Dekrementierten Wert abspeichern

set_max_speed:
	ldi	R16,		0x04		
	ldi	R17,		0x00

save_speed:
	out	OCR1AH,		R16
	out	OCR1AL,		R17

t1i_end:
	pop	R16
	out	SREG,		R16
	pop	R19
	pop	R18
	pop	R17
	pop	R16
	reti


; ###########################################################################
; #### Vorgegebene Unterprogramme ####


; # lcd_clear
; # ---------
; # Löscht das Display
; # I: -
; # O: -
lcd_clear:
	nop
	ret


; # lcd_init
; # ---------
; # Initialisiert das Display
; # I: -
; # O: -
lcd_init:
	nop
	ret


; # lcd_startdisplay
; # ----------------
; # Anzeigen des Startbildschirms
; # I: -
; # O: -
lcd_startdisplay:
	nop
	ret


; # lcd_gameover
; # ----------------
; # Anzeigen des Gameoverbildschirms
; # I: -
; # O: -
lcd_gameover:
	nop
	ret

; # draw_man
; # ---------
; # Zeichnet Spielerfigur
; # I: R16 = Position in x-Richtung (0-11) (Linke Seite)
; # O: -
draw_man:
	nop
	ret


; # draw_box
; # --------
; # Zeichnet Box
; # I: R16 = x-Koordinate der unteren rechte Ecke (4-15)
; #    R17 = y-Koordinate der unteren rechte Ecke (0-77)
; # O: -
draw_box:
	nop
	ret


; # lfsr
; # -------
; # Zufallsgenerator fuer Steinposition
; # I: LFSR = Initialisierungswert != 0
; # O: LFSR = Zufallszahl

; # O: R16 = Zufallszahlzwischen 0 und ????
lfsr:
	nop
	ret


; # div8u
; # ---------
; # 8-Bit Divisionsfunktion
; # I: R16 = Dividend
; #    R17 = Divisor
; # O: R16 = Ergebnis der Division
; #    R15 = Rest der Divison
div8u:
	nop
	ret

; #### Ende vorgegebene Unterprogramme ####