/** \file hd44780.c * Base driver module for Hitachi HD44780 based LCD displays. This is * a modular driver that readily allows support for alternative HD44780 * designs to be added in a flexible and maintainable manner. * * This driver also supports the aggregation of multiple displays to form * a single virtual display. e.g., Four 16x2 displays can be combined * to form a 16x8 display. * * To add support for additional HD44780 connections: * \li Add a connection type and mapping to hd44780-drivers.h * \li Call your initialization routine * \li Create the low-level driver (use hd44780-ext8bit.c as a starting point) * \li Modify the makefile */ /* Modular driver created and generic support for multiple displays added * Dec 1999, Benjamin Tse * * Modified July 2000 by Charles Steinkuehler to use one of 3 methods for delay * timing. I/O reads, gettimeofday, and nanosleep. Of the three, nanosleep * seems to work best, so that's what is set by default. * * Modified May 2001 by Joris Robijn to add Keypad support. * * Character mapping for correct display of some special ASCII chars added * Sep 2001, Mark Haemmerling . * * Modified October 2001 to read the configfile. * * Modified August 2006 by Pillon Matteo to * allow user selecting charmaps * * Moved the delay timing code by Charles Steinkuehler to timing.h. * Guillaume Filion , December 2001 * * This file is released under the GNU General Public License. Refer to the * COPYING file distributed with this package. * * Copyright (c) 1995,1999,2000 Benjamin Tse * 1997 Matthias Prinke * 1998 Richard Rognlie * 1999 Andrew McMeikan * 2000 Charles Steinkuehler * 2001 Joris Robijn * 2001 Guillaume Filion * 2001 Mark Haemmerling * 2006 Matteo Pillon * 2007 Peter Marschall */ // Uncomment one of the lines below to select your desired delay generation // mechanism. If both defines are commented, the original I/O read timing // loop is used. Using DELAY_NANOSLEEP seems to provide the best performance. //#define DELAY_GETTIMEOFDAY #define DELAY_NANOSLEEP //#define DELAY_IOCALLS // Default parallel port address #define LPTPORT 0x378 // Autorepeat values #define KEYPAD_AUTOREPEAT_DELAY 500 #define KEYPAD_AUTOREPEAT_FREQ 15 #include #include #include #include #include #include #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "lcd.h" #include "lcd_lib.h" #include "hd44780.h" #include "report.h" #include "adv_bignum.h" #include "timing.h" #include "hd44780-low.h" #include "hd44780-drivers.h" #include "hd44780-charmap.h" // Only one alternate delay method at a time, please ;-) #if defined DELAY_GETTIMEOFDAY # undef DELAY_NANOSLEEP #elif defined DELAY_NANOSLEEP # include # include #endif static char *defaultKeyMapDirect[KEYPAD_MAXX] = { "A", "B", "C", "D", "E" }; static char *defaultKeyMapMatrix[KEYPAD_MAXY][KEYPAD_MAXX] = { { "1", "2", "3", "A", "E" }, { "4", "5", "6", "B", "F" }, { "7", "8", "9", "C", "G" }, { "*", "0", "#", "D", "H" }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL, NULL }}; /* Vars for the server core */ MODULE_EXPORT char * api_version = API_VERSION; MODULE_EXPORT int stay_in_foreground = 0; MODULE_EXPORT int supports_multiple = 1; // yes, we have no global variables (except for constants) MODULE_EXPORT char *symbol_prefix = "HD44780_"; /* Internal functions */ void HD44780_position(Driver *drvthis, int x, int y); static void uPause(PrivateData *p, int usecs); unsigned char HD44780_scankeypad(PrivateData *p); static int parse_span_list(int *spanListArray[], int *spLsize, int *dispOffsets[], int *dOffsize, int *dispSizeArray[], const char *spanlist); /** * Initialize the driver. * Initialize common part of drive & call sub-initialization * routine depending on connection type. * \param drvthis Pointer to driver structure. * \retval 0 Success. * \retval <0 Error. */ MODULE_EXPORT int HD44780_init(Driver *drvthis) { // TODO: remove the two magic numbers below // TODO: single point of return char buf[40]; const char *s; int i = 0; int (*init_fn)(Driver *drvthis) = NULL; int if_type = IF_TYPE_UNKNOWN; int tmp; PrivateData *p; // Alocate and store private data p = (PrivateData *) calloc(1, sizeof(PrivateData)); if (p == NULL) return -1; if (drvthis->store_private_ptr(drvthis, p)) return -1; // initialize private data p->cellheight = 8; /* Do not change this !!! This is a controller property, not a display property !!! */ p->cellwidth = 5; p->ccmode = standard; //// READ THE CONFIG FILE p->port = drvthis->config_get_int(drvthis->name, "port", 0, LPTPORT); p->ext_mode = drvthis->config_get_bool(drvthis->name, "extendedmode", 0, 0); p->have_keypad = drvthis->config_get_bool(drvthis->name, "keypad", 0, 0); p->have_backlight = drvthis->config_get_bool(drvthis->name, "backlight", 0, 0); p->have_output = drvthis->config_get_bool(drvthis->name, "outputport", 0, 0); p->delayMult = drvthis->config_get_int(drvthis->name, "delaymult", 0, 1); p->delayBus = drvthis->config_get_bool(drvthis->name, "delaybus", 0, 1); p->lastline = drvthis->config_get_bool(drvthis->name, "lastline", 0, 1); p->nextrefresh = 0; p->refreshdisplay = drvthis->config_get_int(drvthis->name, "refreshdisplay", 0, 0); p->nextkeepalive = 0; p->keepalivedisplay = drvthis->config_get_int(drvthis->name, "keepalivedisplay", 0, 0); // Get and search for the connection type s = drvthis->config_get_string(drvthis->name, "ConnectionType", 0, "4bit"); for (i = 0; (connectionMapping[i].name != NULL) && (strcasecmp(s, connectionMapping[i].name) != 0); i++); if (connectionMapping[i].name == NULL) { report(RPT_ERR, "%s: unknown ConnectionType: %s", drvthis->name, s); return -1; // fatal error } else { /* set connection type */ p->connectiontype = connectionMapping[i].connectiontype; report(RPT_INFO,"HD44780: using ConnectionType: %s", connectionMapping[i].name); if_type = connectionMapping[i].if_type; init_fn = connectionMapping[i].init_fn; } // Get and parse vspan only when specified s = drvthis->config_get_string(drvthis->name, "vspan", 0, ""); if (s[0] != '\0') { if (parse_span_list(&(p->spanList), &(p->numLines), &(p->dispVOffset), &(p->numDisplays), &(p->dispSizes), s) == -1) { report(RPT_ERR, "%s: invalid vspan value: %s", drvthis->name, s); return -1; } } // Get and parse size s = drvthis->config_get_string(drvthis->name, "size", 0, "20x4"); if (sscanf(s, "%dx%d", &(p->width), &(p->height)) != 2 || (p->width <= 0) || (p->width > LCD_MAX_WIDTH) || (p->height <= 0) || (p->height > LCD_MAX_HEIGHT)) { report(RPT_ERR, "%s: cannot read Size %s", drvthis->name, s); } /* set contrast */ tmp = drvthis->config_get_int(drvthis->name, "Contrast", 0, DEFAULT_CONTRAST); if ((tmp < 0) || (tmp > 1000)) { report(RPT_WARNING, "%s: Contrast must be between 0 and 1000; using default %d", drvthis->name, DEFAULT_CONTRAST); tmp = DEFAULT_CONTRAST; } p->contrast = tmp; /* set brightness */ tmp = drvthis->config_get_int(drvthis->name, "Brightness", 0, DEFAULT_BRIGHTNESS); if ((tmp < 0) || (tmp > 1000)) { report(RPT_WARNING, "%s: Brightness must be between 0 and 1000; using default %d", drvthis->name, DEFAULT_BRIGHTNESS); tmp = DEFAULT_BRIGHTNESS; } p->brightness = tmp; /* set backlight-off "brightness" */ tmp = drvthis->config_get_int(drvthis->name, "OffBrightness", 0, DEFAULT_OFFBRIGHTNESS); if ((tmp < 0) || (tmp > 1000)) { report(RPT_WARNING, "%s: OffBrightness must be between 0 and 1000; using default %d", drvthis->name, DEFAULT_OFFBRIGHTNESS); tmp = DEFAULT_OFFBRIGHTNESS; } p->offbrightness = tmp; // default case for when spans aren't indicated if (p->numLines == 0) { if ((p->spanList = (int *) calloc(sizeof(int), p->height)) != NULL) { int i; for (i = 0; i < p->height; ++i) { p->spanList[i] = 1; p->numLines = p->height; } } else report(RPT_ERR, "%s: error allocing", drvthis->name); } else { // sanity check against p->height if (p->numLines != p->height) report(RPT_ERR, "%s: height in Size does not match vSpan", drvthis->name); } if (p->numDisplays == 0) { if (((p->dispVOffset = (int *) calloc(1, sizeof(int))) != NULL) && ((p->dispSizes = (int *) calloc(1, sizeof(int))) != NULL)) { p->dispVOffset[0] = 0; p->dispSizes[0] = p->height; p->numDisplays = 1; } else report(RPT_ERR, "%s: error mallocing", drvthis->name); } if (timing_init() == -1) { report(RPT_ERR, "%s: timing_init() failed (%s)", drvthis->name, strerror(errno)); return -1; } #if defined DELAY_NANOSLEEP // Change to Round-Robin scheduling for nanosleep { // Set priority to 1 struct sched_param param; param.sched_priority = 1; if ((sched_setscheduler(0, SCHED_RR, ¶m)) == -1) { report(RPT_ERR, "%s: sched_setscheduler() failed (%s)", drvthis->name, strerror(errno)); return -1; } } #endif // Allocate framebuffer p->framebuf = (unsigned char *) calloc(p->width * p->height, sizeof(char)); if (p->framebuf == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer", drvthis->name); //HD44780_close(); return -1; } // Allocate and clear the buffer for incremental updates p->backingstore = (unsigned char *) calloc(p->width * p->height, sizeof(char)); if (p->backingstore == NULL) { report(RPT_ERR, "%s: unable to allocate framebuffer backing store", drvthis->name); return -1; } // Keypad ? if (p->have_keypad) { int x, y; // Read keymap for (x = 0; x < KEYPAD_MAXX; x++) { char buf[40]; // First fill with default value p->keyMapDirect[x] = defaultKeyMapDirect[x]; // Read config value sprintf(buf, "keydirect_%1d", x+1); s = drvthis->config_get_string(drvthis->name, buf, 0, NULL); // Was a key specified in the config file ? if (s) { p->keyMapDirect[x] = strdup(s); report(RPT_INFO, "HD44780: Direct key %d: \"%s\"", x, s); } } for (x = 0; x < KEYPAD_MAXX; x++) { for (y = 0; y < KEYPAD_MAXY; y++) { char buf[40]; // First fill with default value p->keyMapMatrix[y][x] = defaultKeyMapMatrix[y][x]; // Read config value sprintf(buf, "keymatrix_%1d_%d", x+1, y+1); s = drvthis->config_get_string(drvthis->name, buf, 0, NULL); // Was a key specified in the config file ? if (s) { p->keyMapMatrix[y][x] = strdup(s); report(RPT_INFO, "HD44780: Matrix key %d %d: \"%s\"", x, y, s); } } } } // Get configured charmap char conf_charmap[MAX_CHARMAP_NAME_LENGTH]; strncpy(conf_charmap, drvthis->config_get_string(drvthis->name, "charmap", 0, "hd44780_default"), MAX_CHARMAP_NAME_LENGTH); conf_charmap[MAX_CHARMAP_NAME_LENGTH-1] = '\0'; p->charmap = 0; for (i = 0; i < (sizeof(available_charmaps)/sizeof(struct charmap)); i++) { if (strcasecmp(conf_charmap, available_charmaps[i].name) == 0) { p->charmap = i; break; } } if (p->charmap != i) { report(RPT_ERR, "%s: Charmap %s is unknown", drvthis->name, conf_charmap); report(RPT_ERR, "%s: Available charmaps:", drvthis->name); for (i = 0; i < (sizeof(available_charmaps)/sizeof(struct charmap)); i++) { report(RPT_ERR, " %s", available_charmaps[i].name); } return -1; } report(RPT_INFO, "%s: Using %s charmap", drvthis->name, available_charmaps[p->charmap].name); // Output latch state - init to a non-valid value p->output_state = 999999; // allocate local function pointers if ((p->hd44780_functions = (HD44780_functions *) calloc(1, sizeof(HD44780_functions))) == NULL) { report(RPT_ERR, "%s: error mallocing", drvthis->name); return -1; } // pre-set local function pointers (may be overridden by local init function) p->hd44780_functions->uPause = uPause; p->hd44780_functions->drv_report = report; p->hd44780_functions->drv_debug = debug; p->hd44780_functions->senddata = NULL; p->hd44780_functions->backlight = NULL; p->hd44780_functions->set_contrast = NULL; p->hd44780_functions->readkeypad = NULL; p->hd44780_functions->scankeypad = NULL; p->hd44780_functions->output = NULL; p->hd44780_functions->close = NULL; // Do local (=connection type specific) display init if (init_fn(drvthis) != 0) return -1; // consistency check: fail if local senddata function was not defined if (p->hd44780_functions->senddata == NULL) { report(RPT_ERR, "%s: incomplete functions for connection type"); return -1; } // set scankeypad function if local readkeypad function is defined if ((p->hd44780_functions->readkeypad != NULL) && (p->hd44780_functions->scankeypad == NULL)) { p->hd44780_functions->scankeypad = HD44780_scankeypad; } // consistency check: no local keypad function => no keypad if (p->hd44780_functions->scankeypad == NULL) p->have_keypad = 0; // consistency check: no local backlight function => no backlight if (p->hd44780_functions->backlight == NULL) p->have_backlight = 0; // consistency check: no local output function => no output if (p->hd44780_functions->output == NULL) p->have_output = 0; // set contrast HD44780_set_contrast(drvthis, p->contrast); // Display startup parameters on the LCD HD44780_clear(drvthis); sprintf(buf, "HD44780 %dx%d", p->width, p->height); HD44780_string(drvthis, 1, 1, buf); switch(if_type) { case IF_TYPE_USB: sprintf(buf, "USB %s%s%s", (p->have_backlight?" bl":""), (p->have_keypad?" key":""), (p->have_output?" out":"") ); break; case IF_TYPE_SERIAL: sprintf(buf, "SERIAL %s%s%s", (p->have_backlight?" bl":""), (p->have_keypad?" key":""), (p->have_output?" out":"") ); break; case IF_TYPE_I2C: sprintf(buf, "I2C %s%s%s", (p->have_backlight?" bl":""), (p->have_keypad?" key":""), (p->have_output?" out":"") ); break; case IF_TYPE_TCP: sprintf(buf, "TCP %s%s%s", (p->have_backlight?" bl":""), (p->have_keypad?" key":""), (p->have_output?" out":"") ); break; case IF_TYPE_PARPORT: default: sprintf(buf, "LPT 0x%x%s%s%s", p->port, (p->have_backlight?" bl":""), (p->have_keypad?" key":""), (p->have_output?" out":"") ); } HD44780_string(drvthis, 1, 2, buf); HD44780_flush(drvthis); sleep(2); return 1; } /** * Common initialisation sequence: * - set twoline mode, small (5x8) characters & 4- or 8-bit mode * - set display on & cursor off, not blinking, * - set output mode to left-to-right & turn off display scolling * - clear display & move cursor home * . * \param p Pointer to PrivateData structure. * \param if_bit Command bits for 4- resp. 8-bit mode */ void common_init(PrivateData *p, unsigned char if_bit) { if (p->ext_mode) { // Set up extended mode p->hd44780_functions->senddata(p, 0, RS_INSTR, FUNCSET | if_bit | TWOLINE | SMALLCHAR | EXTREG); p->hd44780_functions->uPause(p, 40); p->hd44780_functions->senddata(p, 0, RS_INSTR, EXTMODESET | FOURLINE); p->hd44780_functions->uPause(p, 40); } p->hd44780_functions->senddata(p, 0, RS_INSTR, FUNCSET | if_bit | TWOLINE | SMALLCHAR); p->hd44780_functions->uPause(p, 40); p->hd44780_functions->senddata(p, 0, RS_INSTR, ONOFFCTRL | DISPON | CURSOROFF | CURSORNOBLINK); p->hd44780_functions->uPause(p, 40); p->hd44780_functions->senddata(p, 0, RS_INSTR, CLEAR); p->hd44780_functions->uPause(p, 1600); p->hd44780_functions->senddata(p, 0, RS_INSTR, ENTRYMODE | E_MOVERIGHT | NOSCROLL); p->hd44780_functions->uPause(p, 40); p->hd44780_functions->senddata(p, 0, RS_INSTR, HOMECURSOR); p->hd44780_functions->uPause(p, 1600); } /** * Delay a number of microseconds. * \param p Pointer to PrivateData structure. * \param usecs Number of micro-seconds to sleep. */ void uPause(PrivateData *p, int usecs) { timing_uPause(usecs * p->delayMult); } /** * Close the driver (do necessary clean-up). * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void HD44780_close(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; if (p != NULL) { if (p->hd44780_functions->close != NULL) p->hd44780_functions->close(p); if (p->framebuf) free(p->framebuf); if (p->backingstore) free(p->backingstore); free(p); } drvthis->store_private_ptr(drvthis, NULL); } /** * Return the display width in characters. * \param drvthis Pointer to driver structure. * \return Number of characters the display is wide. */ MODULE_EXPORT int HD44780_width(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->width; } /** * Return the display height in characters. * \param drvthis Pointer to driver structure. * \return Number of characters the display is high. */ MODULE_EXPORT int HD44780_height(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->height; } /** * Return the width of a character in pixels. * \param drvthis Pointer to driver structure. * \return Number of pixel columns a character cell is wide. */ MODULE_EXPORT int HD44780_cellwidth(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->cellwidth; } /** * Return the height of a character in pixels. * \param drvthis Pointer to driver structure. * \return Number of pixel lines a character cell is high. */ MODULE_EXPORT int HD44780_cellheight(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; return p->cellheight; } /** * Set position (not part of API). * \param drvthis Pointer to driver structure. * \param x X-coordinate to go to. * \param y Y-coordinate to go to. */ void HD44780_position(Driver *drvthis, int x, int y) { PrivateData *p = (PrivateData *) drvthis->private_data; int dispID = p->spanList[y]; int relY = y - p->dispVOffset[dispID - 1]; int DDaddr; if (p->ext_mode) { // Linear addressing, each line starts 0x20 higher. DDaddr = x + relY * 0x20; } else { // 16x1 is a special case if (p->dispSizes[dispID - 1] == 1 && p->width == 16) { if (x >= 8) { x -= 8; relY = 1; } } DDaddr = x + (relY % 2) * 0x40; if ((relY % 4) >= 2) DDaddr += p->width; } p->hd44780_functions->senddata(p, dispID, RS_INSTR, POSITION | DDaddr); p->hd44780_functions->uPause(p, 40); // Minimum exec time for all commands } /** * Flush data on screen to the LCD. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void HD44780_flush(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; int x, y; int wid = p->width; int i; int count; char refreshNow = 0; char keepaliveNow = 0; time_t now = time(NULL); // force full refresh of display if ((p->refreshdisplay > 0) && (now > p->nextrefresh)) { refreshNow = 1; p->nextrefresh = now + p->refreshdisplay; } // keepalive refresh of display if ((p->keepalivedisplay > 0) && (now > p->nextkeepalive)) { keepaliveNow = 1; p->nextkeepalive = now + p->keepalivedisplay; } // Update LCD incrementally by comparing with last contents count = 0; for (y = 0; y < p->height; y++) { int drawing = 0; for (x = 0 ; x < wid; x++) { unsigned char ch = p->framebuf[(y * wid) + x]; if (refreshNow || (x + y == 0 && keepaliveNow) || ch != p->backingstore[(y*wid)+x]) { if (!drawing || x % 8 == 0) { // x%8 is for 16x1 displays ! drawing = 1; HD44780_position(drvthis,x,y); } p->hd44780_functions->senddata(p, p->spanList[y], RS_DATA, available_charmaps[p->charmap].charmap[ch]); p->hd44780_functions->uPause(p, 40); // Minimum exec time for all commands p->backingstore[(y*wid)+x] = ch; count++; } else { drawing = 0; } } } debug(RPT_DEBUG, "HD44780: flushed %d chars", count); /* Check which defineable chars we need to update */ count = 0; for (i = 0; i < NUM_CCs; i++) { if (!p->cc[i].clean) { int row; /* Tell the HD44780 we will redefine char number i */ p->hd44780_functions->senddata(p, 0, RS_INSTR, SETCHAR | i * 8); p->hd44780_functions->uPause(p, 40); // Minimum exec time for all commands /* Send the subsequent rows */ for (row = 0; row < p->cellheight; row++) { p->hd44780_functions->senddata(p, 0, RS_DATA, p->cc[i].cache[row]); p->hd44780_functions->uPause(p, 40); /* Minimum exec time for all commands */ } p->cc[i].clean = 1; /* mark as clean */ count++; } } debug(RPT_DEBUG, "%s: flushed %d custom chars", drvthis->name, count); } /** * Clear the screen. * \param drvthis Pointer to driver structure. */ MODULE_EXPORT void HD44780_clear(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; memset(p->framebuf, ' ', p->width * p->height); p->ccmode = standard; } /** * Print a character on the screen at position (x,y). * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height). * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param c Character that gets written. */ MODULE_EXPORT void HD44780_chr(Driver *drvthis, int x, int y, char ch) { PrivateData *p = (PrivateData *) drvthis->private_data; y--; x--; if ((x >= 0) && (y >= 0) && (x < p->width) && (y < p->height)) p->framebuf[(y * p->width) + x] = ch; } /** * Print a string on the screen at position (x,y). * The upper-left corner is (1,1), the lower-right corner is (p->width, p->height). * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param string String that gets written. */ MODULE_EXPORT void HD44780_string(Driver *drvthis, int x, int y, const char string[]) { PrivateData *p = (PrivateData *) drvthis->private_data; int i; x--; // Convert 1-based coords to 0-based y--; if ((y < 0) || (y >= p->height)) return; for (i = 0; (string[i] != '\0') && (x < p->width); i++, x++) { if (x >= 0) // no write left of left border p->framebuf[(y * p->width) + x] = string[i]; } } /** * Get current LCD contrast. * This is only the locally stored contrast, the contrast value * cannot be retrieved from the LCD. * \param drvthis Pointer to driver structure. * \return Stored contrast in promille. */ MODULE_EXPORT int HD44780_get_contrast(Driver *drvthis) { PrivateData *p = drvthis->private_data; return p->contrast; } /** * Change LCD contrast. * \param drvthis Pointer to driver structure. * \param promille New contrast value in promille. */ MODULE_EXPORT void HD44780_set_contrast (Driver *drvthis, int promille) { PrivateData *p = drvthis->private_data; unsigned char contrast_byte; /* Check it */ if (promille < 0 || promille > 1000) return; /* store the software value since there is not get */ p->contrast = promille; /* map range [0, 1000] -> [0, 255] (for one byte)) */ contrast_byte = (255 * promille) / 1000; /* call local function */ if (p->hd44780_functions->set_contrast != NULL) p->hd44780_functions->set_contrast(p, contrast_byte); } /** * Retrieve brightness. * \param drvthis Pointer to driver structure. * \param state Brightness state (on/off) for which we want the value. * \return Stored brightness in promille. */ MODULE_EXPORT int HD44780_get_brightness(Driver *drvthis, int state) { PrivateData *p = drvthis->private_data; return (state == BACKLIGHT_ON) ? p->brightness : p->offbrightness; } /** * Set on/off brightness. * \param drvthis Pointer to driver structure. * \param state Brightness state (on/off) for which we want to store the value. * \param promille New brightness in promille. */ MODULE_EXPORT void HD44780_set_brightness(Driver *drvthis, int state, int promille) { PrivateData *p = drvthis->private_data; /* Check it */ if (promille < 0 || promille > 1000) return; /* store the software value since there is not get */ if (state == BACKLIGHT_ON) { p->brightness = promille; } else { p->offbrightness = promille; } //HD44780_backlight(drvthis, state); } /** * Turn the LCD backlight on or off. * \param drvthis Pointer to driver structure. * \param on New backlight status. */ MODULE_EXPORT void HD44780_backlight(Driver *drvthis, int on) { PrivateData *p = (PrivateData *) drvthis->private_data; // not sure if this is the correct solution. // alternative: return immediately if (!p->have_backlight) on = 0; if (p->hd44780_functions->backlight != NULL) p->hd44780_functions->backlight(p, on); } /** * Draw a vertical bar bottom-up. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column) of the starting point. * \param y Vertical character position (row) of the starting point. * \param len Number of characters that the bar is high at 100% * \param promille Current height level of the bar in promille. * \param options Options (currently unused). */ MODULE_EXPORT void HD44780_vbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = (PrivateData *) drvthis->private_data; /* x and y are the start position of the bar. * The bar by default grows in the 'up' direction * (other direction not yet implemented). * len is the number of characters that the bar is long at 100% * promille is the number of promilles (0..1000) that the bar should be filled. */ if (p->ccmode != vbar) { unsigned char vBar[p->cellheight]; int i; if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: vbar: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = vbar; memset(vBar, 0x00, sizeof(vBar)); for (i = 1; i < p->cellheight; i++) { // add pixel line per pixel line ... vBar[p->cellheight - i] = 0xFF; HD44780_set_char(drvthis, i, vBar); } } lib_vbar_static(drvthis, x, y, len, promille, options, p->cellheight, 0); } /** * Draw a horizontal bar to the right. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column) of the starting point. * \param y Vertical character position (row) of the starting point. * \param len Number of characters that the bar is long at 100% * \param promille Current length level of the bar in promille. * \param options Options (currently unused). */ MODULE_EXPORT void HD44780_hbar(Driver *drvthis, int x, int y, int len, int promille, int options) { PrivateData *p = (PrivateData *) drvthis->private_data; /* x and y are the start position of the bar. * The bar by default grows in the 'right' direction * (other direction not yet implemented). * len is the number of characters that the bar is long at 100% * promille is the number of promilles (0..1000) that the bar should be filled. */ if (p->ccmode != hbar) { unsigned char hBar[p->cellheight]; int i; if (p->ccmode != standard) { /* Not supported(yet) */ report(RPT_WARNING, "%s: hbar: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = hbar; for (i = 1; i <= p->cellwidth; i++) { // fill pixel columns from left to right. memset(hBar, 0xFF & ~((1 << (p->cellwidth - i)) - 1), sizeof(hBar)); HD44780_set_char(drvthis, i, hBar); } } lib_hbar_static(drvthis, x, y, len, promille, options, p->cellwidth, 0); } /** * Write a big number to the screen. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param num Character to write (0 - 10 with 10 representing ':') */ MODULE_EXPORT void HD44780_num(Driver *drvthis, int x, int num) { PrivateData *p = (PrivateData *) drvthis->private_data; int do_init = 0; if ((num < 0) || (num > 10)) return; if (p->ccmode != bignum) { if (p->ccmode != standard) { /* Not supported (yet) */ report(RPT_WARNING, "%s: num: cannot combine two modes using user-defined characters", drvthis->name); return; } p->ccmode = bignum; do_init = 1; } // Lib_adv_bignum does everything needed to show the bignumbers. lib_adv_bignum(drvthis, x, num, 0, do_init); } /** * Get total number of custom characters available. * \param drvthis Pointer to driver structure. * \return Number of custom characters (always NUM_CCs). */ MODULE_EXPORT int HD44780_get_free_chars(Driver *drvthis) { //PrivateData *p = drvthis->private_data; return NUM_CCs; } /** * Define a custom character and write it to the LCD. * \param drvthis Pointer to driver structure. * \param n Custom character to define [0 - (NUM_CCs-1)]. * \param dat Array of 8(=cellheight) bytes, each representing a pixel row * starting from the top to bottom. * The bits in each byte represent the pixels where the LSB * (least significant bit) is the rightmost pixel in each pixel row. */ MODULE_EXPORT void HD44780_set_char(Driver *drvthis, int n, unsigned char *dat) { PrivateData *p = (PrivateData *) drvthis->private_data; unsigned char mask = (1 << p->cellwidth) - 1; int row; if ((n < 0) || (n >= NUM_CCs)) return; if (!dat) return; for (row = 0; row < p->cellheight; row++) { int letter = 0; if (p->lastline || (row < p->cellheight - 1)) letter = dat[row] & mask; if (p->cc[n].cache[row] != letter) p->cc[n].clean = 0; /* only mark dirty if really different */ p->cc[n].cache[row] = letter; } } /** * Place an icon on the screen. * \param drvthis Pointer to driver structure. * \param x Horizontal character position (column). * \param y Vertical character position (row). * \param icon synbolic value representing the icon. * \return Information whether the icon is handled here or needs to be handled by the server core. */ MODULE_EXPORT int HD44780_icon(Driver *drvthis, int x, int y, int icon) { static unsigned char heart_open[] = { b__XXXXX, b__X_X_X, b_______, b_______, b_______, b__X___X, b__XX_XX, b__XXXXX }; static unsigned char heart_filled[] = { b__XXXXX, b__X_X_X, b___X_X_, b___XXX_, b___XXX_, b__X_X_X, b__XX_XX, b__XXXXX }; static unsigned char arrow_up[] = { b____X__, b___XXX_, b__X_X_X, b____X__, b____X__, b____X__, b____X__, b_______ }; static unsigned char arrow_down[] = { b____X__, b____X__, b____X__, b____X__, b__X_X_X, b___XXX_, b____X__, b_______ }; /* static unsigned char arrow_left[] = { b_______, b____X__, b___X___, b__XXXXX, b___X___, b____X__, b_______, b_______ }; static unsigned char arrow_right[] = { b_______, b____X__, b_____X_, b__XXXXX, b_____X_, b____X__, b_______, b_______ }; */ static unsigned char checkbox_off[] = { b_______, b_______, b__XXXXX, b__X___X, b__X___X, b__X___X, b__XXXXX, b_______ }; static unsigned char checkbox_on[] = { b____X__, b____X__, b__XXX_X, b__X_XX_, b__X_X_X, b__X___X, b__XXXXX, b_______ }; static unsigned char checkbox_gray[] = { b_______, b_______, b__XXXXX, b__X_X_X, b__XX_XX, b__X_X_X, b__XXXXX, b_______ }; /* static unsigned char selector_left[] = { b___X___, b___XX__, b___XXX_, b___XXXX, b___XXX_, b___XX__, b___X___, b_______ }; static unsigned char selector_right[] = { b_____X_, b____XX_, b___XXX_, b__XXXX_, b___XXX_, b____XX_, b_____X_, b_______ }; static unsigned char ellipsis[] = { b_______, b_______, b_______, b_______, b_______, b_______, b__X_X_X, b_______ }; */ static unsigned char block_filled[] = { b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX, b__XXXXX }; /* Yes I know, this is a VERY BAD implementation */ switch (icon) { case ICON_BLOCK_FILLED: HD44780_set_char(drvthis, 6, block_filled); HD44780_chr(drvthis, x, y, 6); break; case ICON_HEART_FILLED: HD44780_set_char(drvthis, 0, heart_filled); HD44780_chr(drvthis, x, y, 0); break; case ICON_HEART_OPEN: HD44780_set_char(drvthis, 0, heart_open); HD44780_chr(drvthis, x, y, 0); break; case ICON_ARROW_UP: HD44780_set_char(drvthis, 1, arrow_up); HD44780_chr(drvthis, x, y, 1); break; case ICON_ARROW_DOWN: HD44780_set_char(drvthis, 2, arrow_down); HD44780_chr(drvthis, x, y, 2); break; case ICON_ARROW_LEFT: HD44780_chr(drvthis, x, y, 0x7F); break; case ICON_ARROW_RIGHT: HD44780_chr(drvthis, x, y, 0x7E); break; case ICON_CHECKBOX_OFF: HD44780_set_char(drvthis, 3, checkbox_off); HD44780_chr(drvthis, x, y, 3); break; case ICON_CHECKBOX_ON: HD44780_set_char(drvthis, 4, checkbox_on); HD44780_chr(drvthis, x, y, 4); break; case ICON_CHECKBOX_GRAY: HD44780_set_char(drvthis, 5, checkbox_gray); HD44780_chr(drvthis, x, y, 5); break; default: return -1; /* Let the core do other icons */ } return 0; } /** * Get next key from the key panel connected to the display. * \param drvthis Pointer to driver structure. * \return String representation of the key. */ MODULE_EXPORT const char * HD44780_get_key(Driver *drvthis) { PrivateData *p = (PrivateData *) drvthis->private_data; unsigned char scancode; char *keystr = NULL; struct timeval curr_time, time_diff; // return "no key pressed" if required functions mission or input disabled if ((!p->have_keypad) || (p->hd44780_functions->scankeypad == NULL)) return NULL; gettimeofday(&curr_time, NULL); scancode = p->hd44780_functions->scankeypad(p); if (scancode != '\0') { // TODO: check if arrays are large enough keystr = (scancode & 0xF0) ? p->keyMapMatrix[((scancode&0xF0)>>4)-1][(scancode&0x0F)-1] : p->keyMapDirect[scancode - 1]; } if (keystr != NULL) { if (keystr == p->pressed_key) { timersub(&curr_time, &(p->pressed_key_time), &time_diff); if (((time_diff.tv_usec / 1000 + time_diff.tv_sec * 1000) - KEYPAD_AUTOREPEAT_DELAY) < 1000 * p->pressed_key_repetitions / KEYPAD_AUTOREPEAT_FREQ) { // The key is already pressed quite some time // but it's not yet time to return a repeated keypress return NULL; } // Otherwise a keypress will be returned p->pressed_key_repetitions++; } else { // It's a new keypress p->pressed_key_time = curr_time; p->pressed_key_repetitions = 0; report(RPT_INFO, "HD44780_get_key: Key pressed: %s (%d,%d)", keystr, scancode&0x0F, (scancode&0xF0)>>4); } } // Store the key for the next round p->pressed_key = keystr; return keystr; } /** * Scan the keypad (not part of the API). */ unsigned char HD44780_scankeypad(PrivateData *p) { unsigned int keybits; unsigned int shiftcount; unsigned int shiftingbit; unsigned int Ypattern; unsigned int Yval; signed char exp; unsigned char scancode = 0; // return "no ke pressed if no keypad reading function defined if (p->hd44780_functions->readkeypad == NULL) return('\0'); // First check if a directly connected key is pressed // Put all zeros on Y of keypad keybits = p->hd44780_functions->readkeypad(p, 0); if (keybits) { // A directly connected key was pressed // Which key was it ? shiftingbit = 1; for (shiftcount = 0; shiftcount < KEYPAD_MAXX && !scancode; shiftcount++) { if (keybits & shiftingbit) { // Found ! Return from function. scancode = shiftcount+1; } shiftingbit <<= 1; } } else { // Now check the matrix // First check with all 1's Ypattern = (1 << KEYPAD_MAXY) - 1; if (p->hd44780_functions->readkeypad(p, Ypattern)) { // Yes, a key on the matrix is pressed // OK, now we know a key is pressed. // Determine which one it is // First determine the row // Do a 'binary search' to minimize I/O Ypattern = 0; Yval = 0; for (exp=3; exp>=0; exp--) { Ypattern = ((1 << (1 << exp)) - 1) << Yval; keybits = p->hd44780_functions->readkeypad(p, Ypattern); if (!keybits) { Yval += (1 << exp); } } // Which key is pressed in that row ? keybits = p->hd44780_functions->readkeypad(p, 1<private_data; // return immediately if output is disabled if (!p->have_output) return; // perhaps it is better just to do this every time in case of a glitch // but leaving this in does make sure that any latch-enable line glitches // are more easily seen if (p->output_state == on) return; p->output_state = on; // call output function only if it is defined for the commenction type if (p->hd44780_functions->output != NULL) p->hd44780_functions->output(p, on); } /** * Parse a span list, a comma separated list of numbers (e.g. "1,4,2"). * \param spanListArray Array to store vertical spans. * \param spLsize Size of spanListArray. * \param dispOffsets Array to store display offsets. * \param dOffsize Size of dispOffsets. * \param dispSizeArray Array of display vertical sizes (= spanlist). * \param spanlist '\0'-terminated input span list in comma delimited format. * \return Number of span elements, -1 on parse error. */ int parse_span_list(int *spanListArray[], int *spLsize, int *dispOffsets[], int *dOffsize, int *dispSizeArray[], const char *spanlist) { int j = 0, retVal = 0; *spLsize = 0; *dOffsize = 0; while (j < strlen(spanlist)) { if (spanlist[j] >= '1' && spanlist[j] <= '9') { int spansize = spanlist[j] - '0'; // add spansize lines to the span list, note the offset to // the previous display and the size of the display if ((*spanListArray = (int *) realloc(*spanListArray, sizeof(int) * (*spLsize + spansize))) && (*dispOffsets = (int *) realloc(*dispOffsets, sizeof(int) * (*dOffsize + 1))) && (*dispSizeArray = (int *) realloc(*dispSizeArray, sizeof(int) * (*dOffsize + 1)))) { int k; for (k = 0; k < spansize; ++k) (*spanListArray)[*spLsize + k] = *dOffsize + 1; (*dispOffsets)[*dOffsize] = *spLsize; (*dispSizeArray)[*dOffsize] = spansize; *spLsize += spansize; ++(*dOffsize); retVal = *dOffsize; // find the next number (\0 is also outside this range) for (++j; spanlist[j] < '1' || spanlist[j] > '9'; ++j); } else { retVal = -1; } } else { retVal = -1; } } return retVal; } /* EOF */