/******* P9 for QwikFlash board ***********************************************************
 *
 * Name: Shane Connelly
 * Name: Grant Lohsen
 *
 * Handle: WSCL
 *
 * PIC18F452's internal clock period is 0.4 microseconds.
 * Use Timer2 for a loop time of approximately ten milliseconds (see Figure 16-3)
 * with A=16, B=223, C=7 for 24976 internal clock cycles = 9990.4 microseconds.
 * Blink "Alive" LED every one seconds.
 * LCD is initalized and top line set to Freq  Hz
 * CCP2CON is set to trigger a high priority interrupt when clocked on pin CCP2/RC1
 * Low priority interupts occur when an overflow in tmr3 occurs
 * Every second the measurement is restarted; then .4 seconds of measurement are taken
 * and displayed on the LCD with a decimal point in the correct position
 *
 ******* Program hierarchy *****************************************************
 *
 * Mainline
 *   Initial
 *     InitLCD
 *     DisplayC
 *       LoopTime
 *   BlinkAlive
 *     StartMeasureFreq
 *   CalculateHz
 *     Normalize
 *     DisplayNormNum
 *       DisplayV
 *
 * HiPriISR
 *
 * LoPriISR
 *
 *******************************************************************************
 */

#include <pic18.h>

/*******************************
 * Assembler directives
 *******************************
 */

/*******************************
 * Definitions and equates
 *******************************
 */


/*******************************
 * Global variables
 *******************************
 */

char TMR3X;             // Eight-bit extension to TMR3
char ALIVECNT;          // Counter for blinking "Alive" LED
unsigned long AARGI;    //integer representation of AARG
char TMP0;              //the position of the decimal for normalized number
char STARTL;            //low byte of the start time for frequency measurement
char STARTH;            //high byte of the start time for frequency measurement
char STARTX;            //highest byte of the start time for frequency measurement
char NX;                //highest byte of the current time for frequency measurement
char NH;                //high byte of the current time for frequency measurement
char NL;                //low byte of the current time for frequency measurement
double N;               //used for NX:NH:NL representation
double M;               //rising edge count
char FIRSTMEAS;         //flag used to determine if the first measurement is in process
char COUNT;
/*******************************
 * Constant strings
 *******************************
 */

const char LCDstr[]  = {0x33,0x32,0x28,0x01,0x0c,0x06,0x00,0x00};// LCD Initialization string
const char FreqStr[] = {0x80,'F','r','e','q',' ',' ','H','z',0};// Frequency instrument
const double PowTen[] = {1000000,100000,10000,1000,100,10,1,.1}; //PowTen table

/*******************************
 * Variable strings
 *******************************
 */

char BYTESTR[8]; // Display string for normalized number
char BYTESTRLCD[10]; // Display string for normalized number

/*******************************
 * Function prototypes
 *******************************
 */

void Initial(void);
void CCP1handler(void);
void TMR1handler(void);
void StartMeasureFreq(void);
void Looptime(void);
void BlinkAlive(void);
void LoopTime(void);
void Normalize(double NormNum);
void DisplayNormNum(void);
void TestNormalize(void);
void TxByte(char);
void InitLCD(void);
void T40(void);
void DisplayC(const char *);
void DisplayV(char *);
void CalculateHz(void);

/////// Mainline program ////////////////////////////////////////

/*******************************
 * main()
 *******************************
 */

void main()
{
        Initial();                      // Initialize everything

        while(1)
        {
          RB0 = !RB0;                   // Toggle pin, to support measuring loop time
          RC2 = 1;
          BlinkAlive();                 // Blink "Alive" LED
          if ((CCP2IE == 0) && (FIRSTMEAS == 0))   // We want to start calculation
          {
	          FIRSTMEAS = 1;            // Set this flag so the high priority interrupt knows to start next
	          CalculateHz();            // calculate and display the frequency
          }
          RC2 = 0;
          LoopTime();                   // Make looptime be ten milliseconds
        }
}

/*******************************
 * Initial()
 *
 * This subroutine performs all initializations of variables and registers.
 *******************************
 */

