/* 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;
}
}
Vorgefundene Kodierung: UTF-8 | 0
|