/*
Beispielprogramm 'reziproker Frequenzzaehler' mit GPS-Signal stabilisiert
fuer ATmega162 und IAR-Kickstart: Berechnung mit 64-bit double!

Funktionsbereich mit Vorteiler 0,004Hz - >50MHz bei 16MHz Prozessortakt

Neben der üblichen Frequenzmessung an ICP1 wird zusätzlich über ICP3 die
interne Taktfrequenz anhand eines 1pps GPS-Signals ermittelt und für die
genaue Auswertung der Frequenz verwendet.
Hierzu muß das GPS-Signal über min. 10 Sekunden stabil anliegen und wird über
10 Intervalle gefiltert. Fehlt dieses Signal, wird der zuletzt errechnete Wert
von 'ref_frequenz' verwendet. Wird es wieder erfasst, muß es erneut ueber
10 Perioden stabil (+/- 100ppm) anliegen.

Der Differenzwert ref_frequenz-F_CPU wird alle 10 Minuten ins EEPROM abgelegt
und beim Einschalten mit als Startwert verwendet. Dieser Offset wird doppelt gespeichert
und nur dann zurueckgelesen, wenn beide Werte identisch sind. Anderfalls wird der
vorletzte Wert verwendet. Der schreib_zeiger fuer das EEPROM wird bei jedem Einschalten
inkrementiert, sodass immer neue Speicherplaetze (modulo 64) verwendet werden.

Das Programm ist mit IAR-Kickstart 5.0 entwickelt; die 'fuses' für XTAL (16MHz)
müssen gesetzt und CKDIV8 gelöscht sein; BOD sollte auf 4,3V eingestellt sein.
C_stack braucht 0x50 Bytes.

http://www.mino-elektronik.de

Alle Angaben wie immer ohne Gewaehr !
2011-12-16
*/

#define nTEST_LAUF       // testweise die int. Taktfrequenz anzeigen
#define F_CPU       16000000    // 16MHz ggf. an eff.Frequenz anpassen
#define VORTEILER_FAKTOR  256   // fuer 74HC393

#define  ENABLE_BIT_DEFINITIONS

#include <iom162.h>
#include <intrinsics.h>

#define MIN_REF     (F_CPU - (F_CPU/10000l))    // -100ppm
#define MAX_REF     (F_CPU + (F_CPU/10000l))    // +100ppm

#define T3_FIL_LEN  10      // gleitenden Mittelwert ueber 10s bilden
#define GPS_TIMEOUT 250     // Referenzfrequenz zu klein / fehlt
#define GPS_TOTZEIT  50     // GPS-Signal nach Einschalten 5 Sekunden unterdruecken


#define STELLEN      7      // angezeigte Stellen, max. Auflösung
#define MESSZEIT    150     // max. 1,6 Messungen/Sek. (150 x 4,1ms)
#define ZEIT_100MS   25     // 0,1 Sek. (25 x 4,1ms)

#define FERTIG_LED_BIT      5       // PORTD.5
#define GPS_LED_BIT         6       // PORTD.6
#define VORTEILER_LED_BIT   7       // PORTD.7

#define VORTEILER_AKTIV (ACSR & (1<<ACIC))  // analog Komparator testen

#define LCD_STROBE  4       // PortA.4 Schreibimpuls E fuers LCD
#define LCD_CMD     5       // PortA.5 '0' fuer Befehls-, '1' fuer Datenausgabe
#define ZEILE1  0x7f        // jeweils 1 addieren fuer 1. Spalte
#define ZEILE2  0xbf        // dto.

#define F_OFFSET_SKAL     20.0 // Skalierung: 32000/20.0=1600 => ca. 100ppm
#define EE_SCHREIB_INTERVALL  600  // alle 10 Minuten
#define EE_SCHREIB_ZEIGER 2 // ins EEPROM adr. 0x02
#define EE_ABLAGE_OFFSET  8 // ab adr. 0x08
#define EE_ADR_MASKE    0x3f
#define EE_DATA_SIZE      4