void Initial()
{
		ADCON0 = 0b01100001;
        ADCON1 = 0b01001110;            // Enable PORTA & PORTE digital I/O pins
        PORTA  = 0b00010000;            // Turn off all four LEDs driven from PORTA
        TRISA  = 0b11100001;            // Set I/O for PORTA
        TRISB  = 0b11001100;            // Set I/O for PORTB
        TRISC  = 0b11010010;            // Set I/0 for PORTC
        TRISD  = 0b00001111;            // Set I/O for PORTD
        TRISE  = 0b00000100;            // Set I/O for PORTE
        CCP2CON = 0b00000101;   // Select compare mode
        PR2 = 222;
        T2CON = 0b10110111;		//setup timer 2 10 ms looptime
        T3CON = 0b10001001;           // Turn on TMR3
        TMR3IP = 0;             // Assign low priority to TMR3 interrupts
        TMR3IF = 0;				//clear flag for overflow interupt
        TMR3IE = 1;				//enable timer 3 overflow interupts
        CCP2IP = 1;				//assign high priority to TMR3 overflow interupt   
        CCP2IF = 0;				//clear flag for overflow interupt
        IPEN = 1;               // Enable priority levels
        GIEL = 1;               // Enable low-priority interrupts to CPU
        GIEH = 1;               // Enable all interrupts
        InitLCD();              //initialize the LCD display
        DisplayC(FreqStr);     //Display "Freq  Hz" at the top of the LCD
        
}


/*******************************
 * InitLCD()
 *
 * Initialize the Optrex 8x2 character LCD.
 * First wait for 0.1 second, to get past display's power-on reset time.
 *******************************
 */

void InitLCD()
{
        char currentChar;
        const char *tempPtr;

        COUNT = 10;                     // Wait 0.1 second
        while (COUNT)
        {
          LoopTime();                   // Call LoopTime() 10 times
          COUNT--;
        }

        RE0 = 0;                        // RS=0 for command
        tempPtr = LCDstr;

        while (*tempPtr)                // if the byte is not zero
        {
          currentChar = *tempPtr;
          RE1 = 1;                      // Drive E pin high
          PORTD = currentChar;          // Send upper nibble
          RE1 = 0;                      // Drive E pin low so LCD will accept nibble
          LoopTime();
          currentChar <<= 4;            // Shift lower nibble to upper nibble
          RE1 = 1;                      // Drive E pin high again
          PORTD = currentChar;          // Write lower nibble
          RE1 = 0;                      // Drive E pin low so LCD will process byte
          LoopTime();                   // Wait 40 usec
          tempPtr++;                    // Increment pointerto next character
        }
}

/****** HiPriISR interrupt service routine ***********************************/
/* The two bytes of CCPR1 match Timer1, so check our extension bytes.  If they
 * match, reset CCPR1 (plus extension) and blink the middle LED. */
void interrupt HiPriISR()
{
		if (FIRSTMEAS)    // Check whether extensions are equal
        {
          FIRSTMEAS = 0;      // We're no longer checking for the first measurement
	      STARTL = CCPR2L;    // Set START to TMR3X:CCPR2H:CCPR2L
          STARTH = CCPR2H;
          STARTX = TMR3X;
        }
        else                  // If this isn't the first time in here
        {
          M++;                // increment the count

		if (TMR3X & 0b01000000)                   // If TMR3 has gone for about a million counts
		{
			NX = TMR3X;                           // Set NX = TMR3X
			CCP2IE=0;                             // Disable future interrupts
			NL = CCPR2L - STARTL;                 // and find how much time has elapsed from start
			NH = CCPR2H - STARTH - (1 - CARRY);
			NX = NX - STARTX - (1 - CARRY);
			
		}
  		}
  		if (TMR3IF && !CCPR2H) // if a low priority interrupt occurred while running the code
        {
      		TMR3X++;		//increment TMR3X
      		TMR3IF = 0;		//clear TMR3IF flag
	    }
  		CCP2IF = 0;             // Clear interrupt flag        
}                               

