Source file: /~heha/basteln/Haus/Telefon/Impulswahl→DTMF/mfv.zip/mfv2d/clip.S

// 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-80