/*
Beispielprogramm 'Lichtschranke' fuer ATmega88 fuer AVR-GCC.
Zwischen zwei Eingangsimpulsen wird die Zeit gemessen und daraus die
Geschwindigkeit in m/s errechnet. Dabei wird von einer Messstrecke von
0,1m ausgegangen; der Wert kann angepasst werden: SENSORABSTAND.

Das Startsignal wird an INT0 erwartet und das Stoppsignal an INT1.
Ohne weitere Beschaltung werden jeweils die negativen Flanken verwendet.
Wird ein Signal an beide Eingänge angelegt, wird mit der Flanke an INT0 
die Messung gestartet und danach der Eingang INT1 freigegeben, der erst 
die nachfolgende Flanke wertet. So kann die Periodendauer eines Signals 
gemessen werden.

Mitttels Schalter an PC.3 (fuer INT0) oder PC.4 (fuer INT1) wird der 
betreffende Eingang auf positive Flanke umgeschaltet. Dadurch lassen sich
die Pulsweiten negativer oder positiver Impulse messen. 

Funktionsbereich etwa 10µs - 200s bei 20MHz Prozessortakt.
Die Auflösung betraegt 50ns, der maximale Fehler ca. 1µs, wenn die Auswertung
einer Flanke durch den T1-OVFL-Int verzoegert wird.
Das Programm schafft max. 100 Messungen/s und kann ggf. "gebremst" werden.

Schaltung dazu siehe: FMETER48 in abgespeckter Form
http://www.mino-elektronik.de

Alle Angaben wie immer ohne Gewaehr !
2011-09-28
*/


#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 STELLEN     	5       // angezeigte Stellen
#define SENSORABSTAND	0.1		// Weg in Meter

#define STARTFLANKE		2		// PC.2
#define STOPPFLANKE		3		// PC.3

#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, INT0_AKTIV, INT1_AKTIV, AUSWERTEN};		// die Phasen der Messung

char text1[]={"t: "};    		// 1.Zeile der Anzeige
char text2[]={"v: "};       	// 2.Zeile der Anzeige
char f_dim[][4]={"sek","ms ","us ","ns","km/s","m/s","mm/s"};	// Dimensionen zum Messwert


// 'volatile' zur Sicherheit
volatile unsigned char mess_status, // Ablaufsteuerung
		tifr1_start, 				// ggf. T1-Ueberlauf beim Startzeitpunkt
		tifr1_stopp;				// ggf. T1-Ueberlauf beim Stoppzeitpunkt

volatile unsigned int ueberlauf,	// T1-Ueberlauf
		start_l, 					// Wert von T1
		start_h, 					// Wert des Ueberlaufzaehlers
		stopp_l, 					// Wert von T1
		stopp_h;					// Wert des Ueberlaufzaehlers

volatile unsigned long start_zeit,	// rel. Zeit: Beginn der Messung
        stopp_zeit,					// rel. Zeit: Ende der Messung
		mess_zeit;           		// Messzeit