/****** LoPriISR interrupt service routine ***********************************/
/* Timer1 has overflowed, so increment the extension byte and blink the right LED. */
void interrupt low_priority LoPriISR()  // Low-priority interrupt service routine
{
        GIEH = 0;               // Disable high priority interrupts
        TMR3X++;                // increment the highest byte of TMR3
        TMR3IF = 0;             // Clear interrupt flag
        GIEH = 1;               // Re-enable high priority interrupts
}                               // Return from interrupt, reenabling GIEL


/*******************************
 * BlinkAlive()
 *
 * This subroutine briefly blinks the LED next to the PIC every two-and-a-half
 * seconds.
 *******************************
 */

void BlinkAlive()
{
        RA4 = 1;                        // Turn off LED
        if (!(--ALIVECNT))              // Decrement loop counter and return if not zero
        {
          ALIVECNT = 200;               // Reinitialize BLNKCNT
          RA4 = 0;                      // Turn on LED for ten milliseconds every 2.5 sec
        }
        if (ALIVECNT == 100)            // every second, restart the frequency measurement
        {
        	StartMeasureFreq();
	    }
}		



/*******************************
 * CalculateHz()
 *
 *******************************
 */

void CalculateHz()
{
	double MyNum;       // This is the number to normalize and display
	
	N = (double)NL + 256*((double)NH) + 65536*((double)NX);  // Set N to NX:NH:NL
	
	MyNum = ((double)(M * 2500000) / N);  // number = M / N * 2500000
			
	Normalize(MyNum);                     // Normalize the number
	DisplayNormNum();                     // And display it on the LCD
}

/*******************************
 * StartMeasureFreq()
 *
 *******************************
 */

void StartMeasureFreq(void)
{
	
	TMR3X = 0x2f;         // Initialize TMR3 to 0x2F0000
	TMR3H = 0;
	TMR3L = 0;
	CCP2IF = 0;           // clear the CCP2 interrupt flag
	M=0;                  // initilize the signal count to 0
	FIRSTMEAS = 1;        // set the first measurement variable
	CCP2IE = 1;           // enable the CCP2 interruppt
}


/*******************************
 * LoopTime()

 *******************************
 */

void LoopTime()
{
  //#define Bignum  65536-25000+12+2
        while (!TMR2IF);                  // Wait until ten milliseconds are up
        TMR2IF = 0;                       // Clear Timer0 flag
}

/*******************************
 * TxByte()
 *
 * This subroutine first waits on the UART if a byte is in the process of being
 * sent. Then it sends the content of Cin to the PC.
 *******************************
 */

void TxByte(char Cin)
{
        while (TXIF==0);   //If an earlier transmission is in progress, then wait
        TXREG = Cin;       //send new byte to the PC
}

/*******************************
 * Normalize(double NormNum)
 *
 * This subroutine normalizes a floating point number to a floating point representation
 * of its decimal equivalent with the decimal point moved to the right of a seven-digit
 * number (i.e., a decimal number between 1,000,000 and 9,999,999).  It is
 * assumed that the decimal equivalent of the actual number is less than
 * 9,999,999 and more than 0.1000000.

 * The location of the actual decimal point is returned in TMP0 with TMP0=0
 * representing a number with no digits to the left of the decimal point.
 * For example, if the floating point number is initially the floating point representation
 * of 12.34567, then this subroutine will return with AARGI holding the floating
 * point representation of 1234567 and with TMP0 = 2.
 *******************************
 */

void Normalize(double NormNum)
{
        char i;                 //variable used for the loop

        for (i=0; i<7; i++)     //loop to find the power of ten needed
        {
          if ((NormNum-PowTen[i]) > 0)
          {
            AARGI = (long)(NormNum*PowTen[6-i]); //normalize the floating number
            TMP0 = 7-i;         //set the decimal position
            return;
          }
        }
        AARGI = (long)(NormNum*10000000); //if floating point number is .1234567
        TMP0 = 0;
}

/*******************************
 * DisplayNormNum()
 *
 * This subroutine displays the normalized number in AARGI and TMP0 to PC.
 *******************************
 */

