Quelltext /~heha/basteln/PC/FunkUsb/dcf77franz9.zip/sdr.S

#define __SFR_OFFSET 0
#include "avr/io.h"
.global TIMER0_COMPB_vect,makeAverage,phi,Cplx24AbsLog,addphasedreh

// Für die Signalverarbeitung
// Prinzip Hoffnung: Keine Aussteuerung die zum Überlauf führt, d.h. 16-Bit-Akku reicht

TIMER0_COMPB_vect:
	in	r3,SREG
	in	r2,ADCL
	sbic	GPIOR2,1
	 rjmp	2f
	sbic	GPIOR2,0
	 rjmp	1f
	add	r4,r2		// I += ADC
	in	r2,ADCH
	adc	r5,r2
	rjmp	4f
1:	add	r6,r2		// Q += ADC
	in	r2,ADCH
	adc	r7,r2
	rjmp	4f
2:	sbic	GPIOR2,0
	 rjmp	3f
	sub	r4,r2		// I -= ADC
	in	r2,ADCH
	sbc	r5,r2
	rjmp	4f
3:	sub	r6,r2		// Q -= ADC
	in	r2,ADCH
	sbc	r7,r2
4:	in	r2,OCR0B	// Int24Rec z = {r8,r9,OCR0B}
	add	r8,r12		// z += {r12,r13,r14}
	adc	r9,r13
	adc	r2,r14
	out	OCR0B,r2	// {r8,r9,OCR0B} = z — C++ hat kein destrukturierendes Assignment
	in	r2,GPIOR2
	inc	r2
5:	out	GPIOR2,r2
	out	SREG,r3
	cpse	r2,r15		// 22 Takte
	 reti			// dazu Aufruf + rjmp + reti + 1 = 33 Takte
	sts	ISR_Buf+0,r4	// Nötig: 62 kHz = 16 µs
	sts	ISR_Buf+1,r5	// Bei der absolut tiefstmöglichen Quarzfrequenz von 1,8432 MHz
	sts	ISR_Buf+2,r6	// sind 29 Takte pro Interrupt da; das geht _nur_ mit Tail-Chaining.
	sts	ISR_Buf+3,r7	// Der nächstbeste Baudratenquarz mit 3,6864 bietet 59 Takte pro Interrupt.
	clr	r4		// Das reicht bereits.
	clr	r5		// Komfortabler sind glatte 4 MHz mit 64 Takten pro Interrupt.
	clr	r6
	clr	r7
	sbi	GPIOR0,0	// Alle 4 ms: Idle-Routine kümmert sich um die Weiterverarbeitung
	clr	r2
	rjmp	5b

// lade R23:21 von Z
24:	ldd	r21,Z+0
	ldd	r22,Z+1
	ldd	r23,Z+2
	ret

makeAverage:
// PE:	X = const int*buf
//	Z = int24*avg
// VR:	W20, L22, X vorgerückt, Z vorgerückt
	ld	r24,X+
	ld	r25,X+
	mov	r0,r25
	lsl	r0
	sbc	r0,r0	// Vorzeichen auf 24 Bit erweitern
	rcall	24b
	ldi	r20,4	// T = 16 als erprobte Zeitkonstante
2:	asr	r23	// schiebe R23:21 um R20 nach rechts, vzb.
	ror	r22
	ror	r21
	dec	r20
	brne	2b
	sub	r24,r21
	sbc	r25,r22
	sbc	r0,r23
	rcall	24b	// Der Trick: 2× laden spart Register
	add	r21,r24
	adc	r22,r25
	adc	r23,r0
	st	Z+,r21
	st	Z+,r22
	st	Z+,r23
	ret

phi:
// PE:	Z = const int24 avg[2]
// PA:	R24 = Phase 0, 0x40, 0xC0, 0x80 je nach Vorzeichen der beiden Mittelwerte
	clr	r24
	clr	r25
	ldd	r0,Z+2
	sbrc	r0,7
	 ldi	r24,0x40
	ldd	r0,Z+5
	sbrc	r0,7
	 ldi	r25,0xC0
	eor	r24,r25
	ret

// 24-Bit-Wert laden und Betrag bilden
23:	rcall	24b
	sbrs	r23,7
	 ret
	com	r23
	com	r22
	neg	r21
	sbci	r22,-1
	sbci	r23,-1
	ret

// Quadrieren / Multiplizieren vzl. mit konstanter Rundenzahl, 16×16 => 32 Bit
// (Die avr-gcc-laufzeitoptimierte Version nützt hier nichts da die Faktoren eher groß sind!)
// PE:	R25:24 = W = 16-Bit-Zahl
// PA:	R23:20 = L20 = Quadrat der Zahl
// VR: R1=0
usqr:	movw	r20,r24
// PE:	R21:R20 = W20 = zweiter Faktor
umul:	clr	r22
	clr	r23
0:	sbrc	r20,0
	 add	r22,r24
	sbrc	r20,0
	 adc	r23,r25
	ror	r23
	ror	r22
	ror	r21
	ror	r20
	inc	r1
	sbrc	r1,4
	 rjmp	0b
	clr	r1
	ret