enum status {MESSEN=0, AUSLESEN, AUSWERTEN};    // die Phasen der Messung

#ifdef TEST_LAUF            // zur Anzeige der int. Taktfrequenz
char string[20];
char text1[]={"Fin"};       // 1.Zeile der Anzeige
char text2[]={"Qu "};       // 2.Zeile der Anzeige
#else
char text1[]={"F.:"};       // 1.Zeile der Anzeige
char text2[]={"P.:"};       // 2.Zeile der Anzeige
#endif

char f_dim[][4]={"GHz","MHz","kHz","Hz ","mHz","sek","ms ","us ","ns ","ps "};  // Dimensionen zum Messwert
char stellen;

double frequenz, ref_frequenz; // Ergebnis


// 'volatile' zur Sicherheit
volatile unsigned char messwert_vorhanden,  // nur gueltige Messungen anzeigen
        mess_status,        // Ablaufsteuerung
        sync_flag,          // wird mit T1OV gesetzt
        fertig_led_cnt,     // FERTIG-LED kurz aufleuchten lassen
        gps_led_cnt,        // GPS-LED Blinken+Dauerlicht
        t3_tout_cnt,        // T3 Messdauer bewerten
        t3_timeout,         // 1Hz-Referenz fehlt
        t3_fertig;          // wird mit ICP3 gesetzt


volatile unsigned int zeit_low,     // Timer1-Anteil
        zeit_high,          // T1 Ueberlauf-Anteil
        t3_zeit_low,        // Timer3-Anteil
        t3_zeit_high,       // T3 Ueberlauf-Anteil
        mess_dauer,         // minimale Wartezeit
        ueberlauf,          // von T1
        t3_ueberlauf,       // von T3
        ee_schreib_intervall;   // alle 10 Minuten EEPROM schreiben


volatile unsigned long //ref_frequenz,    // gemessene interne Taktfrequenz
        start_ereignis,     // Impulse zu Beginn der Messung
        end_ereignis,       // Impulse am Ende der Messung
        start_zeit;         // rel. Zeit: Beginn der Messung

char    t3_fil_cnt,         // T3-Filter, Anzahl Werte
        t3_fil_index,       // T3-Filter, Index auf Einzelwerte
        gps_totzeit,        // Einschaltverzoegerung abwarten
        ee_schreib_zeiger;  // nur 0 - 63 werden verwertet

unsigned long t3_end_zeit,        // Zeitpunkt der Auswertung
        end_zeit,           // Zeitpunkt der Auswertung
        t3_start_zeit,      // rel. Zeit: Beginn der Messung
        t3_fil[T3_FIL_LEN], // letzte Einzelwerte
        t3_fil_sum;         // akt. Summe der Einzelwerte

// Basis EEPROM-Routinen

unsigned char ee_read_char(int adr)
{
  while(EECR & 1<<EEWE);	// abwarten
  EEAR = adr;
  EECR |= 1<<EERE;		// lesebit setzen
  return(EEDR);
}

void ee_write_char(int adr, unsigned char data)
{
  if(data == ee_read_char(adr)) return;	// wert steht schon dort
  __disable_interrupt();		// gleich wieder freigeben
  EEAR = adr;
  EEDR = data;
  EECR |= 1<<EEMWE;		// master schreibbit setzen
  EECR |= 1<<EEWE;		// schreibbit setzen
  __enable_interrupt();		// gleich wieder freigeben
}

unsigned int ee_read_int(int adr)
{
unsigned int temp;
  temp = ee_read_char(adr);
  temp += ee_read_char(adr+1)<<8;
  return(temp);
}

void ee_write_int(int adr, unsigned int data)
{
  ee_write_char(adr, data&0xff);
  ee_write_char(adr+1, data>>8);
}

