Source file: /~heha/basteln/Konsumgüter/Kram/2022/rundumT13.zip/rundumT13.S

#define __SFR_OFFSET 0
#include "avr/io.h"
/*
Rundumleuchte mit 16 LEDs im Charlieplex verschaltet, mit Dimmfunktion.
Hardware: ATtiny13A-DIP8, 16 LEDs, 2 Taster, 1 Potenziometer, RCD-Fußvolk
			1╔════════╗8
Taster			─╢PB5   5P╟─
LED Zeile 3, Spalte 3	─╢PB3  PB2╟─ LED Zeile 2, Spalte 2, Poti A
Poti S+E, Spalte 4	─╢PB4  PB1╟─ LED Zeile 1, Spalte 1, Taster1
			─╢00   PB0╟─ LED Zeile 0, Spalte 0, Taster0
			4╚════════╝5

Funktion der Eingabeelemente, Vorlage = chinesisches QX-506:
* T1 = 1-Bit (im Schiebetakt) einschieben
* T2 = 0-Bit (im Schiebetakt) einschieben
* Potenziometer = Geschwindigkeit
Weiterhin — alles neu:
* T1+T2 gemeinsam = Richtungsumkehr
* Sanfter Übergang zwischen 2 Verschiebungen per PWM
* Rechts- und Linkslauf
* Abschaltung nach programmierbarer Zeit, Vorgabe ca. 2 h
* Erneuter Start durch Drücken von T1 oder T2
* Speicherung von Bitmuster und Umlaufrichtung im EEPROM
* Hochlauf Helligkeit beim Einschalten/Wecken,
  Ausdimmen vor dem Herunterfahren
Vielleicht später:
* Beeinflussbarkeit der Helligkeit
* Hochlauf der Taktfrequenz (Drehzahl) beim Einschalten,
  Auslauf beim Herunterfahren

Multiplexschema (gemeinsame Katode; 5 Anodenwiderstände):
╔═══════╦═══════╤═══════╤═══════╤═══════╤═══════╤═══════╗
║Zeile	║PB0	│PB1	│PB2	│PB3	│PB4	│PB5	║	L = Low
╠═══════╬═══════╪═══════╪═══════╪═══════╪═══════╪═══════╣	H = High
║0	║L	│H0/z	│H1/z	│H2/z	│H3/z	│z	║	H<n>/z = je nach LED<n>
║1	║H4/z	│L	│H5/z	│H6/z	│H7/z	│z	║	h = Pullup
║2	║H8/z	│H9/z	│L	│H10/z	│H11/z	│z	║	z = hochohmig
║3	║H12/z	│H13/z	│H14/z	│L	│H15/z	│z	║	(D) = Digitaleingang
║4	║z	│z	│h(A)Po	│z	│L	│z	║	(A) = Analogeingang
║	║L	│z	│z	│z	│z	│h(D)T1	║	Denkbar: Verbleibender
║	║z	│L	│z	│z	│z	│h(D)T2	║	PB3-Portpin fragt
╟───────╫───────┼───────┼───────┼───────┼───────┼───────╢	Fotowiderstand ab
║Standby║L	│L	│z	│z	│z	│h(D)	║
╚═══════╩═══════╧═══════╧═══════╧═══════╧═══════╧═══════╝
Gemessen habe ich 9 MHz Taktfrequenz, nicht 9,6 MHz.
Zeiten: 1/9 MHz × 64 (Timer0-Vorteiler) × 256 (Timer0-Periode) ≈ 1,8 ms
	*4 (Zeilen) ≈ 7,3 ms
	+ Tastenabfrage (wenige µs)
	+ 1/9 MHz × 128 (ADC-Vorteiler) × 25 (ADC-Takte) ≈ 0,35 ms
	= 7,6 ms ≙ 130 Hz (Flimmerfrequenz)
Umlaufzeit bei Gleitlicht-Tempo „1 PWM-Schritt pro Bild“:
	7,6 ms × 256 (Schritte) × 16 (LEDs) ≈ 30 s
Für einen Leuchtturm schon langsam, für eine Rundumleuchte per Poti schneller
	…			„128 PWM-Schritte pro Bild“:
	7,6 ms × 2 (Schritte) × 16 (LEDs) ≈ 0,24 s
	Unangenehmer Stellbereich! Was zu testen wäre!
*/
.section .signature,"a",@progbits
	.byte	SIGNATURE_2,SIGNATURE_1,SIGNATURE_0 
	
