/*
Spannungsgesteuerter Oszillator (VCO) für niedrige Frequenzen mit ATtiny25/45/85.

Das gezeigte Programm erzeugt ein Rechtecksignal mit 50% Tastverhältnis. Die Frequenz
ist proportional zur Eingangsspannung U-in im Bereich 5 Hz - 5 kHz. Bei Vcc = 5V beträgt
die Ausgangsfrequenz 1 kHz/V. Besonders stabil wird die Ausgangsfrequenz, wenn ein
ext. Quarz die Taktfrequenz liefert und die Einstellung mit einem Poti erfolgt, welches
die Steuerspannung zwischen GND und Vcc einstellt. Schwankungen der Temperatur und der
Versorgungsspannung werden so wirksam unterdrückt.


Pinbelegung des ATtiny:
Pin 4 = 0V
Pin 8 = 5V
Pin 7 = U-in (ADC1)
Pin 6 = Frequenzausgang

optional:
Pin 1 = pullup für /Reset-Eingang
Pin 2 = ext. Quarz f-max = 20 MHz
Pin 3 = ext. Quarz

Das Schaltbild zum Programm findet sich hier:
http://www.mino-elektronik.de/bilder/mini_U2F/mini_U2F.png

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-12-02

*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>
#include <math.h>

#define BIT(x)  (1<<x)
#define F_CPU             8000000               // ggf. an eff.Frequenz anpassen
                                                // max. Ausgangsfrequenz: 25 kHz @ 8 MHz F_CPU
  
#define ADC_MAX           1023.0                // bei 10 Bit
#define F_MIN             5                     // 5 Hz bei ADC-Eingang = 0V
#define F_MAX             5.0e3                 // 5 kHz bei ADC-Eingang = Vcc

#define ADC_IN            1                     // ADC-Eingang 1
#define F_OUT             1                     // F_OUT an PB.1 

#define PB0               0 
#define PB3               3 
#define PB4               4 

#define FILTER_LAENGE     16                    // nach Bedarf

volatile uint8_t neue_werte_fertig,             // zur Uebergabe neuer Periodendauer
                 freigabe;                      // 1 = Taktausgabe aktiv
volatile uint32_t neue_periode;                 // errechneter Wert für Periodendauer

uint16_t  filter_f[FILTER_LAENGE];              // Filter für ADC
uint32_t  filter_summe_f;
uint8_t   filter_index;


void adc_eingang_filtern(void)
{
uint16_t  temp;
  ADMUX =  ADC_IN;                              // Vcc als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);                          // starten
  while(ADCSRA & BIT(ADSC));                    // warten
  temp = ADC;
  if(filter_index >= FILTER_LAENGE) 
    filter_index = 0;
  filter_summe_f -= filter_f[filter_index];
  filter_f[filter_index] = temp;
  filter_summe_f += temp;
  filter_index++;
}

ISR(TIMER1_COMPA_vect)
{
static uint8_t trigger_pw;
static uint32_t pw_nachlade_temp, nachlade_temp;
static uint32_t pulsweite =  1000, periode =  2000; // default Startwert
uint16_t temp = 0xff;

  if(trigger_pw) {
    if(pw_nachlade_temp <= temp) temp = pw_nachlade_temp;         
    else if(pw_nachlade_temp < 0x1ff) temp = pw_nachlade_temp/2; 
    pw_nachlade_temp -= temp;                   // laengere PW-Intervalle stueckweise abarbeiten
    OCR1A += temp;
    if(pw_nachlade_temp == 0) {                 // Pulsweite macht den letzten Zyklus
      TCCR1 &= ~BIT(COM1A0);                    // danach OC1A synchron loeschen wenn TCNT1 == OCR1A
      nachlade_temp = periode - pulsweite;      // Restwert für Periodendauer verwenden
      trigger_pw = 0;
    }
    return;                                     // Pulsweite ist noch aktiv
  }

  if(nachlade_temp <= temp) temp = nachlade_temp;         
  else if(nachlade_temp < 0x1ff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1A += temp;
  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen

  trigger_pw = 1;
  pw_nachlade_temp = pulsweite;
  if(freigabe) TCCR1 |= BIT(COM1A0);            // beim nächsten Interrupt OC1A synchron setzen
  if(neue_werte_fertig) {                       // update synchronisieren
    periode = neue_periode;                     // neue Periodendauer
    pulsweite = neue_periode/2;
    neue_werte_fertig = 0;                      // bereit fuer neue Werte
  }
  nachlade_temp = periode;                      // neue Periode mit akt. Wert ausgeben
}


// Hauptprogramm
int main(void)
{
uint8_t n;
float f_temp;

  CLKPR = 0x80; CLKPR = 0x0;                    // CKDIV auf 0; voller CPU-Takt
  DDRB  |= BIT(F_OUT);                          // Takt-Ausgang
  PORTB = BIT(PB0) | BIT(PB3) | BIT(PB4);       // Pullup für stabile EIngaenge
  TCCR1 = BIT(CS10)| BIT(COM1A1);               // freilaufend mit vorteiler /1
  TIFR  =  BIT(OCF1A);                          // OCF1A flag loeschen
  TIMSK |= BIT(OCIE1A);
  ADCSRA = BIT(ADEN) + 0x07;                    // ADC einschalten mit Teiler /128
  sei();

  for(n = 0; n > FILTER_LAENGE; n++)            // Filter einschwingen lassen
    adc_eingang_filtern();
  while(1) {                                    // Endlosschleife
    adc_eingang_filtern();                      // aktuellen Wert holen
    if(!neue_werte_fertig) {                    // neue Berechnung/Ausgabe mit ISR synchronisieren
      f_temp = (float)filter_summe_f/(float)FILTER_LAENGE;  // gemittelter ADC-Wert
      f_temp = f_temp / ADC_MAX;                // nur eine Richtung drehen 0.0 -> +1.0 normieren
      f_temp = fabs(f_temp) * F_MAX;            // eff. Ausgangsfrequenz
      if(f_temp < F_MIN) {
        f_temp = F_MIN;                         // nach unten begrenzen
        freigabe = 0;
      } else freigabe = 1;                      // Signal nur bei gueltiger Frequenz ausgeben
      neue_periode = F_CPU/f_temp;              // eff. Periodendauer
      neue_werte_fertig = 1;  
    }    
  }
}


