/*
Beispielprogramm '4-Kanal Drehzahlmessung' fuer ATmega88
entwickelt mit AVR-Studio 4.18 
Funktionsbereich etwa 1 U/min - 999999 U/min bei 20MHz Prozessortakt
entsprechend 0,016Hz - 16kHz Eingangsfrequenz

Als Signaleingänge dienen PC.2 - PC.5, deren Pullups aktiviert sind.
Hallsensoren (z. B. TLE4905) können somit direkt angeschlossen werden.
Schaltung dazu siehe: FMETER48 in abgespeckter Form
http://www.mino-elektronik.de

Alle Angaben ohne Gewaehr !

Letzte Änderungen: 2017-07-20
*/

#define F_CPU   20000000UL      // _delay() sind 2x schneller als erwartet

#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 MAX_KANAL       4
#define TYP_STELLEN     5       // angezeigte Stellen
#define MIN_MESSZEIT    100     // max. 3 Messungen/Sek. (100 x 3,3ms)
#define TIMEOUT     (330 * 60)  // 60 Sekunden

#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.

//               1234567890123456
char text1[] = {" 1.Kanal 2.Kanal"};    // 1.Zeile der Anzeige
char text2[] = {" 3.Kanal 4.Kanal"};    // 2.Zeile der Anzeige
char kein_wert_str[] = {"   -.-  "};    // kein gueltiges Ergebnis 

char anzeige_position[MAX_KANAL] = {ZEILE1+2, ZEILE1+10, ZEILE2+2, ZEILE2+10};


volatile unsigned int ueberlauf;    // von T1

volatile unsigned int kanal_impulse[MAX_KANAL],         // akt. Zaehlerstand
                      letzte_kanal_impulse[MAX_KANAL],  // Zaehlerstand letzte Messung
                      kanal_mess_dauer[MAX_KANAL];      // in 3,3ms Einheiten
volatile unsigned long kanal_zeit[MAX_KANAL],           // Zeitpunkt Eingangsimpuls
                      letzte_kanal_zeit[MAX_KANAL];     // End-Zeitpunkt letzte 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)
{
char n = 10;
  if(mode) PORTC |= LCD_CMD;
  else PORTC &= ~LCD_CMD;
  lcd_nibble(z);
  z <<= 4;
  lcd_nibble(z);
  while(n--) _delay_us(20);
}

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_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_string(text1);
  lcd_cmd(ZEILE2+1);        // 2.Zeile, 1.Spalte
  lcd_string(text2);
}


// Routine zur Wandlung und Ausgabe eines Meßwertes: DREHZAHL

void zeige_drehzahl(float x, signed char kanal) // Anzeige von Frequenz oder Periode
{
signed char stelle,ziffer,dez_punkt=0, max_stellen = TYP_STELLEN;

  lcd_cmd(anzeige_position[kanal]); // richtige Position
  if(x >= 10000 && x < 100000) {
    dez_punkt=55;                   // 10000 - 99999 ohne dez_punkt
    lcd_zeichen(' ');               // und nach rechts verschieben
  }  
  if(x >= 100000) {                 // 6-stellige Anzeige notwendig
    dez_punkt=55;                   // 100000 - 999999 ohne dez_punkt
    max_stellen++;                  // und eine '0' anhaengen
  }  
// moeglich, wenn nur ein Kanal aktiv ist
  if(x >= 1000000) {                // 7-stellige Anzeige notwendig
    dez_punkt=55;                   // 1000000 - 9999999 ohne dez_punkt
    max_stellen++;                  // und noch eine '0' anhaengen
  }  
  if(x >= 1.0) {                    // 1 U/min ist Untergrenze
    while(x >= 10.0) {  
      x *= 0.1;
      dez_punkt++;
    }
    x += 5e-5;                      // 5-stellig runden
    if(x >= 10.0) {
      x *= 0.1; dez_punkt++;        // Ueberlauf bei Rundung
    }
    for(stelle=0;stelle<TYP_STELLEN;stelle++) {
      ziffer = x;
      lcd_zeichen(ziffer+'0');
      if(stelle == dez_punkt) lcd_zeichen('.');
      x -= ziffer;
     x *= 10;
    }
    while(stelle++ < max_stellen)
      lcd_zeichen('0');             // Restnullen auffuellen
    lcd_zeichen(' ');               // ueberzaehlige Zeichen loeschen
  } else {
    lcd_string(kein_wert_str);
  }
}

// Routinen zur Erfassung der Eingangsimpulse

ISR (PCINT1_vect)                       // fuer PCINT10...13
{
static char port_alt;
char port_neu, aenderung;
unsigned int temp_ueberlauf, T1_temp;
unsigned long zeitpunkt;

  port_neu = ~PINC;                     // negative Flanken vonPC.2 - PC.5
  port_neu &= (1<<PCINT10 | 1<<PCINT11 | 1<<PCINT12 | 1<<PCINT13);

  aenderung = (port_alt ^ port_neu) & port_neu; // aktive flanken ermitteln
  port_alt = port_neu;                  // neu zu alt
  if(aenderung) {
    T1_temp = TCNT1;                    // Timer1 lesen: untere 16bit
    temp_ueberlauf = ueberlauf;         // dazu die oberen 16bit
    if((TIFR1 & 1<<TOV1) && (T1_temp < 0x8000))     // evtl. Ueberlauf T1 noch offen?
      temp_ueberlauf++;                 // nur, wenn PCINT + overflow-int gleichzeitig !
    zeitpunkt = temp_ueberlauf * 0x10000 + T1_temp;

    if(aenderung & (1<<PINC2)) {        // PC.2
      kanal_impulse[0]++;
      kanal_zeit[0] = zeitpunkt;
    }
    if(aenderung & (1<<PINC3)) {        // PC.3
      kanal_impulse[1]++;
      kanal_zeit[1] = zeitpunkt;
    }
    if(aenderung & (1<<PINC4)) {        // PC.4
      kanal_impulse[2]++;
      kanal_zeit[2] = zeitpunkt;
    }
    if(aenderung & (1<<PINC5)) {        // PC.5
      kanal_impulse[3]++;
      kanal_zeit[3] = zeitpunkt;
    }
  }
}
    

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
  kanal_mess_dauer[0]++;
  kanal_mess_dauer[1]++;
  kanal_mess_dauer[2]++;
  kanal_mess_dauer[3]++;
}