.section .fuse,"a",@progbits
	.byte	0b10111010	// kein SPI-Prog, EEPROM behalten, 9,6 MHz
	.byte	0b11111110	// kein Reset, kein BrownOut: Strom sparen

.section .bss

// Da der ATtiny13 kein GPIOR0 hat, dieser bitadressierbare Ersatz:
#define GPIOR0 PCMSK
#define f_ovf0 0
#define f_nokey 1		// Tastenauswertung nach Wakeup unterdrücken (Muster nicht ändern)
#define f_dir 2
//#define f_jmp 3		// TODO: Sprung von LED zu LED statt sanfter Übergang
#define f_wdt 4

// r0 = __temp__
// r1 = __zero__
#define gl_pha	r2	// Gleitlicht-Phase, 0 = nur „srg_cur“, 255 = fast nur „srg_nxt“
#define bright	r3	// Exponentielle Helligkeit, nur 2ⁿ-1 = 0-1-3-7-15-31-63-127-255
#define outBp	r4	// PORT/DDR-Paar für Gleitlicht: Nächste Periode
#define outBd	r5
#define outCp	r6	// PORT/DDR-Paar für Gleitlicht: Übernächste Periode
#define outCd	r7	// Temporär für Charlieplexumformung
#define adcAccL	r8
#define adcAccH	r9
#define ct_keys	r10	// Bit 7:2 = Zähler 0..64 ergibt Messperiode 450 ms
			// Bit 1:0 = Tastenstatus
#define speed	r11	// High-Teil der ADC-Summe
#define srg_curL r12	// W12 = aktueller Leuchtzustand
#define srg_curH r13	// Beim 0-1-Übergang wird hochgedimmt
#define srg_nxtL r14	// W14 = künftiger Leuchtzustand
#define srg_nxtH r15
		// Zeile:  0      1      2      3      4		    Standby
#define outAp	r16	// 0dcba0 0dcb0a 0dc0ba 0d0cba 100000 100000 101000 100000
#define outAd	r17	// 0dcba1 0dcb1a 0dc1ba 0d1cba 000001 000010 011000 000011
#define wdtcntL	r18
#define	wdtcntH	r19
#define clrOC	r20	// = 0x0C
#define srg_nsh	r21	// Anzahl Schiebungen: Speicherversuch nach min. 16 Schiebungen
#define curLn	r22	// Zeile 0..4
#define curLm	r23	// Zeilen-Bitmaske

#define TIMEOUT 60*60	// 1 h Betrieb, dann Batterie schonen
#define MINSPEED 1
#define MAXSPEED 128
	
.section .vectors
	rjmp	reset
	reti	// INT0		ungenutzt
	ret	// PCINT0	Aus sleep erwachen und Interrupts sperren
	rjmp	isr_ovf0
	reti	// ERDY		ungenutzt
	reti	// AC		ungenutzt
	rjmp	isr_oc0a
	rjmp	isr_oc0b
	rjmp	isr_wdt
	reti	// ADC		Noise-Reduction-Mode erfordert Interrupt
.text
// Zur Zeilenumschaltung (der Charlieplex-LED-Matrix und Tasten/Potiabfrage)
// Ist höher priorisiert als isr_oc0a und isr_oc0b
// * Bei OCR0B==0 kommt TIFR0.OCF0A nach 64 CPU-Takten: problemlos, wie 1
// * Bei OCR0B==0xFF kommt TIFR0.OCF0A _gleichzeitig_ mit TIFR0.TOV0: Wird mit clrOC getilgt!
// * Sinngemäß für OCR0A
isr_ovf0:
	out	TIFR0,clrOC	// Gleichzeitige Compare-Interrupts tilgen
	out	PORTB,r1	// Alles aus
	out	DDRB,outAd	// umschalten
	out	PORTB,outAp	// Zeile aktivieren
	movw	outBp,outCp	// PWM-Modus nachbilden: Aktion bei TCNT0=0
	sbi	GPIOR0,f_ovf0	// Meldung an Hauptschleife
	reti

