Source file: /~heha/basteln/Konsumgüter/Kram/GF22.zip/main.cpp

/* Sinusgenerator mittels Frequenzsynthese (DDS)
   zum Ersatz des Wien-Brückenoszillators des GF22,
   dessen Amplitudenregelung zu wünschen übrig lässt.
   für ATtiny25 @ 16..20 MHz
   „h#s“ Henrik Haftmann, TU Chemnitz, 22. August 2019
   tabsize = 8, encoding = utf-8 (ohne Windows-Header BOM)
Hardware:
1	PB5	ADC0	Drehschalter mit 0-22-68-∞ kΩ
2	PB3	XTAL1	Quarz
3	PB4	XTAL2	Quarz
4		GND	0 V
5	PB0		Lerntaste
6	PB1	OC1A	PWM-Ausgang
7	PB2	ADC1	Potenziometer
8		Ucc	5 V

Timer0: DDS-Frequenzgenerator (CTC-Modus)
Timer1: High-Speed-PWM-Generator
Bereichswähler für LVISP:		Ra theoretisch	erprobt
15/16..16/16 = 4,69..5,00 V =  2 .. 20 kHz	 ∞  kΩ	 ∞  kΩ	
14/16..15/16 = 4,38..4,69 V = 0,2..  2 kHz	560 kΩ	  1 MΩ
13/16..14/16 = 4,06..4,38 V = 20 ..200  Hz	330 kΩ	560 kΩ
12/16..13/16 = 3,75..4,06 V =  2 .. 20  Hz	180 kΩ	220 kΩ
*/

#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/fuse.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/signature.h>
#include <util/delay.h>

FUSES={
#if F_CPU == 16000001
 0b10110001,	// kein Taktteiler, PLL-Oszillator 16 MHz
#else
 0b11111111,	// kein Taktteiler, Quarzoszillator
#endif
#ifdef LVISP
 0b11010111,	// LV-ISP erlauben
#else
 0b01010111,	// Kein !Reset erlauben
#endif
 0b11111111
};

typedef uint8_t byte;
typedef uint16_t word;
typedef uint32_t dword;
#define NOINIT __attribute__((section(".noinit")))

register dword Freq	asm("r2");
register byte savesreg	asm("r6");
register byte phaseL	asm("r7");	// fünftes Byte
register dword phase	asm("r8");
register word saveZ	asm("r12");
register byte calibStep	asm("r14");
register byte adcCnt	asm("r15");

word adc0 NOINIT;	// enthält 64-fach akkumulierte Schalterstellung
word adc1 NOINIT;	// enthält 64-fach akkumulierten Potenziometerwert

const byte stuetz=12;
const byte CTC0=64;	// CTC-Wert für Timer0
#if __cplusplus >= 201100
constexpr word FREQ(float x) {return word(x*1024*256*CTC0/F_CPU+0.5);}
#else
# define FREQ(x) word(x*1024.0*256*CTC0/F_CPU+0.5)
#endif

const PROGMEM word freqTab[stuetz]={
 FREQ( 1800),FREQ( 2000),FREQ( 2500),FREQ( 3000),
 FREQ( 4000),FREQ( 5000),FREQ( 6000),FREQ( 8000),
 FREQ(10000),FREQ(12000),FREQ(15000),FREQ(20000),	// 12 Frequenzstützstellen
};

static EEMEM struct{
 word adcvalues[stuetz];
}eedata={
 {0xFFFF,1000,7000,13000,19000,25000,31000,37000,43000,49000,55000,61000}	// Stützstellen für ADC1, Index 0 ungenutzt
};

static dword divideFreq(word f,byte adc0h) {
 static const PROGMEM word divisor[]={1000,100,10,1};
 static dword r NOINIT;	// erspart Y-Stapelrahmen
 r=(dword)f<<16;
#ifdef LVISP
 adc0h>>=4;	// vom ADC0 = PB5 = !Reset wird 11XX ---- ---- ---- benutzt.
 if (adc0h<12) adc0h=12;	// wenn kleiner als 12/16 Eingangsspannung UND noch kein !Reset
 adc0h &=3;
#else
 adc0h>>=6;
#endif
 return  r/pgm_read_word(divisor+adc0h);
}

static word interpolateFreq(word adc1) {
 word refH=0;
 word refL;
 byte i=0;
 do{
  refL=refH,refH=eeprom_read_word(eedata.adcvalues+ ++i);
 }while (refH<adc1 && i!=stuetz-1);
 word fL=pgm_read_word(freqTab+i-1);
 word fH=pgm_read_word(freqTab+i);
 return fL+word(fH-fL)*dword(adc1-refL)/word(refH-refL);
}

static void initHardware() {
 DDRB |= 0x02;	// Ausgang aktivieren
 PORTB = 0x39;	// Pullup für ADC0 und PB0
 DIDR0 = 0x3E;	// Alles deaktivieren was kein digitaler Eingang ist
 ACSR |= 0x80;	// Analogvergleicher deaktivieren
 PLLCSR= 0x02;	// PLL (64 MHz) aktivieren
 _delay_us(100);
 while (!PLLCSR&1);
 PLLCSR= 0x06;
 TCCR1 = 0x61;	// PWM mit 64 MHz Trägerfrequenz
 TCCR0A= 0x02;	// CTC-Modus
 TCCR0B= 0x01;	// mit 16 bis 20 MHz, durch 64 ergibt 250..312 kHz
 OCR0A = CTC0-1;	// durch 64
 TIMSK = 0x10;	// Einzige Interruptroutine
 MCUCR = 0x20;	// sleep aktivieren
 ADCSRA= 0x97;	// ADC aktivieren: Einmalige A/D-Wandlung, Vorteiler 128
 sei();
}
/* A/D-Wandler im kontinuierlichen Modus geht hier nicht,
   weil die CPU mit Interrupts nahezu ausgelastet ist;
   der A/D-Wert kann nicht zügig genug gelesen werden,
   und die A/D-Werte verhaspeln sich bisweilen.
 */
int main() {
 initHardware();
 calibStep=0;
 for(;;) {
  adc0=adc1=0;
  adcCnt=0;
  for(;;) {
   ADCSRA|=0x40;	// ADC starten
   do sleep_cpu(); while (ADCSRA&0x40);	// nur Timer0-Interrupts bis ADC fertig
   if (ADMUX&1) {
    adc1+=ADC;	// hier ADC1 = PB2 = Pin7 = Potenziometer
    ADMUX&=~1;
    if (++adcCnt==64) break;	// mit F_CPU/128/25/64/2 = 39 Hz (maximal)
   }else{
    adc0+=ADC;	// hier ADC0 = PB5 = !Reset = Pin1 = dekadischer Bereichsumschalter
    ADMUX|=1;
   }
  }
// Hier wenn beide Analogeingänge 64× akkumuliert wurden (>≈ 25 ms)
  dword f=divideFreq(calibStep
    ?pgm_read_word(freqTab+calibStep)	// glatte Frequenz
    :interpolateFreq(adc1),adc0>>8);	// stetig stellbare Frequenz
  cli();
  Freq=f;			// ausgeben: DDS-Frequenz setzen
  sei();
  byte pinb=PINB;
  static byte opinb NOINIT;	// zufällig initialisiert: macht nichts!
  if ((byte)~pinb&opinb&1) {	// Fallende Flanke an PB0?
   if (calibStep) eeprom_write_word(eedata.adcvalues+calibStep,adc1);	// Index 0 muss 0 bleiben
   if (++calibStep==stuetz) calibStep=0;
  }
  opinb=pinb;
 }
}
Detected encoding: UTF-80