/*
Das Programm 'f2f_konverter' erwartet eine Eingangsfrequenz an AIN1 (PORTD7),
berechnet den genauen Wert ueber eine reziproke Frequenzmessung und gibt
anschliessend eine neue Frequenz an OC1B (PORTB2) aus. Die Ausgabefrequenz
kann beliebig aus der Eingangsfrequenz abgeleitet werden, muss allerdings im
vorgegebenen Rahmen liegen. Die untere Ausgangsfrequenz ist willkuerlich auf
10 Hz gesetzt und die hoechste Frequenz ergibt sich aus der Taktfrequenz des
Prozessors / 400. Bei 20 MHz Prozessortakt sind dies 50 kHz.
Das Ausgangssignal wird auf 50% Tastverhaeltnis eingestellt.

Bei fehlendem Eingangssignal wird die niedrigste Frequenz ausgegeben.

Das Programm ist für den ATmega48 geschrieben, laeuft aber auch ohne Aenderung
direkt auf einem ATmega88. 
Beispielhaft wird hier in "main()" die Berechnung: frequenz = frequenz * 1.12246;
durchgefuehrt, wodurch die Ausgangsfrequenz um einen Ganzton hoeher als die
Eingangsfrequenz liegt.

Signaleingang:  AIN1 (PORTD7)
Signalausgang:  OC1B (PORTB2)


http://www.mino-elektronik.de
Alle Angaben wie immer ohne Gewaehr !
2013-11-26
*/


#include <avr/io.h>
#include <avr/interrupt.h>

#define BIT(x)  (1<<x)

#define F_CPU           10000000L         // 10 MHz ggf. anpassen
#define MIN_F           10.0              // 1 Hz
#define MAX_F           F_CPU/400         // 25kHz bei 10 MHz

#define DEF_PERIODE     F_CPU/10          // 10 Hz
#define DEF_PULSWEITE   (DEF_PERIODE/2)   // 50%-Tastverhaeltnis

uint8_t  neue_werte_bereit, neuer_wert;   // Sync-Flags
uint32_t neue_periode = DEF_PERIODE;      // Wert für Periode
uint32_t neue_pulsweite = DEF_PULSWEITE;  // Wert für Pulsweite


enum status {MESSEN=0, AUSLESEN, AUSWERTEN};    // die Phasen der Messung

volatile uint8_t messwert_vorhanden,      // nur gueltige Messungen anzeigen
        ovf_flag,
        mess_status;                      // Ablaufsteuerung


volatile uint16_t zeit_low,               // Timer1-Anteil
        zeit_high,                        // T1 Ueberlauf-Anteil
        ueberlauf;

volatile uint32_t start_ereignis,         // Impulse zu Beginn der Messung
        end_ereignis,                     // Impulse am Ende der Messung
        start_zeit,                       // rel. Zeit: Beginn der Messung
        end_zeit,                         // Zeitpunkt der Auswertung
        mess_zeit,                        // genaue Zeit der Messung
        mess_ereignisse;                  // Impulse der Messung

// nur Timer1 wird verwendet
// Frequenzmessung mit ICP1-Interrupt und Signalerzeugung mit T1-COMPB
void init_timer(void)
{
  TCNT1 = 0;
  TCCR1B = 0x01;                          // Timer1 ohne Vorteiler
  TCCR1A = BIT(COM1B1);                   // OC1B auf 0 lassen
  TIMSK1 = BIT(ICIE1) |                   // ICP1 Int-enable Frequenz vom Vorteiler
           BIT(TOIE1) |                   // OVFL Int-enable 
           BIT(OCIE1B);                   // COMP1B Int-enable
  TIFR1  = BIT(OCF1B);                    // OCF1B flag loeschen
}

// Routinen zur Erfassung der Eingangsimpulse
// Ueberlaeufe von T1
ISR(TIMER1_OVF_vect)            
{
  ueberlauf++;                            // Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
  ovf_flag++;
}