// Binärer Logarithmus
// PE:	L20 = 32-Bit-Zahl vzl.
// PA:	W = Binärlogarithmus Q8.8, maximal „31.99“, negativ für Argument=0
// VR:	Z
// Benötigt externe (von C++ zur Compilezeit generierte) Logarithmentabelle
lb8:	ldi	r25,31
1:	tst	r23
	brmi	3f
2:	dec	r25
	brmi	9f	// Notbremse für Argument 0: Liefert negativen Wert
	lsl	r20
	rol	r21
	rol	r22
	rol	r23
	brpl	2b
// An dieser Stelle ist das Bit 7 von R23 stets gesetzt, und R25 enthält den ganzzahligen Binärlogarithmus
3:	lsr	r23	// 6-Bit-Rest für Tabellenzugriff zurechtrücken
	andi	r23,63
	mov	r30,r23
	clr	ZH
	subi	r30,lo8(-(logtab))
	sbci	r31,hi8(-(logtab))
	lpm	r24,Z	// Den Nachkommateil laden
9:	ret

Cplx24AbsLog:
// Logarithmus des Betrags einer komplexen Zahl = log. Amplitude eines I/Q-Signals
// PE:	Z = const-Zeiger auf komplexe Zahl: 2× 24 Bit vzb.
// PA:	W24 = Zweierlogarithmus Q8.8, 1=> 0.0, 2=> 1.0, 4=> 2.0 usw. max. <31.5
// VR:	Nach avr-gcc-Konvention alle: R20..R27, R30..R31. W18 wird NICHT eingesaut!
	rcall	23b	// I laden und Betrag => R22:R20
	movw	r26,r22
	mov	r25,r21	// => L25
	adiw	ZL,3
	rcall	23b	// Q laden und Betrag => L21
// ab hier Z frei für Arithmetik
	ldi	r20,9	// Mehr als 9 0-Bits einschieben ist Quatsch da Multiplikation 16-bittrig
	rjmp	3f
// Einzelbitweise linksschieben bis eine der beiden Zahlen >=0x400000 ist
2:	lsl	r21	// Alle 24 Bit
	rol	r22
	rol	r23	// Q<<=1
	lsl	r25
	rol	r26
	rol	r27	// I<<=1
	dec	r20	// Schiebeoperationen zählen bis -1
	brmi	5f	// Schluss bei -1, um den Rest kümmert sich der Logarithmus
3:	mov	r24,r27
	or	r24,r23
	andi	r24,0xC0
	breq	2b
5:	mov	0,r20
	// Ab hier |Q| in W22 und |I| in W26, jeweils 16 Bit. R20 ist hier maximal 9.
	movw	r24,r22	// |Q| positionieren
	rcall	usqr	// Q² in L20, max. 0x3FFF'0001
	movw	r24,r26	// |I| positionieren
	movw	XL,r20
	movw	ZL,r22	// Q² in Z:X
	rcall	usqr	// I² in L20, max. 0x3FFF'0001
	add	r20,XL
	adc	r21,XH
	adc	r22,ZL
	adc	r23,ZH	// I² + Q², max. 0x7FFE'0002
	rcall	lb8	// Logarithmus daraus machen => W24, max. <32.0
	asr	r25
	ror	r24	// halbieren entspricht Wurzel ziehen, max. <16.5
	add	r25,r0	// Skalierung hinzufügen, max. <31.5
	ret
// „Oben“ sind noch 3 Bit frei, wenn man das Ergebnis „unsigned“ interpretiert, sonst 2.

// Diese Lösung mit 16-MHz-Quarz frisst mit sleep_cpu() und ohne Verwendung von Timer1
// nur noch 6 mA. Das ist für viele Anwendungen zu viel, da eine Akkustütze vorgesehen ist.
// Daher ist angedacht, die Quarzfrequenz radikal zu senken.
// Bei seriellem Interface (Blinkbit, UART-TxD oder I²C) ist das möglich.
// Bereits die bisherige ISR erlaubt theoretisch 3,579545 (NTSC-Quarz),
// 3,6864 MHz (Baudratenquarz) oder 4 MHz (glatter Quarz).
// Mit der hier angegebenen ISR komme ich auf 2 MHz — nur I²C oder gaanz langsame UART sinnvoll.
// Die ISR-Last liegt dann bei extrem sportlichen 96 % — durch Tail Chaining komme ich auf
// 80 %, was einen Baudratenquarz von 1,8432 MHz zulässig erscheinen lässt, dann 87 % ISR-Last
// und „Dauer-Tail-Chaining“ von vielleicht 5~10 Runden!
// Damit sollte die Stromaufnahme auf 2 mA sinken, und die Stromaufnahme des Vorverstärkers von
// (gemessen) 1,3 mA fällt ins Gewicht. Nicht vergessen die LFuse an den langsamen Quarz anzupassen!
// (Mit FET-Vorstufe 2 mA für den Vorverstärker. Der bringt gar nichts an Empfangsqualität.)
// Bei einer V-USB-Lösung sind ohnehin 15 MHz für den Quarz nötig, und der gefräßige Timer1
// muss laufen um unbediente Timer0-CompareB-Interrupts zu detektieren.
// I²C-Slave erfordert deaktiviertes Reset und damit ein Hochvolt-Programmiergerät zum Entwickeln.
	
Vorgefundene Kodierung: UTF-80