Source file: /~heha/Mikrocontroller/Sternhimmel/Firmware.zip/stern2.S

#define __SFR_OFFSET 0
#include <avr/io.h>

.section .bss
azt:	.ds.b	34	// 31 Byte Anoden-Zeit-Tabelle für Compare-ISR
azt2:	.ds.b	34	// Anoden-Zeit-Tabelle für Vorberechnung
repid3:	.ds.b	1	// Report-ID (=3), auch als Guard verwendbar?
lht:	.ds.b	100	// 100 Byte LED-Helligkeits-Tabelle
			// Diese Tabelle ragt in die 100h-Adressen hinein;
			// die 8-bit-Programmlogik funktioniert gerade so

#define DEFD r8		// Default für PortD
#define DEFB r9		// Default für PortB
#define	CurKatode r15	// Gerade aktiver Katodentreiber (0..9)
#define	ONES r14	// immer 0xFF
#define ZERO YH		// YH ist immer 0 (r1 nicht!), Y zeigt in azt
// ebenfalls „giftig“: R2 (ISR-Temp), R12(=40h), R13(für SREG)

.section .text
	rjmp	init
	reti
	reti
/*
 Timer2-Vergleich: Anode (oder auch Katode) weiterschalten, max. 32 Takte!
vorgehalten:
 R2, R3 = wird von der ISR zerstört
 R8, R9 = Konstanten
 R14 = immer 0xFF
 Y = Zeiger in Anoden-Zeit-Tabelle (Y ist bei avr-gcc der Stapelrahmenzeiger)
Die Anoden-Zeit-Tabelle "azt" besteht aus 1 bis 11 Einträgen der Form
 1 Byte für nächsten Output-Compare-Wert, regulär maximal 230
der erste Eintrag enthält zusätzlich zum Schalten von Katoden:
  1 Byte für eine Katode (von 6) an PortC
 1 Byte für 4 Anoden (PortD) )
 1 Byte für 6 Anoden (PortB) ) 0-Bits für eingeschaltete Anoden, Katodenbits unverändert
   Der Wert 255 für den Output-Compare-Wert markiert das Ende der Tabelle,
   dann noch eingeschaltete Anoden lassen die LEDs bis zum Ende leuchten.
   Der maximale Wert ergibt sich daraus, dass für das Kopieren
   der nächsten <azt>-Tabelle Zeit erforderlich ist.
Damit kann der Output-Compare-Interrupt sehr schnell arbeiten.
Folgende Tricks werden für kürzeste Latenz eingesetzt:
 * ISR-Start direkt in Interruptvektortabelle, kein Sprung, -2 Takte
 * ISR wird bei OCR2=0xFF gleichzeitig mit Überlaufinterruptflag gestartet,
   dies wird ausgewertet zum Katode weiterschalten.
 * Eine extra Overflow-ISR ist umständlicher, weil man den
   Compare-Interrupt irgendwie totlegen müsste
 * Kein Register-Push/Pop, vorgehaltene „globale“ Register
 * Keine Veränderung der Flags
*/				//(4) ISR-Aufruf (Latenz)
	ld	r2,Y+		// alle folgenden Befehle ändern SREG nicht!
	out	OCR2,r2		//(3) nächster Interruptzeitpunkt
	in	r2,TIFR		// (leider nicht bitadressierbar!)
	sbrs	r2,6		// Überlauf-Interrupt gleichzeitig?
	 rjmp	no_ovl		//(3-4)
	out	TIFR,r12	// (1) anhängigen Interrupt löschen
	out	PORTD,DEFD	// alle Anoden (und Katoden) ausschalten
	out	PORTB,DEFB	// (2) (Geisterbilder vermeiden)
	ld	r2,Y+
	out	PORTC,r2	// (3) Neue Katode
no_ovl:	ld	r2,Y+
	out	PORTD,r2	//(3) 4 Anoden (mit Katode)
	ld	r2,Y+
	out	PORTB,r2	//(3) 6 Anoden (mit Katode)
	in	r2,OCR2
	cpse	r2,ONES		//(2) Vergleich mit 0FFh
	reti			//(4)
				//Summe (28) bzw. (23)
	ldi	YL,azt
	sbic	SPCR,0
	 rjmp	reent
	sbi	SPCR,0		// Reentranz-Sperre
	//push	r13
again:	 cbi	SPCR,1
	 in	r13,SREG
	 sei
//Da bei (langen) USB-Interrupts der Y-Zeiger temporär wild herumgeistern kann,
//sollten ungenutzte Speicherbereiche mit 0FFh gefüllt werden.?
//Mit Flackereffekten muss man leben.
	 rcall	calc_azt	//(viele Takte)
	 out	SREG,r13
	 sbic	SPCR,1
	  rjmp	again
	//pop	r13
	cbi	SPCR,0
	reti
