    /* vim: set sw=8 ts=8 si : */
    /*************************************************************************
    Title:    linuxfocus frequency counter
    Author:   guido socher <guido(at)linuxfocus.org>
    Copyright: GPL
    **************************************************************************/

    #include <io.h>
    #include <progmem.h>
    #include <interrupt.h>
    #include <string-avr.h>
    #include <stdlib.h>
    #include <sig-avr.h>
    #include "lcd.h"	/* these are the prototypes and defines for lcd.c */
    #include "avr-util.h"
    #include "uart.h"

    /* Input Push Buttons */
    #define BUTTON0 PIND6
    #define BUTTON0DDR DDRD
    #define BUTTON0PIN PIND
    #define BUTTON1 PIND3
    #define BUTTON1DDR DDRD
    #define BUTTON1PIN PIND
    /* reset line to 74hc390 */
    #define CCLEAR PC4
    #define CCLEARDDR DDRC
    #define CCLEARPORT PORTC
    /* LED0=green */
    #define LED0 PC5
    #define LED0DDR DDRC
    #define LED0PORT PORTC
    static char uart_outbuf[UART_RX_BUFFER_SIZE+1];
    static unsigned char clock64Hz;
    static unsigned char hflag;
    static unsigned char mode; /* 0=cout up, 1=freq 1Hz resultion, 2=freq 64Hz resolution */
    static unsigned char dmode; /* add 2 zeros behind the display output */
    static unsigned char clocklimit;
    /* our counter is 16bit wide but we multiply in the fast mode
    * with 64 (gate frequency) therefore we need a 24 bit variable */
    static unsigned char counterval[3]; /* 24 bit */
    static unsigned char counterval_cp[3]; /* 24 bit */
    static unsigned char overflowflag;

    #define reply_ok() uart_sendstr_P("ok\n")
    #define reply_err() uart_sendstr_P("err\n")

    /* devide the 24 bit integer unsigned char i[3] by 10 and return
    * the value in i[3] */
    void divby10(unsigned char *i,unsigned char *rest){
            unsigned char loop;
            unsigned int tmp; // 16 bit
            loop=3;
            /* start with highest byte */
            *rest=0;
            while(loop>0){
                    tmp=*rest * 256 + i[loop-1];
                    i[loop-1]=tmp / 10;
                    *rest=tmp % 10;
                    loop--;
            }
    }

    /* convert a 3 time 8 bit interger (24 bit=i[3]) to decimal ascii and
    * print it using the printfun. */
    void longtoascii(void (*printfun)(char),unsigned char *i,unsigned char dodot)
    {
            char str[14];
            unsigned char pos=14;
    	unsigned char dot=0;
    	if (dmode==1){
    		/* take the 2 zeros that will be added into account */
    		dot=2;
    	}
            do{
    		/* write a  "." every 1000 and 1000000 to make the
    		* number more readable. 1234 will be 1.234 */
    		if (dot==3 && dodot==1){
    			dot=0;
    			str[pos-1]='.';
    			pos--;
    		}else{
    			divby10(i,&str[pos-1]);
    			str[pos-1]+= '0';
    			pos--;
    			dot++;
    		}
            }while((i[0]|i[1]|i[2]) && pos > 0);
            /* now reverse the string and print */
            while(pos<14){
                    (*printfun)(str[pos]);
                    pos++;
            }
    	if (dmode==1){
    		(*printfun)('0');
    		(*printfun)('0');
    	}
    }

    /* setup the T1 (PD5) counter to get ready for counting.
    * You must call once sei() in the main program */
    void cnt_readyforstart(void)
    {
            /* add: set nand gates */
            /* timer should generate interrupt on compate match */
            sbi(TIMSK,TOIE1);
            /* write high byte first */
            outp(0x0,TCNT1H); /* set counter to zero*/
            outp(0x0,TCNT1L); /* set counter to zero*/
            /* write high byte first */
            outp(0xFF,OCR1H); /* set internal compare register to max */
            outp(0xFF,OCR1L); /* set internal compare register to max */
            outp(0,TCCR1A); /* Timer/Counter1 disconnected from output pin OC1 */
            outp(0,TCCR1B); /* off */
    }
    /* write info about mode to display */
    void displaymode(void)
    {
    	lcd_gotoxy(0,1);
    	if (mode==0){
    		lcd_puts_P("gate:cont   ");
    	}
    	if (mode==1){
    		lcd_puts_P("gate:1sec   ");
    	}
    	if (mode==2){
    		lcd_puts_P("gate:1/64sec");
    	}
    }

    /* the following function will be called when the counter
    * is out of range. This is a 16bit counter! */
    SIGNAL(SIG_OVERFLOW1) {
            /* add: set nand gates */
            overflowflag=1;
            outp(0x0,TCNT1H); /* set counter to zero*/
            outp(0x0,TCNT1L); /* set counter to zero*/
            outp(0,TCCR1B); // stop counter
    }

    /* we can not use large functions in
    * an interrupt handle. Therefore the interrupt routine reads only
    * the counter values and the rest is done later via this function
    * which is called from the main program */
    void handlecounterresult(void){
    	/* display the result value e.g c:123 */
    	uart_sendchar('c');
    	uart_sendchar(':');
    	lcd_clrscr();
    	if (overflowflag){
    		/* overflow */
    		uart_sendchar('-');
    		uart_sendchar('1');
    		lcd_puts_P("overflow");
    	}else{
    		if (mode==2) {
    			/* mult by 64 */
    			counterval[2]=counterval[1]>>2;
    			counterval[1]=((counterval[0]>>2)|(counterval[1]<<6));
    			counterval[0]=counterval[0]<<6;
    		}
    		/* make a copy of the counter value */
    		counterval_cp[2]=counterval[2];
    		counterval_cp[1]=counterval[1];
    		counterval_cp[0]=counterval[0];
    		/* longtoascii sets counterval to zero */
    		longtoascii(lcd_putc,counterval_cp,1);
    		longtoascii(uart_sendchar,counterval,0);
    		if (mode){
    			/* not for cont count */
    			lcd_puts_P(" Hz ");
    		}
    		if (dmode==1){
    			/* indicate that the dispaly has 2 additional zeros */
    			lcd_puts_P("x100");
    		}
    	}
    	uart_sendchar('\n');
    	displaymode();
    	clock64Hz=0;
    }

    /* this function will be called in 64Hz intervals if
    * a 4.194304 MHz crystal is used */
    SIGNAL(SIG_OVERFLOW0)
    {
    	outp(0,TCNT0); /* clear 64Hz timer value */
    	if (clock64Hz == clocklimit){
    		/* read counter */
    		/* stop 64Hz timer */
    		outp(0,TCCR0);
    		//if (mode==1){
    			/* use this to calibrate the counter result
    			* for 1 sec gate */
    		//	delay_us(700);
    		//}else{
    			/* calibrate for 1/64 sec gate: */
    		//	delay_us(10);
    		//}
    		outp(0,TCCR1B); // stop counter
    		counterval[0]=inp(TCNT1L); /* read low first !! */
    		counterval[1]=inp(TCNT1H);
    		hflag=1; /* call the handlecounterresult function */
    		sbi(LED0PORT,LED0);
    	}else{
    		if (clock64Hz==0){
    			overflowflag=0;
    			cbi(LED0PORT,LED0);
    			/* start counter */
    			/* write high byte first */
    			outp(0x0,TCNT1H); /* set counter to zero*/
    			outp(0x0,TCNT1L); /* set counter to zero*/
    			outp(7,TCCR1B); /* enable 16bin counter from T1 pin */
    		}
    	}
    	clock64Hz++;
    }
    void changemode(void)
    {
    	outp(0x0,TCNT1H); /* set counter to zero*/
    	outp(0x0,TCNT1L); /* set counter to zero*/
    	hflag=0;
    	clock64Hz=0;
    	overflowflag=0;
    	/* clear the 74hc390 divider, clear = 5V */
    	sbi(CCLEARPORT,CCLEAR);
    	counterval[0]=0;
    	counterval[1]=0;
    	counterval[2]=0;
    	outp(0,TCCR1B); // stop counter
    	/* LED off */
    	sbi(LED0PORT,LED0);
    	if (mode==0){
    		/* continious up counting */
    		outp(0,TCCR0); /* stop 64Hz timer */
    		outp(0,TCNT0); /* clear timer value */
    		outp(7,TCCR1B); /* enable counter from T1 pin */
    		/* LED on */
    		cbi(LED0PORT,LED0);
    	}
    	if (mode==1){
    		/* one sec gate */
    		clocklimit=64;
    		/* start 64Hz timer, clock/256 overflow0 every 256 */
    		outp(4,TCCR0);
    	}
    	if (mode==2){
    		clocklimit=1;
    		/* start 64Hz timer, clock/256 overflow0 every 256 */
    		outp(4,TCCR0);
    	}
    	displaymode();
    	/* enable counting for the 74hc390 divider, clear=0V */
    	cbi(CCLEARPORT,CCLEAR);
    }

    int main(void)
    {
    	unsigned char i,rxresult,status;
    	unsigned int ignorebutton,pausetodisplay;
    	char cmd;
    	char *val;

    	ignorebutton=0;
    	pausetodisplay=0;
    	/* initialize display, cursor off */
    	lcd_init(LCD_DISP_ON);
    	/* initialize LED as output */
    	sbi(LED0DDR,LED0);
    	/* initialize clear line to 74hc390 as output */
    	sbi(CCLEARDDR,CCLEAR);
    	/* clear line off */
    	cbi(CCLEARPORT,CCLEAR);
    	/* button as digital input */
    	cbi(BUTTON0DDR,BUTTON0);
    	cbi(BUTTON1DDR,BUTTON1);
    	/* initialize rs232 */
    	uart_init();

    	/* setup the counter */
            cnt_readyforstart();

    	/* setup the 64Hz timer */
    	sbi(TIMSK,TOIE0); /* enable T0 overflow0 interrupt */
    	outp(0,TCNT0); /* start value */

    	mode=1;
    	dmode=0;
    	changemode();

    	sei(); /* enable interrupt */
    	lcd_clrscr();

    	while(1){
    		if (hflag==1){
    			handlecounterresult();
    			hflag=2;
    			pausetodisplay=65000; // wait a bit for the LCD to show result
    		}
    		if (hflag==2 && pausetodisplay==0){
    			/* prevent flickering of LCD display */
    			hflag=0;
    			/* start 64Hz timer */
    			outp(4,TCCR0);
    		}
    		/* for countup mode display result in regular intervalls */
    		if (mode==0 && pausetodisplay==0){
    			pausetodisplay=65000;
    			counterval[0]=inp(TCNT1L); /* read low first !! */
    			counterval[1]=inp(TCNT1H);
    			handlecounterresult();
    		}

    		rxresult=uart_getrxbufnl(uart_outbuf);
    		if (ignorebutton==0){
    			/* button 0 is the mode button */
    			if (bit_is_clear(BUTTON0PIN,BUTTON0)){
    				mode++;
    				if (mode>2) mode=0;
    				changemode();
    				ignorebutton=55000; // debounce, ignore button for a while
    			}
    			/* button 1 is the clear button
    			* press twice to add 00 behind the numbers */
    			if (bit_is_clear(BUTTON1PIN,BUTTON1)){
    				/* call changemode not to change the mode
    				* but to clear the 74hc390 counter */
    				changemode();
    				/* the second function of this clear button
    				* is to show 00 on the display */
    				dmode++;
    				if (dmode > 1){
    					dmode=0;
    				}
    				outp(0x0,TCNT1H); /* set counter to zero*/
    				outp(0x0,TCNT1L); /* set counter to zero*/
    				ignorebutton=55000; // debounce, ignore button for a while
    				outp(7,TCCR1B); /* enable counter from T1 pin, needed in case there was an overflow */
    				overflowflag=0;
    			}
    		}
    		if (ignorebutton) ignorebutton--;
    		if (pausetodisplay) pausetodisplay--;
    		if (rxresult==0){
    			continue;
    		}
    		/* valid command are:
    		* */
    		/* now parse the comands and act on them */
    		if (strlen(uart_outbuf) < 3) {
    			reply_err();
    			continue;
    		}
    		if (uart_outbuf[1] != '='){
    			reply_err();
    			continue;
    		}
    		val=&(uart_outbuf[2]);
    		cmd=uart_outbuf[0];
    		status=0;
    		/* act on the commands */
    		if (cmd=='h'){
    			if (*val =='1'){
    				dmode=1;
    				status=1;
    			}
    			if (*val =='0'){
    				dmode=0;
    				status=1;
    			}
    			if (*val == '?'){
    				uart_sendstr_P("h:");
    				uart_sendchar('0'+ dmode);
    				uart_sendchar('\n');
    				continue;
    			}
    		}
    		if (cmd=='c' && *val =='1'){
    			/* call changemode not to change the mode
    			* but to clear the 74hc390 counter */
    			changemode();
    			outp(0x0,TCNT1H); /* set counter to zero*/
    			outp(0x0,TCNT1L); /* set counter to zero*/
    			status=1;
    		}
    		if (cmd=='m'){
    			i=*val;
    			if (i == '?'){
    				uart_sendstr_P("m:");
    				uart_sendchar('0'+ mode);
    				uart_sendchar('\n');
    				continue;
    			}
    			if (i < '3' && i >= '0'){
    				mode= i - '0'; // convert to integer;
    				changemode();
    				status=1;
    			}
    		}
    		/* command handling done. now return status */
    		if (status==1){
    			reply_ok();
    		}
    		if (status==0){
    			reply_err();
    		}
    	}

    }