// Als allgemeine Helligkeitsbegrenzung beim Ein- und Ausschalten des Leuchtturms
// Muss höher priorisiert sein als die Überblendfunktion!
isr_oc0a:
	out	TIFR0,clrOC	// gleichzeitigen OCF0B löschen
	out	PORTB,r1	// Alles aus
	reti

// Zum Umschalten zwischen den LEDs die runterdimmen auf die die raufdimmen
isr_oc0b:
	out	PORTB,r1	// wie oben
	out	DDRB,outBd
	out	PORTB,outBp
	reti

isr_wdt:
	wdr
	sbi	GPIOR0,f_wdt	// in Hauptschleife synchron verarbeiten
	reti

reset:
	clr	r1
	out	MCUSR,r1	// Reset-Ursache interessiert nicht
	out	GPIOR0,r1
	clr	srg_nxtL	// 1 Leuchtpunkt, dann Muster aus EEPROM
	inc	srg_nxtL
	clr	srg_nxtH
	clr	adcAccL
	clr	adcAccH
	clr	ct_keys
	ldi	r24,0x20
	mov	speed,r24
	clr	bright
0:	sbic	EECR,1		// Warte bis EEPROM bereit
	 rjmp	0b
	out	EEARL,r1
	sbi	EECR,0
	in	r24,EEDR
	sbi	EEARL,0
	sbi	EECR,0
	in	r25,EEDR
	adiw	r24,1
	breq	1f		// 0xFFFF = ungültig, 0x0001 annehmen
	sbiw	r24,1
	breq	1f		// 0x0000 = ungültig, 0x0001 annehmen
	movw	srg_nxtL,r24
	movw	srg_curL,srg_nxtL
// Die Schieberichtung wird nicht direkt gespeichert,
// sondern ergibt sich aus dem Bit 15 des Bitmusters.
// Da die Bitmuster 0x0000 und 0xFFFF verboten sind,
// lässt sich das Muster stets so rotieren,
// dass das Bit 15 die Schieberichtung repräsentiert.
	sbrc	srg_nxtH,7
	 sbi	GPIOR0,f_dir
1:	sbi	ACSR,7		// Analogvergleicher aus
	ldi	clrOC,1<<OCF0B|1<<OCF0A	// Schnelle Konstante für isr_ovf0
	ldi	srg_nsh,15	// 16 Schiebungen bevor versucht wird zu speichern
	sbi	ADMUX,0		// A/D-Wandlung nur auf PB2
// Die Verwendung des PWM-Modus ist wichtig, damit das Wirksamwerden der OC-Register
// per Hardware auf den Timerüberlauf fällt und so keinesfalls 2 Compare-Ereignisse
// pro Periode (und OC-Register) generiert werden.
// Die PWM-Erzeugung ist eine andere Sache und muss per Software erfolgen.
	ldi	r24,3
	out	TCCR0A,r24	// Timer0: Schnelle PWM, TOP=0xFF
	ldi	r24,1<<OCIE0B|1<<OCIE0A|1<<TOIE0
	out	TIMSK0,r24	// Timer0 alle 3 Interrupts
loop_after_powerdown:
	rcall	init_watchdog
loop:
	ldi	r24,1<<SE
	out	MCUCR,r24	// Sleep-Modus: Idle: Timer0 muss laufen
	ldi	r24,0x1F
	out	DIDR0,r24	// Digitaler Eingang nur an PB5
	ldi	curLn,0		// Zeile als Nummer
	ldi	curLm,1		// Zeile als Bitmaske
	out	GTCCR,curLm	// Timer0-Vorteiler löschen
	ldi	r24,255
	out	TCNT0,r24	// 64 Takte bis zum Überlaufinterrupt (sicher!)
	ldi	r24,3
	out	TCCR0B,r24	// Timer0: Vorteiler 64 und starten