reent:	sbi	SPCR,1		// "bitte <calc_azt> wiederholen"
	reti

//Anoden-Tabelle, low-aktiv, für PortD und PortB, ungenutzte Bits 1
ant:	.byte	0xEF,0xFF
	.byte	0xDF,0xFF
	.byte	0xBF,0xFF
	.byte	0x7F,0xFF
	.byte	0xFF,0xFE
	.byte	0xFF,0xFD
	.byte	0xFF,0xFB
	.byte	0xFF,0xF7
	.byte	0xFF,0xEF
	.byte	0xFF,0xDF

//Katoden-Tabelle, high-aktiv, für PortC, PortD und PortB, ungenutzte Bits LOW
kat:	.byte	0x00,0x00,0x80, 0x00,0x00,0x40
	.byte	0x00,0x02,0x00, 0x00,0x01,0x00
	.byte	0x20,0x00,0x00, 0x10,0x00,0x00
	.byte	0x08,0x00,0x00, 0x04,0x00,0x00
	.byte	0x02,0x00,0x00, 0x01,0x00,0x00

//Exponentialfunktion (generiert durch exp.awk) für LED-Pulsweite, 32 Stufen
exp:	.byte	0,1,2,3,5,6,8,10,12,15,17,20,24,27,32,36
	.byte	42,47,54,61,70,79,89,100,113,127,143,161,181,203,227,255
//Kode-Byte-Adresse hier < 256!! Im Listing kontrollieren!

calc_azt:	//darf R2, R3 und Y nicht verändern
	push	r0
	push	r1
//	push	r3
//	push	r4
//	push	r5
//	push	r6
//	push	r7
//	push	r10
	push	XL
	push	XH
	push	ZL
	push	ZH
//Daten aus azt2 nach azt vorkopieren
//memcpy(azt,azt2,sizeof(azt));
	 ldi	ZL,lo8(azt)
	 ldi	ZH,hi8(azt)
	 ldi	XL,lo8(azt2)
	 ldi	XH,hi8(azt2)
ca0:	 ld	r0,X+
	 st	Z+,r0
	 cpi	XL,lo8(repid3)
	 brne	ca0
//const BYTE *p=lht;
	 inc	XL
//BYTE *q=azt2;
#define q r10
	 mov	q,ZL
//Berechnen der Anoden-Zeit-Tabelle "azt" anhand der LED-Helligkeits-Tabelle "lht"
//CurKatode++;
	 inc	CurKatode
//if (CurKatode>=10) CurKatode=0;
	 ldi	ZL,10
	 cp	CurKatode,ZL
	 brcs	ca9
	 clr	CurKatode
ca9:
//p+=10*CurKatode;
	 mul	CurKatode,ZL
	 add	XL,r0
//BYTE u=0;	// untere Grenze
#define u r6
	 clr	u
//bool f=false;
	 clt
//do{
ca1:
// BYTE o=255;	// obere Grenze
#define o r7
	 mov	o,ONES
// WORD m=0xFC3F;	// Bit-Maske für Anoden
#define mD r4
#define mB r5
	 movw	mD,DEFD
	 ldi	ZL,ant		// im AVR-GCC ist dies die Byte-Adresse
// for (BYTE i=0; i<10; i++) {
ca2:
//  BYTE t=exp[p[i]>>3];
#define t r3
	 mov	r0,ZL
	 ld	t,X+		// LED-Helligkeit
	 ldi	ZL,exp		// das kürzere "subi ZL,-exp" kann der Linker nicht auflösen
	 add	ZL,t
	 lpm	t,Z		// Exponentialfunktion anwenden, t = Zeit
	 mov	ZL,r0
	 lpm	r0,Z+		// Zugehörige Anoden-Maske für PortD
	 lpm	r1,Z+		// Zugehörige Anoden-Maske für PortB
//  if (t>u) {	// Minimum suchen, welches größer als "u" ist
	 cp	u,t
	 brcc	ca3
//   m&=ant[i];			// Anode zuschalten (wenn u<t)
	 and	mD,r0		// Gesamt-Maske bearbeiten: PortD
	 and	mB,r1		// dito für PortB
//   if (o>t) o=t;	// Compare-Match-Wert ermitteln
	 cp	t,o
	 brcc	ca3
	 mov	o,t		// Minimum finden
//  }
ca3:	 cpi	ZL,kat		// alle 10 LEDs (Anoden) absuchen
	 brne	ca2
	 sbiw	XL,10		// Zeiger auf LED-Helligkeiten (lht) zurücksetzen, XH wieder 0
// }
	 add	ZL,CurKatode	// Z zeigt auf Katoden-Tabelle, passende Katode adressieren
	 add	ZL,CurKatode
	 add	ZL,CurKatode
	 mov	r1,XL
	 mov	XL,q
