#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-8 | 0
|