/*
Spannungs-Frequenz und Spanungs-Pulsweiten-Wandler mit ATmega48/88

Am Ausgang OC1B wird ein Impuls von 1-30 µs Dauer in Abhängigkeit der
Eingangsspannung am ADC-Eingang3 erzeugt.
Die Pulswiederholfrequenz wird mit der Eingangsspannung am ADC-Eingang2
eingestellt und liegt im Bereich 10Hz - 5kHz.

Die Grenzwerte werden durch die entsprechenden #define-Anweisungen eingestellt.

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-10-15

*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdlib.h>

#define BIT(x)  (1<<x)
#define F_CPU           10000000    // 10MHz ggf. an eff.Frequenz anpassen

#define ADC_MAX         1023.0      // bei 10 Bit
#define F_MIN           10.0        // bei ADC-Eingang = 0
#define F_MAX           5000.0      // bei ADC-Endanschlag
#define PW_MIN          1.0e-6      // 1µs
#define PW_MAX          30.0e-6     // 50µs in Sekunden
#define PW_LIMIT        65500       // wg. 16 Bit Register

#define ZEITVERSATZ_PW  20          // Korrekturwert der Pulsweite
#define F_ADC           2           // ADC-Eingang 2
#define PW_ADC          3           // ADC-Eingang 3
#define PW_AUSGANG      2           // PW-Signal an PB.2  
#define F_AUSGANG       1           // kurzer Impuls an PB.1
#define FILTER_LAENGE   16          // nach Bedarf


volatile uint8_t neue_werte_fertig; // Sync-Flag
volatile uint32_t neue_frequenz;    // errechnter Wert für f
volatile uint16_t neue_pulsweite;   // errechneter Wert für PW

uint16_t  filter_f[FILTER_LAENGE];  // Filter für beide ADC-Kanäle
uint16_t  filter_pw[FILTER_LAENGE];
uint32_t  filter_summe_f;
uint32_t  filter_summe_pw;
uint8_t   filter_index;


uint16_t lese_adc(uint8_t kanal)
{
  ADMUX =  BIT(REFS0)| kanal;       // Vcc als Vref, rechtsbuendig
  ADCSRA |= BIT(ADSC);              // starten
  while(ADCSRA & BIT(ADSC));        // warten
// return(ADC_MAX);                 // Maximum testen
  return(ADC);                      // fertig  
}

void adc_eingaenge_filtern(void)
{
  if(filter_index >= FILTER_LAENGE)
    filter_index = 0;
  filter_summe_f -= filter_f[filter_index];
  filter_f[filter_index] = lese_adc(F_ADC);
  filter_summe_f += filter_f[filter_index];
  filter_summe_pw -= filter_pw[filter_index];
  filter_pw[filter_index] = lese_adc(PW_ADC);
  filter_summe_pw += filter_pw[filter_index];
  filter_index++;
}



ISR(TIMER1_COMPA_vect)
{
static uint32_t nachlade_temp=0;
static uint8_t trigger_pw;
static uint32_t frequenz=100000;                // Startwerte vorgeben
static uint16_t pulsweite=500;                  // dto.
uint32_t temp = 0xffff;

  if(trigger_pw) {
    PORTB |= BIT(F_AUSGANG);
    OCR1B = TCNT1 + ZEITVERSATZ_PW +pulsweite;  // + VERSATZ;
    TCCR1A = BIT(COM1B1) | BIT(COM1B0);         // OC1B setzen
    TCCR1C = BIT(FOC1B);                        // und per Foce Output Compare erzwingen
    TCCR1A = BIT(COM1B1);                       // danach OC1B loeschen wenn TCNT1 == OCR1B
    trigger_pw = 0;
    PORTB &= ~BIT(F_AUSGANG);
  }


  if(!nachlade_temp) nachlade_temp = frequenz;  // neuer Zyklus 
  if(nachlade_temp <= temp) temp = nachlade_temp;         
  else if(nachlade_temp < 0x1ffff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1A += temp;
  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen
  trigger_pw = 1;                               // Ausgangsimpuls im nächsten Zyklus ausgeben
  if(neue_werte_fertig) {
    frequenz = neue_frequenz;
    pulsweite = neue_pulsweite;
    neue_werte_fertig = 0;
  }
}

void init_timer1_adc()
{
  TCCR1B = 0;                                   // stoppen
  TCNT1  = 0;                             
  TCCR1A = BIT(COM1B0) | BIT(COM1B1);           // OC1B setzen wenn TCNT1 == OCR1B
  TCCR1B = BIT(CS10);                           // freilaufend mit vorteiler /1
  OCR1A  = 0;                                 
  TIFR1  =  BIT(OCF1A);                         // OCF1A flag loeschen
  TIMSK1 |= BIT(OCIE1A);
  ADCSRA = BIT(ADEN) + 0x07;                    // ADC einschalten mit Teiler /128
  sei();
}



// Hauptprogramm
int main(void)
{
uint8_t n;
float f_temp;

  CLKPR = 0x80; CLKPR = 0x0;                              // CKDIV auf 0; voller CPU-Takt
  DDRB = BIT(F_AUSGANG) | BIT(PW_AUSGANG);                // PB.1 + PB.2 als Ausgang
  init_timer1_adc();
  for(n = 0; n > FILTER_LAENGE; n++)
    adc_eingaenge_filtern();
  while(1) {
    adc_eingaenge_filtern();
    if(!neue_werte_fertig) {
      f_temp = (float)filter_summe_f/(float)FILTER_LAENGE;// gemittelter ADC-Wert
      f_temp = f_temp / ADC_MAX * F_MAX;                  // eff. Ausgangsfrequenz
      if(f_temp < F_MIN) f_temp = F_MIN;                  // nach unten begrenzen
      f_temp = F_CPU/f_temp;                              // eff. Periodendauer
      neue_frequenz = (f_temp + 0.5);                     // ggf. aufgerundet

      f_temp = (float)filter_summe_pw/(float)FILTER_LAENGE;// gemittelter ADC-Wert
      f_temp = f_temp / ADC_MAX * PW_MAX;                 // eff. Ausgangsfrequenz
      if(f_temp < PW_MIN) f_temp = PW_MIN;                // nach unten begrenzen
      f_temp = f_temp * F_CPU;                            // eff. Pulsweite
      neue_pulsweite = (f_temp + 0.5);                    // ggf. aufgerundet
      if(neue_pulsweite > PW_LIMIT) 
        neue_pulsweite = PW_LIMIT;                        // und begrenzen
      neue_werte_fertig = 1;  
    }    
  }
}


