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

// DTMF-Dekoder auf ATtiny85 mittels A/D-Wandler
// Grundlage: Diskrete Fouriertransformation
// CPU-Taktfrequenz: maximal, mindestens 4 MHz
#ifdef __AVR_ATtiny85__
#define __SFR_OFFSET 0
#include <avr/io.h>

// 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
#else
# warning F_CPU zu klein, keine DTMF-Dekodierung
#endif

// Taktteiler 64, bei F_CPU == 8 MHz: Abtastrate 8M÷64÷13 = 9615 Hz; 26,6 ms
//		  bei F_CPU == 4 MHz: Abtastrate 4M÷64÷13 = 4808 Hz; 53,2 ms
// CPU-Takte pro A/D-Wert: 64×13 = 832 (Rechenzeit für 16 Multiplikationen)

// 16-Bit-Wert <h:l> negieren
// PE:	<h:l> = 16-Bit-Wert, R1=0
// VR:	-; Takte: 3
	.macro neg16 h,l	//bspw.	0001		0100
	neg	\h		//	0001		FF00
	neg	\l		//	00FF, C=1	FF00, C=0
	sbc	\h,r1		//	FFFF		FF00
	.endm

// Für 8-Bit-Multiplikationen fehlt die Zeit für den Low-Teil und "ror r0".
// Selbst wenn man auf den Low-Teil verzichtet müsste man stets CY löschen.
// Daher ist der Trick, mit nur 7 Bit zu rechnen, absolut entscheidend!
	.macro mulbit8 a,b,c
	sbrc	\b,\c
	 add	r0,\a	// Bei 7-Bit-Operanden niemals Überlauf
	lsr	r0	// Immer 0-Bit einschieben
	.endm

// Gefensterten A/D-Wert mit Wert aus Sinustabelle multiplizieren
//PE:	a = vzl. A/D-Wert 0..127, T = Vorzeichen
//	b = vzb. Wert aus Sinustabelle, ±122
//PA:	r1:r0 = a×b>>7 vzb.
//VR:	b (Betrag)
//Zeit:	5 + 7×3 + 4 = 30 Takte (ausgerollte Schleife)
	.macro fmuls8 a,b
	bld	r1,7
	eor	r1,\b
	sbrc	\b,7
	 neg	\b
	clr	r0		// 5
	mulbit8	\a,\b,0
	mulbit8	\a,\b,1
	mulbit8	\a,\b,2
	mulbit8	\a,\b,3
	mulbit8	\a,\b,4
	mulbit8	\a,\b,5
	mulbit8	\a,\b,6		// 7×3
	sbrc	r1,7
	sbrc	r1,7
	 neg	r0
	lsl	r1
	sbc	r1,r1		// 4
	.endm

// Nullzentrierten A/D-Wert fenstern
//PE:	a = vzl. A/D-Wert (0..127) — für 8 Bit reicht die Rechenleistung nicht!
//	b = vzl. Wert (0..127) aus Fenstertabelle
//PA:	r0 = a×b>>7 vzl.
//VR:	a (Betrag); 1 + 7×3 = 22 Takte (ausgerollte Schleife)
	.macro fmul8 a,b
	clr	r0
	mulbit8	\a,\b,0
	mulbit8	\a,\b,1
	mulbit8	\a,\b,2
	mulbit8	\a,\b,3
	mulbit8	\a,\b,4
	mulbit8	\a,\b,5
	mulbit8	\a,\b,6		// 7×3
	.endm

// 16-Bit-Summand in Speicher aufaddieren
// PE:	<h:l> = Summand
//	Y = Zeiger auf 16-Bit-Summe
// PA:	[Y] = Summe bearbeitet, Y vorgerückt
// VR:	R25; 10 Takte
	.macro addY h,l
	ld	r25,Y
	add	r25,\l
	st	Y+,r25
	ld	r25,Y
	adc	r25,\h
	st	Y+,r25
	.endm