// *q++ = o==255 ? o : o-1;
	 mov	r0,o
	 cpse	r0,ONES
	  dec	r0
	 st	X+,r0		// nächster Compare-Wert
// if (!f) *q++=katC[CurKatode];
	 lpm	r0,Z+		// schon mal Katodenbit für PortC laden
	 brts	ca4		// Erster Durchlauf?
	 st	X+,r0		// Katoden umschalten (Anoden sind aus)
ca4:
// *q++=LOWORD(m)|katD[CurKatode];
// *q++=HIWORD(m)|katB[CurKatode];
	 lpm	r0,Z+
	 or	mD,r0		// Anodenwert für PortD mit Katodenmaske
	 st	X+,mD
	 lpm
	 or	mB,r0		// dito für PortB
	 st	X+,mB
	 mov	q,XL
	 mov	XL,r1
// u=o;
	 mov	u,o
// f=true;
	 set			// nächste Runde kennzeichnen (ohne Header)
//}while (u<255);
	 cp	u,ONES
	 brne	ca1
#undef u
#undef o
#undef mB
#undef mD
#undef t
	pop	ZH
	pop	ZL
	pop	XH
	pop	XL
//	pop	r10
//	pop	r7
//	pop	r6
//	pop	r5
//	pop	r4
//	pop	r3
	pop	r1
	pop	r0
	ret

/******************
 * Unterprogramme *	
 ******************/
.section .bss
seed:	.ds.b	2
.section .text
/*=========================
  = Zufallszahl ermitteln =
  =========================*/
// PE: R24 = gewünschtes Maximum (inklusive)
// PA: R16 = Ergebnis, <= R24
// VR: R0,R1,R16,R17,R18=0,R25
random0:
	lds	r0,seed
	lds	r1,seed+1
ra0:	clr	r16
	mov	r18,r24
ra1:	mov	r25,r1
	andi	r25,0xB4	// gewünschte Bits
	clr	r17
ra2:	eor	r25,r17		// Schleife bildet ungerade Parität im MSB
	mov	r17,r25
	andi	r25,0x80	// MSB stehen lassen
	add	r17,r17		// linksschieben
	brne	ra2		// Schleife solange 1-Bits vorhanden
	rol	r25
	rol	r0
	rol	r1
	rol	r16		// Ergebnisbits
	lsr	r18		// Rundenzähler als Schieberegister
	brne	ra1		// nächstes Bit
	cp	r24,r16		// Ergebnis zu groß?
	brcs	ra0		// noch einmal würfeln
storeseed:	
	sts	seed,r0
	sts	seed+1,r1
	ret

// Speicher (RAM) durchsuchen	
// PE: Z = Adresse
//     R1 = Länge 1..256 (0 = 256)
//     R16 = Vergleichsbyte
// PA: ZF=1 wenn gefunden, sonst ZF=0
//     Z zeigt hinter gefundene Position
//     R1 = verbleibende Bytes
// VR: R0,R1,Z
repne_scasb:
	ld	r0,Z+
	cp	r0,r16
	breq	sce	// raus mit ZF=1
	dec	r1
	brne	repne_scasb
	clz
sce:	ret

// PE: R24 = gewünschtes Maximum
//     R19 = gewünschtes Minimum
//     Z = RAM-Zeiger auf vorhandene Zufallszahlen, die NICHT herauskommen sollen
//     R20 = Länge RAM-Bereich, 0..255
// PA: R16 = Ergebnis
// VR: R0,R1,R16,R17,R18=0,R24,R25
random2:
	sub	r24,r19
ra4:	rcall	random0
	add	r16,r19
	tst	r20
	breq	ra5		// Nicht suchen
	mov	r1,r20
	push	ZL
	push	ZH
	 rcall	repne_scasb	// Vorkommnis?
	pop	ZH
	pop	ZL
	breq	ra4		// noch einmal würfeln
ra5:	ret

.section .eeprom
bootloader_magic:
	.byte	0
osccal_vorgabe:
	.byte	0
bilder_vorgabe:			// wird in RAM kopiert
	.byte	0x00,0x00,0x01,0x00,0x0B,0x00,0x09,0x03,0x0A,0x03
	.byte	0x0E,0x0E,0x0D,0x03,0x01,0x0B,0x03,0x0A,0x0C,0x0C
	.byte	0x0D,0x00,0x00,0x08,0x09,0x03,0x00,0x0A,0x0E,0x0B
	.byte	0x01,0x03,0x0E,0x09,0x08,0x0A,0x0B,0x00,0x00,0x0A
	.byte	0x00,0x02,0x00,0x02,0x0D,0x01,0x0C,0x06,0x0C,0x0B
	.byte	0x02,0x08,0x09,0x03,0x01,0x0A,0x0C,0x06,0x0D,0x02
	.byte	0x0E,0x0D,0x08,0x01,0x04,0x02,0x06,0x06,0x06,0x01
	.byte	0x0D,0x09,0x04,0x07,0x05,0x04,0x05,0x06,0x05,0x00
	.byte	0x09,0x04,0x07,0x07,0x07,0x05,0x0F,0x05,0x0F,0x00
	.byte	0x04,0x00,0x04,0x07,0x0F,0x01,0x02,0x0F,0x0F,0x02
