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