muxloop:	// 4 Runden
// Werte für Software-Multiplex berechnen
	movw	r24,srg_curL
	rcall	get_split
	movw	outAp,r24	// Wird für nächsten Timer0-Überlauf wirksam
	movw	r24,srg_nxtL
	rcall	get_split
	movw	outCp,r24	// Wird im nächsten Timer0-Überlauf nach outBd verschoben,
// … um dann für den darauf folgenden nächsten OC0A-Interrupt bereit zu stehen.
// Der OC0A-Interrupt _dieser_ Periode kann noch ausstehen und braucht das aktuelle outBp.
	sei			// bis hierher max. 64 Takte
// Auf Timer0-Periode warten
	rcall	w_ovf0		// Zeile 4, 0, 1, 2 abwarten
	lsl	curLm
	inc	curLn
	sbrs	curLm,4
	 rjmp	muxloop
// LEDs in Zeile 3: Nochmal Timer0-Periode abwarten und Zeit für Berechnungen nutzen
	ldi	outAp,0b000100	// Portpins für A/D-Wandler vorbereiten
	ldi	outAd,0b010000	// das aktiviert D18
	movw	outCp,outAp	// Nicht vom Compare-Interrupt ändern
// Animationsschritt für LED-Zustand, wirksam im nächsten Vollbild
	add	gl_pha,speed	// Geschwindigkeit vom Poti
	brcc	noshift		// nicht schieben und keine Tasten auswerten
// Bei Überlauf erfolgt die Übernahme von „srg_nxt“ nach „srg_cur“
// und die Neuberechnung des nächsten „srg_nxt“,
// welches bei gl_pha==0 noch nicht sichtbar wird.
	movw	srg_curL,srg_nxtL	// Ganze Periode um: Schieben oder Rotieren
// Ringschieben: Je nach Rotationsrichtung Bit 0 oder Bit 15 nehmen
	movw	r24,srg_nxtL
	lsl	r25		// links rotieren: Nimm MSB
	sbic	GPIOR0,f_dir
	 lsr	r24		// rechts rotieren: Nimm LSB
// Je nach Tasten das Bit manipulieren
	sbic	GPIOR0,f_nokey	// Tastenstatus vom Aufwecken?
	 rjmp	62f		// Ja, Bitmuster nicht ändern
	sbrc	ct_keys,0	// T1 gedrückt?
	 sbrs	ct_keys,1	// T2 gedrückt?
	 rjmp	61f		// Nicht beide gedrückt
	rjmp	62f		// Beide gedrückt: T-Bit unverändert
61:	sbrc	ct_keys,0	// T1 gedrückt?
	 sec			// ja, Einsen einschieben
	sbrc	ct_keys,1	// T1 gedrückt?
	 clc			// ja, Nullen einschieben
62:// Je nach Rotationsrichtung rechts oder links schieben
	sbic	GPIOR0,f_dir
	 rjmp	63f		// rechts schieben/rotieren
	rol	srg_nxtL	// links schieben/rotieren
	rol	srg_nxtH
	rjmp	rot_ok
63:	ror	srg_nxtH
	ror	srg_nxtL
rot_ok:// Nach 16 Verschiebungen im EEPROM sichern.
// Die EEPROM-Schreibroutine tut nichts wenn sich nichts verändert.
// Sollte das Richtungskennbit nicht passen
// wird nach 17, 18 usw. Verschiebungen gespeichert.
	dec	srg_nsh
	brpl	9f