/* Low-Nibble = Sternbild-Zuordnung, High-Nibble = relative Helligkeit?
 0 = frei
 1 = Orion
 2 = großer Bär
 3 = kleiner Bär
 4 = großer Hund
 5 = Himmels-W
 6 = Schwan
 7 = Kreuz des Südens
 8 = Zwillinge
 9 = Schlange
 A = Löwe
 B = Stier
 C = Perseus
 D = Pegasus
 E = Zentaur
 F = Skorpion
*/

.section .bss
bilder:	.ds.b	100	// Sternbild-Tabelle aus EEPROM

phase:	.ds.b	2	// Durchlaufender Zähler
bild:	.ds.b	1	// Gezeigtes Sternbild
bild0:	.ds.b	1	// vorhergehendes Bild (zum Ausblenden bei kleinen Phasenwerten)
quasar:	.ds.b	8	// Stern-Indizes der Quasare
super:	.ds.b	1	// Stern mit Supernova
quasph:	.ds.b	8	// Phasen für Quasare (Frequenz gleich)
quasi:	.ds.b	1	// Index für auszuwechselnden Quasar
disko:	.ds.b	1	// Phase für Disko, Helligkeitssteller = Freuenz
flimm:	.ds.b	16	// 16 Flimmer-Frequenzen (Phasen)
langs:	.ds.b	16	// 16 langsame Frequenzen (Phasen)
wasser:	.ds.b	1	// Richtung der Wellenfront (0..7);
prog1:	.ds.b	1	// Programm-Nummer, s.u.
prog2:	.ds.b	1	// Sekundär-Programm
teil2:	.ds.b	1	// Anteil der Helligkeit bei der Mischung (0..3)
	
repid1:	.ds.b	1	// Report-ID = 1 (konstant, für USB)
hell:	.ds.b	1	// Helligkeits-Wert 0..31 (vom Potenziometer)
repid2:	.ds.b	1	// Report-ID = 2 (konstant, für USB)
prog:	.ds.b	1	// Programm-Wert 0..31 (vom 2. Potenziometer)

.section .text
/*=========================
  = 8 Animationsprogramme =
  =========================*/
// PE: R24 = Index 0..99
// PA: R0 = Helligkeit 0..31
// VR: R0,R1,R16,R17,R24,R25,Z

// 0 = Gleichmäßige Helligkeit
Prog0H:
	lds	r0,hell
	ret

// 1 = Sternbilder zeigen
Prog1H:
	lds	r16,phase	// R16 = Anteil
	cpi	r16,64
	brcs	p1h1
	ldi	r16,64
p1h1:	ldi	ZL,lo8(bilder)
	ldi	ZH,hi8(bilder)
	add	ZL,r24
	adc	ZH,ZERO
	ld	r0,Z
	lds	r17,bild
	eor	r17,r0
	andi	r17,0x0F	// R17!=0: nicht aktuelles Sternbild
	breq	p1h2
	cpi	r16,64
	brcc	ret0
	lds	r17,bild0
	eor	r17,r0
	andi	r17,0x0F	// R17!=0: nicht vorheriges Sternbild
	brne	ret0
	neg	r16
	subi	r16,-63
p1h2:	ldi	r17,6		// Schiebeoperationen
retant:	lds	r0,hell
	mul	r0,r16
reta1:	lsr	r1
	ror	r0
	dec	r17
	brne	reta1
	ret
ret0:	clr	r0
	ret

// 2 = Quasare
Prog2H:
	ldi	ZL,lo8(quasar)
	ldi	ZH,hi8(quasar)
	ldi	r16,8
	mov	r1,r16
	mov	r16,r24
	rcall	repne_scasb
	brne	ret0		// kein Quasar
	ldd	r0,Z+(quasph-quasar-1)	// passende Phase
	lds	r1,phase
	add	r0,r1
	mov	r16,r0		// R16 = Anteil
	andi	r16,0x07	// 0..7
	sbrc	r0,3
	 neg	r16
	sbrc	r0,3
	 sbci	r16,-8		// 8..1
	ldi	r17,3
	rjmp	retant

// Programmverteiler (eingeschoben wegen Sprungdistanzen)
// PE: R25 = Programmnummer 0..7
//     X = Zeiger in lht
// PA: R0 = Helligkeit 0..31
// VR: R0,R1,R16,R17,R24,R25,Z
ProgV:
	mov	r24,XL
	subi	r24,lo8(lht)	// 0..99
	cpi	r25,1
	breq	Prog1H
	cpi	r25,2
	breq	Prog2H
	cpi	r25,3
	breq	Prog3H
	cpi	r25,4
	breq	Prog4H
	cpi	r25,5
	breq	Prog5H
	cpi	r25,6
	breq	Prog6H
	cpi	r25,7
	breq	Prog7H
	rjmp	Prog0H		// alles andere (bspw. im Fehlerfall)