// Gefensterten A/D-Wert mit Sinustabellenwert vorzeichenrichtig
// multiplizieren und in 16-Bit-Akkumulator aufsummieren
// PE:	R24 = gefensterter A/D-Wert-Betrag, 0..127, T = Vorzeichenbit
//	R21 = High-Phasenwinkel umlaufend
//	ZH = hi8(sinus122), darin Werte ±122
//	Y = Zeiger auf 16-Bit-Akkumulator
// PA:	[Y] berechnet, Y vorgerückt
// VR:	XH,ZL, R0,R1,R25; 39 Takte
	.macro mulAddSin
	mov	ZL,r21
	lpm	XH,Z		// 4
	fmuls8	r24,XH		// 25
	addY	r1,r0		// 10
	.endm

// Gefensterten A/D-Wert mit Sinustabellenwert 2× (0° und 90°)
// vorzeichenrichtig multiplizieren und jweils in 16-Bit-Akkumulator aufsummieren
// PE:	R24 = gefensterter A/D-Wert-Betrag, 0..127, T = Vorzeichenbit
//	ZH = hi8(sinus122), darin Werte ±122
//	Y = Zeiger auf Phasenaddierwert
// PA:	Sinus- und Kosinussumme berechnet, Y vorgerückt
// VR:	XH,ZL, R0,R1,R21,R25; 93 Takte
	.macro	mulAddSinCos
	ld	r1,Y+
	ld	r21,Y+
	ld	r0,Y
	add	r1,r0
	st	Y+,r1
	ld	r0,Y
	adc	r21,r0
	st	Y+,r21		// 14
	mulAddSin		// 39
	subi	r21,-0x40	// 1	90°
	mulAddSin		// 39
	.endm

// Sinus- und Kosinussumme nullsetzen. Sowie DC-Wert und Absolutsumme.
// PE:	Y zeigt auf Phasenaddierwert; R1=0
// PA:	Sinus- und Kosinussumme nullgesetzt; Y vorgerückt
// VR:	-; 10 Takte
	.macro	clrAkku
	st	Y+,r1
	st	Y+,r1
	st	Y+,r1
	st	Y+,r1
	adiw	YL,4
	.endm

// Gefensterten A/D-Wert: Wert summieren
// VR:	R1:R0, R25; 14-15 Takte
	.macro	dcAdd
	mov	r0,r24
	clr	r1
	brtc	71f
	neg	r0
	sbc	r1,r1
71:	addY	r1,r0		// 10
	.endm
	
// Gefensterten A/D-Wert: Betrag summieren
// PE:	R24 = Betrag (7 Bit) des gefensterten Wertes; T = Vorzeichen ungenutzt
//	Y zeigt auf Betrag-Summe (16 Bit)
// PA:	[Y] = neue Summe, Y vorgerückt, R1=0
// VR:	R1, R25; 11 Takte
	.macro	absAdd
	clr	r1
	addY	r1,r24		// 10
	.endm

// 16-Bit-A/D-Wert eingrenzen und in Vorzeichen+Betrag umwandeln
// PE:	h:l = bereits nullzentrierter A/D-Wert
//	R1=0
// PA:	l = auf 0..127 begrenzter vzl. A/D-Wert
//	t = Vorzeichen, 1 = negativ
// VR:	XH; 7-9 Takte
	.macro	limitc h,l
	bst	\h,7
	brtc	41f
	neg16	\h,\l
41:	cpi	\l,128
	cpc	\h,r1
	brcs	42f
	ldi	\l,127
#if PB5DEBUG&2
	sbi	PINB,5		// Überlauf / Begrenzung anzeigen
#endif
42:
	.endm

// Erfasst Sinus- und Kosinussumme und bildet einen gerundeten Betrag.
// Nicht hypot(y,x) sondern |y|>|x| ? |y|+|x|>>1 : |y|>>1+|x|
// PE:	Y zeigt auf Sinus-Summe (Imaginärteil)
//	R1 = 0
// PA:	Kosinus-Summe (Realteil) ersetzt durch o.a. Betrag
//	Y vorgerückt
// VR:	R25:R24, ZH:ZL; 22-31 Takte
	.macro	betrag
	ld	r24,Y+
	ld	r25,Y+
	sbrs	r25,7
	 rjmp	51f
	neg16	r25,r24		// 5-9
