/* HCS PICLCD Link - njc (c) 1998 Creative Control Concepts **** Things to add to v1.1 **** 1) Move to a tristate serial setup with IO and Enable - gain us back a pin (See #3) 2) Move configuration to HOST using HCS serial instead of RS232 Perhaps we'll still have a 'config' mode to be safe - what if the addr is forgotten or lost? Perhaps a failsafe command that will reset the address no matter what? Or one that only works in a config mode - I like that! The others would ignore the command but this one would not 3) Support both beeper and backlight instead of either or 4) As custom characters are stored, show them on the display 5) Output test banner to display in config mode. 6) Fix \e[A & D commands so they can't go negative 7) Add HCS RESET command 8) Enable watchdog support 9) See if non main memory usage can be reduced and boost buffer size above 62 The HCS PICLCD Link is a cost effective alternative to the existing Comm-Link and AnswerMan Jr LCD Network nodes for the HCS Home Automation System. HCS PICLCD-Link is 100% backward compatible with the existing HCS LCD-Link commands. The PICLCD-Link supports these existing commands plus some new ones which add some additional features: C-Style Escape Characters: \b will pulse a PIC output for 0.5 seconds (Beeper) \e Escape Charater \f FormFeed Character (What does this do to an LCD??) \n New Line (linefeed/carriage return) \r Carriage Return (same line, leftmost column) \xnn Send hex character \\ Output a single backslash The PICLCD-Link will also support these ANSI commands: \e[#A Move cursor up # rows \e[#B Move cursor down # rows \e[#C Move cursor right # columns \e[#D Move cursor left # columns \e[#;#H Move cursor to row,col 1,1 (\e[H Home) \e[#;#f Move cursor to row,col #,# (Any spot) \e[#;#j Move cursor to row,col 3,1 (Row setting \e[3j Row 3) \e[s Save current cursor position \e[u Restore saved cursor position \e[2J Clear display & home cursor \e[K Clear from cursor to end of row \e[7# Set Display Mode (\e[7h to wrap at end of row onto same line) (\e[7l to force CR/LF at end of row - may auto scroll Custom non-ANSI commands \e[b Turn line cursor off \e[c Turn line cursor on \e[o Turn on backlight \e[p Turn off backlight \e[x Turn Blinking box cursor on \e[y Turn Blinking box cursor off The PICLCD-Link will hang off the HCS 485 network. It can be configured as TERM0-7. The Communications can be with or without checksums. The modules will understand the following HCS commands: Q which will return HH NNNNNNNN where H is a two digit hex value representing the top 8 buttons which are not supported so they will always be 00 The N's will be 0 to 8 hex digits representing the last 8 key presses since the last query. The 558A version will always return $XX TERMX 00lf/cr The C66A version will return the keypress buffer On the 16C558A version, no buttons are supported (no I/O lines left) On the 16C66A version, 16 key 4x4 keypad scanned [Future] S= which returns nothing. Anything following the S will be printed on the LCD display. The PICLCD Link will have a ?? character buffer for incoming messages so long strings must be sent in multiple packets. The PICLCD-Link will support multiple display types. See below for list Pin Assignments: RA0 - Serial Out RA1 - Serial Enable RA2 - EEPROM I2C SDA Line [256x8 EEPROM] RA3 - EEPROM I2C SCL Line RA4 - Beeper/Backlight Output RB0 - Serial In (Pull low for 50ms to enter config) RB1 - LCD RS RB2 - LCD R/W RB3 - LCD E RB4-7 - LCD Data Bits Configuration: Connect the PIC-LCD to a serial terminal using an RS-232 interface such as a MAX232 Users will then be given a prompt > The following commands are valid: B [0-2] Beeper (0) [Default]/HCS Controlled Backlight (1)/Auto Backlight (2) A [0-7] Network Name (TERM0-7) [Default TERM0] S [0-7] [00-FF] x 8 Char Number followed by 8 bytes representing bitmap from top to bottom] L [0-5] Set LCD Type 0 = 20x4 (default!) 1 = 24x2 2 = 40x2 3 = 40x4 (NOT VALID on 558!) 4 = 16x2 5 = 20x2 All values are stored in the external EEPROM External EEPROM Memory Map 00 - Node Number 01 - Beeper/Backlight Config 02 - LCD Type 03 - 07 Future Use 08 - 15 Custom Char #1 16 - 23 Custom Char #2 24 - 31 Custom Char #3 32 - 39 Custom Char #4 40 - 47 Custom Char #5 48 - 55 Custom Char #6 56 - 63 Custom Char #7 64 - 71 Custom Char #8 */ #INCLUDE <16C558.H> #FUSES XT, NOWDT, NOPROTECT, NOPUT // Power Up Timer IS enabled. Parallax Programmer wants it backwards! #USE DELAY (CLOCK=4000000) #USE FAST_IO(A) // LCD bit direction handled by routines #USE FAST_IO(B) #USE I2C(master, sda=PIN_A2, scl=PIN_A3) #ZERO_RAM // Saves us any trouble! #INCLUDE "HCSLCD_D.C" // Here are some various definitions for the serial interface struct io_map { boolean serial_out; boolean data_enable; boolean i2c_sda; boolean i2c_scl; boolean b_out; int notused : 3; } io_a; #byte io_a = 0x05 // Map pin names to Port A #Bit INTF = 0x0B.1 // Map name to EXT interrupt flag #BIT s_in = 0x06.0 #Bit T0IF = 0x0B.2 // RTCC Interrupt Flag #byte OPTION = 0x81 #define BUFFER_SIZE 62 // Since we include the checksum, etc // Globals // We split the buffer to avoid needless page switching of variables char buffer1[BUFFER_SIZE]; int serial_idx, process_idx, idx, scratch; boolean p_idle; // When TRUE, the buffer is not being processed boolean s_idle; // When TRUE, we are NOT receiving a packet boolean go_data; // When TRUE, we have a packet to munch boolean a_check; boolean config; byte CONST c_char[8] = {8,16,24,32,40,48,56,64}; byte CONST addr_name[4] = {'T','E','R','M'}; // Serial Interface // This routine sends a serial byte // TUNED FOR 4MHz! // MUST TUNE FOR EACH DEVICE!!! Bank switching changes delays! void send_serial_byte(byte byte_in) { int bitc; // Sending a serial byte is fairly easy // Disable interrupt since data is looped in RS485 chip // Send RS485 enable bit high // Send start bit (LOW) // Delay 104us // Shift data bits (8) onto serial out - wait 104us // Send stop bit (HIGH) // Clear EXT interrupt flag // Re-enable EXT interrupt // Delay 104us for stop bit disable_interrupts(INT_EXT); io_a.data_enable = 1; delay_cycles(1); io_a.serial_out = 0; // Delay 104us minus 10 inst cycles to get first bit out - 10us = 94 delay_us(94); for(bitc = 0; bitc < 8; ++bitc) { // Lets send the bits io_a.serial_out = shift_right(&byte_in,1,0); // Loop takes 14 instructions. @4MHz - 14us (104 - 14 = 90us) delay_us(90); } // Now we send the stop bit and re-enable the interrupts delay_us(6); // Allow last bit to finish 104us (104 - (90 + 8) = 6) io_a.serial_out = 1; // Now we can re-enable our interrupts INTF = FALSE; // Clear the INT bit which would be set above during send enable_interrupts(INT_EXT); // Delay for the rest of the stop bit time // We have 3 inst above plus 3 to return -> 104 - 6 = 98us delay_us(98); io_a.data_enable = 0; // Disable 485 driver } // This routine reads in serial data #INT_EXT void serial_in() { char data_in; int sidx, samp_cnt; // *** 4MHz tuned serial input routine! // Note delay times vary between devices due to bank switching!!! // Lets receive the data at 9600 bps (104us per bit) // We have 1 us per inst at 4MHz // Int Handler takes ~21 instructions // Ensure it was not a glitch (this takes 2 us) if (s_in) { return; } delay_us(120); // Need to delay 1.5B (156us) We already have 21 int instructions = 21us + 3us above // There are 12 inst until the middle sample - remove 12us more so we sync properly // Thus we delay 156-(21 + 3 + 12) = 124us (Subtract 3 more for initial 3us delay) for(sidx=0; sidx<8; sidx++) { // To ensure the bit is read ok, lets sample the bit 3 times at 3us intervals // Majority rules for the value // Lets sample over 15us samp_cnt = s_in; delay_us(3); if (s_in) { samp_cnt++; } delay_us(3); if (s_in) { samp_cnt++; } delay_us(3); shift_right(&data_in, 1, bit_test(samp_cnt,1)); // If samp_cnt = 0|1 -> 0 2|3 -> 1 delay_us(76); // 104us - 28/29 us of instructions (28 inst) for loop = 76us } // That's it! Lets act on the data... // We are looking for the start of a packet. Otherwise we stay in this state and ignore the incoming char if (s_idle) { if ((data_in == '!') || (data_in == '#') || config) { // New Packet - Lets go to the front of the buffer serial_idx = 0; buffer1[serial_idx] = data_in; s_idle = FALSE; } } else { if (++serial_idx == BUFFER_SIZE) { // Buffer Overflow - lets dump it since the checksum won't validate anyway s_idle = TRUE; a_check = FALSE; // Reset the address check for the next packet } else { // If the buffer is being processed, make sure we don't catch up if (!p_idle) { if (serial_idx == process_idx) { // We caught up! Dump the buffer s_idle = TRUE; a_check = FALSE; // Reset the address check for the next packet return; } } // Check for the end of the packet if ((data_in == '\r') || (data_in == '\n')) { go_data = TRUE; // Tell main program to process buffer s_idle = TRUE; } else { // Save the data buffer1[serial_idx] = data_in; } } } } // This routine is used to convert characters for checking the checksum // It simply converts an ASCII hex value into true decimal form // ASCII allows for a quick and dirty conversion routine! byte gethexbyte(char digit) { // This also handles upper or lower case! if(!BIT_TEST(digit,6)) { // Its a number if bit 6 is 0 return(digit & 0x0F); // Simple way to convert 0-9 } else { return((digit & 0x0F) + 9); } } byte ascii_to_hex(byte char1, byte char2) { return ((gethexbyte(char1) << 4) | gethexbyte(char2)); } void move_line(byte start, byte dest, int cols) { byte tbuff[20]; lcd_send_byte(0,start); for(scratch = 0; scratch < cols; scratch++) { while ( bit_test(lcd_read_byte(),7) ) ; // Wait for LCD lcd.rs=1; tbuff[scratch] = lcd_read_byte(); // This will auto increment (Much faster) lcd.rs=0; } // Now lets reset and dump the line on the line above (ie move the line up) lcd_send_byte(0,dest); for(scratch = 0; scratch < cols; scratch++) { lcd_send_byte(1,tbuff[scratch]); // This will auto increment } } void scroll_lcd() { // This routine simply moves the display up 20 chars at a time // 16x2 & 20x2 displays are easy // 20x4, 40x2, and 40x4 take a little more doing // All displays do 0x40 to 0x00 move_line(0xC0,0x80,20); // So we waste 4 chars on a 16x2 display // For lcdtypes 0-2 (20x4, 24x2, 40x2) we need 0x54 to 0x14 if (lcdtype < 3) { // Move 3rd line on a 20x4 if (lcdtype == 0) { // Extra call for 20x4 move_line(0x94,0xC0,20); } // 24x2 display only needs 4 more if (lcdtype == 1) { move_line(0xD4,0x94,4); } else { move_line(0xD4,0x94,20); } } // Lets clear out the bottom line - can be done straight across 0-40 cols // Need to add enable check for 40x4! if (lcdtype == 0) { lcd_send_byte(0,0xD4); // Skip to bottom line (4-line display) (0x80 | 0x54) } else { lcd_send_byte(0,0xC0); // Skip to bottom line (2 line display) (0x80 | 0x40) } for(idx = 0; idx < lcdcols[lcdtype]; idx++) { lcd_send_byte(1, ' '); } } byte get_ansi_number() { int tval1, tval2; // This routine grabs the numbers for the ANSI commands // It is used for both row and column numbers which may be one or two digits tval1 = (buffer1[process_idx++] & 0x0F); // Quick & dirty ASCII conversion tval2 = buffer1[process_idx]; if ((tval2 > '/') && (tval2 < ':')) { // Lets add it together and skip the ; tval1 *= 10; // Move first digit to tens position tval1 += (tval2 & 0x0F); // Grab ones digit & point to next char process_idx++; } return tval1; } // This routine converts an 4-bit hex value into an associated ASCII character byte byte_to_ascii(byte tdata) { // Will automatically wipe out top 4 bits if (tdata <= 9) { return (tdata & 0x3F); } else { return ((tdata - 9) & 0x4F); } } // These routines take a LOT of code (lots of bit twiddling) // Lets make them routines instead of having them inline everywhere void i2c_start_sub() { i2c_start(); } void i2c_stop_sub() { i2c_stop(); } // EEPROM Read & Write Routines void write_ext_eeprom(byte address, byte data) { i2c_start_sub(); i2c_write(0xa0); i2c_write(address); i2c_write(data); i2c_stop_sub(); delay_ms(12); // Can poll instead to ensure we are done? } byte read_ext_eeprom(byte address) { byte data; i2c_start_sub(); i2c_write(0xa0); i2c_write(address); i2c_start_sub(); i2c_write(0xa1); data=i2c_read(0); i2c_stop_sub(); return(data); } void main () { int endbuff, x, y, sx, sy, beep_back, node_total; boolean cursor, wrap, blink, skipgoto, checksum, timeok; char addr_num; // char data_char[2]; int b_time; // We use this in the config and beep/backlight sections OPTION = 0b10000111; set_tris_a(0b11100000); // Setup io pin directions set_tris_b(0b00000001); // Setup LCD and Serial IO directions // Lets set port a at one time since its all outputs io_a = (0b00010101); // We need to initialize the LCD display lcd_init(); // Setup interrupts - but don't enable yet disable_interrupts(GLOBAL); ext_int_edge( H_TO_L ); // Trigger on end of start bit enable_interrupts(INT_EXT); // Initial values s_idle = TRUE; p_idle = TRUE; x = 1; // Default starting position for cursor y = 1; sx = 1; // Init saved cursor position sy = 1; // cursor = FALSE; wrap = FALSE; blink = FALSE; // config = FALSE; timeok = FALSE; // Check serial in If LOW, we probably need to enter config mode if (!s_in) { // Lets make sure the line stays low for 2ms set_rtcc(0x00); while(get_rtcc() < 8) { if (s_in) { // Must be serial, lets bail goto Mod_Init; } } // Okay, lets wait for data from the serial in routine // Lets enable the interrupts! enable_interrupts(GLOBAL); config = TRUE; // Now wait until serial in is brought high // Wait until they hit return a couple times while(!go_data) { } // Lets output the greeting line printf(send_serial_byte, "\nCreative Control Concepts PIC-LCD v1.00\n"); printf(lcd_putc, "LCD OK"); Config_Loop: go_data = FALSE; printf(send_serial_byte, "TERM%c> ", (read_ext_eeprom(0x00) + 0x30)); config = TRUE; // Our command format is !(command) (data) while(!go_data) { // Just wait until a new line is available } config = FALSE; // Avoid extra \n or \r from screwing up next command wrap = FALSE; // Use this as an error flag // Now, lets see what the command was, set the proper value, and return the confirmation // Lets bump up anything lower case. scratch = buffer1[2] & 0x0F; // Quick & dirty ASCII -> num convert idx = buffer1[0] & 0x5F; // Lets get the command letter and make it upper case if need be switch(idx) { case 'B': if (scratch < 3) { // Lets save and confirm write_ext_eeprom(0x01, scratch); } else { // Error! wrap = TRUE; } break; case 'A': if (scratch < 8) { // Lets save and confirm write_ext_eeprom(0x00, scratch); } else { // Error! wrap = TRUE; } break; case 'S': // here we save all 8 bytes with no checking except that 8 bytes are received // Special char id is in buffer[2] if (scratch > 7) { // Bogus char value! wrap = TRUE; break; } // Lets parse out the hex bytes and store them idx = 4; // Data starts at buffer address 4 x = c_char[scratch]; // Custom Char Addr #1 while (idx < 28) { // Lets convert the ASCII into a hex value write_ext_eeprom(x++, ascii_to_hex(buffer1[idx++], buffer1[idx++])); idx++; // Skip space } // Now lets output something to say all is okay idx = 'S'; break; case 'L': if (scratch < 6 && scratch != 3) { // Lets save and confirm write_ext_eeprom(0x02, scratch); } else { // Error! wrap = TRUE; } break; default: // Huh? Have no idea what they typed in wrap = TRUE; break; } if (wrap) { printf(send_serial_byte, "Error\n"); } else { printf(send_serial_byte, "%c:%c\n", idx, (scratch + 0x30)); } goto Config_Loop; } Mod_Init: // Load the configuration from EEPROM addr_num = read_ext_eeprom(0x00) + 0x30; // Convert to ASCII beep_back = read_ext_eeprom(0x01); lcdtype = read_ext_eeprom(0x02); // Set LCD to point to first LCD CGRAM location - it auto increments lcd_send_byte(0, 0x40); // Point to first CG RAM address // Lets read all the custom chars and write them to the LCD for(idx = 8; idx < 72; idx++) { // Read the byte and write it to the LCD lcd_send_byte(1, read_ext_eeprom(idx)); } // Lets clear the display and jump to position 1,1 since we are pointing to CG RAM from above lcd_send_byte(0,1); // Clear the display delay_ms(2); // Lets start totalling up the checksum ahead of time node_total = '#' + '0' + '0' + ' ' + ' ' + 'T' + 'E' + 'R' + 'M'; node_total += addr_num; // Lets enable the interrupts! enable_interrupts(GLOBAL); Buffer_Scan: p_idle = TRUE; a_check = FALSE; // Make sure we run address check only once process_idx = 0; // Lets wait until the buffer has a valid packet for our node // We check here and quite using the buffer if it isn't our packet while((!a_check) || (!go_data)) { // While we wait, lets check if the backlight or beeper needs to be shut off if (timeok && T0IF) { // The RTCC overflowed at some point. T0IF = 0; // Clear overflow bit if (--b_time == 0) { // Time out - lets turn off the output (its inverted) io_a.b_out = 1; timeok = FALSE; // Stop moniotring the interrupt bit } } // Lets see if we get a packet with our address // We can verify the checksum later - this gets us a quicker start // Once we hit the 9th buffer slot, lets check the address for a match // a_check ensures we only run this once // !s_idle || go_data ensures we are either receiving or have received a new buffer // This is required since there is a chance we'll get an entire new buffer (s_idle TRUE) // before we even enter this loop (long previous packet) if (!a_check && (!s_idle || go_data) && (serial_idx > 7)) { // 9th character is now in the buffer a_check = TRUE; if (buffer1[0] == '#') { scratch = 4; checksum = TRUE; } else { scratch = 2; checksum = FALSE; } // Let's check the address. If it isn't ours, we skip for(idx = 0; idx < 5; idx++) { // This is hokey, but it lets me save some RAM and reduce bank switching // in some time critical spots. Trade off. if (BIT_TEST(idx, 2)) { // Check Address Number endbuff = addr_num; } else { // Check Address Name endbuff = addr_name[idx]; } if (endbuff != buffer1[scratch++]) { // Not our packet! s_idle = TRUE; // Reset buffer - scan for packet start go_data = FALSE; // Until the next valid packet goto Buffer_Scan; } } if (checksum) { scratch = ascii_to_hex(buffer1[1], buffer1[2]) + node_total; process_idx = 10; // Give the buffer some breathing room } else { process_idx = 8; // Give the buffer some breathing room } } } // Okay, lets process the packet p_idle = FALSE; // Let the interrupt routine know we are processing endbuff = serial_idx; // So we know where the end of the buffer is! go_data = FALSE; // Lets finish adding and validate the checksum if (checksum) { idx = 10; // Skip first 10 chars in buffer while (idx < endbuff) { scratch += buffer1[idx++]; // This skips the cr at the end } // Need the lower 8 bits to compare if (scratch != 0) { // Bad Checksum goto Buffer_Scan; } } // What is the command? Q or S= switch(buffer1[process_idx++]) { case('Q'): // Lets return the keypad stuff if we should p_idle = TRUE; // We don't need the buffer anymore // Should we send back a checksum? if (checksum) { // Lets build the checksum. We'll cheat and use some predetermined values here scratch = '$' + '0' + '0' + ' ' + 'T' + 'E' + 'R' + 'M' + ' ' + '0' + '0'; // In 8 bits only! scratch += addr_num; // Now lets take the 2's complement of the checksum subtotal result scratch ^= 0xFF; scratch++; } // Now lets send the keypad result via RS-485 delay_ms(50); // give the HCS a chance to get ready for the response send_serial_byte('$'); if (checksum) { printf(send_serial_byte, "%2X", scratch); } printf(send_serial_byte, " TERM%c 00\n", addr_num); break; case('S'): // Lets turn on the backlight for 15 seconds if they selected Auto backlight if (bit_test(beep_back, 1)) { // Its a two - lets turn on the backlight and set the timer io_a.b_out = 0; b_time = 229; // This will give us approx 15 secs (256 (divide) * 256 (rtcc count) * 229) timeok = TRUE; } // Lets send the data to the LCD and process any \ or Esc commands while(++process_idx < endbuff) { // Lets scan the buffer and output to the LCD // First lets check for an escape sequence (\) scratch = buffer1[process_idx]; if (scratch == '\\') { switch (buffer1[++process_idx]) { case 'b': // Beep if we should if (beep_back == 0) { io_a.b_out = 0; b_time = 3; set_rtcc(0x00); // Give us a more exact time timeok = TRUE; } break; case 'n': // New Line (linefeed + carriage return) x=1; if (y == lcdlines[lcdtype]) { scroll_lcd(); } else { y++; } lcd_gotoxy(1,y); break; case 'r': // Carriage Return only x=1; lcd_gotoxy(1,y); break; case 't': // Tab 4, 8, 12, 16, 20... x += 4 - (x % 4); if (x > lcdcols[lcdtype]) { x = lcdcols[lcdtype]; } lcd_gotoxy(x,y); break; case 'x': // Next two bytes are a hex number! // convert ascii to hex byte scratch = ascii_to_hex(buffer1[++process_idx], buffer1[++process_idx]); // Lets output the character lcd_send_byte(1,scratch); if (++x > lcdcols[lcdtype]) { x = 1; if (!wrap) { if (y == lcdlines[lcdtype]) { scroll_lcd(); } else { y++; } } lcd_gotoxy(x,y); } break; case 'e': // Escape char! Could be lots of things! // ESC[(idx);(scratch) process_idx += 2; // Skip [ sign scratch = 1; // Defaults (idx is set below) // Lets figure out what digits we have here idx = buffer1[process_idx]; if ((idx > '/') && (idx < ':')) { // We've got one number! idx = get_ansi_number(); if (buffer1[process_idx] == ';') { process_idx++; // Point to next number scratch = get_ansi_number(); } } else { idx = 1; } // Now lets execute the proper routine based on the command letter skipgoto = FALSE; // Most commands need a gotoxy call switch (buffer1[process_idx]) { case 'A': y -= idx; if (y < 1) { y = 1; } break; case 'B': y += idx; if (y > lcdlines[lcdtype]) { y = lcdlines[lcdtype]; } break; case 'C': x += idx; if (x > lcdcols[lcdtype]) { x = lcdcols[lcdtype]; } break; case 'D': x -= idx; if (x < 1) { x = 1; } break; case 'H': y = idx; x = scratch; break; case 'f': y = idx; x = scratch; break; case 'j': y = idx; x = scratch; break; case 'K': for(idx = x; idx <= lcdcols[lcdtype]; idx++) { lcd_send_byte(1, ' '); } break; case 'J': if (idx == 2) { // Clear Screen lcd_send_byte(0,1); delay_ms(2); x = 1; y = 1; } skipgoto = TRUE; break; case 's': sx = x; sy = y; skipgoto = TRUE; break; case 'u': x = sx; y = sy; break; case 'h': if (idx == 7) { wrap = TRUE; } skipgoto = TRUE; break; case 'l': if (idx == 7) { wrap = FALSE; } skipgoto = TRUE; break; case 'b': // Turn the cursor off cursor = FALSE; lcd_send_byte(0,(0b00001100 | blink)); skipgoto = TRUE; break; case 'c': // Turn the cursor on cursor = TRUE; lcd_send_byte(0,(0b00001110 | blink)); skipgoto = TRUE; break; case 'x': // Blink cursor blink = TRUE; if (cursor) { lcd_send_byte(0,0b00001111); } else { lcd_send_byte(0,0b00001101); } skipgoto = TRUE; break; case 'y': // Don't blink cursor blink = FALSE; if (cursor) { lcd_send_byte(0,0b00001110); } else { lcd_send_byte(0,0b00001100); } skipgoto = TRUE; break; case 'o': // Backlight pin on if (beep_back == 1) { io_a.b_out = 0; } skipgoto = TRUE; break; case 'p': // Backlight pin off if (beep_back == 1) { io_a.b_out = 1; } skipgoto = TRUE; break; default: break; } if (!skipgoto) { lcd_gotoxy(x,y); } break; default: break; } } else { // Lets output the character lcd_send_byte(1,scratch); if (++x > lcdcols[lcdtype]) { x = 1; if (!wrap) { if (y == lcdlines[lcdtype]) { scroll_lcd(); } else { y++; } } lcd_gotoxy(x,y); } } } // while loop break; default: break; } // Command case (Q, S, ?) goto Buffer_Scan; }