// 3 = Supernova
// Problem: - Sollte auch gemischt mit maximaler Helligkeit leuchten
Prog3H:
	lds	r0,super
	cp	r0,r24
	breq	p3h1
	lds	r0,hell
	lsr	r0		// halbe Grundhelligkeit
	ret
p3h1:	lds	r0,phase
	lds	r1,phase+1
	lsr	r1
	ror	r0
	rcall	r0lsr3		// jetzt 0..31
	ldi	r16,31
	sbrc	r1,0
	 mov	r0,r16		// Maximum (2. Hälfte der Phase)
	ret

// 4 = Disko (alle LEDs gleich; Frequenz statt Helligkeit einstellbar)
Prog4H:
	lds	r0,disko
	com	r0		// Fallende Intensität
r0lsr3:	lsr	r0
	lsr	r0
	lsr	r0		// jetzt 0..31
	ret

// Hilfsprozedur für Flimmern und Langsam: Determinierte Zufallszahl 0..15
// aus Index (R24, 0..99) ermitteln (svw. ein Hash-Wert)
makeposrand:
	ldi	ZL,lo8(bilder)
	ldi	ZH,hi8(bilder)
	add	ZL,r24
	adc	ZH,ZERO
	ld	r0,Z
	eor	r24,r0	// 16 Zahlen, determiniert aber verstreut verteilt
	ret

// 5 = Flimmern (schnelles Auf und Ab der Helligkeit in 16 Frequenzen)
Prog5H:
	rcall	makeposrand
	andi	r24,0x0F	// R24 = posrand
	ldi	ZL,lo8(flimm)
	ldi	ZH,hi8(flimm)
p5h1:	add	ZL,r24
	adc	ZH,ZERO
	ld	r0,Z
	sbrc	r0,7
	 neg	r0		// 0..128
	mov	r16,r0
	ldi	r17,7
	rjmp	retant		// Zwei Flanken

// 6 = Langsam (sonst wie Flimmern)
Prog6H:
	rcall	makeposrand
	com	r24
	andi	r24,0x0F	// R24 = posrand
	ldi	ZL,lo8(langs)
	ldi	ZH,hi8(langs)
	rjmp	p5h1
	
// 6 = Wasserfront (hell oder dunkel je nach Helligkeitssteller)
// Zurzeit Implementierung anhand Matrixposition, später ggf. mit
// Positionsangaben-Array im EEPROM ähnlich Sternbild-Vorgabe
Prog7H:
	mov	r25,ONES
p7h0:	inc	r25
	subi	r24,10		// Division durch 10
	brcc	p7h0
	subi	r24,-10		// Korrektur, R24 = Rest, R25 = Ergebnis
	lds	r16,wasser
	cpi	r16,1		// R24 = logpos
	brne	p7h1
	 mov	r24,r25		// gerade (0..9)
p7h1:	cpi	r16,2
	brne	p7h2
	 add	r24,r25		// diagonal (0..18)
p7h2:	cpi	r16,3
	brne	p7h3
	 sub	r24,r25		// andersherum diagonal (-9..9)
p7h3:	sbrc	r16,2
	 neg	r24		// Gegenrichtung (-18..18 möglich)
	clr	r17		// R17 = h (Helligkeit, 0 = keine Aktivität)
	lds	r16,phase
	cpi	r16,64
	brcc	p7h4		// keine Aktivität
	mov	r17,r16
	subi	r17,32		// -32 .. 31
	add	r17,24		// -50 .. 49
	sbrs	r17,7
	 com	r17	// Bei Null maximale Helligkeit, neg. Betrag bilden (-50..0)
	subi	r17,-31		// -19 .. 31
	sbrc	r17,7
	 clr	r17		// alles Negative wegschneiden
p7h4:	lds	r16,hell
	cpi	r16,16
	brcs	p7h5
	neg	r17		// hell: dunkle Welle
	subi	r17,-31
	cp	r17,r16
	brcs	p7he
	 rjmp	p7h6
p7h5:	cp	r17,r16
	brcc	p7he
p7h6:	 mov	r17,r16
p7he:	mov	r0,r17
	ret
	
/*============================
  = Verwaltung der Animation =
  ============================*/

PeriodicAction:
	ldi	ZL,lo8(phase)
	ldi	ZH,hi8(phase)
	ldd	r16,Z+prog-phase
	cpi	r16,31
	brcc	pa1		// variable Programme am Endanschlag
	 mov	r17,r16
	 lsr	r17
	 lsr	r17		// 0..7
	 std	Z+prog1-phase,r17	// festes Programm
	 inc	r17
	 andi	r17,7		// 0..7
	 std	Z+prog2-phase,r17
	 andi	r16,3		// 0..3
	 std	Z+teil2-phase,r16	// fester Anteil
