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

// CLIP-Dekoder auf ATtiny45 mittels A/D-Wandler
// 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	// Beides funktioniert.
// Theoretisch müsste IRQMODE etwas Strom sparen
// Ich hatte mal für IRQMODE 2200 µF ausgerechnet, ohne 4700 µF.
// Aber tatsächlich reichen sogar 220 µF, mit 470 µF ist man schon sicher.
// Selbst diesen gibt es mit schlanken 6 mm Durchmesser, als 6,3-V-Typ.

// Hochpass-Funktion in Software
#define TAU_SH 4	// 0, 4, 6 oder 8: Ausprobieren!
// Genauere Nulldurchgangsbestimmung
#define SUBSAMP 0	// 1 oder 0: Ausprobieren!
 
// 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 MEANL r8
#define MEANH r9
#if TAU_SH > 6
# define MEANE r10	// 18 Bit
#endif
#define DIFFL r12
#define DIFFH r13
#define TLEVEL r14	// Gefundener Schwellwert
#if SUBSAMP
# define TBEFORE r15	// 0..1
#endif

// 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_ADC (F_CPU/(1<<CLOG2)/4/13)

// Erstinitialisierung von AKKU mittels ADLAR
// Zeit zwischen 2 Abtastwerten: 52 Takte
.global _Z6acInitv,_Z6acMeanv,_Z6acOnesv,_Z6acBytev,_Z6acDonev
_Z6acInitv:
#if PB5DEBUG&3
	sbi	DDRB,5
	cbi	GPIOR0,5
#endif
#if IRQMODE
	ldi	r24,0x28	// ADC-Störunterdrückung
	out	MCUCR,r24
	ldi	r24,0xFA
#else
	ldi	r24,0xF2	// Start; ADC-Takt = 250 kHz (4 µs), ADC-Rate = 19,23 kHz (52 µs)
#endif
	out	ADCSRA,r24
#if IRQMODE
	sei			// ADC = einzige Interruptquelle
	sleep			// Die ISR sperrt die Interrupts, sonst nichts
#else
1:	sbis	ADCSRA,4
	 rjmp	1b
	sbi	ADCSRA,4	// Interruptflag löschen
#endif
#if TAU_SH == 6
	in	MEANL,ADCL
	in	MEANH,ADCH
	cbi	ADMUX,ADLAR	// ab jetzt rechtsbündig
#elif TAU_SH == 4
	cbi	ADMUX,ADLAR	// rechtsbündig auslesen
	in	r24,ADCL
	in	r25,ADCH
	swap	r24
	swap	r25
	eor	r25,r24
	andi	r24,0xF0
	eor	r25,r24
	movw	MEANL,r24	// x 16
#elif TAU_SH == 8
	cbi	ADMUX,ADLAR	// rechtsbündig
	clr	MEANL
	in	MEANH,ADCL
	in	MEANE,ADCH
#elif TAU_SH == 0		// nur differenzieren (1. Ableitung)
	cbi	ADMUX,ADLAR	// immer rechtsbündig
	in	MEANL,ADCL
	in	MEANH,ADCH
#else
# error TAU_SH fehlt oder falsch!
#endif
#if SUBSAMP
	clr	TBEFORE
#endif
	ret

// kehrt bei Flanke zurück und liefert die Zeit in 26-µs-Schritten seit der letzten Flanke
// VR: W20-R24
acEdge:	clr	r24
1:	movw	r20,DIFFL	// vorherigen Messwert retten
	subi	r24,-2
// Nächsten A/D-Wert holen und Flanke feststellen
// NZ = Flanke, Takte: 19 + (CallRet)7 = 24
#if IRQMODE
	sei
	sleep			// wartet bis A/D-Wandler fertig
#else
0:	sbis	ADCSRA,4
	 rjmp	0b
	sbi	ADCSRA,4	// Interruptflag löschen
#endif
	in	DIFFL,ADCL
	in	DIFFH,ADCH
