/*
Das Programm 'F2U_konverter' erwartet eine Eingangsfrequenz an AIN1 (PORTD7),
berechnet den genauen Wert ueber eine reziproke Frequenzmessung und gibt
anschliessend den ermittelten Wert ueber einen DA-Wandler MCP4821 oder MCP4822
an Kanal A aus. Der DAC bietet 12-Bit Aufloesung, wobei der hoechste Wert 0x0fff
sein kann. Praktischerweise wird hier (beispielhaft) die maximale Eingangsfrequenz
auf 40,95 kHz eingestellt, was zu einem Umsetzfaktor von 100 mV/kHz fuehrt. Bei
Frequenzen >= F_MAX wird der maximale Wert des DAC mit 4,095 V ausgegeben,
Bei fehlendem Eingangssignal oder TIMEOUT >= 0,1 s, wird 0,000 V ausgegeben.

Die maximale Eingangsfrequenz beträgt ca. 300 kHz bei F_CPU = 20 MHz. Die minimale
Messzeit ist per #define auf 1 ms eingestellt. Daher wird die maximale Messrate 
auf 1 kHz begrenzt, bei Signalen < 1 kHz entspricht die maximale Messrate der
Periodendauer des Signals.
Allgemein formuliert beträgt die maximale Messdauer 1 ms + 1 Periode.

Das Programm ist für den ATmega48 geschrieben, laeuft aber auch ohne Aenderung
direkt auf einem ATmega88. 

Signaleingang:  AIN1 (PORTD7)
Signale für den MCP482x:
ser. Daten:     PORTB.2
ser. Takt:      PORTB.5
ser. CSelect:   PORTB.3
ser. Strobe:    PORTB.4


http://www.mino-elektronik.de
Alle Angaben wie immer ohne Gewaehr !
2013-12-06
*/


#include <avr/io.h>
#include <avr/interrupt.h>

#define BIT(x)  (1<<x)

#define F_CPU           10000000L         // 10 MHz ggf. anpassen

#define F_MAX           40950.0           // 40,95 kHz 
#define DAC_MAX         4095.0            // max. Wert bei 12-Bit DAC

#define EINE_MS         (F_CPU/1000)      // 1 ms
#define TIMEOUT         100               // 0,1 Sekunde
#define MIN_MESSZEIT    1                 // 1 ms

// Ausgabe an PORTB: PB2 - PB5
#define AUSGABE_PORT    PORTB
#define DATA        BIT(2)                // SI beim MCP4822
#define CLOCK       BIT(5)                // SCK
#define STROBE      BIT(4)                // LDAC
#define CS          BIT(3)                // CS

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;
  TCCR1B = 0x01;                          // 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(TIMER1_OVF_vect)            
{
  ueberlauf++;                            // Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
}

// 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
  }
}

// 1ms Signal erzeugen
ISR(TIMER1_COMPB_vect)
{
  OCR1B += EINE_MS;
  messdauer++;
}

// Ausgabe von 12 Bits an DAC MCP482x
void dac_ausgabe(uint16_t wert)
{
uint8_t n;
  wert &= 0x0fff;                             // Kontrollbits zunaechst auf 0
  wert |= 0x1000;                             // kanal A, U-max = 4,095V, kein shutdown
  AUSGABE_PORT |= STROBE;                     // passiv = 1
  AUSGABE_PORT &= ~CS;                        // CS = 0
  for(n=0; n<16; n++) {                       
    AUSGABE_PORT &= ~CLOCK;                   // Clock = 0
    if(wert & 0x8000) AUSGABE_PORT |= DATA;   // MSB zuerst
    else AUSGABE_PORT &= ~DATA;
    wert <<= 1;
    AUSGABE_PORT |= CLOCK;                    // Clock = 1
  }
  AUSGABE_PORT |= CS;                         // CS = 1
  AUSGABE_PORT &= ~STROBE;                    // aktiv = 0

  AUSGABE_PORT |= STROBE;                     // passiv = 1
  AUSGABE_PORT &= ~CLOCK;                     // Clock = 0
  AUSGABE_PORT &= ~DATA;
}

// Hauptprogramm
int main(void)
{
float frequenz, temp;

  CLKPR = 0x80; CLKPR = 0x0;                  // CKDIV auf 0; voller CPU-Takt
  DDRB = STROBE | CS | DATA | CLOCK;          // Ausgaenge für MCP482x
  PORTB = BIT(PORTB0);                        // Pullup ICP
  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(;;) {

    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 = 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 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
    }
  }
}
