/* $Id: drv_ula200.c 1126 2010-07-13 03:25:44Z michael $ * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/drv_ula200.c $ * * ULA200 driver for lcd4linux * * Copyright (C) 2008 Bernhard Walle * * This file is part of LCD4Linux. * * LCD4Linux is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * LCD4Linux is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * Driver for the ELV ULA200 USB device. The device can control one * HD44780 display up to 4x20 characters. * * Implemented functions: * - displaying characters :-) * - controlling the backlight * * Todo: * - input buttons * * Configuration: * - Size (XxY): size of the display (e.g. '20x4') * - Backlight (0/1): initial state of the backlight * * Author: * Bernhard Walle * * exported fuctions: * struct DRIVER drv_ula200 * */ #include "config.h" #include #include #include #include #include #include #include "debug.h" #include "cfg.h" #include "qprintf.h" #include "udelay.h" #include "plugin.h" #include "widget.h" #include "widget_text.h" #include "widget_icon.h" #include "widget_bar.h" #include "drv.h" /* text mode display? */ #include "drv_generic_text.h" /****************************************/ /*** Global variables ***/ /****************************************/ static char Name[] = "ULA200"; static struct ftdi_context *Ftdi = NULL; /****************************************/ /*** Constants ***/ /****************************************/ /* USB connection */ #define ULA200_VENDOR_ID 0x0403 #define ULA200_PRODUCT_ID 0xf06d /* connection parameters */ #define ULA200_BAUDRATE 19200 #define ULA200_DATABITS BITS_8 #define ULA200_STOPBITS STOP_BIT_1 #define ULA200_PARITY EVEN /* character constants used for the communication */ #define ULA200_CH_STX 0x02 #define ULA200_CH_ETX 0x03 #define ULA200_CH_ENQ 0x05 #define ULA200_CH_ACK 0x06 #define ULA200_CH_NAK 0x15 #define ULA200_CH_DC2 0x12 #define ULA200_CH_DC3 0x13 /* commands used for the communication (names are German) */ #define ULA200_CMD_POSITION 'p' /* 'position' */ #define ULA200_CMD_STRING 's' /* 'string' */ #define ULA200_CMD_CLEAR 'l' /* 'loeschen' */ #define ULA200_CMD_BACKLIGHT 'h' /* 'hintergrund' */ #define ULA200_CMD_CHAR 'c' /* 'character' */ /* raw register access */ #define ULA200_RS_DATA 0x00 /* data */ #define ULA200_RS_INSTR 0x01 /* instruction */ #define ULA200_SETCHAR 0x40 /* set user-defined character */ /* character sizes */ #define ULA200_CELLWIDTH 5 #define ULA200_CELLHEIGHT 8 /* internal implementation constants */ #define ULA200_BUFFER_LENGTH 1024 #define ULA200_MAXLEN 512 #define ULA200_MAX_REPEATS 20 /* define TRUE and FALSE for better code readability if not already defined */ #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /****************************************/ /*** Macros ***/ /****************************************/ #define ULA200_ERROR(msg, ...) \ error("%s: In %s():%d: " msg, Name, \ __FUNCTION__, __LINE__, ##__VA_ARGS__) #define ULA200_INFO(msg, ...) \ info("%s: " msg, Name, ##__VA_ARGS__) #define ULA200_DEBUG(msg, ...) \ debug("%s: In %s():%d: " msg, Name, \ __FUNCTION__, __LINE__, ##__VA_ARGS__) #define ULA200_TRACE() \ debug("%s: Calling %s()", Name, __FUNCTION__) /****************************************/ /*** Prototypes ***/ /****************************************/ static int drv_ula200_ftdi_read_response(void); static int drv_ula200_ftdi_usb_read(void); static int drv_ula200_ftdi_write_command(const unsigned char *, int); static int drv_ula200_backlight(int); static int drv_ula200_close(void); static void plugin_backlight(RESULT *, RESULT *); /****************************************/ /*** Internal (helper) funcs ***/ /****************************************/ /** * Write a command to the display. Adds the STX and ETX header/trailer. * * @param[in] data the data bytes * @param[in] length the number of bytes in data which are valid * @return 0 on success, negative value on error */ static int drv_ula200_ftdi_write_command(const unsigned char *data, int length) { int i, err; int repeat_count = 0; int pos = 0; unsigned char buffer[ULA200_BUFFER_LENGTH]; /* check for the maximum length */ if (length > ULA200_MAXLEN) { return -EINVAL; } /* fill the array */ buffer[pos++] = ULA200_CH_STX; for (i = 0; i < length; i++) { if (data[i] == ULA200_CH_STX) { buffer[pos++] = ULA200_CH_ENQ; buffer[pos++] = ULA200_CH_DC2; } else if (data[i] == ULA200_CH_ETX) { buffer[pos++] = ULA200_CH_ENQ; buffer[pos++] = ULA200_CH_DC3; } else if (data[i] == ULA200_CH_ENQ) { buffer[pos++] = ULA200_CH_ENQ; buffer[pos++] = ULA200_CH_NAK; } else { buffer[pos++] = data[i]; } } buffer[pos++] = ULA200_CH_ETX; do { /* ULA200_DEBUG("ftdi_write_data(%p, %d)", buffer, pos); */ err = ftdi_write_data(Ftdi, buffer, pos); if (err < 0) { ULA200_ERROR("ftdi_write_data() failed"); return -1; } } while (!drv_ula200_ftdi_read_response() && (repeat_count++ < ULA200_MAX_REPEATS)); return 0; } /** * Reads a character from USB. * * @return a positive value between 0 and 255 indicates the character that * has been read successfully, -1 indicates an error */ static int drv_ula200_ftdi_usb_read(void) { unsigned char buffer[1]; int err; while ((err = ftdi_read_data(Ftdi, buffer, 1)) == 0); return err >= 0 ? buffer[0] : -1; } /** * Reads the response of the display. Currently, key input is ignored * and only ACK / NACK is read. * * @return TRUE on success (ACK), FALSE on failure (NACK) */ static int drv_ula200_ftdi_read_response(void) { int result = FALSE; int answer_read = FALSE; int ret; int ch; while (!answer_read) { /* wait until STX */ do { ret = drv_ula200_ftdi_usb_read(); /* ULA200_DEBUG("STX drv_ula200_ftdi_usb_read = %d", ret); */ } while ((ret != ULA200_CH_STX) && (ret > 0)); if (ret < 0) { return FALSE; } /* read next char */ ch = drv_ula200_ftdi_usb_read(); /* ULA200_DEBUG("drv_ula200_ftdi_usb_read = %d", ch); */ switch (ch) { case 't': ch = drv_ula200_ftdi_usb_read(); /* ULA200_DEBUG("drv_ula200_ftdi_usb_read = %d", ch); */ /* ignore currently */ break; case ULA200_CH_ACK: answer_read = TRUE; result = TRUE; break; case ULA200_CH_NAK: answer_read = TRUE; result = FALSE; break; default: answer_read = TRUE; ULA200_ERROR("Read invalid answer"); } /* wait until ETX */ do { ret = drv_ula200_ftdi_usb_read(); /* ULA200_DEBUG("ETX drv_ula200_ftdi_usb_read = %d", ret); */ } while ((ret != ULA200_CH_ETX) && (ret > 0)); if (ret < 0) { return FALSE; } } return result; } static int drv_ula200_ftdi_enable_raw_mode(void) { unsigned char command[3]; command[0] = 'R'; command[1] = 'E'; command[2] = '1'; return drv_ula200_ftdi_write_command(command, 3); } /** * Writes raw data (access the HD44780 registers directly. * * @param[in] flags ULA200_RS_DATA or ULA200_RS_INSTR * @param[in] ch the real data * @return 0 on success, a negative value on error */ static int drv_ula200_ftdi_rawdata(unsigned char flags, unsigned char ch) { unsigned char command[3]; int err; command[0] = 'R'; command[1] = flags == ULA200_RS_DATA ? '2' : '0'; command[2] = ch; err = drv_ula200_ftdi_write_command(command, 3); if (err < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); return -1; } return 0; } /** * Sets the cursor position. * * @param[in] x the x coordinate of the position * @param[in] y the y coordinate of the position * @return 0 on success, a negative value on error */ static int drv_ula200_set_position(int x, int y) { unsigned char command[3]; int err; if (y >= 2) { y -= 2; x += DCOLS; /* XXX: multiply by 2? */ } command[0] = ULA200_CMD_POSITION; command[1] = x; command[2] = y; err = drv_ula200_ftdi_write_command(command, 3); if (err < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); } return err; } /** * Sends the text * * @param[in] data the data bytes * @param[in] len the number of valid bytes in @p data * @return 0 on success, a negative value on error */ static int drv_ula200_send_text(const unsigned char *data, int len) { unsigned char buffer[ULA200_BUFFER_LENGTH]; int err; if (len > ULA200_MAXLEN) { return -EINVAL; } buffer[0] = ULA200_CMD_STRING; buffer[1] = len; memcpy(buffer + 2, data, len); buffer[2 + len] = 0; /* only necessary for the debug message */ /* ULA200_DEBUG("Text: =%s= (%d)", buffer+2, len); */ err = drv_ula200_ftdi_write_command(buffer, len + 2); if (err < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); return -1; } return 0; } /** * Sends one character. * * @param[in] ch the character to send * @return 0 on success, a negative value on error */ static int drv_ula200_send_char(char ch) { unsigned char buffer[2]; int err; buffer[0] = ULA200_CMD_CHAR; buffer[1] = ch; err = drv_ula200_ftdi_write_command(buffer, 2); if (err < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); return -1; } return 0; } /** * Opens the ULA200 display. Uses libftdi to initialise the USB communication to * the display. * @ @return a value less then zero on failure, 0 on success */ static int drv_ula200_open(void) { int err; /* check if the device was already open */ if (Ftdi != NULL) { ULA200_ERROR("open called although device was already open"); drv_ula200_close(); } /* get memory for the device descriptor */ Ftdi = malloc(sizeof(struct ftdi_context)); if (Ftdi == NULL) { ULA200_ERROR("Memory allocation failed"); return -1; } /* open the ftdi library */ ftdi_init(Ftdi); Ftdi->usb_write_timeout = 20; Ftdi->usb_read_timeout = 20; /* open the device */ err = ftdi_usb_open(Ftdi, ULA200_VENDOR_ID, ULA200_PRODUCT_ID); if (err < 0) { ULA200_ERROR("ftdi_usb_open() failed"); free(Ftdi); Ftdi = NULL; return -1; } /* set the baudrate */ err = ftdi_set_baudrate(Ftdi, ULA200_BAUDRATE); if (err < 0) { ULA200_ERROR("ftdi_set_baudrate() failed"); ftdi_usb_close(Ftdi); free(Ftdi); Ftdi = NULL; return -1; } /* set communication parameters */ err = ftdi_set_line_property(Ftdi, ULA200_DATABITS, ULA200_STOPBITS, ULA200_PARITY); if (err < 0) { ULA200_ERROR("ftdi_set_line_property() failed"); ftdi_usb_close(Ftdi); free(Ftdi); Ftdi = NULL; return -1; } return 0; } /** * Closes the display. * * @return 0 on success, a negative value on failure */ static int drv_ula200_close(void) { ULA200_TRACE(); ftdi_usb_purge_buffers(Ftdi); ftdi_usb_close(Ftdi); ftdi_deinit(Ftdi); free(Ftdi); Ftdi = NULL; return 0; } /** * Clears the contents of the display. * * @return 0 on success, a negative value on error */ static void drv_ula200_clear(void) { unsigned const char command[] = { ULA200_CMD_CLEAR }; int err; ULA200_TRACE(); err = drv_ula200_ftdi_write_command(command, 1); if (err < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); } } /** * Writes data to the display. * * @param[in] row the row where the data should be written to * @param[in] col the column where the data should be written to * @param[in] data the data that should actually be written * @param[in] len the number of valid bytes in @p data */ static void drv_ula200_write(const int row, const int col, const char *data, int len) { int ret; /* do the cursor positioning here */ ret = drv_ula200_set_position(col, row); if (ret < 0) { ULA200_ERROR("drv_ula200_set_position() failed"); return; } /* send string to the display */ if (len == 1) { ret = drv_ula200_send_char(data[0]); } else { ret = drv_ula200_send_text((unsigned char *) data, len); } if (ret < 0) { ULA200_ERROR("drv_ula200_send_text() failed"); return; } } /* text mode displays only */ static void drv_ula200_defchar(const int ascii, const unsigned char *matrix) { int err, i; if (ascii >= 8) { ULA200_ERROR("Invalid value in drv_ula200_defchar"); return; } /* Tell the HD44780 we will redefine char number 'ascii' */ err = drv_ula200_ftdi_rawdata(ULA200_RS_INSTR, ULA200_SETCHAR | (ascii * 8)); if (err < 0) { ULA200_ERROR("drv_ula200_ftdi_rawdata() failed"); return; } /* Send the subsequent rows */ for (i = 0; i < YRES; i++) { err = drv_ula200_ftdi_rawdata(ULA200_RS_DATA, *matrix++ & 0x1f); if (err < 0) { ULA200_ERROR("ula200_ftdi_rawdata() failed"); return; } } } /** * Controls the backlight of the ULA200 display. * * @param[in] backlight a negative value if the backlight should be turned off, * a positive value if it should be turned on * @return 0 on success, any other value on failure */ static int drv_ula200_backlight(int backlight) { unsigned char cmd[2] = { ULA200_CMD_BACKLIGHT }; int ret; if (backlight <= 0) { backlight = '0'; } else { backlight = '1'; } cmd[1] = backlight; ret = drv_ula200_ftdi_write_command(cmd, 2); if (ret < 0) { ULA200_ERROR("ula200_ftdi_write_command() failed"); } return backlight == '1'; } /** * Starts the display. * * @param[in] section the section of the configuration file * @return 0 on success, a negative value on failure */ static int drv_ula200_start(const char *section) { int rows = -1, cols = -1; char *s; int backlight = 0; int err; s = cfg_get(section, "Size", NULL); if (s == NULL || *s == '\0') { ULA200_ERROR("No '%s.Size' entry from %s", section, cfg_source()); return -1; } if (sscanf(s, "%dx%d", &cols, &rows) != 2 || rows < 1 || cols < 1) { ULA200_ERROR("Bad %s.Size '%s' from %s", section, s, cfg_source()); free(s); return -1; } DROWS = rows; DCOLS = cols; /* open communication with the display */ err = drv_ula200_open(); if (err < 0) { return -1; } cfg_number(section, "Backlight", 0, 0, 1, &backlight); err = drv_ula200_backlight(backlight); if (err < 0) { ULA200_ERROR("drv_ula200_backlight() failed"); return -1; } /* clear display */ drv_ula200_clear(); /* enable raw mode for defining own chars */ drv_ula200_ftdi_enable_raw_mode(); return 0; } /****************************************/ /*** plugins ***/ /****************************************/ /** * Backlight plugin */ static void plugin_backlight(RESULT * result, RESULT * arg1) { double backlight; backlight = drv_ula200_backlight(R2N(arg1)); SetResult(&result, R_NUMBER, &backlight); } /****************************************/ /*** exported functions ***/ /****************************************/ /** * list models * * @return 0 on success, a negative value on failure */ int drv_ula200_list(void) { printf("ULA200"); return 0; } /** * initialize driver & display * * @param[in] section the name of the section in the configuration file * @param[in] quiet TRUE on quiet mode * @return 0 on success, any negative error value on failure */ /* use this function for a text display */ int drv_ula200_init(const char *section, const int quiet) { WIDGET_CLASS wc; int ret; ULA200_INFO("%s", "$Rev: 1126 $"); /* display preferences */ XRES = ULA200_CELLWIDTH; /* pixel width of one char */ YRES = ULA200_CELLHEIGHT; /* pixel height of one char */ CHARS = 7; /* number of user-defineable characters */ CHAR0 = 1; /* ASCII of first user-defineable char */ GOTO_COST = 4; /* number of bytes a goto command requires */ /* real worker functions */ drv_generic_text_real_write = drv_ula200_write; drv_generic_text_real_defchar = drv_ula200_defchar; /* start display */ if ((ret = drv_ula200_start(section)) != 0) { return ret; } if (!quiet) { char buffer[40]; qprintf(buffer, sizeof(buffer), "%s %dx%d", Name, DCOLS, DROWS); if (drv_generic_text_greet(buffer, "ULA 200")) { sleep(3); drv_ula200_clear(); } } /* initialize generic text driver */ if ((ret = drv_generic_text_init(section, Name)) != 0) return ret; /* initialize generic icon driver */ if ((ret = drv_generic_text_icon_init()) != 0) return ret; /* initialize generic bar driver */ if ((ret = drv_generic_text_bar_init(0)) != 0) return ret; /* add fixed chars to the bar driver */ drv_generic_text_bar_add_segment(0, 0, 255, 32); /* ASCII 32 = blank */ /* register text widget */ wc = Widget_Text; wc.draw = drv_generic_text_draw; widget_register(&wc); /* register icon widget */ wc = Widget_Icon; wc.draw = drv_generic_text_icon_draw; widget_register(&wc); /* register bar widget */ wc = Widget_Bar; wc.draw = drv_generic_text_bar_draw; widget_register(&wc); /* register plugins */ AddFunction("LCD::backlight", -1, plugin_backlight); return 0; } /** * close driver & display * * @param[in] quiet TRUE on quiet mode * @return 0 on success, any negative error value on failure */ /* use this function for a text display */ int drv_ula200_quit(int quiet) { ULA200_INFO("shutting down."); drv_generic_text_quit(); /* turn backlight off */ drv_ula200_backlight(0); /* clear display */ drv_ula200_clear(); /* say goodbye... */ if (!quiet) { drv_generic_text_greet("goodbye!", NULL); } debug("closing connection"); drv_ula200_close(); return 0; } /* use this one for a text display */ DRIVER drv_ula200 = { .name = Name, .list = drv_ula200_list, .init = drv_ula200_init, .quit = drv_ula200_quit, }; /* :indentSize=4:tabSize=8:noTabs=false: */