void DisplayNormNum(void)
{
        char i;              //variable used for the loop
        long ATEMP = AARGI;  //create temp variable for AARGI, so AARGI will not be changed

        for (i=0; i<(7-TMP0); i++)   //process the number after the decimal
        {
          BYTESTR[i] = (ATEMP%10)+0x30; //save ASCII representation to BYTESTR
          ATEMP = ATEMP/10;
        }
        BYTESTR[i] = '.';            //add the decimal character in the correct position
        for (i=(8-TMP0); i<8; i++)   //process the number before the decimal
        {
          BYTESTR[i] = (ATEMP%10)+0x30; //save ASCII representation to BYTESTR
          ATEMP = ATEMP/10;
        }
        BYTESTRLCD[0] = 0xc0;
        for (i=0; i<8; i++)          //send the normalized number to PC
        {
	      BYTESTRLCD[i+1] = BYTESTR[7-i];
          //TxByte(BYTESTR[7-i]);
        }
        BYTESTRLCD[9] = 0x00;
        DisplayV(BYTESTRLCD);
        //TxByte(0x0d);         // And then go on to the next line: carriage return
        //TxByte(0x0a);         //   and line feed
}


/*******************************
 * T40()
 *
 * Pause for 40 microseconds  or 40/0.4 = 100 clock cycles.
 * Assumes 10/4 = 2.5 MHz internal clock rate.
 *******************************
 */

void T40()
{       
        // Measured with oscilloscope to be about 42.80 us
        // including the time to call this routine.  Decrementing each
        // "cCOUNT" takes approximately 4 us.

        unsigned char cCOUNT = 11;
        while (cCOUNT)
          cCOUNT--;
}

/*******************************
 * DisplayC(const char *) 
 *
 * This subroutine is called with the passing in of an array of a constant
 * display string.  It sends the bytes of the string to the LCD.  The first
 * byte sets the cursor position.  The remaining bytes are displayed, beginning
 * at that position.
 * This subroutine expects a normal one-byte cursor-positioning code, 0xhh, or
 * an occasionally used two-byte cursor-positioning code of the form 0x00hh.
 *******************************
 */

void DisplayC(const char * tempPtr)
{
        char currentChar;

        RE0 = 0;                        // Drive RS pin low for cursor-positioning code
        while (*tempPtr)                // if the byte is not zero
        {
          currentChar = *tempPtr;
          RE1 = 1;                      // Drive E pin high
          PORTD = currentChar;          // Send upper nibble
          RE1 = 0;                      // Drive E pin low so LCD will accept nibble
          currentChar <<= 4;            // Shift lower nibble to upper nibble
          RE1 = 1;                      // Drive E pin high again
          PORTD = currentChar;          // Write lower nibble
          RE1 = 0;                      // Drive E pin low so LCD will process byte
          T40();                        // Wait 40 usec
          RE0 = 1;                      // Drive RS pin high for displayable characters
          tempPtr++;                    // Increment pointerto next character
        }
}

/*******************************
 * DisplayV(char *)
 *
 * This subroutine is called with the passing in of an array of a variable
 * display string.  It sends the bytes of the string to the LCD.  The first
 * byte sets the cursor position.  The remaining bytes are displayed, beginning
 * at that position.
 *******************************
 */

void DisplayV(char * tempPtr)
{
        char currentChar;

        RE0 = 0;                        // Drive RS pin low for cursor-positioning code
        while (*tempPtr)                // if the byte is not zero
        {
          currentChar = *tempPtr;
          RE1 = 1;                      // Drive E pin high
          PORTD = currentChar;          // Send upper nibble
          RE1 = 0;                      // Drive E pin low so LCD will accept nibble
          currentChar <<= 4;            // Shift lower nibble to upper nibble
          RE1 = 1;                      // Drive E pin high again
          PORTD = currentChar;          // Write lower nibble
          RE1 = 0;                      // Drive E pin low so LCD will process byte
          T40();                        // Wait 40 usec
          RE0 = 1;                      // Drive RS pin high for displayable characters
          tempPtr++;                    // Increment pointerto next character
        }
}

