/*
Beispielprogramm 'einfacher Drehzahlmesser' fuer ATmega88
entwickelt mit AVR-Studio 4.18 
Funktionsbereich etwa 0,3 rpm - 15 Mrpm bei 20MHz Prozessortakt
entsprechend 0,005Hz - 250kHz Eingangsfrequenz

Schaltung dazu siehe: FMETER48 in abgespeckter Form
http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !
2011-09-07

2013-08-11: automatischer Abgleich mit 1Hz Referenzsignal
oder manueller Abgleich durch ext. Programmierung des EEPROM
an Adresse EE_ADR_OFFSET.
*/


#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 BIT(x)  (1<<x)
#define F_CLOCK         20000000    // 20MHz ggf. an eff.Frequenz anpassen
#define EE_ADR_OFFSET   2   // Platz für Korrekturwert
#define KAL_TASTE       2   // PORTC-2

#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[]={"D:"};        // 1.Zeile der Anzeige
char text2[]={"T:"};        // 2.Zeile der Anzeige
char f_dim[][5]={"Mrpm","krpm","rpm ","mrpm","sek ","ms  ","us  "}; // Dimensionen zum Messwert

float frequenz;             // float-Ergebnis
float f_clock;              // korrigierter Referenzwert

// 'volatile' zur Sicherheit
volatile unsigned char messwert_vorhanden,  // nur gueltige Messungen anzeigen
        mess_status,        // Ablaufsteuerung
        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
{
volatile 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; 
  if(x >= 0.001) {                          // 1mHz ist Untergrenze
    if(!zeige_periode) {                    // Frequenz
      x *= 60.0;                            // auf Drehzahl skalieren
      dimension=2;                          // und in Sekunden als Dimension
    } else {                                // Zeit fuer eine Umdrehung
      x = 1/x;                              // Kehrwert bilden
      dimension=4;                          // 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=2;      // 0.00000 rpm 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)
{
  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
  }
}

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
  }
}

uint8_t ee_read_byte(uint16_t adr)
{
  while(EECR & BIT(EEPE));      // abwarten
  EEAR = adr;
  EECR |= BIT(EERE);            // lesebit setzen
  return(EEDR);
}

void ee_write_byte(uint8_t data, uint16_t adr)
{
  if(data != ee_read_byte(adr)) {
    cli();
    EEAR = adr;
    EEDR = data;
    EECR  = BIT(EEMPE);         // master schreibbit setzen, EEPMx löschen
    EECR |= BIT(EEPE);          // schreibbit setzen
    sei();
  }
}

void ee_write_word(int16_t data, uint16_t adr)
{
  ee_write_byte((uint16_t)(data)%256, adr);
  ee_write_byte((uint16_t)(data)/256, adr+1);
}

int16_t ee_read_word(uint16_t adr)
{
int16_t temp;
  temp = ee_read_byte(adr);
  temp += ee_read_byte(adr+1) * 256;
  return(temp);
}



/* Test auf gedrückte Taste an PortC.2 und externes Referenzsignal von 1Hz.
Wenn beides vorliegt, wird die Referenzfrequenz 'f_clock' um den 
Korrekturwert 'f_offset' angepasst. 'f_offset' wird im EEPROM gespeichert
und nach einem Reset erneut geladen und mit F_CLOCK verrechnet.
*/

void teste_kalibrierung(void)
{
static uint8_t temp_alt;                            // zur Ermittelung der aktiven Flanke
uint8_t temp;                                       
int16_t f_offset;                                   // Abweichung zu F_CLOCK
  temp = (PINC ^ BIT(KAL_TASTE)) & BIT(KAL_TASTE);  // Zustand des Tasters lesen
  if((temp ^ temp_alt) & temp) {                    // Taste wurde gedrückt
    if(frequenz > 0.999 && frequenz < 1.001) {    // Frequenzbereich eingrenzen +/- 1000ppm
      f_offset = mess_zeit - F_CLOCK;               // Differenz ist-soll ermitteln
      f_clock = (float) (F_CLOCK + f_offset);       // korrigierter Takt
      lcd_cmd(ZEILE1+5);                            // 1.Zeile, 5.Spalte
      lcd_f_string("Kal. FERTIG");
      ee_write_word(f_offset, EE_ADR_OFFSET);       // Korrekturwert speichern
      messwert_vorhanden = 0;                       // nächte Messung verwerfen
    }
  }
  temp_alt = temp;                                  // fürs nächste Mal merken
}

// Hauptprogramm
int main(void)
{
  CLKPR = 0x80; CLKPR = 0x0;        // CKDIV auf 0; voller CPU-Takt
  PORTB = 0x3;                      // Pullup ungenutzte Pins
  PORTC = 0x3c;                     // dto.
  PORTD = 0x1f;                     // dto.
  DDRB = 0x3c;                      // fuer LC-Anzeige Datenleitungen 
  DDRC = 0x03;                      // fuer LCD CMD+Strobe
  DDRD = 0x20;                      // fuer LED
  init_lcd();

  messwert_vorhanden = 0;           // 1.Ergebnis unterdruecken, weil es falsch ist
  ACSR |= 1<<ACIC;                  // analog Komparator verwenden
  mess_status = MESSEN;             // zuerst messen
  TCCR1B = 0x01;                    // Timer1 ohne Vorteiler
  TIMSK1 = 1<<ICIE1 | 1<<TOIE1;     // t1-overflow und cap. interrupts
  f_clock = (float) (F_CLOCK + ee_read_word(EE_ADR_OFFSET));    // eff. Referenzfrequenz
  sei();

  for(;;) {
    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
        lcd_cmd(ZEILE1+5);          // 1.Zeile, 5.Spalte
        zeige_x(frequenz,0);        // Drehzahl anzeigen
        lcd_cmd(ZEILE2+5);          // 2.Zeile, 5.Spalte
        zeige_x(frequenz,1);        // Drehzahl anzeigen
        teste_kalibrierung();       // nach gültiger Messung ggf. neu abgleichen
      }
      else messwert_vorhanden = 1;  // sperre wieder aufheben
    }
  }
  return(815);
}