// Während es durch Tastendruck möglich ist,
// ein komplett-ein bzw. komplett-aus zu generieren
// wird so etwas nicht gespeichert,
// sodass nach dem Aus- und Einschalten alles erwartungsgemäß funktionieren wird.
// Alle 1 Runde Bitmuster speichern wenn verändert
	mov	r24,srg_nxtL
	or	r24,srg_nxtH
	breq	8f		// 0x0000 nie speichern, Rundenzähler zurück
	mov	r24,srg_nxtL
	and	r24,srg_nxtH
	com	r24
	breq	8f		// 0xFFFF nie speichern, Rundenzähler zurück
	sbic	GPIOR0,f_dir
	 rjmp	1f
	sbrc	srg_nxtH,7
	 rjmp	9f		// Bit 15 (Richtungskennbit) nicht 0, weiterschieben
	rjmp	2f
1:	sbrs	srg_nxtH,7
	 rjmp	9f		// Bit 15 (Richtungskennbit) nicht 1, weiterschieben
2:	mov	r24,srg_nxtL
	ldi	r25,0
	rcall	eesave
	mov	r24,srg_nxtH
	ldi	r25,1
	rcall	eesave
8:	ldi	srg_nsh,15
9:
noshift:// Gleitlicht-Phase an PWM durchreichen
// gl_pha = 0 bedeutet 256/256 „srg_cur“ und 0/256 „srg_nxt“ und entspricht OCR0B = 255
// gl_pha = 255 bedeutet 1/256 „srg_cur“ und 255/256 „srg_nxt“ und entspricht OCR0B = 0
// Das gilt für „bright“ = 255 = OCR0A; für heruntergeteile Helligkeit muss auch OCR0B
// entsprechend geteilt werden.
// Da „bright“ binärgestuft ist, wird kein MulDiv benötigt:
// Der für OCR0B vorgesehene Wert wird um die Anzahl der führenden Nullen von „bright“
// rechts verschoben.
	mov	r0,gl_pha
	com	r0		// stürzen
	mov	r24,bright
	rjmp	71f
70:	lsr	r0		// PWM-Länge jeweils halbieren, kann 0 ergeben
71:	sec
	rol	r24		// 1-Bit einschieben damit's auf jeden Fall terminiert
	brcc	70b
	out	OCR0B,r0
	out	OCR0A,bright
	rcall	w_ovf0		// LED-Zeile 3 abwarten, die ISR schaltet PORTB und DDRB auf o.a. Defaultwerte
	out	TCCR0B,r1	// Timer0 anhalten
// Ab hier Timing von ADC und Software
// Das Aktivieren eines digitalen Eingangs (zur Tastenabfrage) dauert einige 100 CPU-Takte!
// Daher schon vor der A/D-Wandlung einleiten.
// A/D-Wandler starten
	ldi	r24,1<<SE|1<<SM0
	out	MCUCR,r24	// Sleep-Modus: ADC Noise Reduction
	ldi	r24,0b10011111	// A/D-Wandler einschalten
	out	ADCSRA,r24
18:	sleep			// Startet A/D-Wandler (setzt ADCSRA.ADSC)
	sbic	ADCSRA,ADSC
	 rjmp	18b		// warte bis A/D-Konversion fertig (der Watchdog kann dazwischenfunken)
	out	ADCSRA,r1	// A/D-Wandler ausschalten (wichtig!)
// Portpins schon mal aufs Tasten-Einlesen vorbereiten
	out	DDRB,r1		// PB4 von L auf z
	cbi	PORTB,2		// PB2 von h auf z
	sbi	PORTB,5		// PB5 von z auf h
// A/D-Wandlungsergebnis lesen und glätten (geht nach dem Ausschalten des ADC)
	rcall	adc_smooth
// Tasten abfragen (alle 7,6 ms)
	ldi	r24,0
	sbi	DDRB,0		// PB0 auf L, Taste T1 zieht PB5 via D16 nach Lo
	ldi	r25,50		// Warum auch immer das so lange dauert!
0:	dec	r25
	brne	0b
	sbis	PINB,5
	 ori	r24,1		// T1 gedrückt
	cbi	DDRB,0		// PB0 auf z, D16 sperrt
	sbi	DDRB,1		// PB1 auf L
	ldi	r25,50
0:	dec	r25
	brne	0b
	sbis	PINB,5
	 ori	r24,2		// T2 gedrückt
	cbi	DDRB,1		// PB1 auf z, D17 sperrt