#if TAU_SH == 6			// R23       R22
	movw	r22,MEANL	// AAAA BBBB CCCC DDDD
	lsl	r22		//           CCCD DDD0
	rol	r23		// AAAB BBBC
	rol	r22		//           CCDD DD0A
	rol	r23		// AABB BBCC
	rol	r22		//           CDDD D0AA
	andi	r22,3		//           0000 00AA
	sub	DIFFL,r23	// τ = 64
	sbc	DIFFH,r22	// Differenz ermittelt
	add	MEANL,DIFFL
	adc	MEANH,DIFFH
#elif TAU_SH == 4		// R23       R22
	movw	r22,MEANL	// 00AA BBBB CCCC DDDD
	swap	r22		//           DDDD CCCC
	swap	r23		// BBBB 00AA
	andi	r22,0x0F	//           0000 CCCC
	eor	r22,r23		//           BBBB CCčč	č = C^A
	andi	r23,3		// 0000 00AA
	eor	r22,r23		//           BBBB CCCC
	sub	DIFFL,r22	// τ = 16 (mehr Hochpasswirkung)
	sbc	DIFFH,r23	// Differenz ermittelt
	add	MEANL,DIFFL
	adc	MEANH,DIFFH
#elif TAU_SH == 8
	sub	DIFFL,MEANH	// 8 bit, τ = 256
	sbc	DIFFH,MEANE
	sbc	r22,r22
	add	MEANL,DIFFL
	adc	MEANH,DIFFH
	adc	MEANE,r22
#elif TAU_SH == 0
	sub	DIFFL,MEANL
	sbc	DIFFH,MEANH
	add	MEANL,DIFFL
	adc	MEANH,DIFFH
#else
# error unsupported TAU_SH
#endif
	movw	r22,DIFFL
	eor	r23,r21
	brpl	1b		// weitersuchen wenn keine Flanke
#if PB5DEBUG&1
	sbi	PINB,5		// PB5 toggeln bei Flanke
#endif
#if SUBSAMP
// W22 = neuer Messwert, W20 = alter Messwert
	eor	r23,r21
	tst	r21
	brpl	5f
// W22 positiv, W20 negativ
	com	r20
	com	r21		// positiv machen
	rjmp	20f
5:// W22 negativ, W20 positiv
	com	r22
	com	r23
20:	cp	r22,r20
	cpc	r23,r21		// Neuer Wert betragsmäßig kleiner dem alten?
	adc	r24,r1		// Dann Nulldurchgang in „zweier Hälfte“
	mov	r22,r24
	andi	r22,1
	sub	r24,TBEFORE	// Vom vorherigen Nulldurchgang Zeit fürs Ergebnis abziehen
	mov	TBEFORE,r22	// Jetzigen Bruchteil merken
#endif
#if PB5DEBUG&2
	sbis	GPIOR0,5	// TLEVEL 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

// Ermitteln der durchschnittlichen Abstände der Nulldurchgänge
// 256 gültige Flanken ~ 200 ms
// R24 = durchschnittlicher Pegelwechsel-Abstand in 26 µs
//	1300 Hz => 385 µs => 14,8
//	2100 Hz => 238 µs => 9,2
_Z6acMeanv:
1:	clr	TLEVEL
	clr	r0
	clr	r25
2:	rcall	acEdge
	cpi	r24,F_ADC/800	// 22: 520 µs => 1 kHz
	brcc	1b		// Zu groß: Neustart
	cpi	r24,F_ADC/4800	// 4: 104 µs => 4 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 11 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
	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_ADC*2+FBIT/2)/FBIT)	// 32 @ 4 MHz, *2 wegen Subsampling
#define NSTA (NBIT*38/32)		// 38 @ 4 MHz, erprobt
_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 32 x 26 µ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		// 32 × 26 µs = 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:
	sbi	ADMUX,5		// Ergebnis links ausrichten um nur 8 Bit auswerten zu müssen
	ldi	r24,0xD1
	out	ADCSRA,r24	// Start mit Ergebnis in 50 µs ohne Interrupt
#if PB5DEBUG&3
	cbi	DDRB,5;		// DEBUG
#endif
	ret
Detected encoding: UTF-80