void schreibe_f_offset(unsigned int offset)     // Offset-Wert 2x schreiben
{
int schreib_adresse = (ee_schreib_zeiger & EE_ADR_MASKE) * EE_DATA_SIZE + EE_ABLAGE_OFFSET;
  ee_write_int(schreib_adresse, offset);
  ee_write_int(schreib_adresse+2, offset);
  ee_write_char(EE_SCHREIB_ZEIGER, ee_schreib_zeiger);  // auch schreib_zeiger speichern
}

unsigned int lese_f_offset(void)  // Offset-Wert lesen, wenn beide Werte gleich sind
{                                 // andernfalls vorletzten Wert zuruecklesen
int schreib_adresse, temp;
  ee_schreib_zeiger = ee_read_char(EE_SCHREIB_ZEIGER);  // letzte adresse lesen
  schreib_adresse = (ee_schreib_zeiger & EE_ADR_MASKE) * EE_DATA_SIZE + EE_ABLAGE_OFFSET;
  temp = ee_read_int(schreib_adresse);
  if(temp != ee_read_int(schreib_adresse+2)) {    // beide Werte vergleichen
    ee_schreib_zeiger--;                          // wenn ungleich, vorherigen Wert verwenden
    temp = ee_read_int(schreib_adresse-2);
  }
  return(temp);
}



// Routinen zur Ausgabe auf LCD 2x16

void warten(void)		// Warteschleife fuer LCD-Init
{
  __delay_cycles(50000);
}

void lcd_impuls(void)           // Schreibimpuls mit Mindestlaenge
{
  PORTA |= (1<<LCD_STROBE);
  __delay_cycles(10);
  PORTA &= ~(1<<LCD_STROBE);
}

void lcd_nibble(char c)         // 4-bit-Wert ausgeben: oberes nibble!
{
char temp;
  temp = PORTA & 0xf0;          // Datenleitungen an PortA.0 -> PortA.3
  temp |= c>>4;
  PORTA = temp;
  lcd_impuls();
}

void lcd_out(char z,char mode)  // Ausgabe von Daten (mode==1) oder CMDs (mode==0)
{
  if(mode) PORTA |= (1<<LCD_CMD);
  else PORTA &= ~(1<<LCD_CMD);
  lcd_nibble(z);
  z <<= 4;
  lcd_nibble(z);
  __delay_cycles(1000);
}

void lcd_cmd(char z)            // einen LCD-Befehl ausgeben
{
  lcd_out(z,0);
}

void lcd_zeichen(char z)        // ein Zeichen ausgeben
{
  lcd_out(z,1);
}

void lcd_f_string(char *s)      // Zeichenkette auf LCD
{
signed char temp;
  while((temp=*s++) != 0) {
     lcd_zeichen(temp);
  }
}

void lcd_init_nibble(char c)    // 4-bit-Wert ausgeben LCD_INIT
{
  lcd_nibble(c);
  warten();                     // genug Zeit lassen
}

void init_lcd(void)             // LCD initialisieren
{
  warten();
  lcd_init_nibble(0x30);
  lcd_init_nibble(0x30);
  lcd_init_nibble(0x30);
  lcd_init_nibble(0x20);
  lcd_cmd(0x28);                // im 4-bit mode
  lcd_cmd(0xc);
  lcd_cmd(1);
  warten();

  lcd_cmd(ZEILE1+1);            // 1.Zeile, 1.Spalte
  lcd_f_string(text1);
  lcd_cmd(ZEILE2+1);            // 2.Zeile, 1.Spalte
  lcd_f_string(text2);
}


// Routine zur Wandlung und Ausgabe eines Meßwertes