51:	ld	ZL,Y
	ldd	ZH,Y+1
	sbrs	ZH,7
	 rjmp	51f
	neg16	ZH,ZL		// 5-9
51:	cp	r24,ZL
	cpc	r25,ZH
	brcc	51f
	lsr	r25
	ror	r24
	rjmp	52f
51:	lsr	ZH
	ror	ZL
52:	add	r24,ZH
	adc	r25,ZL
	st	Y+,r24
	st	Y+,r25		// 12-13
	.endm

.global _ZN7DftScanclEhhi
// Von C++ aufrufbare Klassenmemberfunktion: operator()(byte,byte,int)
// this-Operand = R25:R24 = Datenstruktur
// Byte-Operand = R22 = Anzahl zu suchender Spektrallinien
	// Für ADC-Taktteiler 64 maximal 7 (für DTMF-Töne)
	// Für ADC-Taktteiler 32 maximal 2? (für Freiton)
	// Abtastrate @ F_CPU == 8 MHz: 9,6 kSa/s (104 µs)
	// Abtastrate @ F_CPU == 4 MHz: 4,8 kSa/s (208 µs)
// Byte-Operand = R20 = Inkrement für Fensterindex (begrenzt Gesamtdauer)
// int-Operand = R19:R18 = ADC-Mittelwert
_ZN7DftScanclEhhi:
#if PB5DEBUG&3
	sbi	DDRB,5
	cbi	PORTB,5
#endif
	movw	r12,YL
	movw	r10,r24
	movw	YL,r10
	mov	r23,r22
0:	clrAkku			// 8×10 = 80 Takte (irrelevant)
	subi	r23,1
	brcc	0b
	clr	XL		// 256 (oder 128) Runden: Index in Fenstertabelle
// Es sind 832 Takte verfügbar!
0:	sbis	ADCSRA,4	// ADC-Interrupt pollen
	 rjmp	0b
	sbi	ADCSRA,4		// 5
#if PB5DEBUG&1
	sbi	PINB,5		// Abtastung anzeigen
#endif
	in	r24,ADCL
	in	r25,ADCH
	sub	r24,r18
	sbc	r25,r19			// 4
	limitc	r25,r24			// 9
// Die entscheidende Idee ist es, ab hier den A/D-Wert als 7-Bit-Betrag
// zu führen und das Vorzeichen im T-Bit.
// Obwohl es naheliegend wäre, dann 8 Bit Betrag zu verwenden,
// würde dann die Multiplikationsroutine zu langsam werden.
// Nicht (nur) wegen der weiteren Adition sondern (vor allem)
// weil das C-Flag mit jedem Bit gelöscht werden muss,
// was in der Standardimplementierung das zweite "ror r0" mit erledigt.
2:	mov	ZL,XL
	ldi	ZH,hi8(fenster127)
	lpm	XH,Z			// 5
	fmul8	r24,XH			// 22
	mov	r24,r0
// Durch die Fensterung ändert sich das Vorzeichen nicht
	movw	YL,r10			// 2
	dcAdd				// 15
	absAdd				// 11
	ldi	ZH,hi8(sinus127)
	mov	r23,r22			// 2; bis hierhin 75 Takte
3:	mulAddSinCos		// 93 (für jede einzelne Frequenz)
	dec	r23
	breq	3f
	rjmp	3b		// 7×97=679 Takte, bis hierhin 754 Takte
3:	add	XL,r20		// Bei 4 MHz Schrittweite 2 = 128 Samples,
	breq	0f		// bei 8 MHz Schrittweite 1 = 256 Samples
	 rjmp	0b		// 4, bis hierhin 758 Takte: Noch 74 Takte Zeit
// Keine Zeit, weitere Frequenzen zu suchen!!
0:	movw	YL,r10
	adiw	YL,4
	mov	r23,r22
0:	adiw	YL,4
	betrag
	dec	r23
	brne	0b
	movw	YL,r12
#if PB5DEBUG&3
	sbi	PORTB,5
	cbi	DDRB,5
#endif
	ret
#endif
Detected encoding: UTF-80