/*
Das Programm 'F2U_Tiny44' erwartet eine Eingangsfrequenz an ICP1 (PORTA.7),
berechnet den genauen Wert ueber eine reziproke Frequenzmessung und gibt
anschliessend den ermittelten Wert ueber PortA.6 als PWM-Signal aus. Die
PWM-Frequenz liegt bei rund 11,7 kHz und laesst sich mit RC-Tiefpass gut filtern.
Das PWM-Signal bietet 10-Bit Aufloesung, wobei der hoechste Wert PWM = 1023 sein kann.
Nach analoger Filterung des PWM-Signals ergibt sich ein Spannungswert von Vcc/1024*PWM.

Praktischerweise wird hier (beispielhaft) die maximale Eingangsfrequenz
auf 4,995 kHz eingestellt, was zu einem Umsetzfaktor von 1V/kHz fuehrt. Bei
Frequenzen >= F_MAX wird der maximale PWM-Wert mit 4,995 V (Vcc = 5 V) ausgegeben,
Bei fehlendem Eingangssignal oder TIMEOUT >= 1 s, wird 0,000 V ausgegeben.

Die maximale Eingangsfrequenz betraegt ca. 100 kHz bei F_CPU = 12 MHz. Die minimale
Messzeit ist per #define auf 10 ms eingestellt. Daher wird die maximale Messrate 
auf 100 Hz begrenzt, bei Signalen < 100 Hz entspricht die maximale Messrate der
Periodendauer des Signals.
Allgemein formuliert beträgt die maximale Messdauer 10 ms + 1 Periode.

Das Programm ist für den ATtiny44 geschrieben und verwendet einzig den Timer1 fuer
die genaue Frequenzmessung per input-capture, dessen Ueberlaeufe und PWM-Ausgabe. 

Signaleingang:  Fin PA.7
PWM-Ausgang:    PWM PA.6

http://www.mino-elektronik.de
Alle Angaben wie immer ohne Gewaehr !
2014-10-30
*/


#include <avr/io.h>
#include <avr/interrupt.h>

#define BIT(x)  (1<<x)

#define F_CPU           12000000L         // 12 MHz ggf. anpassen

#define F_MAX           4995.1            // fuer 1 V/kHz 
#define T1_MAX          0x400             // wg. fast PWM
#define DAC_MAX         (T1_MAX-1)        // max. Wert bei 10-Bit DAC

#define OCR1B_VORTEILER 1000
#define EINE_MS         (F_CPU/OCR1B_VORTEILER/1000)      // 1 ms
#define TIMEOUT         1000              // 1 Sekunde
#define MIN_MESSZEIT    10                // 10 ms

#define F_EINGANG       7                 // PortA.7
#define PWM_AUSGANG     6                 // PortA.6

enum status {MESSEN=0, AUSLESEN, AUSWERTEN};    // die Phasen der Messung

volatile uint8_t messwert_vorhanden,      // nur gueltige Werte ausgeben
        mess_status;                      // Ablaufsteuerung


volatile uint16_t zeit_low,               // Timer1-Anteil
        zeit_high,                        // T1 Ueberlauf-Anteil
        messdauer,                        // in ms
        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;
  TCCR1A |= BIT(COM1A1)| BIT(COM1A0) |    // inverse PWM, damit bei 0 Ausgang auf 0 bleibt
            BIT(WGM11) | BIT(WGM10);      // fast PWM mit 0x3ff
  TCCR1B = 0x01 | BIT(WGM12);             // Timer1 ohne Vorteiler
  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(TIM1_OVF_vect)            
{
  ueberlauf++;                            // Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
}

// CAPT-Ereignisse an AIN1
ISR(TIM1_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 < T1_MAX/2))              // evtl. Ueberlauf T1 noch offen?
      zeit_high++;                        // nur, wenn capture-int + overflow-int gleichzeitig !
    mess_status = AUSWERTEN;              // Daten fertig fuer Auswertung
  }
}

// 1ms Signal erzeugen
ISR(TIM1_COMPB_vect)
{
static uint8_t delay;
  OCR1B += OCR1B_VORTEILER;
  delay++;
  if(delay >= EINE_MS) {
    delay = 0;
    messdauer++;
  }
}

// Ausgabe von 10 Bits an OCR1B
void dac_ausgabe(uint16_t wert)
{
  wert = DAC_MAX-wert;                    // invers ausgeben!
  wert &= DAC_MAX;
  cli();
  OCR1A = wert;                           // darf nicht unterbrochen werden
  sei();
}

// Hauptprogramm
int main(void)
{
float frequenz, temp;

  CLKPR = 0x80; CLKPR = 0x0;                  // CKDIV auf 0; voller CPU-Takt
  DDRA =  BIT(PWM_AUSGANG);                   // Ausgang PA.6
  PORTA |= BIT(F_EINGANG);                    // Pullup ICP PA.7
 
  mess_status = MESSEN;                       // zuerst messen
  messwert_vorhanden = 0;                     // 1.Ergebnis unterdruecken, da es falsch ist

  init_timer();
  sei();

  for(;;) {

    TIMSK1 &= ~BIT(OCIE1B);                   // COMP1B Int-disable wegen Abfrage von messdauer
    if(messdauer >= TIMEOUT) {                // bei timeout default-Werte setzen
      messwert_vorhanden = 0;                 // naechster Eregbnis verwerfen
      dac_ausgabe(0);                         // kein Eingangssignal
    }
    TIMSK1 |= BIT(OCIE1B);                    // COMP1B Int-enable
    TIMSK1 &= ~BIT(OCIE1B);                   // COMP1B Int-disable wegen Abfrage von messdauer
    if(mess_status == MESSEN &&               // bei laufender Messung
       messdauer >= MIN_MESSZEIT) {           // Messrate auf minimale Messzeit begrenzen
      mess_status = AUSLESEN;                 // Auswertung starten
      messdauer = 0;
    }
    TIMSK1 |= BIT(OCIE1B);                    // COMP1B Int-enable

    if(mess_status==AUSWERTEN) {
      end_zeit = (uint32_t)zeit_high * T1_MAX + 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 gueltigen Wert anzeigen
        frequenz = ((float)mess_ereignisse * F_CPU) / mess_zeit;  // Frequenz berechnen
        if(frequenz > F_MAX) frequenz = F_MAX;// auf max. Wert begrenzen
        temp = (DAC_MAX/F_MAX) * frequenz + 0.5;
        dac_ausgabe(temp);                    // skaliertes Ergebnis auf DA-Wandler
      }
      else messwert_vorhanden = 1;            // sperre wieder aufheben
    }
  }
}