// CAPT-Ereignisse an AIN1
ISR(TIMER1_CAPT_vect)                     // Eingangsimpulse mit genauem Zeitpunkt erfassen
{
static unsigned long count;               // Impulse per Interrupt                
  count++;
  if(mess_status == AUSLESEN) {           // Ergebnisse synchron zum Eingangsimpuls auslesen+ablegen
    end_ereignis = count;                 // Anzahl der Impulse lesen
    zeit_low = ICR1;                      // capture-reg lesen: untere 16bit
    zeit_high = ueberlauf;                // dazu die oberen 16bit
    if((TIFR1 & BIT(TOV1)) && 
      (zeit_low < 0x8000))                // evtl. Ueberlauf T1 noch offen?
      zeit_high++;                        // nur, wenn capture-int + overflow-int gleichzeitig !
    mess_status = AUSWERTEN;              // Daten fertig fuer Auswertung
  }
}

// Ausgangssignal erzeugen
ISR(TIMER1_COMPB_vect)
{
static uint32_t nachlade_temp, pw_nachlade_temp;
static uint8_t trigger_pw = 1;
static uint32_t periode  =  DEF_PERIODE;  // default Startwerte
static uint32_t pulsweite = DEF_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(COM1B0);             // danach OC1B synchron loeschen wenn TCNT1 == OCR1B
      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 < 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(COM1B0);                  // beim nächsten Interrupt OC1B synchron setzen
  pw_nachlade_temp = pulsweite;
  if(neue_werte_bereit) {                 // update synchronisieren
    periode = neue_periode;
    pulsweite = neue_pulsweite;
    neue_werte_bereit = 0;
  }
}

// Hauptprogramm
int main(void)
{
float frequenz;
  CLKPR = 0x80; CLKPR = 0x0;                  // CKDIV auf 0; voller CPU-Takt

  PORTB = BIT(PORTB0);                        // Pullup ICP
  DDRB =  BIT(PORTB2);                        // 1kHz signal an OC1B
  PORTD = BIT(PORTD7);                        // Pullup AIN1-Eingang
  ACSR  = BIT(ACIC) | BIT(ACBG);              // analog Komparator verwenden
 
  mess_status = MESSEN;                       // zuerst messen
  messwert_vorhanden = 0;                     // 1.Ergebnis unterdruecken, da es falsch ist

  init_timer();
  sei();

  for(;;) {

    if(ovf_flag >= 250) {                     // bei timeout default-Werte setzen
      messwert_vorhanden = 0;                 // naechster Eregbnis verwerfen
      ovf_flag = 0;
      neue_periode = DEF_PERIODE;             // bei Timeout auf Default-Werte
      neue_pulsweite = neue_periode/2;        // 50% Tastverhaeltnis
      neue_werte_bereit = 1;                  // werden uebergeben
    }

    cli();
    if(mess_status == MESSEN && ovf_flag >= 3) {  // Messrate etwas begrenzen
      mess_status = AUSLESEN;                 // Auswertung starten
      ovf_flag = 0;
    }
    sei();

    if(mess_status==AUSWERTEN) {
      end_zeit = zeit_high*0x10000 + zeit_low;
      mess_zeit = end_zeit - start_zeit;      // Zeit-Differenz bilden
      start_zeit = end_zeit;                  // neue startzeit merken
      mess_ereignisse = end_ereignis - start_ereignis;  // Impuls-Differenz
      start_ereignis = end_ereignis;          // fuers naechste Intervall
      mess_status = MESSEN;                   // neue Messung beginnen
      if(messwert_vorhanden) {                // und g?ltigen Wert anzeigen
        frequenz = ((float)mess_ereignisse * F_CPU) / mess_zeit;  // Frequenz berechnen
        if(!neue_werte_bereit) {              // Uebergabe kann erfolge

// Ausgabefrequenz festlegen
// Die Berechnungen sind beliebig, sofern die Ausgangsfrequenz im erlaubten Bereich liegt
          frequenz = frequenz * 1.12246;      // Beispiel einen Ganztonschritt hoeher als Fin
                 
          if(frequenz >= MIN_F && frequenz <= MAX_F) {    // nur gueltige Werte verwenden
            neue_periode = F_CPU/frequenz;    // eff. Periodendauer
            neue_pulsweite = neue_periode/2;  // 50% Tastverhaeltnis
            neue_werte_bereit = 1;            // koennen nun uebernommen werden
          }
        }
      }
      else messwert_vorhanden = 1;            // sperre wieder aufheben
    }
  }
}