void zeige_x(double x,char zeige_periode, char stellen)    // Anzeige von Frequenz oder Periode
{
signed char i,j,dez_punkt,dimension;

  dez_punkt = 0;
  dimension = 3;
  if(x >= 0.001) {                          // 1mHz ist Untergrenze
    if(zeige_periode) {
      x = 1/x;                              // Kehrwert bilden
      dimension=5;                          // und in Sekunden als Dimension
    }
    while(x<1.0) {x*=1000.0;dimension++;}   // in den Hz-Bereich bringen
    while(x>=1000.0) {x*=0.001;dimension--;}
    while(x >= 10.0) {
      x *= 0.1;
      dez_punkt++;
    }
  } else {
    x = 0.0;                                // sonst 0.000000 Hz ausgeben
  }
  if(stellen == STELLEN) {                  // nur bei regulaerer Ausgabe
    x += 5e-7;                              // runden
    if(x >= 10.0) {			    // Ueberlauf bei Rundung
      x *= 0.1;
      dez_punkt++;
      if(dez_punkt > 2) {		    // Ueberlauf der Dimension
        dimension--;
        dez_punkt = 0;
      }
    }
  }
  for(i=0;i<stellen;i++) {
    j = (char)x;
    lcd_zeichen(j+'0');
    if(i == dez_punkt) lcd_zeichen('.');
    x -= j;
    x *= 10;
  }
  lcd_zeichen(' ');
  lcd_f_string(f_dim[dimension]);
  lcd_zeichen(' ');
}

// Routinen zur Erfassung der Eingangsimpulse

#pragma vector = TIMER1_OVF_vect
__interrupt void t1ovf_int()    // wird mit ca. 244Hz aufgerufen (16MHz/65536)
{
static char n;
  ueberlauf++;                  // Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
  __enable_interrupt();		// gleich wieder freigeben

// ab hier unkritisches Timing
  mess_dauer++;                 // ungefaehre Dauer der Messung
  n++;                          // fuer Test, ob Vorteiler notwendig
  if(n >= ZEIT_100MS) {
    n = 0;
    sync_flag = 1;              // ca. alle 100ms
  }

  if(fertig_led_cnt) {              // 'Messung fertig'
    fertig_led_cnt--;               // LED Einschaltdauer abzaehlen
    PORTD |= 1<<FERTIG_LED_BIT;     // LED einschalten
  } else {
    PORTD &= ~(1<<FERTIG_LED_BIT);  // LED ausschalten
  }

  if(gps_led_cnt) {                 //'GPS aktiv'
    gps_led_cnt--;                  // LED Einschaltdauer abzaehlen
    PORTD |= 1<<GPS_LED_BIT;        // LED einschalten
  } else {
    PORTD &= ~(1<<GPS_LED_BIT);     // LED ausschalten
  }
}