// 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_geschwindigkeit)    // Anzeige von t oder v
{
signed char i,j,dez_punkt,dimension;

  dez_punkt = 0; 
  dimension = 0;
  if(x >= 0.00001) {                        // 10us ist Untergrenze
    if(zeige_geschwindigkeit) {
      dimension=5;                          // und in m/s als Dimension
    }
    while(x<1.0) {x*=1000.0;dimension++;}   // auf 1-9.9999 normieren
    while(x>=1000.0) {x*=0.001;dimension--;}
    while(x >= 10.0) {
      x *= 0.1;
      dez_punkt++;
    }
    x += 5e-5;                              // runden
    if(x >= 10.0) {
	  dez_punkt++;
	  x *= 0.1; 
	  if(dez_punkt > 2) {
     	 dimension--; 
		 dez_punkt = 0;						// Ueberlauf bei Rundung
	  }
    }
  } else {
    x = 0.0; dez_punkt=0; dimension=3;      // 0.00000 ns 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
}


ISR(INT0_vect)           		// Start-Zeitpunkt erfassen
{
    start_l = TCNT1;			// T1 lesen: untere 16bit
    start_h = ueberlauf;		// dazu die oberen 16bit
    tifr1_start = TIFR1;		// overflow-status festhalten
	EIFR = 1 << INT1;			// flag INT1 loeschen
	EIMSK = 1 << INT1;			// und INT1 freigeben
	mess_status = INT1_AKTIV;	// auf INT1 warten
}


ISR(INT1_vect)        	   		// Stopp-Zeitpunkt erfassen
{
    stopp_l = TCNT1;			// T1 lesen: untere 16bit
    stopp_h = ueberlauf;		// dazu die oberen 16bit
    tifr1_stopp = TIFR1;		// overflow-status festhalten
	EIFR = 1 << INT1;			// flag loeschen
	EIMSK = 0;					// INT0 und INT1 sperren
	mess_status = AUSWERTEN;
}


// Hauptprogramm


int main(void)
{
float zeit;
char n;
char flanken_wahl=0;
  PORTB = 0x3;                  // Pullup fuer Eingang
  PORTC = 0xfc;					// auch fuer PortC
  PORTD = 0xff;                 // und PortD
  DDRB = 0x3c;                  // fuer Lc-Anzeige 
  DDRC = 0x03;                  // fuer LCD
  TCCR0B = 0x07;                // T0 zaehlt Impulse des Vorteilers
  init_lcd();

  mess_status = MESSEN;         // zuerst messen
  TCCR1B = 0x01;                // Timer1 ohne Vorteiler
  TIMSK1 =  1<<TOIE1; 			// t1-overflow interrupt
  sei();

  for(;;) {
	if(mess_status == MESSEN) {			// Schalterstellung fuer Flanken auswerten
		flanken_wahl = PINC & (1<<STARTFLANKE | 1<<STOPPFLANKE);
		EICRA = 1<<ISC11 | 1<<ISC01;	// default neg. Flanken von INT0 und INT1
		if((flanken_wahl & (1 << STARTFLANKE)) == 0)
			EICRA |= (1 << ISC00);		// +Flanke an INT0 einstellen
		if((flanken_wahl & (1 << STOPPFLANKE)) == 0)
			EICRA |= (1 << ISC10);		// +Flanke an INT1 einstellen
		EIFR = 1 << INT0;				// flag INT0 loeschen
		EIMSK = 1 << INT0;				// INT0 freigeben
		mess_status = INT0_AKTIV;		// auf INT0 warten
	}

	if(mess_status == INT0_AKTIV) {		// auf START warten und Schalteraenderungen beachten
		if(flanken_wahl != (PINC & (1<<STARTFLANKE | 1<<STOPPFLANKE))) {    // veraendert!
			EIMSK = 0;					// INT0 und INT1 stoppen
			mess_status = MESSEN;		// und flanken_wahl neu bewerten
		}
	}

    if(mess_status==AUSWERTEN) {		// 32-Bit Werte ermitteln und Ueberlauf ggf. anpassen
    	if((tifr1_start & 1<<TOV1) && (start_l < 0x8000))  	// evtl. Ueberlauf T1 noch offen?
      		start_h++;										// dann Ueberlauf beruecksichtigen
		start_zeit = start_h * 0x10000 + start_l;
    	if((tifr1_stopp & 1<<TOV1) && (stopp_l < 0x8000)) 	// evtl. Ueberlauf T1 noch offen?
      		stopp_h++;										// dann Ueberlauf beruecksichtigen
		stopp_zeit = stopp_h * 0x10000 + stopp_l;
		
     	mess_zeit = stopp_zeit - start_zeit;         		// Zeit-Differenz bilden
        zeit = ((float)mess_zeit / F_CLOCK);				// Zeit in Sekunden

        lcd_cmd(ZEILE1+6);         		// 1.Zeile, 6.Spalte
        zeige_x(zeit,0);        		// abs. Messzeit anzeigen
        lcd_cmd(ZEILE2+6);          	// 2.Zeile, 6.Spalte
        zeige_x(SENSORABSTAND/zeit,1);  // Geschwindigkeit anzeigen
		mess_status = MESSEN;
      n=2; while(n--) warten();	    // falls Messfolge zu schnell
    }
  }
  return(999);
}