// Neuer Tastenstatus in R24: Tasten-Flanken auswerten
	eor	r24,ct_keys	// Änderungen
	andi	r24,3		// Nur Bit 1 und 0 beachten
	breq	52f		// Nichts geändert
	eor	ct_keys,r24	// Neuer Tastenzustand
	cbi	GPIOR0,f_nokey	// Ab jetzt Bitmusteränderung zulassen
	rcall	reset_timeout
	mov	r24,ct_keys
	com	r24
	andi	r24,3		// T1+T2 frisch = Richtung umschalten?
	brne	52f
// Rotationsrichtung umkehren (so ist's unterbrechungssicher!)
	sbic	GPIOR0,f_dir
	 rjmp	51f
	sbi	GPIOR0,f_dir
	rjmp	52f
51:	cbi	GPIOR0,f_dir
52:// Zeit der Inaktivität messen und PowerDown
	sbis	GPIOR0,f_wdt
54:	 rjmp	loop
	cbi	GPIOR0,f_wdt
// Mit dem Watchdog-Takt erfolgt die Animation der Helligkeit:
// (a) aufhellen auf Maximum
	sec
	rol	bright
// (b) abdunkeln vor Timeout
	rcall	bright_before_timeout
	cp	bright,r24
	brcs	53f
	mov	bright,r24	// Den kleineren Wert nehmen
53:// Sekundenzähler herunterzählen
	subi	wdtcntL,1
	sbci	wdtcntH,0
	brcc	54b
// Bei 0 angekommen PowerDown und Restart mit irgendeiner Taste
	cli
	sbi	PORTB,5
	ldi	r24,0b010011
	out	DDRB,r24
	in	r0,PCMSK	// Richtungsbit retten
	ldi	r24,0b100000
	out	PCMSK,r24
	in	r24,WDTCR
	ori	r24,1<<WDCE|1<<WDE
	out	WDTCR,r24
	out	WDTCR,r1	// Watchdog abschalten: Strom sparen
	ldi	r24,1<<SE|1<<SM1
	out	MCUCR,r24	// Schlafmodus: Oszillator deaktivieren
	ldi	r24,1<<PCIF
	out	GIFR,r24
	out	GIMSK,r24	// Pegelwechsel-Interrupt aktivieren
	sei
	sleep			// RC-Oszillator aus und warten auf Pegelwechsel
	out	GIMSK,r1	// ISR sperrt Interrupt
	out	PCMSK,r0	// Flags wiederherstellen
	sbi	GPIOR0,f_nokey
	rjmp	loop_after_powerdown
// gemessen: 90 nA @ 5 V
// Ein 24-h-Restart via Watchdog wäre zu ungenau, mit 6 µA.
// Ansonsten: Uhrenquarz; für schnelle LED-Animation geht das nur
// mit umschaltbarem Oszillator (kein AVR, siehe MSP430 oder PIC)
// oder Zweitoszillator (AT90S4433, ATmega8 und vergleichbare),
// dann mit extrapoliert 10 µA.
// Eine Batterie 3×R3 (1,2 Ah) ist mit 10 µA nach 14 Jahren leer,
// ein Superkondensator (1 F, Entladung um 2,5 V, also 2,5 As) nach 3 Tagen.

adc_smooth:
// A/D-Wandlungsergebnis lesen, glätten und eingrenzen
// Das Spannungsangebot liegt bei Ucc = 5V zwischen 0,6 und 1,9 V.
	in	r24,ADCL
	add	adcAccL,r24
	in	r24,ADCH
	adc	adcAccH,r24	// Ergebnis: 0 .. 0x03FF
	ldi	r24,4
	add	ct_keys,r24	// 64 Runden abzählen
	brcc	29f		// Ergebnis in adcAccH: 30 bis 100
// Der sinnvolle Speed-Bereich liegt IMHO zwischen 2 und 128.
	mov	r24,adcAccH
	subi	r24,40		// ausprobiert
	brcs	21f		// Bei Unterlauf Ergebnis ignorieren und Minimum setzen
	cpi	r24,MINSPEED
	brcc	22f