pa1:	ldi	XL,lo8(lht)
	ldi	XH,hi8(lht)
	ldd	r23,Z+teil2-phase	// R23 = teil2 (0..3)
	ldi	r22,4
	sub	r22,r23		// R22 = teil1 (4..1)
pa2:	lds	r25,prog1	// ab hier ist Z versaut
	rcall	ProgV
	mul	r0,r22
	tst	r23
	breq	pa3
	mov	r19,r0		// R19 = h (retten)
	lds	r25,prog2
	rcall	ProgV
	mul	r0,r23
	add	r0,r19
pa3:	lsr	r0
	lsr	r0		// hier evtl. runden!!
	st	X+,r0
	cpi	XL,lo8(lht+100)
	brne	pa2
// jetzt Zähler aktualisieren
	ldi	ZL,lo8(phase)
	ldi	ZH,hi8(phase)
	ld	r24,Z
	ldd	r25,Z+1
	adiw	r24,1		// vorrücken
	std	Z+1,r25
	st	Z,r24
	movw	r22,r24		// R23:R22 = Phase
	adiw	ZL,bild-phase
	tst	r22
	brne	pa4
	ld	r21,Z
	ldi	r24,15
	ldi	r19,1
	ldi	r20,2		// nicht das letzte und vorletzte Bild
	rcall	random2
	st	Z,r16
	std	Z+bild0-bild,r21
pa4:	adiw	ZL,quasar-bild
	mov	r16,r22
	andi	r16,63
	brne	pa5
	ldi	r24,99
	ldi	r19,0
	ldi	r20,9		// keiner der 8 Quasare und nicht der Supernova-Stern
	rcall	random2
	ldd	r21,Z+quasi-quasar
	movw	XL,ZL
	add	XL,r21
	adc	XH,ZERO
	st	X,r16
	ldi	r24,255
	rcall	random0
	adiw	XL,quasph-quasar	// X ist bereits indiziert
	st	X,r16
	inc	r21
	andi	r21,7
	std	Z+quasi-quasar,r21
pa5:	mov	r16,r23
	andi	r16,hi8(1023)		// = 3
	or	r16,r22
	brne	pa6
	ldi	r24,99
	ldi	r19,0
	ldi	r20,9		// keiner der 8 Quasare und nicht der Supernova-Stern
	rcall	random2
	std	Z+super-quasar,r16
// Im automatischen Modus jetzt Programm wechseln (mit Übergang)	
	ldd	r16,Z+prog-quasar
	cpi	r16,31
	brcs	pa6
	adiw	ZL,prog2-quasar
	ldi	r19,0
	ldi	r20,1
pa7:	ldi	r24,7
	rcall	random2
	cpi	r16,4		// Disko ausschließen
	breq	pa7
	st	Z,r16		// neues Programm (Übergang später)
pa6:	ldi	ZL,lo8(phase)
	ldi	ZH,hi8(phase)	// Z war versaut hier
// Disko-Phase aktualisieren
	ldd	r17,Z+hell-phase
	subi	r17,-16		// Stellverhältnis 3:1
	ldd	r16,Z+disko-phase
	add	r16,r17
	std	Z+disko-phase,r16
// Flimmer-Phasen aktualisieren
	adiw	ZL,flimm-phase
	mov	r17,3		// 3..19
pa8:	ld	r16,Z
	add	r16,r17
	st	Z+,r16
	inc	r17
	cpi	ZL,lo8(flimm+16)
	brne	pa8
// Langsam-Phasen aktualisieren (Z steht schon richtig!)
	mov	r16,r22
	andi	r16,15
	brne	pa10
pa9:	ld	r16,Z
	mov	r17,ZL
	subi	r17,lo8(langs)
	lsr	r17
	lsr	r17		// 4 Geschwindigkeiten
	inc	r17
	add	r16,r17
	st	Z+,r16
	cpi	ZL,lo8(langs+16)
	brne	pa9
	sbiw	ZL,16
// Wasser-Richtung ändern (Z steht auf "langs")
pa10:	tst	r22
	brne	pa11
	adiw	ZL,wasser-langs
	ldi	r24,7
	ldi	r19,0
	ldi	r20,1
	rcall	random2
	st	Z,r16
	sbiw	ZL,wasser-langs
pa11:	ldd	r16,Z+prog-langs
	cpi	r16,31
	brne	pae
	mov	r16,r22
	andi	r16,0x0F
	brne	pae
	swap	r22
	andi	r22,0x0F
	swap	r23
	andi	r23,0x30
	or	r22,r23		// R22 = (phase&1023)>>4
	cpi	r22,4
	brcc	pae
	inc	r22
	andi	r22,3		// 1-2-3-0	(Intensität des neuen Motivs)
	std	Z+teil2-langs,r22
	brne	pae
	ldd	r16,Z+prog2-langs
	std	Z+prog1-langs,r16	// neues Motiv = erstes Motiv
