From e6f31f6b7949440cd735d0184c846aa2a3330f23 Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 3 Sep 2008 03:28:52 +0000 Subject: ULA200 driver by Bernhard Walle git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@892 3ae390bd-cb1e-0410-b409-cd5a39f66f1f --- drv_ula200.c | 791 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 791 insertions(+) create mode 100644 drv_ula200.c (limited to 'drv_ula200.c') diff --git a/drv_ula200.c b/drv_ula200.c new file mode 100644 index 0000000..1e8eed5 --- /dev/null +++ b/drv_ula200.c @@ -0,0 +1,791 @@ +/* $Id: drv_Sample.c 840 2007-09-09 12:17:42Z michael $ + * $URL: https://ssl.bulix.org/svn/lcd4linux/trunk/drv_Sample.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("generic"); + 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: 840 $"); + + /* 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: */ -- cgit v1.2.3