21:	 ldi	r24,MINSPEED	// Minimum
22:	cpi	r24,MAXSPEED
	brcs	23f
	 ldi	r24,MAXSPEED	// Maximum
23:	mov	speed,r24
	clr	adcAccL
	clr	adcAccH
29:	ret

get_split:
// Entnimmt das für curLn passende Nibble und generiert daraus die Werte für PORTB und DDRB	
// PE: R25:24 = Bitmuster
// PA, VR siehe unten
	sbrc	curLn,1
	 mov	r24,r25
	sbrc	curLn,0
	 swap	r24
// Teilt 4 Bits aus r24 charlieplex-gerecht auf
// PE: R24 = Bits (Lo-Nibble: dcba), curLm = Zeile-als-Bit (1,2,4 oder 8)
// PA: R24 = Bits für DDRB, R25 = Bits für PORTB (je 5 Bit)
// VR: R7:R6
	andi	r24,0x0F	// 0000dcba
	mov	r25,r24
	lsl	r25		// 000dcba0
	mov	r7,curLm	// 00000001 00000010 00000100 00001000
	mov	r6,r7
	com	r7		// 11111110 11111101 11111011 11110111
	dec	r6		// 00000000 00000001 00000011 00000111
	sub	r7,r6		// 11111110 11111100 11111000 11110000
	and	r24,r6		// 00000000 0000000a 000000ba 00000cba: gebliebene Bits
	and	r25,r7		// 000dcba0 000dcb00 000dc000 000d0000: verschobene Bits
	or	r24,r25		// 000dcba0 000dcb0a 000dc0ba 000d0cba: PORT-Bit für Katode bleibt 0
	mov	r25,r24
	or	r25,curLm	// 000dcba1 000dcb1a 000dc1ba 000d1cba: DDR-Bit für Katode ist 1
	ret
	
// Idle bis Timer0-Überlauf, dann erst weitere Berechnungen
w_ovf0:	sleep
	sbis	GPIOR0,f_ovf0
	 rjmp	w_ovf0
	cbi	GPIOR0,f_ovf0	// quittieren
	ret

init_watchdog:
	wdr
	in	r24,WDTCR
	ori	r24,1<<WDCE|1<<WDE
	out	WDTCR,r24
	ldi	r24,0b11000100
	out	WDTCR,r24	// Watchdog-Interrupt alle 0,25 s:
reset_timeout:	// Licht aus nach maximal 64K×0,25s = 4½ h
	ldi	wdtcntL,lo8(TIMEOUT*4)
	ldi	wdtcntH,hi8(TIMEOUT*4)		// Timeout zurücksetzen
	ret

// Berechnet Helligkeit vor Timeout für Herunterdimmen in 9 s.
bright_before_timeout:
	ldi	r24,255
	tst	wdtcntH
	brne	39f
	mov	r25,wdtcntL
	lsl	r25		// für 1-s-Schritte
	brcs	39f
	lsl	r25
	brcs	39f
30:	cpi	r25,9
	brcc	39f
	lsr	r24
	inc	r25
	rjmp	30b
39:	ret

// Speichert R24 auf Adresse R25, aber nur wenn Eeprom unbeschäftigt
eesave:
	sbic	EECR,1
	 ret		// Nichts tun wenn beschäftigt
	out	EEARL,r25
	sbi	EECR,0	// Erst lesen
	in	r25,EEDR
	cp	r25,r24
	breq	49f	// Gleich? Nichts tun!
	out	EEDR,r24
	com	r25
	and	r25,r24	// Kein 0-1-Bitübergang? Nur schreiben
	ldi	r25,0b100100
	breq	41f
	com	r24
	ldi	r25,0b010100
	breq	41f	// R24 = 0xFF: Nur löschen
	ldi	r25,0b000100	// Löschen + schreiben
41:	cli
	out	EECR,r25
	sei
	sbi	EECR,1
49:	ret
Detected encoding: UTF-80