/*
Beispielprogramm 'einfacher Frequenzzaehler' fuer ATmega88 fuer AVR-GCC
Funktionsbereich etwa 0,005Hz - 200MHz bei 20MHz Prozessortakt
ohne Vorteiler bis ca. 300kHz

Schaltung dazu siehe: FMETER48 in abgespeckter Form
http://www.mino-elektronik.de

Das ursprüngliche Programm lief auf einem AT90S2313 erzeugt mit IAR-Compiler.

Alle Angaben wie immer ohne Gewaehr !
2011-08-23
*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/eeprom.h>
#include <util/delay.h>
#include <stdlib.h>


#define F_CLOCK         20000000    // 20MHz ggf. an eff.Frequenz anpassen
//#define VORTEILER_FAKTOR   256    // 8 bit-Vorteiler
#define VORTEILER_FAKTOR    1024    // 10 bit-Vorteiler

#define STELLEN     6       // angezeigte Stellen
#define MESSZEIT    100     // max. 3 Messungen/Sek. (100 x 3,3ms)
#define LED_EIN     30      // 0,1 Sek. (30 x 3,3ms)
#define LED_BIT     5       // PORTD.5

#define LCD_CMD     1       // wenn ein Befehl ausgegeben wird
#define LCD_STROBE  2       // Schreibimpuls E fuers LCD
#define ZEILE1  0x7f        // jeweils 1 addieren fuer 1. Spalte
#define ZEILE2  0xbf        // dto.

enum status {MESSEN=0, AUSLESEN, AUSWERTEN};    // die Phasen der Messung

char text1[]={"F.:"};       // 1.Zeile der Anzeige
char text2[]={"P.:"};       // 2.Zeile der Anzeige
char f_dim[][4]={"GHz","MHz","kHz","Hz ","mHz","sek","ms ","us ","ns ","ps "};  // Dimensionen zum Messwert

float frequenz;             // float-Ergebnis

// 'volatile' zur Sicherheit
volatile unsigned char messwert_vorhanden,  // nur gueltige Messungen anzeigen
        mess_status,        // Ablaufsteuerung
        vorteiler_aktiv,    // zur Berechnung der Frequenz
        led_cnt,            // LED kurz aufleuchten lassen
        sync_flag;          // wird mit T1OV gesetzt

volatile unsigned int zeit_low,     // Timer1-Anteil
        zeit_high,          // T1 Ueberlauf-Anteil
        mess_dauer,         // minimale Wartezeit
        ueberlauf;

volatile unsigned long 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


// Routinen zur Ausgabe auf LCD 2x16

void warten(void)               // Warteschleife fuer LCD-Init
{
char n = 50;
  while(n--) _delay_ms(1);
}

void lcd_impuls(void)           // Schreibimpuls mit Mindestlaenge
{
  PORTC |= LCD_STROBE;
  _delay_us(2);
  PORTC &= ~LCD_STROBE;
}

void lcd_nibble(char c)     // 4-bit-Wert ausgeben
{
char temp;
  temp = PORTB & 0xc3;      // Datenleitungen an PortB.2 -> PortB.5
  temp |= ((c>>2) & 0x3c);
  PORTB = temp;
  lcd_impuls();
}

void lcd_out(char z,char mode)  // Ausgabe von Daten (mode==1) oder CMDs (mode==0)
{
  if(mode) PORTC |= LCD_CMD;
  else PORTC &= ~LCD_CMD;
  lcd_nibble(z);
  z <<= 4;
  lcd_nibble(z);
  _delay_ms(1);             // wir haben Zeit
}

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);
  _delay_ms(10);            // 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(float x,char zeige_periode)    // 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++;
    }
    x += 5e-6;                              // runden
    if(x >= 10.0) {							// Ueberlauf bei Rundung
      x *= 0.1; 
      dez_punkt++;
      if(dez_punkt > 2) {						// Ueberlauf der Dimension
        dimension--; 
        dez_punkt = 0;
      }  
    }
  } else {
    x = 0.0; dez_punkt=0; dimension=3;      // 0.00000 Hz ausgeben
  }
  for(i=0;i<STELLEN;i++) {
    j = 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

ISR(TIMER1_OVF_vect)            // wird mit ca. 305Hz aufgerufen (20MHz/65536)
{
static char n;
  ueberlauf++;                  // Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
  sei();                        // gleich wieder freigeben
// ab hier unkritisches Timing
  mess_dauer++;                 // ungefaehre Dauer der Messung
  if(led_cnt) {
    led_cnt--;                  // LED Einschaltdauer abzaehlen
    PORTD |= 1<<LED_BIT;        // LED einschalten
  } else {
    PORTD &= ~(1<<LED_BIT);     // LED ausschalten
  }
  n++;
  if(n>=30) {
    n = 0;
    sync_flag = 1;              // ca. alle 100ms
  }
}

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 & 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(TIFR1 & 1<<ICF1) {         // falls neuer Eingangsimpuls gekommen ist
    count++;                    // gleich hier auswerten
    TIFR1 = 1<<ICF1;            // und flag loeschen
  }
}


// Hauptprogramm


int main(void)
{
char temp;
  PORTB = 0x1;                  // Pullup ICP-Eingang
  DDRB = 0x3c;                  // fuer Lc-Anzeige 
  DDRC = 0x03;                  // fuer LCD
  DDRD = 0x20;                  // fuer LED
  PORTD = 0x1c;                 // Pullup T0-Eingang
  TCCR0B = 0x07;                // T0 zaehlt Impulse des Vorteilers
  init_lcd();

  messwert_vorhanden = 0;       // 1.Ergebnis unterdruecken, weil es falsch ist
  ACSR |= 1<<ACIC;              // analog Komparator verwenden
  mess_status = MESSEN;         // zuerst messen
  vorteiler_aktiv = 0;          // Vorteiler ist abgeschaltet
  TCCR1B = 0x01;                // Timer1 ohne Vorteiler
  TIMSK1 = 1<<ICIE1 | 1<<TOIE1; // t1-overflow und cap. interrupts
  sei();

  for(;;) {
    if(sync_flag) {             // alle 0,1sek. Frequenz an T0 bewerten
      sync_flag = 0;            // wieder loeschen
      temp = TCNT0;             // T0 lesen
      if(TIFR0 & 1<<TOV0) {     // bei Ueberlauf
        TIFR0 = 1<<TOV0;        // flag loeschen
        temp = 255;             // und max. Wert nehmen
      } 
      TCNT0 = 0;                // Messung am Vorteiler neu starten
      if(vorteiler_aktiv) {
        if(temp <= 8) {         // Vorteiler unterhalb ca. 80kHz abschalten
          ACSR |= 1<<ACIC;          // analog Komparator verwenden
          mess_status = AUSLESEN;   // aktuelle Ergebnisse lesen
          messwert_vorhanden = 0;   // aber nicht anzeigen
          vorteiler_aktiv = 0;      // wieder auf ANA-COMP umschalten   
        }
      } else {
        if(temp > 16) {             // Vorteiler ab ca. 160kHz zuschalten
          vorteiler_aktiv = 1;
          ACSR &= ~1<<ACIC;         // ICP aktivieren
          mess_status = AUSLESEN;   // aktuelle Ergebnisse lesen
          messwert_vorhanden = 0;   // aber nicht anzeigen
        }  
      }
    }
    
    TIMSK1 &= ~(1<<TOIE1);          // t1-overflow interrupt sperren
    if(mess_status==MESSEN  && mess_dauer >= MESSZEIT) {
      mess_status=AUSLESEN;         // Auswertung starten
      mess_dauer = 0;               // wieder Messzeit abwarten
    }
    TIMSK1 |= 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) {
        led_cnt = LED_EIN;          // LED-Zeit vorgeben
        frequenz = ((float)mess_ereignisse * F_CLOCK) / mess_zeit;  // Frequenz berechnen
        if(vorteiler_aktiv) frequenz *= VORTEILER_FAKTOR;
        lcd_cmd(ZEILE1+5);          // 1.Zeile, 5.Spalte
        zeige_x(frequenz,0);        // Frequenz anzeigen
        lcd_cmd(ZEILE2+5);          // 2.Zeile, 5.Spalte
        zeige_x(frequenz,1);        // Periodendauer anzeigen
      }
      else messwert_vorhanden = 1;  // sperre wieder aufheben
    }
  }
  return(999);
}

