// CLIP-Dekoder auf ATtiny45 mittels Analogvergleicher
// Grundlage: Messung der Abstände der Nulldurchgänge
// CPU-Taktfrequenz: 1..1,99 MHz
#define __SFR_OFFSET 0
#include <avr/io.h>
#define IRQMODE 1
// Die Register R8..R15 sind für den DDS-Tongenerator (Frequenzsynthese)
// reserviert und hier frei, weil dieser nicht gleichzeitig mit der
// CLIP-Erkennung läuft.
#define TLEVEL r14 // Gefundener Schwellwert
#define TIC r15 // Timer0-Capture (vorheriger Wert)
// Es hat sich gezeigt, dass am Thomson-Kabelmodem in Chemnitz
// es nicht genügt, einfach auf beide Flanken zu triggern,
// da anscheinend eine vagabundierende klitzekleine Hochfrequenz
// gelegentliche Mehrfachauslösungen (Mehrfachinterrrupts) verursachen.
// Nicht so an der Fritzbox in Nieschütz, trotz des gleichen Versuchsaufbaus
// mit Trenntrafo für das Digitaloszilloskop.
// Daher wird hier stets einige µs nach Interrupt-Return ein
// eventuell anhängiger (erneuter) Interrupt vorsorglich gelöscht,
// das löst das Problem auch ohne manuelle Umschaltung der Flankenrichtung.
// F_CPU ist historisch die Resonatorfrequenz, nicht die CPU-Taktfrequenz
#if F_CPU>=16000000
# error F_CPU zu groß!
#elif F_CPU>=8000000
# define CLOG2 3
#elif F_CPU>=4000000
# define CLOG2 2
#elif F_CPU>=2000000
# define CLOG2 1
#elif F_CPU>=1000000
# define CLOG2 0
#else
# error F_CPU zu klein!
#endif
# define F_TMR0 (F_CPU/(1<<CLOG2)/8)
// Nulldurchgangs-Interrupts treten - bei 1200 Hz - alle 416 µs auf.
// Daher eignet sich Timer0 mit Vorteiler 8 zum Zählen, Zählumfang 2..1,01 ms.
.global _Z6acInitv,_Z6acMeanv,_Z6acOnesv,_Z6acBytev,_Z6acDonev
_Z6acInitv:
#if PB5DEBUG&3
sbi DDRB,5 // PB5 zum Ausgang machen (für Oszilloskop)
cbi GPIOR0,5
#endif
sbi PORTB,0 // PB0 Pullup aktivieren
sbi PORTB,1
sbi DDRB,1 // PB1 High
ldi r24,0x02
out TCCR0B,r24 // Vorteiler 8 und Start
#if IRQMODE
ldi r24,0x20
out MCUCR,r24 // Sleep, kein PowerDown
ldi r24,0x18 // Analogvergleicher mit Interrupt bei beiden Flanken
out ACSR,r24
sei // AC = Interruptquelle, Watchdog(1s) = Reset
sleep // Die ISR sperrt die Interrupts und erlöst vom Idle, nichts weiter
#else
ldi r24,0x10 // Analogvergleicher aktivieren, Interruptflag löschen
out ACSR,r24
1: sbis ACSR,4
rjmp 1b
sbi ACSR,4 // Interruptflag löschen
#endif
#if PB5DEBUG&1
sbi PINB,5 // PB5 toggeln bei Flanke
#endif
in TIC,TCNT0
sbi ACSR,4 // Interruptflag vorsorglich nochmal löschen
ret
// kehrt bei Flanke zurück und liefert die Zeit in 8-µs-Schritten seit der letzten Flanke
// Da ein ATtinyX5 keine Capture-Hardware besitzt, geschieht das Capture in Software.
// Das ist genau genug, da keine sonstigen Interruptquellen hineinfunken.
// VR: R24
acEdge:
// Flanke feststellen
// Takte: 15 + (CallRet)7 = 22
#if IRQMODE
sei
sleep // wartet bis Flanke
#else
0: sbis ACSR,4
rjmp 0b
sbi ACSR,4 // Interruptflag löschen
#endif
#if PB5DEBUG&1
sbi PINB,5 // PB5 toggeln bei Flanke
#endif
in r24,TCNT0 // Zeit nehmen
sub r24,TIC // R24 = Zeitdifferenz
add TIC,r24 // TIC = TCNT0-Zählerstand
sbi ACSR,4 // Interruptflag vorsorglich nochmal löschen
#if PB5DEBUG&2
sbis GPIOR0,5 // TLEVEL (nach 256 Nulldurchgängen) gültig?
rjmp 0f // nein, nichts tun
cp r24,TLEVEL
sbi PORTB,5
brcc 0f // Länger als TLEVEL, lange Zeit, tiefe Frequenz = 0-Bit
cbi PORTB,5
0:
#endif
ret
// Abstand zweier Nulldurchgänge für gegebene Frequenz <f> in Hz
#define TZCF(f) (F_TMR0/2/(f))
// Ermitteln der durchschnittlichen Abstände der Nulldurchgänge
// 256 gültige Flanken ~ 200 ms
// R24 = durchschnittlicher Pegelwechsel-Abstand in 8 µs
// 1300 Hz => 385 µs => 48,1
// 2100 Hz => 238 µs => 29,7
_Z6acMeanv:
1: clr TLEVEL
clr r0
clr r25
2: rcall acEdge
cpi r24,TZCF(1000) // 62: 500 µs => 1 kHz
brcc 1b // Zu groß: Neustart
cpi r24,TZCF(2500) // 25: 200 µs => 2,5 kHz
brcs 1b // Zu klein: Neustart
add r0,r24
adc TLEVEL,r1
dec r25
brne 2b // 256 Flanken ausmitteln
;inc TLEVEL // runden (gemessen: +1)
mov r24,TLEVEL // Hier kommt 36 raus
#if PB5DEBUG&3
sbi GPIOR0,5
#endif
ret
//Beim ATtiny85 gibt es Debug-Funktionalität
#ifdef __AVR_ATtiny85__
.global _Z7acHistoPhh
_Z7acHistoPhh: // R25:R24 = Zeiger auf <r22> Byte RAM
movw XL,r24 // RAM-Zeiger -> X
mov r25,r22 // Länge -> R25
clr r0
1: rcall acEdge
lsr r24 // halbieren für die 30 Eimer
cp r24,r25
brcs 2f
mov r24,r25
dec r24
2: add XL,r24
adc XH,r1
ld r1,X
inc r1
st X,r1
clr r1
sub XL,r24
sbc XH,r1
dec r0
brne 1b
ret
#endif
// Sucht eine lange Kette von Einsen (tiefe Frequenz)
_Z6acOnesv:
1: ldi r25,100
2: rcall acEdge
cp r24,TLEVEL
brcs 1b
dec r25
brne 2b
ret
#define FBIT 1200
#define NBIT (F_TMR0/FBIT) // 104 (=2³*13) Timerticks pro Bit
#define NSTA (NBIT*38/32) // 123 (ausprobierter Startwert)
_Z6acBytev:
0: rcall acEdge // Warten auf lange Abstände (1-Bits)
cp r24,TLEVEL
brcs 0b
1: rcall acEdge // Warten auf kurzen Abstand (0-Bit = Startbit)
cp r24,TLEVEL
brcc 1b
// Startbit erkannt; jedes Bit ist 832 µs lang
ldi r25,8 // Bitzähler
ldi r18,-NSTA // Bitzeit (hier 1. Abtastzeitpunkt einstellen! Lange habe ich probiert.)
2: rcall acEdge
add r18,r24
brcc 2b // 832 µs (1 Bitzeit) abwarten
subi r18,NBIT
cp r24,TLEVEL // Länge des letzten Abstands
ror r19 // Bit einschieben (noch invertiert)
dec r25
brne 2b // 8 Runden
com r19
mov r24,r19
ret
_Z6acDonev:
ldi r24,0x80
out ACSR,r24
cbi DDRB,1 // PB1 hochohmig
#if PB5DEBUG&3
cbi DDRB,5 // PB5 zum Eingang machen
#endif
out PORTB,r1 // keine Pullups
ret
Detected encoding: UTF-8 | 0
|