pae:	ret

// PE: R0 = neuer <hell>-Wert, von außen oder per Poti
// PA, VR: -
SetHell:
	sts	hell,r0
	cbi	SPCR,2		// Programmlauf freigeben
	ret
// PE: R0 = neuer <prog>-Wert, von außen oder per Poti
// PA, VR: -
SetProg:
	sts	prog,r0
	cbi	SPCR,2		// Programmlauf freigeben
	ret

/*==================================================
  = A/D-Wandler (Potenziometerstellungen) abfragen =
  ==================================================*/
// Abfrage 8-bit-A/D-Wandler mit exponentieller Mittelung
// PE: R24 = vorhergehender Wert (0..FF)
// PA: R0 = neuer Wert (0..FF)
// VR: R0,R1,R16
MeanAdcVal:
	ldi	r16,7
	mul	r16,r24
	in	r16,ADCH
	add	r0,r16
	adc	r1,ZERO
	ldi	r16,4
	add	r0,r16
	adc	r1,ZERO
	lsr	r1
	ror	r0
	lsr	r1
	ror	r0
	lsr	r1
	ror	r0
	ret
	
// PE: Z = Speicherzelle vorhergehender A/D-Wert (8 bit)
// PA: ZF=0 bei relevanter Änderung
//     R0 = neuer Wert (0..31, also 5 bit)
// VR: R0,R1,R16,R24
ChangedAdcVal:
	ld	r24,Z
	rcall	MeanAdcVal
	st	Z,r0
	eor	r24,r0
	rcall	r0lsr3
	andi	r24,0xF8	// Wesentliche Bits geändert?
	ret
	
.section .bss
adcv6:	.ds.b	1
adcv7:	.ds.b	1

.section .text
// Abfrage der A/D-Kanäle 6 und 7 (wechselweise)
// Bei Änderung wird SetHell() bzw. SetProg() aufgerufen
// PE: - (A/D-Wandler muss initialisiert und gestartet sein)
// PA: -
// VR: R0,R1,R16,R24,Z
AdcPoll:
	sbis	ADCSRA,4	// ADIF
	 ret			// Konversion in Arbeit
	ldi	ZL,lo8(adcv6)
	ldi	ZH,hi8(adcv6)
	sbis	ADMUX,0
	 rjmp	ap1
	cbi	ADMUX,0		// umschalten auf ADC6
	adiw	ZL,adcv7-adcv6
	rcall	ChangedAdcVal
	breq	ap2
	rcall	SetProg
	rjmp	ap2
ap1:	sbi	ADMUX,0		// umschalten auf ADC7
	rcall	ChangedAdcVal
	breq	ap2
	rcall	SetHell
ap2:	sbi	ADCSRA,6	// Konversion starten
	ret
	
/**********
 * EEPROM *
 **********/
// Liest EEPROM-Speicherzelle
// PE: X = EEPROM-Adresse
// PA: R0 = EEPROM-Datenbyte (auch EEDR)
EepromRead:
	out	EEARH,XH
	out	EEARL,XL
	sbi	EECR,0
	in	r0,EEDR
	ret

// Lädt Vorgaben aus EEPROM: OSCCAL und Sternbild-Zuordnungen
// PE,PA: -
// VR: R0,R16,X,Z
EepromLoad:
	ldi	XL,lo8(osccal_vorgabe)
	ldi	XH,hi8(osccal_vorgabe)
	rcall	EepromRead
	tst	r0
	breq	ee1
	cp	r0,ONES
	breq	ee1
	out	OSCCAL,r0
ee1:	ldi	r16,100
	ldi	ZL,lo8(bilder)
	ldi	ZH,hi8(bilder)
ee2:	adiw	XL,1
	rcall	EepromRead
	st	Z+,r0
	dec	r16
	brne	ee2
	ret

/*************************************
 * Initialisierung und Hauptschleife *
 *************************************/
 
init:
// RAM löschen
	clr	ZERO
	ldi	ZH,0
	ldi	ZL,0x60
i1:	st	Z+,ZERO
	cpi	ZL,0x5F
	brne	i1
	cpi	ZH,0x04
	brne	i1
// Stack initialisieren
	out	SPH,ZH
	out	SPL,ZL
// Konstanten initialisieren	
	clr	ONES
	dec	ONES
	ldi	ZL,0x40
	mov	r12,ZL
	ldi	ZL,0x3F		// Defaults für PORTB und PORTD
	mov	DEFB,ZL
#ifdef ENABLE_USB
	ldi	ZL,0xFC
