/* HCS-II LCD-Term Copyright 2000 Creative Control Concepts Current Version: 0.9b Revision History ================ v1.0 10/27/00 - Initial Release written by Mike Baptiste The HCS-II LCD-Term is a self contained LCD-Terminal This terminal contains a customizable 12 button keypad and 20x4 backlit LCD display. The terminal is based on the Rabbit 2000 microprocessor. It contains an RS-485 interface, 128K battery backed SRAM, 256K of Flash memory, real-time clock, and a variable frequency speaker. The LCD-Term emulates an HCS-II LCD-Link. Up to 8 can be connected to the HCS-II RS-485 Network. It includes the following features: - Complete set of cursor and text control codes - Multi-feature LCD backlight o Turn on/off with control codes o Auto mode where it turns on for keypress and/or new display data o Time mode where it turns on/off at preset times - Multi-tone speaker with variable frequency, volume, and length beeps o Includes alarm beep which will sound until a key is pressed - Keypad press tones can be turned on/off - Circular ring buffer ensures back to back command packets can be handled - 128 Pre stored screens that are called using simple control commands o Screens are stored using the included serial port o Screens are stored in FLASH memory for retention during power outages - Customizable characters accessible with simple control codes - Built in RTC allows time & date to be displayed using %x char codes o Includes versions that auto updated in place o RTC can be updated by HCS-II using control codes - 4 buffered inputs can also be used to simulate keypresses - 4 high current outputs can be controlled using simple control codes - Cursor and cursor blinking can be turned on/off - Display contrast can be controlled via control codes or preset via RS-232 - Display can be turned on and off via control codes - Drop in replacement for LCD-Link or AMAN Jr based LCD Displays - Terminal can be ordered with custom key labels - Terminal can be surface or panel mounted and is water resistant - Battery backup allows terminal to return to previous state when power returns o This is a configurable option allowing for power resets to reset the device Complete Command Set ==================== 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 HH is always 00. The N's will be 0 to 8 hex digits representing the last 8 key presses since the last query. Note that buttons 13-16 are controlled via buffered inputs. 1-12 are from the built in keypad S= which returns nothing. Anything following the S will be printed on the LCD display or processed as a control code. The LCD-Term has a 256 byte buffer for incoming messages. Note the maximum # of characters that can be sent by the HCS-II is 96. The 256 bytes allow for the buffering of multiple command packets. R which will RESET the board to the configuration stored in FLASH */ #define VERSION '0.9b' #define TRUE 1 #define FALSE 0 // MUST stay 256 since the ptr is 8-bit and loops properly #define SERIAL_BUFF_SIZE 256 #define TX_BUFF_SIZE 32 #define KEY_BUFF_SIZE 8 // RS-485 & RS-232 buffer sizes #define BINBUFSIZE 127 #define BOUTBUFSIZE 31 #define CINBUFSIZE 127 #define COUTBUFSIZE 127 #define NODE_ADDR_LENGTH 5 #define REAL_DATA_STARTS 6 // Offset for space and node addr = 6 #define START_CHECKSUM 9 // Start saving checksum values after 10 chars // Define the FLASH config structure // Data is read from Flash sequentially and stored in this structure // during initialization. // Here we store the various parms for each LCD type // 0 = 20x4 // 1 = 24x2 // 2 = 40x2 // 3 = 40x4 // 4 = 16x2 // 5 = 20x2 #define lcdtype 0 // 20x4 // All values -1 since we do 0 to x char lcdcols[6] = {19,23,39,39,15,19}; char lcdlines[6] = {3,1,1,3,1,1}; // Globals (Boo hiss! :) ) int lcd_x, lcd_y; // Make store as big as it can be 4x40 max size char lcdstore[40][4]; int buff_end_ptr, buff_read_ptr; char SerBuffer[SERIAL_BUFF_SIZE]; // FLASH Config data structure typedef struct { char netaddr; char light_onstart; // Determines if backlight comes on after reset char retain_display; // Retains display in event of a power failure char beep_on_key_press; // Beep default freq on keypress char autobacklight; char lcd_wrap; int rs485_baud; unsigned long rs232_baud; unsigned int beeper_volume; unsigned int beeper_frequency; } config_struct; // Get_User_Config // // This routine communicates with a user via the RS_232 port to allow // for easy configuration of the LCD Terminal void Get_User_Config() {} // gethexbyte // // 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! char gethexbyte(char digit) { if(!BIT(&digit,6)) // Its a number if bit 6 is 0 return(digit & 0x0F); // Simple way to convert 0-9 else return((digit & 0x0F) + 9); } // Convert two characters into real hex value char ascii_to_hex(char char1, char char2) { return ((gethexbyte(char1) << 4) | gethexbyte(char2)); } // This routine clears the LCD and storage memory void clear_lcd() { int tx, ty; dispClear(); lcd_x = 0; lcd_y = 0; for (ty = 0;ty <= lcdlines[lcdtype];ty++) { for (tx = 0;tx <= lcdcols[lcdtype];tx++) { lcdstore[tx][ty] = ' '; } } } // get_ansi_number // 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 = (SerBuffer[buff_read_ptr] & 0x0F); // Quick & dirty ASCII conversion if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } tval2 = SerBuffer[buff_read_ptr]; 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 if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } } return tval1; } // scroll_lcd // // This routine will scroll the LCD using the stored data for the LCD // Thus it simply overwrites the data on teh screen // // Note we YIELD after each char to ensure data or keypresses are not // missed void scroll_lcd() { } // Main routine main() { // Lets setup the storage we need // Serial task variables static char TxBuffer[TX_BUFF_SIZE]; static int buff_write_ptr; static char char_in, serial_idle, checksum, packet_ctr, packet_idx; static char check_val, bad_hcs_packet, node_total, check_out, echo_in; static char thisnode[5], checksum_in[2]; static char KeyPressBuff[KEY_BUFF_SIZE]; static int KeyIndex, lcd_sx, lcd_sy, firstpass, tx_ptr; static int i,j,k; // Scratch vars for loops static char skipgoto, cursor_on; // Current LCD Position static config_struct nodeconf; static char key_in; // Input buffer for keypresses static unsigned int beep_vol, beep_freq, beep_dur; static unsigned int backlight_duration; // Lets initialize the system brdInit(); // Initialize the Intellicom Board // Initialize variables beep_dur = 0; backlight_duration = 0; buff_read_ptr = 0x00; buff_end_ptr = 0x00; buff_write_ptr = 0x01; // Need to differ from read ptr packet_ctr = 0; serial_idle = TRUE; thisnode[0] = 'T'; thisnode[1] = 'E'; thisnode[2] = 'R'; thisnode[3] = 'M'; KeyIndex = 0; // Key Buffer Pointer for (i=0;i= SERIAL_BUFF_SIZE) buff_write_ptr = 0x00; } // if (!bad_hcs_packet) } // if (packet_ctr < (REAL_DATA_STARTS + packet_idx)) serial_idle = TRUE; } else { // We have a regular char - lets process it. // Lets store the checksum bytes or validate the node address if necessary if (packet_ctr < 2 && checksum) { checksum_in[packet_ctr] = char_in; } else { if ((packet_ctr >= packet_idx) && (packet_ctr < (packet_idx + NODE_ADDR_LENGTH))) { // Its an node address character - lets check it if (thisnode[(packet_ctr-packet_idx)] != char_in) { // It isn't our packet - break out and wait for next one bad_hcs_packet = TRUE; } } } // if (packet_ctr < 2 && checksum) // Lets add the character into the checksum value if necessary if (checksum) { // Lets add the char to the checksum value if needed if (packet_ctr >= START_CHECKSUM) { check_val += char_in; } } // Save the data if we need to if (!bad_hcs_packet && (packet_ctr >= (REAL_DATA_STARTS + packet_idx))) { SerBuffer[buff_write_ptr] = char_in; if (++buff_write_ptr >= SERIAL_BUFF_SIZE) buff_write_ptr = 0x00; } packet_ctr++; // Increment the position counter } // if ((char_in == '\r') || (char_in == '\n')) } // if (buff_write_ptr == buff_read_ptr) if (bad_hcs_packet == TRUE) { // Lets move the pointer back to the 1st byte after buff_end_ptr buff_write_ptr = buff_end_ptr; if (++buff_write_ptr >= SERIAL_BUFF_SIZE) buff_write_ptr = 0x00; serial_idle = TRUE; bad_hcs_packet = FALSE; // Reset the flag } // if (bad_hcs_packet) } // if (serial_idle) } // This task checks the beeper globals. If the duration is non-zero, it // turns on the beeper for the specified duration // // Globals used are beep_vol, beep_freq, and beep_dur // // beep_dur is set to 0 after reset. If it is none zero, a beep is // initiated for beep_dur time at beep_freq frequency costate beeper always_on { waitfor(beep_dur != 0); spkrOut(beep_freq, beep_vol); waitfor(DelayMs(beep_dur)); spkrOut(beep_freq, 0); beep_dur = 0; } // This task checks to see if the backlight duration is not 0 and // then turns on the backlight for that many seconds costate backlight always_on { waitfor(backlight_duration != 0); dispBacklight(1); waitfor(DelaySec(backlight_duration)); dispBacklight(0); backlight_duration = 0; } // These tasks scans the keypad for any keypress and stores the result in the // keypress buffer costate key_scan always_on { keyProcess(); waitfor(DelayMs(10)); } costate key_read always_on { waitfor(key_in = keyGet()); // Only 8 keypresses are stored on each query // KeyIndex == 0 means no keypresses! We increment after storage if (KeyIndex < KEY_BUFF_SIZE) KeyPressBuff[KeyIndex++] = key_in; // Beep after each keypress if configured if (nodeconf.beep_on_key_press != 0) beep_dur = 100; } // This task reads the inputs and creates a keypress if an output toggles // Note the input can be 'flipped' so a low pulse (i.e. pulled up) // initiates a keypress. costate input_scan always_on { yield; } // This is the main task which will take any incoming network traffic and // process it. New network data is indicated by the Ring End ptr not // equaling the ring_read pointer costate process_packet always_on { // Lets wait until we have new data waitfor(buff_read_ptr != buff_end_ptr); // Increment to command char off the 0x0D (or 0x00 on first packet) if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; switch(SerBuffer[buff_read_ptr]) { case('Q'): // Return keypress data to HCS-II strcpy(TxBuffer, "$"); // Should we send back a checksum? if (checksum == TRUE) { // Lets build the checksum. We'll cheat and use some predetermined values here check_out = '$' + '0' + '0' + ' ' + 'T' + 'E' + 'R' + 'M' + ' ' + '0' + '0'; // In 8 bits only! check_out += nodeconf.netaddr; if (KeyIndex != 0) { check_out += ' '; // Space between 00 and KeyPresses for(i=0;i= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } while(buff_read_ptr != buff_end_ptr) { // Lets scan the buffer and output to the LCD // First lets check for an escape sequence (\) i = SerBuffer[buff_read_ptr]; if (i == '\\') { if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } switch (SerBuffer[buff_read_ptr]) { case 'b': beep_dur = 100; break; case 'f': clear_lcd(); break; case 'n': // New Line (linefeed + carriage return) lcd_x=0; if (lcd_y == lcdlines[lcdtype]) { scroll_lcd(); } else { lcd_y++; } dispGoto(0,lcd_y); break; case 'r': // Carriage Return only lcd_x=0; dispGoto(0,lcd_y); break; case 't': // Tab 4, 8, 12, 16, 20... lcd_x += 4 - ((lcd_x+1) % 4); if (lcd_x > lcdcols[lcdtype]) { lcd_x = lcdcols[lcdtype]; } dispGoto(lcd_x,lcd_y); break; case 'x': // Next two bytes are a hex number! // convert ascii to hex byte if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } i = SerBuffer[buff_read_ptr]; if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } j = SerBuffer[buff_read_ptr]; // Lets output the character lcdstore[lcd_x][lcd_y] = ascii_to_hex(i,j); dispPutc(lcdstore[lcd_x][lcd_y]); if (++lcd_x > lcdcols[lcdtype]) { lcd_x = 0; if (nodeconf.lcd_wrap == FALSE) { if (lcd_y == lcdlines[lcdtype]) { scroll_lcd(); } else { lcd_y++; } } dispGoto(lcd_x,lcd_y); } break; case 'e': // Escape char! Could be lots of things! // ESC[(j);(i) if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { buff_read_ptr += 2; if (buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } i = 0; // Defaults (idx is set below) // Lets figure out what digits we have here j = SerBuffer[buff_read_ptr]; if ((j > '/') && (j < ':')) { // We've got one number! j = get_ansi_number(); if (SerBuffer[buff_read_ptr] == ';') { if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } i = get_ansi_number(); } } else { j = 0; } // Now lets execute the proper routine based on the command letter skipgoto = FALSE; // Most commands need a gotoxy call switch (SerBuffer[buff_read_ptr]) { case 'A': lcd_y -= j; if (lcd_y < 0) lcd_y = 0; break; case 'B': lcd_y += j; if (lcd_y > lcdlines[lcdtype]) lcd_y = lcdlines[lcdtype]; break; case 'C': lcd_x += j; if (lcd_x > lcdcols[lcdtype]) { lcd_x = lcdcols[lcdtype]; } break; case 'D': lcd_x -= j; if (lcd_x < 0) { lcd_x = 0; } break; case 'H': lcd_y = j; lcd_x = i; break; case 'f': lcd_y = j; lcd_x = i; break; case 'j': lcd_y = j; lcd_x = i; break; case 'K': for(j = lcd_x; j <= lcdcols[lcdtype]; j++) { dispPutc(' '); } break; case 'J': if (j == 2) { // Clear Screen clear_lcd(); } skipgoto = TRUE; break; case 's': lcd_sx = lcd_x; lcd_sy = lcd_y; skipgoto = TRUE; break; case 'u': lcd_x = lcd_sx; lcd_y = lcd_sy; break; case 'h': if (j == 7) { nodeconf.lcd_wrap = TRUE; } skipgoto = TRUE; break; case 'l': if (j == 7) { nodeconf.lcd_wrap = FALSE; } skipgoto = TRUE; break; case 'b': // Turn the cursor off dispCursor(DISP_CUROFF); skipgoto = TRUE; break; case 'c': // Turn the cursor on cursor_on = TRUE; dispCursor(DISP_CURON); skipgoto = TRUE; break; case 'x': // Blink cursor (will turn cursor on) dispCursor(DISP_CURBLINK); cursor_on = TRUE; skipgoto = TRUE; break; case 'y': // Stop blinking cursor if (cursor_on = TRUE) { dispCursor(DISP_CURON); } else { dispCursor(DISP_CUROFF); } skipgoto = TRUE; break; case 'o': // Backlight on dispBacklight(1); skipgoto = TRUE; break; case 'p': // Backlight off dispBacklight(0); skipgoto = TRUE; break; default: break; } if (!skipgoto) { dispGoto(lcd_x,lcd_y); } break; default: break; } // switch (SerBuffer[buff_read_ptr]) } else { // Lets output the character dispPutc(i); lcdstore[lcd_x][lcd_y] = i; if (++lcd_x > lcdcols[lcdtype]) { lcd_x = 0; if (nodeconf.lcd_wrap == FALSE) { if (lcd_y == lcdlines[lcdtype]) { scroll_lcd(); } else { lcd_y++; } } dispGoto(lcd_x,lcd_y); } // if (++lcd_x > lcdcols[lcdtype]) } // if (i == '\\') // Lets point to the next char in the buffer if (SerBuffer[buff_read_ptr] != 0x0D && buff_read_ptr != buff_end_ptr) { if (++buff_read_ptr >= SERIAL_BUFF_SIZE) buff_read_ptr = 0x00; } yield; // Let other routines have a go } // while(buff_read_ptr != buff_end_ptr) break; default: break; } // switch(SerBuffer[buff_read_ptr]) { } // costate } // while(1) } // main()