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

/* Kode für mfv2c: 425-Hz-Messung mittels A/D-Wandler an PB2 = ADC1 = Pin7
	und Nulldurchgangs-Abstandsmessung.
	Genauso wie beim CLIP-Empfang, nur mit freigegebenen Interrupts
	und (damit) ohne ADC-ISR.
	Einzige exportierte Funktion ist „bool mess425()“,
	die bis zum Watchdog-Interrupt (Flags&0x80) = 16 ms arbeiten darf.
 220410	erstellt
*/

#include <avr/io.h>
typedef unsigned char byte;
typedef unsigned short word;
constexpr char LOG2(unsigned v) {return 15-__builtin_clz(v);}
constexpr char CLOG2 = LOG2(F_CPU/1000000);
constexpr char BLOG2 = LOG2(1000/125);		// 3 = ÷8 → 125 kHz → 9,6 kSa/s
#define Flags	GPIOR0	// Bits für NSK() u.a.
extern void eechange(byte);	// Debug
static_assert(CLOG2>=1,"F_CPU zu gering, mindestens 2 MHz!");
extern byte SinTab[];
extern void clock_set(byte div);

static void adcwait() {		// Warte bis ADC-Wert fertig (Ohne ADC-Interrupt!)
 while (!(ADCSRA&0x10));
 ADCSRA|=0x10;
}

// R19, R21:R20, XH:XL bleiben unverändert, aber das kann avr-gcc nicht optimieren
static int mulSin(char adcval,word pha) {
 int ret;	// ret = adcval*sin(pha)*0.57 (73/128)
 asm(
"	.macro	mulbit b	\n"
"	sbrc	%A2,\\b		\n"
"	 add	%A0,%1		\n"	// niemals Carry
"	lsr	%A0		\n"
"	.endm			\n"
"	mov	ZL,%B2		\n"
"	cbr	ZL,1<<7		\n"	// Halbsinus: Nur positiv
"	ldi	ZH,hi8(SinTab)	\n"
"	lpm	%A2,Z		\n"
"	subi	%A2,73		\n"	// TODO? Kein Offset
"	eor	%B2,%1		\n"
"	sbrc	%1,7		\n"
"	 neg	%1		\n"
"	clr	%A0		\n"
"	mulbit	0		\n"
"	mulbit	1		\n"
"	mulbit	2		\n"
"	mulbit	3		\n"
"	mulbit	4		\n"
"	mulbit	5		\n"
"	mulbit	6		\n"
"	sbrc	%B2,7		\n"
"	 neg	%A0		\n"
"	lsl	%B2		\n"
"	sbc	%B0,%B0		\n"	// 36 Takte
 :"=&d"(ret):"d"(adcval),"r"(pha));
 return ret;
}

// Aufteilung: Nicht-statisches Unterprogramm,
// damit dieses (während Tonausgabe nicht läuft)
// R8..R15 benutzen kann. Leider unsinnges PUSH/POP dafür.
// Stack ist beim ATtiny45 knapp!
word internMess() {
 if (PB5DEBUG&3) {
  DDRB |= 0x20;
  PORTB&=~0x20;
 }
 constexpr word ADD = 425.0*65536*(1<<CLOG2+BLOG2)*13/F_CPU;
 int a=0, b=0;
 word pha=0;
 byte period=6;
 do{
  adcwait();			// Der Gleichspannungspegel liegt bei über 75%
  if (PB5DEBUG&1) PINB|=0x20;	// Sample anzeigen
  int adcval = ADC-0x380;
  if (adcval<-127) {
   adcval=-127;
   if (PB5DEBUG&1) PINB|=0x20;	// Abschneiden anzeigen
  }
  a+=mulSin(adcval,pha+0x4000);	// Kosinus (Realteil)
  b+=mulSin(adcval,pha);	// Sinus (Imaginärteil)
  asm(				// if (overflows(pha+=ADD)) --period
"	add	%A0,%A2		\n"
"	adc	%B0,%B2		\n"
"	sbc	%1,r1		\n"
  :"+r"(pha),"+r"(period):"r"(ADD));
 }while (period);		// 6 Perioden = 14 ms erwarten
 if (PB5DEBUG&3) {
  DDRB &=~0x20;
  PORTB|= 0x20;
 }
 if (a<0) a=-a;
 if (b<0) b=-b;
 return a>=b ? a+(b>>1) : (a>>1)+b;	// Betrag der komplexen Zahl a+ib ohne Quadrat und Wurzel
// spektrale Spannung bei 425 Hz im Rechteckfenster von 6 Perioden
}

bool mess425() {	// Messung 425 Hz mit A/D-Wandler
 if (DDRB&2) return false;	// Tonausgabe läuft: Messung sinnlos
 if (ADMUX&0x20) return false;	// A/D-Wandler mit Messung der Betriebsspannung beschäftigt
// Bei geteiltem CPU-Takt (1 MHz) und ADC-Taktteiler 8 stehen
// 104 Takte pro A/D-Wert zur Verfügung: Zu wenig!
// 208 Takte reichen hier schon.
 ADCSRA=0xA0|CLOG2+BLOG2;	// Passender Taktteiler
 clock_set(0);			// Maximale Taktfrequenz
 word c=internMess();
 clock_set(CLOG2);
 ADCSRA=0xA0|BLOG2;	// Taktteiler zurückstellen
 return c>=2000;
}
Detected encoding: UTF-80