/******* FiveWaySwitch.c ******* * * Display one 5x8 pixel character and move it with five-way switch. * * VDD = 5V for use with standard off-the-shelf 24x2 character LCD. * Use Fosc = 4 MHz for Fcpu = Fosc/4 = 1 MHz. * Sleep for 16 ms, using watchdog timer for wakeup. * Blink LED every two seconds. * * Current draw = 29 uA (w/o LED and LCD) * Current draw with LCD = 1.177 uA (w/o LED) * ******* Program hierarchy ***** * * main * Initial * InitLCD * DisplayC * UpdatePosition * SendByte * BlinkAlive * Switch * EraseOldPosition * SendByte * UpdatePosition * SendByte * LoopTime * ******************************* */ #include // Define PIC18LF4321 registers and bits /******************************* * Configuration selections ******************************* */ #pragma config OSC = INTIO1 // Use internal osc, RA6=Fosc/4, RA7=I/O #pragma config PWRT = ON // Enable power-up delay #pragma config LVP = OFF // Disable low-voltage programming #pragma config WDT = OFF // Disable watchdog timer initially #pragma config WDTPS = 4 // 16 millisecond WDT timeout period, nominal #pragma config MCLRE = ON // Enable master clear pin #pragma config PBADEN = DIG // PORTB<4:0> = digital #pragma config CCP2MX = RB3 // Connect CCP2 internally to RB3 pin #pragma config BOR = SOFT // Brown-out reset controlled by software #pragma config BORV = 3 // Brown-out voltage set for 2.1V, nominal #pragma config LPT1OSC = OFF // Deselect low-power Timer1 oscillator /******************************* * Global variables ******************************* */ unsigned int DELAY; // Counter for obtaining a delay unsigned char ALIVECNT; // Counter for blinking "Alive" LED unsigned char NEWSWITCH; // State of switches unsigned char OLDSWITCH; // State of switches last loop time unsigned char DEL; // Switch-change state unsigned char PORTACOPY; // Used by InitLCD, DisplayC, and DisplayV const rom char *romPtr; // Pointer into ROM strings unsigned char CPC; // Cursor position code unsigned char BBYTE = 0; // Single byte to send to the LCD /******************************* * Constant strings ******************************* */ const rom char LCDstr[] = { 0x33, 0x32, 0x28, 0x01, 0x0c, 0x06, 0x00, 0x00 }; // LCD controller // initialization // string const rom char BarChars[] = { 0x00, 0x40, // CGRAM-positioning code 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // Empty 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // Column 1 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, // Columns 1,2 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, // Columns 1,2,3 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, // Columns 1,2,3,4 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, // Columns 1,2,3,4,5 0x00, 0x00 // End of String terminator }; /******************************* * Function prototypes ******************************* */ void Initial(void); void BlinkAlive(void); void Switch(void); void EraseOldPosition(void); void UpdatePosition(void); void SendByte(void); void LoopTime(void); void InitLCD(void); void DisplayC(void); /******************************* * Macros ******************************* */ #define Delay(x) DELAY = x; while(--DELAY){ Nop(); Nop(); } /////// Main program ///////////////////////////////////////////////////// /******************************* * main ******************************* */ void main() { Initial(); // Initialize everything while (1) { BlinkAlive(); // Blink LED every two seconds Switch(); // Check the five-way switch LoopTime(); // Sleep for 16 ms } } /******************************* * Initial * * This function performs all initializations of variables and registers. ******************************* */ void Initial() { OSCCON = 0b01100010; // Use Fosc = 4 MHz (Fcpu = 1 MHz) ADCON1 = 0b00001111; // RA0,1,2,... digital TRISA = 0b00000000; // Set I/O for PORTA TRISB = 0b01110100; // Set I/O for PORTB TRISC = 0b10000000; // Set I/O for PORTC TRISD = 0b01111100; // Set I/O for PORTD TRISE = 0b00000000; // Set I/O for PORTE PORTA = 0; // Set initial state for all outputs PORTB = 0; PORTC = 0; PORTD = 0; PORTE = 0; Delay(50000); // Pause for half a second RCONbits.SBOREN = 0; // Now disable brown-out reset ALIVECNT = 122; // Blink immediately InitLCD(); // Initialize LCD romPtr = BarChars; // Load ROM pointer to BarChars table DisplayC(); // Send BarChars to LCD controller PORTDbits.RD7 = 1; // Power pull-up resistors Nop(); // Wait a microsecond for powerup OLDSWITCH = (!PORTD & 0b01111100); // Read switches PORTDbits.RD7 = 0; // Power down pull-up resistors CPC = 0xCC; // Initial block position UpdatePosition(); // Send initial block character WDTCONbits.SWDTEN = 1; // Enable watchdog timer } /******************************* * InitLCD() * * Initialize the LCD for a four-bit interface, a two-line display. Then clear * display, turn off cursor, turn on display and increment cursor automatically. ******************************* */ void InitLCD() { PORTEbits.RE0 = 0; // RS=0 for command Delay(5000); // Wait 50 ms for LCD controller to power up romPtr = LCDstr; // Load pointer to beginning of string while (*romPtr) // Send char if the byte is not zero { PORTACOPY = *romPtr; // Get byte PORTACOPY >>= 2; // Shift upper nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will accept nibble Delay(500); // Wait for 5 ms PORTACOPY = *romPtr; // Get byte again PORTACOPY <<= 2; // Shift lower nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will process byte Delay(500); // Wait 5 ms romPtr++; // Increment pointer to next character } } /******************************* * DisplayC() * * This subroutine is called with romPtr loaded with the name of a * constant display string stored in program memory. The format of the * constant string is: * Cursor-position code (CPC) * Displayable characters * A null character (0x00) to terminate the string * The cursor-position code identifies the location to be used by the * first displayable character. * For the top row of the 24x2 LCD, the CPC's are 0x80, 0x81,..., 0x97 * For the second row of the 24x2 LCD, the CPC's are 0xC0, 0xC1,..., 0D7 ******************************* */ void DisplayC() { PORTEbits.RE0 = 0; // RS=0 for command (CPC) while (!*romPtr) romPtr++; // Skip leading zeroes while (*romPtr) // Send char if the byte is not zero { PORTACOPY = *romPtr; PORTACOPY >>= 2; // Shift upper nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will accept nibble PORTACOPY = *romPtr; PORTACOPY <<= 2; // Shift lower nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will process byte Delay(4); // Wait 40 usec PORTEbits.RE0 = 1; // Drive RS pin high for displayable chars romPtr++; // Increment pointer to next character } } /******************************* * BlinkAlive() * * This function briefly blinks the LED every two seconds * With a looptime of 16 ms, count 125 looptimes ******************************* */ void BlinkAlive() { PORTEbits.RE2 = 0; // Turn off LED if (++ALIVECNT == 125) // Increment counter and return if not 125 { ALIVECNT = 0; // Reset ALIVECNT PORTEbits.RE2 = 1; // Turn on LED for 16 ms every 2 secs } } /******************************* * Switch() * * Check the five-way switch for a change and set bits of DEL accordingly, * where a 1 in a bit position of DEL corresponds to a newly pressed switch. * Then use DEL to update the display. ******************************* */ void Switch() { DEL = 0; // Reset DEL PORTDbits.RD7 = 1; // Power pull-up resistors Nop(); // Wait a microsecond for powerup NEWSWITCH = (~PORTD & 0b01111100); // Read switches PORTDbits.RD7 = 0; // Power down pull-up resistors DEL = NEWSWITCH & ~OLDSWITCH; // Select bits that were 0 and are now 1 if (DEL) { if (0b01000000 & DEL) // Right switch pressed { EraseOldPosition(); // Clear the old CPC += 1; // Move right if (CPC == 0x98) CPC -= 1; // Stop at right end of top row if (CPC == 0xD8) CPC -= 1; // Stop at right end of bottom row UpdatePosition(); // Write the new } else if (0b00100000 & DEL) // Left switch pressed { EraseOldPosition(); // Clear the old CPC -= 1; // Move left if (CPC == 0x7F) CPC += 1; // Stop at left end of top row if (CPC == 0xBF) CPC += 1; // Stop at left end of bottom row UpdatePosition(); // Write the new } } OLDSWITCH = NEWSWITCH; // Save RPG value for next time } /****************************** * EraseOldPosition * * Write a blank to the present position on the LCD ******************************* */ void EraseOldPosition() { PORTEbits.RE0 = 0; // RS=0 for command (CPC) BBYTE = CPC; // Set BBYTE to CPC SendByte(); // Send CPC PORTEbits.RE0 = 1; // Drive RS pin high for displayable chars BBYTE = 0x20; // Send a blank SendByte(); } /****************************** * UpdatePosition * * Move block on LCD ******************************* */ void UpdatePosition() { PORTEbits.RE0 = 0; // RS=0 for command (CPC) BBYTE = CPC; // Set BBYTE to CPC SendByte(); // Send CPC PORTEbits.RE0 = 1; // Drive RS pin high for displayable chars BBYTE = 5; // Send user-defined character 5 SendByte(); } /****************************** * SendByte * * Send a single byte to the LCD ******************************* */ void SendByte(void) { PORTACOPY = BBYTE; PORTACOPY >>= 2; // Shift upper nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will accept nibble PORTACOPY = BBYTE; PORTACOPY <<= 2; // Shift lower nibble into position PORTA = PORTACOPY & 0b00111100; // Force other four bits to zero PORTEbits.RE1 = 1; // Drive E pin high PORTEbits.RE1 = 0; // and low so LCD will process byte Delay(4); // Wait 40 usec } /****************************** * LoopTime * * This function puts the chip to sleep upon entry. * It uses the watchdog timer to awaken again. ******************************* */ void LoopTime() { Sleep(); Nop(); }