/*
Spannungs-Frequenz und Spanungs-Pulsweiten-Wandler mit ATmega48/88

Am Ausgang OC1B wird ein Impuls von 10µs -> 100ms 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 - 10kHz.

Ein '0' Signal am LOCK_EINGANG (PB.1) verriegelt die aktuellen Einstellungen.
Spannungsänderungen an den ADC-Eingängen werden ignoriert.

Die Grenzwerte werden durch die entsprechenden #define-Anweisungen eingestellt.

http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2013-10-18

*/


#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 F_T1            (F_CPU)

#define ADC_MAX         1023.0      // bei 10 Bit
#define F_MIN           10.0        // 10Hz bei ADC-Eingang = 0
#define F_MAX           10.0e3      // 10kHz bei ADC-Endanschlag
#define PW_MIN          10.0e-6     // 10µs
#define PW_MAX          100.0e-3    // 100ms 
#define PW_RESERVE      500         // 50µs @ 10MHz PW-max immer kleiner als Periodendauer-PW_RESERVE

#define F_ADC           2           // ADC-Eingang 2
#define PW_ADC          3           // ADC-Eingang 3
#define PW_AUSGANG      2           // PW-Signal an PB.2  
#define LOCK_EINGANG    1           // 0, wenn aktuelle Einstellung verriegelt werden soll
#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 uint32_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_COMPB_vect)
{
static uint32_t nachlade_temp, pw_nachlade_temp;
static uint8_t trigger_pw = 1;
static uint32_t frequenz =  200000;   // default Startwerte @ 10MHz
static uint32_t pulsweite = 100000;   // 50Hz und 10ms Pulsweite 
uint32_t temp = 0xffff;

  if(trigger_pw) {
    if(pw_nachlade_temp <= temp) temp = pw_nachlade_temp;         
    else if(pw_nachlade_temp < 0x1ffff) temp = pw_nachlade_temp/2; 
    pw_nachlade_temp -= temp;                   // laengere PW-Intervalle stueckweise abarbeiten
    OCR1B += temp;
    if(pw_nachlade_temp == 0) {                 // Pulsweite macht den letzten Zyklus
      TCCR1A = BIT(COM1B1);                     // danach OC1B synchron loeschen wenn TCNT1 == OCR1B
      nachlade_temp = frequenz - 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 < 0x1ffff) temp =nachlade_temp/2; 
  nachlade_temp -= temp;                        // laengere Intervalle stueckweise abarbeiten
  OCR1B += temp;
  if(nachlade_temp) return;                     // Intervall noch nicht abgelaufen
  trigger_pw = 1;                               // Ausgangsimpuls im nächsten Zyklus ausgeben
  TCCR1A = BIT(COM1B1) | BIT(COM1B0);           // beim nächsten Interrupt OC1B synchron setzen
  pw_nachlade_temp = pulsweite;
  if(neue_werte_fertig) {                       // update synchronisieren
    frequenz = neue_frequenz;
    pulsweite = neue_pulsweite;
    neue_werte_fertig = 0;
  }
}

void init_timer1_adc()
{
  TCCR1B = 0;                                   // stoppen
  TCNT1  = 0;                             
  TCCR1B = BIT(CS10);                           // freilaufend mit vorteiler /1
  OCR1B  = 0;                                 
  TIFR1  =  BIT(OCF1B);                         // OCF1B flag loeschen
  TIMSK1 |= BIT(OCIE1B);
  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(PW_AUSGANG);                                 // PB.2 als Ausgang
  PORTB = BIT(LOCK_EINGANG);                              // pullup
  init_timer1_adc();
  for(n = 0; n > FILTER_LAENGE; n++)
    adc_eingaenge_filtern();
  while(1) {
    adc_eingaenge_filtern();
    if(!neue_werte_fertig && (PINB & BIT(LOCK_EINGANG))) {  // update nur, wenn LOCK_EINGANG == 1
      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_T1/f_temp;
      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_T1;                             // eff. Pulsweite
      neue_pulsweite = (f_temp + 0.5);                    // ggf. aufgerundet
      if(neue_pulsweite > neue_frequenz-PW_RESERVE) 
        neue_pulsweite = neue_frequenz-PW_RESERVE;        // und begrenzen
      neue_werte_fertig = 1;  
    }    
  }
}


