/******* P3_MCC18.c ************************************************************ * * Use 10 MHz crystal frequency. * Use Timer0 for ten millisecond looptime. * Blink "Alive" LED every two and a half seconds. * Use pushbutton to exercise Screens utility. * ******* Program hierarchy ***************************************************** * * Mainline * Initial * InitLCD * LoopTime * DisplayC * T40 * BlinkAlive * Pbutton * Screens * DisplayC * T40 * Frequency * Period * PWmax * RateRPG * ByteDisplay * DisplayC * T40 * DisplayV * T40 * LoopTime * ******************************************************************************* */ #include /******************************* * Assembler directives ******************************* */ /******************************* * Definitions and equates ******************************* */ #define PBthres 30 // Pushbutton threshold for a long press /******************************* * Global variables ******************************* */ char TMR0LCOPY; // Copy of sixteen-bit Timer0 used by LoopTime char TMR0HCOPY; char INTCONCOPY; // Copy of INTCON for LoopTime subroutine char COUNT; // Counter available as local to subroutines char TEMP; // Temporary local variable char ALIVECNT; // Counter for blinking "Alive" LED char BYTE; // Eight-bit byte to be displayed char OLDPORTD; // Holds previous value of inputs char DELRPG; // Generated by RPG char RPGCNT; // Used to display RPG changes char PBCOUNT; // Counter for measuring duration of press char SCREEN; // State of LCD subroutine char LOOP10; // Scale of ten loop counter char THR; // Threshold value used by Pbutton struct { // Control/status bits for pushbutton unsigned ISC:1; // Initiate screen change for slow press unsigned ISA:1; // Initiate secondary action for fast press unsigned PDONE:1; // Pushbutton action has been taken unsigned OLDPB:1; // Old state of pushbutton unsigned NEWPB:1; // New state of pushbutton } PBSTATEbits; /******************************* * Constant strings ******************************* */ // For stability reasons, create an EVEN number of elements in any given array const char LCDstr[] = {0x33,0x32,0x28,0x01,0x0c,0x06,0x00,0x00};// LCD Initialization string const char StrtStr[] = {0x80,'P','u','s','h',' ','P','B',' ',0};// Startup screen const char BYTE_1[] = {0x80,'B','Y','T','E','=',' ',' ',' ',0};// Write "BYTE=" 1st line of LCD const char Clear1[] = {0x80,' ',' ',' ',' ',' ',' ',' ',' ',0};// Clear line 1 const char Clear2[] = {0xC0,' ',' ',' ',' ',' ',' ',' ',' ',0};// Clear line 2 const char FreqStr[] = {0x80,'F','r','e','q',' ','k','H','z',0};// Frequency instrument const char PerStr[] = {0x80,'P','e','r',' ',' ',' ','u','s',0};// Period instrument const char PWmaxStr[] = {0x80,'P','W','m','a','x',' ','u','s',0};// Max pulse width instrument /******************************* * Variable strings ******************************* */ char BYTESTR[] = {0,0,0,0,0,0,0,0,0,0}; // Display string for binary version of BYTE /******************************* * Function prototypes ******************************* */ void Initial(void); void InitLCD(void); void Looptime(void); void T40(void); void DisplayC(const char *); void DisplayV(char *); void BlinkAlive(void); void LoopTime(void); void ByteDisplay(void); void RateRPG(void); void Pbutton(void); void Screens(void); void Frequency(void); void Period(void); void PWmax(void); /////// Mainline program //////////////////////////////////////// /******************************* * main() ******************************* */ void main() { Initial(); // Initialize everything while(1) { PORTBbits.RB0 = !PORTBbits.RB0; // Toggle pin, to support measuring loop time BlinkAlive(); // Blink "Alive" LED Pbutton(); // Check pushbutton Screens(); // Deal with SCREEN state LoopTime(); // Make looptime be ten milliseconds } } /******************************* * Initial() * * This subroutine performs all initializations of variables and registers. ******************************* */ void Initial() { ADCON1 = 0b10001110; // Enable PORTA & PORTE digital I/O pins TRISA = 0b11100001; // Set I/O for PORTA TRISB = 0b11011100; // Set I/O for PORTB TRISC = 0b11010000; // Set I/0 for PORTC TRISD = 0b00001111; // Set I/O for PORTD TRISE = 0b00000000; // Set I/O for PORTE T0CON = 0b10001000; // Set up Timer0 for a looptime of 10 ms PORTA = 0b00010000; // Turn off all four LEDs driven from PORTA OLDPORTD = PORTD; // Initialize "old" value RPGCNT = 0; // Clear counter to be displayed PBCOUNT = 0; // and pushbutton count SCREEN = 0; // Initialize LCD's SCREEN variable THR = 0; // Initialize Pbutton's THR variable PBSTATEbits.ISC = 0; // Initialize pushbutton state PBSTATEbits.ISA = 0; PBSTATEbits.PDONE = 0; PBSTATEbits.OLDPB = 1; PBSTATEbits.NEWPB = 0; InitLCD(); // Initialize LCD DisplayC(StrtStr); // Display startup message } /******************************* * 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; char *tempPtr; COUNT = 10; // Wait 0.1 second while (COUNT) { LoopTime(); // Call LoopTime() 10 times COUNT--; } PORTEbits.RE0 = 0; // RS=0 for command tempPtr = LCDstr; while (*tempPtr) // if the byte is not zero { currentChar = *tempPtr; PORTEbits.RE1 = 1; // Drive E pin high PORTD = currentChar; // Send upper nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will accept nibble LoopTime(); currentChar <<= 4; // Shift lower nibble to upper nibble PORTEbits.RE1 = 1; // Drive E pin high again PORTD = currentChar; // Write lower nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will process byte LoopTime(); // Wait 40 usec tempPtr++; // Increment pointerto next character } } /******************************* * 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 = 7; 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; PORTEbits.RE0 = 0; // Drive RS pin low for cursor-positioning code while (*tempPtr) // if the byte is not zero { currentChar = *tempPtr; PORTEbits.RE1 = 1; // Drive E pin high PORTD = currentChar; // Send upper nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will accept nibble currentChar <<= 4; // Shift lower nibble to upper nibble PORTEbits.RE1 = 1; // Drive E pin high again PORTD = currentChar; // Write lower nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will process byte T40(); // Wait 40 usec PORTEbits.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; PORTEbits.RE0 = 0; // Drive RS pin low for cursor-positioning code while (*tempPtr) // if the byte is not zero { currentChar = *tempPtr; PORTEbits.RE1 = 1; // Drive E pin high PORTD = currentChar; // Send upper nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will accept nibble currentChar <<= 4; // Shift lower nibble to upper nibble PORTEbits.RE1 = 1; // Drive E pin high again PORTD = currentChar; // Write lower nibble PORTEbits.RE1 = 0; // Drive E pin low so LCD will process byte T40(); // Wait 40 usec PORTEbits.RE0 = 1; // Drive RS pin high for displayable characters tempPtr++; // Increment pointerto next character } } /******************************* * BlinkAlive() * * This subroutine briefly blinks the LED next to the PIC every two-and-a-half * seconds. ******************************* */ void BlinkAlive() { PORTAbits.RA4 = 1; // Turn off LED if (!(--ALIVECNT)) // Decrement loop counter and return if not zero { ALIVECNT = 250; // Reinitialize BLNKCNT PORTAbits.RA4 = 0; // Turn on LED for ten milliseconds every 2.5 sec } } /******************************* * LoopTime() * * This subroutine waits for Timer0 to complete its ten millisecond count * sequence. It does so by waiting for sixteen-bit Timer0 to roll over. To obtain * a period of precisely 10000/0.4 = 25000 clock periods, it needs to remove * 65536-25000 or 40536 counts from the sixteen-bit count sequence. The * algorithm below first copies Timer0 to RAM, adds "Bignum" to the copy ,and * then writes the result back to Timer0. It actually needs to add somewhat more * counts to Timer0 than 40536. The extra number of 12+2 counts added into * "Bignum" makes the precise correction. ******************************* */ void LoopTime() { #define Bignum 65536-25000+12+2 while (!INTCONbits.T0IF); // Wait until ten milliseconds are up INTCONCOPY = INTCON; // Save INTCON bits INTCONbits.GIEH = 0; // Disable all interrupts from CPU INTCONbits.GIEL = 0; TMR0LCOPY = TMR0L; // Read 16-bit counter at this moment TMR0HCOPY = TMR0H; TMR0LCOPY += Bignum & 0x00FF; // add LSB if (STATUSbits.C) // If Carry, increment high byte TMR0HCOPY++; TMR0HCOPY += (Bignum>>8) & 0x00FF; // add MSB TMR0H = TMR0HCOPY; TMR0L = TMR0LCOPY; // Write 16-bit counter at this moment WREG = INTCONCOPY & 0b11000000; // Reenable interrupts to CPU if enabled prior INTCON = INTCON | WREG; // to LoopTime INTCONbits.T0IF = 0; // Clear Timer0 flag } /******************************* * ByteDisplay() * * Display whatever is in BYTE as a binary number. ******************************* */ void ByteDisplay() { DisplayC(BYTE_1); // Display "BYTE=" COUNT = 8; // 8 bits in BYTE while (COUNT) { TEMP = (BYTE & 0b00000001); // Move bit 0 of BYTE into TEMP TEMP |= 0x30; // Convert to ASCII BYTESTR[COUNT] = TEMP; // and move to string BYTE = BYTE>>1; // Right shift bits in BYTE by 1 COUNT--; // Decrement COUNT; } BYTESTR[0] = 0xC0; // Add cursor-positioning code BYTESTR[9] = 0; // and end-of-string terminator DisplayV(BYTESTR); // Display the string } /******************************* * RateRPG() * * This subroutine decyphers RPG changes into values of DELRPG. * DELRPG = +2 for fast CW change, +1 for slow CW change, 0 for no change, * -1 for slow CCW change and -2 for fast CCW change. ******************************* */ void RateRPG() { #define Threshold 3 // Value to distinguish between slow and fast char W_temp; PORTAbits.RA2 = 0; // Turn LED off DELRPG = 0; // Clear for "no change" return value W_temp = PORTD; // Copy PORTD into W_temp TEMP = W_temp; // and TEMP W_temp = W_temp ^ OLDPORTD; // Any change? W_temp = W_temp & 0b00000011; // If not, W_temp = 0 if (W_temp != 0) // If the two bits have changed then... { W_temp = OLDPORTD>>1; // Form what a CCW change would produce if (OLDPORTD & 0x01) // Make new bit 1 = complement of old bit 0 W_temp &= 0b11111101; else W_temp |= 0b00000010; W_temp = (W_temp ^ TEMP); // Did the RPG actually change to this output? W_temp &= 0b00000011; // Mask off upper 6 bits if (W_temp == 0) // If so, then change DELRPG to -1 for CCW { DELRPG--; if (THR != 0) { DELRPG--; // If fast turning, decrement again PORTAbits.RA2 = 1; // Turn LED on } } else { DELRPG++; // Otherwise, change DELRPG to +1 for CW if (THR != 0) { DELRPG++; // If fast turning, increment again PORTAbits.RA2 = 1; // Turn LED on } } THR = Threshold; // Reinitialize THR } if (THR != 0) // Does THR equal zero THR--; // If not, then decrement it OLDPORTD = TEMP; // Save PORTD as OLDPORTD for ten ms from now } /******************************* * Pbutton() * * This subroutine sorts out long and short pushbutton presses into two outputs: * ISC=1: Initiate screen change for slow press * ISA=1: Initiate secondary action for fast press * PDONE=1 One of the above actions has occurred for this press ******************************* */ void Pbutton() { PBSTATEbits.ISC = 0; // Clear Initiate Screen Change bit (if set) PBSTATEbits.ISA = 0; // Clear Initiate Secondary Action bit (if set) if (PORTDbits.RD3 == 1) // Copy pushbutton state to NEWPB PBSTATEbits.NEWPB = 1; else PBSTATEbits.NEWPB = 0; if (PBSTATEbits.OLDPB) // Look for leading edge (OLDPB=1, NEWPB=0) { if (!PBSTATEbits.NEWPB) PBCOUNT = PBthres; // Start counter } if (!PBSTATEbits.NEWPB) // Pushbutton is still pressed { if (!PBCOUNT) // and counter has passed threshold if (!PBSTATEbits.PDONE) // and no action has yet been taken { PBSTATEbits.ISC = 1; // Initiate screen change PBSTATEbits.PDONE = 1; // Done with pulse } } else // Pushbutton has been released PBSTATEbits.PDONE = 0; // so clear PDONE if (!PBSTATEbits.OLDPB) // Look for trailing edge (OLDPB=0, NEWPB=1) if (PBSTATEbits.NEWPB) { if (PBCOUNT) // Fast pulse PBSTATEbits.ISA = 1; // Initiate secondary action PBSTATEbits.PDONE = 0; // Done with pulse PBCOUNT = 0; // Finish counting } if (PBCOUNT) // Has counter reached zero? PBCOUNT--; // If not, then decrement it if (PBSTATEbits.NEWPB) // Copy NEWPB to OLDPB PBSTATEbits.OLDPB = 1; else PBSTATEbits.OLDPB = 0; } /******************************* * Screens() * * This subroutine uses the ISC bit from the Pbutton subroutine to cycle the * state of SCREEN and to take action based upon its value. * Initially SCREEN=0, so that whatever screen is displayed by the Initial * subroutine is not changed until a PB switch press. Then the screen * corresponding to SCREEN=1 is displayed. Subsequent PB switch * presses cycle through SCREEN=2, 3, etc., recycling back to SCREEN=1. ******************************* */ void Screens() { #define NumberOfScreens 4 // Change this value if new screens are added if (PBSTATEbits.ISC) { SCREEN++; if (SCREEN == (NumberOfScreens+1)) // Check if past last screen SCREEN = 1; // Cycle back to SCREEN = 1 DisplayC(Clear1); // Clear the display when switching screens DisplayC(Clear2); } if (SCREEN == 1) { if (PBSTATEbits.ISC) DisplayC(FreqStr); Frequency(); } if (SCREEN == 2) { if (PBSTATEbits.ISC) DisplayC(PerStr); Period(); } if (SCREEN == 3) { if (PBSTATEbits.ISC) DisplayC(PWmaxStr); PWmax(); if (PBSTATEbits.ISA) // Fast pulse, toggle PORTAbits.RA1 PORTAbits.RA1 = !PORTAbits.RA1; } if (SCREEN == 4) { RateRPG(); // Decypher RPG inputs into DELRPG RPGCNT += DELRPG; // Increment or decrement RPGCNT from RPG BYTE = RPGCNT; // Point BYTE to RPGCNT ByteDisplay(); if (PBSTATEbits.ISA) // Fast pulse, reset RPGCNT RPGCNT = 0; } } /////// Stubs for measurement subroutines ///////////////////////////////////// void Frequency() { } void Period() { } void PWmax() { }