// Hauptprogramm

int main(void)
{
signed char auswerte_kanal;         // Index auf akt. Kanal
char gueltige_messung[MAX_KANAL];   // 1, wenn sinnvoller Wert
unsigned int auswerte_impulse,      // eff. Wert: akt. Wert - alter Wert
             temp_auswerte_impulse, // abs. Zaehlerstand
             messzeit;              // Dauer der aktuellen Messung in 3,3ms
unsigned long auswerte_zeit,        // eff. Wert: akt. Wert - alter Wert
             temp_auswerte_zeit;    // abs. Zeitpunkt des letzten Impulses
float drehzahl;                     // Ergebnis fuer die Anzeige
  CLKPR = 0x80; CLKPR = 0x0;        // Vorteiler abschalten
  PORTC = 0x3c;                     // Pullups fuer die Signaleingaenge PC.2 - PC.5
  PORTB = 0x3;                      // Pullup ungenutzte Pins
  PORTD = 0xff;                     // dto.
  DDRB = 0x3c;                      // fuer LC-Anzeige Datenleitungen 
  DDRC = 0x03;                      // fuer LCD CMD+Strobe
  init_lcd();

// beim Start Kanalzuordnung kurze Zeit angezeigt lassen
  for(messzeit = 0; messzeit < 100; messzeit++) 
    warten();

  for(auswerte_kanal = 0; auswerte_kanal < MAX_KANAL; auswerte_kanal++) {
    gueltige_messung[auswerte_kanal] = 0;   // 1. Ergebnis unterdruecken
    zeige_drehzahl(0,auswerte_kanal);       // und -.- anzeigen
  }

  TCCR1B = 1<<CS10;                         // Timer1 ohne Vorteiler
  TIMSK1 = 1<<TOIE1;                        // t1-overflow interrupt
  PCMSK1 = 1<<PCINT10 | 1<<PCINT11 | 1<<PCINT12 | 1<<PCINT13;// PC.2 - PC.5 zulassen
  PCICR  = 1<<PCIE1;                        // PCINTs aktivieren
  sei();                                    // und los

  for(;;) {
    auswerte_kanal++;
    if(auswerte_kanal >= MAX_KANAL) auswerte_kanal = 0;
    
    TIMSK1 &= ~(1<<TOIE1);                          // t1 interrupts sperren
    messzeit = kanal_mess_dauer[auswerte_kanal];    // aktuelle Werte nur lesen
    temp_auswerte_impulse = kanal_impulse[auswerte_kanal];
    temp_auswerte_zeit = kanal_zeit[auswerte_kanal];
    TIMSK1 |= (1<<TOIE1);                           // t1 interrupts freigeben
    
    if(messzeit >= MIN_MESSZEIT) {                  // effektive Werte als Differenzwert: neu - alt
      auswerte_impulse = temp_auswerte_impulse - letzte_kanal_impulse[auswerte_kanal];
      auswerte_zeit = temp_auswerte_zeit - letzte_kanal_zeit[auswerte_kanal];
      if(auswerte_impulse >= 1 &&                   // mindestens ein Eingangsimpuls und
         auswerte_zeit >= F_CLOCK/4) {              // Mindest-Intervall einhalten
        if(gueltige_messung[auswerte_kanal]) {      // nur sinnvolle Werte auswerten 
          drehzahl = ((float)auswerte_impulse * (float)F_CLOCK) / (float)auswerte_zeit;
          drehzahl *= 60.0;                         // Skalierung: Frequenz -> Drehzahl          
          zeige_drehzahl(drehzahl, auswerte_kanal); // und anzeigen
        }
        gueltige_messung[auswerte_kanal] = 1;       // naechste messung auswerten
        letzte_kanal_impulse[auswerte_kanal] = temp_auswerte_impulse;
        letzte_kanal_zeit[auswerte_kanal] = temp_auswerte_zeit;
        TIMSK1 &= ~(1<<TOIE1);                      // t1 interrupts sperren
        kanal_mess_dauer[auswerte_kanal] = 0;;
        TIMSK1 |= (1<<TOIE1);                       // t1 interrupts freigeben
      } else {
        if(messzeit >= TIMEOUT) {
          zeige_drehzahl(0, auswerte_kanal);        // -.- anzeigen 
          gueltige_messung[auswerte_kanal] = 0;     // naechste messung nicht auswerten
          TIMSK1 &= ~(1<<TOIE1);                    // t1 interrupts sperren
          kanal_mess_dauer[auswerte_kanal] = 0;;
          TIMSK1 |= (1<<TOIE1);                     // t1 interrupts freigeben
        }
      }
    }
  }
  return(007);
}