#else
	ldi	ZL,0xF0
#endif
	mov	DEFD,ZL
	ldi	YL,azt
	ldi	ZL,1
	sts	repid1,ZL
	ldi	ZL,2
	sts	repid2,ZL
	ldi	ZL,3
	sts	repid3,ZL
// Pseudozufallsgenerator initialisieren
	mov	r1,ONES		// R1:R0 != 0 garantieren
	rcall	storeseed	// r0 sollte zufällig sein, oder??
// ISR initialisieren (CurKatode muss nicht initialisiert werden)
	rcall	calc_azt	// Erste Tabelle berechnen
	rcall	calc_azt	// Zweite Tabelle berechnen
	out	OCR2,ONES	// erster Interrupt bei Überlauf
	ldi	ZL,0x03		// Vorteiler 32
	out	TCCR2,ZL
	ldi	ZL,0x80		// nur Compare-Interrupt
	out	TIMSK,ZL
	out	MCUCR,ZL	// Sleep aktivieren
	ldi	ZL,0x0D		// CTC-Modus, Teiler=1024
	out	TCCR1A,ZERO
	out	TCCR1B,ZL
	ldi	ZL,lo8(320)	// 4 * 10 * 32 * 256 / 1024: aller 4 Zyklen
	ldi	ZH,hi8(320)
	out	OCR1AH,ZH
	out	OCR1AL,ZL
// Ports initialisieren
	out	PORTB,DEFB
	out	PORTC,ZERO
	out	PORTD,DEFD
	out	DDRB,ONES
	out	DDRC,ONES
#ifdef ENABLE_USB
	ldi	ZL,0xF3
	out	DDRD,ZL
#else
	out	DDRD,ONES
#endif
// ADU für die 2 Potis initialisieren (5 bit werden verwendet)
	ldi	ZL,0x66		// potenziometrisch, linksbündig, ADC6
	out	ADMUX,ZL
	ldi	ZL,0xC7		// Start, Taktteiler maximal
	out	ADCSRA,ZL
#if 1
#if 0
// Debug-Beispiel laden
	ldi	ZL,lo8(lht)
	ldi	ZH,hi8(lht)
	ldi	r16,100
fillh:	//mov	r17,r16
	ldi	r17,1
	cpi	r16,11
	brcc	f1
	ldi	r17,1
f1:	st	Z+,r17
	dec	r16
	brne	fillh
	mov	r0,r17
	rcall	SetHell
#else
aw1:	sbis	ADCSRA,4	// ADIF
	 rjmp	aw1
	rcall	AdcPoll		// SetProg() auslösen
aw2:	sbis	ADCSRA,4	// ADIF
	 rjmp	aw2
	rcall	AdcPoll		// SetHell() auslösen
#endif
	rcall	EepromLoad
	sei
mloop:	wdr
mw1:	sleep
	in	r16,TIFR
	sbrs	r16,4		// Timer1-Umlauf? (CTC)
	 rjmp	mw1
	ldi	r16,0x10
	out	TIFR,r16	// Bit löschen
//	sbi	PORTD,2
	rcall	AdcPoll
	rcall	PeriodicAction
//	cbi	PORTD,2
	rjmp	mloop
#else

	ldi	ZL,0x10
	sts	lht+0,ZL
	ldi	ZL,30
	sts	lht+2,ZL
	sts	lht+8,ONES
	sts	lht+7,ONES

	sei
mainloop:
w1:	sleep
	tst	CurKatode
	brne	w1
w2:	sleep
	tst	CurKatode
	breq	w2
	inc	r9
	inc	r9
	sts	lht+22,r9
	sbrs	r10,0		//Richtungsbit
	 rjmp	d1
	dec	r10
	breq	d2		//aufwärts
d3:	dec	r10
	rjmp	d2
d1:	inc	r10
	inc	r10
	breq	d3		//abwärts
d2:	sts	lht+42,r10
	sts	lht+43,r10

	in	r0,ADCSRA
	rcall	OutBits

	sbis	ADCSRA,4	//Konversion fertig?
	 rjmp	mainloop	//nein
	in	r0,ADCH
	sbis	ADMUX,0
	 rjmp	a1
	cbi	ADMUX,0		//umschalten auf ADC6
	sts	lht+30,r0
	sts	lht+31,r0
	rjmp	a2
a1:	sbi	ADMUX,0		//umschalten auf ADC7
	sts	lht+20,r0
	sts	lht+21,r0
a2:	sbi	ADCSRA,6	//Konversion starten
	rjmp	mainloop

OutBits:
	ldi	ZL,lo8(lht+50)
	ldi	ZH,hi8(lht+50)
	ldi	XH,8
t1:	ldi	XL,0x80
	and	XL,r0
	st	Z+,XL
	add	r0,r0
	dec	XH
	brne	t1
	ret
#endif
Detected encoding: UTF-80