#pragma vector = TIMER1_CAPT_vect
__interrupt void t1capt_int()	// Eingangsimpulse mit genauem Zeitpunkt erfassen
{
static unsigned long count;     // Impulse per Interrupt
  count++;                      // zaehlen
  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((TIFR & 1<<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
  }
  if(TIFR & 1<<ICF1) {          // falls neuer Eingangsimpuls gekommen ist
    count++;                    // gleich hier auswerten
    TIFR = 1<<ICF1;             // und flag loeschen
  }
}

// Routinen zur Erfassung der Referenzfrequenz: 1Hz

#pragma vector = TIMER3_OVF_vect
__interrupt void t3ovf_int()    // wird mit ca. 244Hz aufgerufen (16MHz/65536)
{
  t3_ueberlauf++;               // Ueberlaeufe von T3 ergeben obere 16bit der Messzeit
  __enable_interrupt();		// gleich wieder freigeben
  t3_tout_cnt++;
  if(t3_tout_cnt >= GPS_TIMEOUT) {      // 1Hz-Referenz fehlt
    t3_tout_cnt = 0;
    t3_timeout = 1;             // und registrieren
  }
}

#pragma vector = TIMER3_CAPT_vect
__interrupt void t3capt_int()	// Eingangsimpulse mit genauem Zeitpunkt erfassen
{
    t3_zeit_low = ICR3;         // capture-reg lesen: untere 16bit
    t3_zeit_high = t3_ueberlauf;      // dazu die oberen 16bit
    if((ETIFR & 1<<TOV3) && (zeit_low < 0x8000))    // evtl. Ueberlauf T3 noch offen?
      zeit_high++;              // nur, wenn capture-int + overflow-int gleichzeitig !
    t3_fertig = 1;              // Daten fertig fuer Auswertung
    t3_tout_cnt = 0;
}

void bewerte_referenz_frequenz(void) {
unsigned long temp_frequenz;
int offset;

    if(t3_timeout) {                // 1Hz-Referenz fehlt
      t3_timeout = 0;               // auf Startwert setzen
      for(t3_fil_index = 0; t3_fil_index < T3_FIL_LEN; t3_fil_index++)
         t3_fil[t3_fil_index] = 0l;
      t3_fil_index = 0;
      t3_fil_cnt = 0;
      t3_fil_sum = 0l;
      gps_led_cnt = 0;            // GPS-Signal fehlt: LED ausschalten
      ee_schreib_intervall = 0;   // neu warten
      gps_totzeit = GPS_TOTZEIT;
    }
    if(t3_fertig) {                 // neuer Referenzimpuls eingetroffen
      t3_fertig = 0;
      t3_end_zeit = t3_zeit_high*0x10000 + t3_zeit_low;
      temp_frequenz = t3_end_zeit - t3_start_zeit;
      t3_start_zeit = t3_end_zeit;
      if(temp_frequenz < MAX_REF && temp_frequenz > MIN_REF) {    // im Bereich +/- 100ppm
        t3_fil_sum -= t3_fil[t3_fil_index]; // letzen Wert abziehen
        t3_fil_sum += temp_frequenz;        // neuen Wert addieren
        t3_fil[t3_fil_index++] = temp_frequenz;     // und ablegen
        if(t3_fil_index >= T3_FIL_LEN)
          t3_fil_index = 0;
        t3_fil_cnt++;                   // mitzaehlen, ob Filter 'gefuellt'
        if(t3_fil_cnt >= T3_FIL_LEN) {  // Filter ist 'eingeschwungen'
          t3_fil_cnt = T3_FIL_LEN;      // nicht weiter zaehlen
          ref_frequenz = ((double)t3_fil_sum) / T3_FIL_LEN; // Mittelwert verwenden
          gps_led_cnt = GPS_TIMEOUT;    // Dauerlicht durch Nachtriggern
          ee_schreib_intervall++;
          if(ee_schreib_intervall >= EE_SCHREIB_INTERVALL) {
            ee_schreib_intervall = 0;   // neu zaehlen
            offset = (int)(((ref_frequenz - (double)F_CPU) * F_OFFSET_SKAL));
            schreibe_f_offset(offset);
          }
        } else {
          gps_led_cnt = GPS_TIMEOUT/3;  // Blinken, wenn Filter noch nicht voll ist
        }
      }
#ifdef TEST_LAUF                    // nur bedingt anzeigen
      lcd_cmd(ZEILE2+4);            // 2.Zeile, 6.Spalte
      zeige_x(ref_frequenz,0,9);    // Quarzfrequenz mit 8 Stellen anzeigen
#endif

    }
}


// Hauptprogramm

__task main(void)               // fuer main() keine Register retten
{
char temp;
unsigned long mess_zeit, mess_ereignisse;

  PORTE = 0x1;                  // Pullup ICP1-Eingang
  PORTD = 0x8;                  // Pullup ICP3-Eingang
  DDRA = 0x3f;                  // fuer Lc-Anzeige
  DDRD = 0xe0;                  // fuer LEDs
  TCCR0 = 0x07;                 // T0 zaehlt Impulse des Vorteilers
  init_lcd();

  ref_frequenz = ((int)lese_f_offset()) / F_OFFSET_SKAL; // Nachkommastellen erzeugen
  ref_frequenz += F_CPU;         // startwert
  ee_schreib_zeiger++;          // fuer naechste Schreibadresse

  ACSR |= 1<<ACBG;              // analog Komparator: bandgap als AIN0 Ersatz

  messwert_vorhanden = 0;       // 1.Ergebnis unterdruecken, weil es falsch ist
  mess_status = AUSLESEN;       // ohne volle Messdauer verwerfen
  TCCR1B = 0x01;                // Timer1 ohne Vorteiler
  TIMSK = 1<<TICIE1 | 1<<TOIE1; // T1 overflow und cap. interrupts
  TCCR3B = 0x01;                // Timer3 ohne Vorteiler
  ETIMSK = 1<<TICIE3 | 1<<TOIE3;// T3 overflow und cap. interrupts
  gps_totzeit = GPS_TOTZEIT;   // ca. 5s
  __enable_interrupt();		

  for(;;) {

    if(!gps_totzeit) bewerte_referenz_frequenz();    // bessere Uebersicht in separater Funktion

    if(sync_flag) {                 // alle 0,1sek. Frequenz an T0 bewerten
      if(gps_totzeit) gps_totzeit--;
      sync_flag = 0;                // wieder loeschen
      temp = TCNT0;                 // T0 lesen
      if(TIFR & 1<<TOV0) {          // bei Ueberlauf
        TIFR = 1<<TOV0;             // flag loeschen
        temp = 255;                 // und max. Wert nehmen
      }
      TCNT0 = 0;                    // Messung am Vorteiler neu starten
      if(VORTEILER_AKTIV) {         // Status der ICP-Quelle
        if(temp < 20) {             // Vorteiler unterhalb ca. 50kHz abschalten
          PORTD &= ~(1<<VORTEILER_LED_BIT);     // LED ausschalten
          ACSR &= ~1<<ACIC;         // ICP aktivieren
          mess_status = AUSLESEN;   // aktuelle Ergebnisse lesen
          messwert_vorhanden = 0;   // aber nicht anzeigen
        }
      } else {
        if(temp > 40) {             // Vorteiler ab ca. 100kHz zuschalten
          PORTD |= 1<<VORTEILER_LED_BIT;        // LED einschalten
          ACSR |= 1<<ACIC;          // analog Komparator verwenden
          mess_status = AUSLESEN;   // aktuelle Ergebnisse lesen
          messwert_vorhanden = 0;   // aber nicht anzeigen
        }
      }
    }

    TIMSK &= ~(1<<TOIE1);           // t1-overflow interrupt sperren
    if(mess_status==MESSEN  && mess_dauer >= MESSZEIT) {
      mess_status=AUSLESEN;         // Auswertung starten
      mess_dauer = 0;               // wieder Messzeit abwarten
    }
    TIMSK |= 1<<TOIE1;              // t1-overflow interrupt freigeben

    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;         // wieder warten
      if(messwert_vorhanden) {
        fertig_led_cnt = ZEIT_100MS;  // LED-Zeit vorgeben
        frequenz = (mess_ereignisse * ref_frequenz) / mess_zeit;  // Frequenz berechnen
        if(VORTEILER_AKTIV) frequenz *= VORTEILER_FAKTOR;
        lcd_cmd(ZEILE1+5);            // 1.Zeile, 5.Spalte
        zeige_x(frequenz,0,STELLEN);        // Frequenz anzeigen
//        zeige_x(ee_schreib_zeiger,0,STELLEN);        // Frequenz anzeigen
#ifndef TEST_LAUF
        lcd_cmd(ZEILE2+5);          // 2.Zeile, 5.Spalte
        zeige_x(frequenz,1,STELLEN);        // Periodendauer anzeigen
#endif
      }
      else messwert_vorhanden = 1;  // sperre wieder aufheben
    }
  }
}

