diff options
author | michael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f> | 2012-02-14 03:17:04 +0000 |
---|---|---|
committer | michael <michael@3ae390bd-cb1e-0410-b409-cd5a39f66f1f> | 2012-02-14 03:17:04 +0000 |
commit | cab1c1a4002c5967a26f1ecd1e6384c0f890d454 (patch) | |
tree | e95f079a865f981e06324cc963492b98b37f6923 | |
parent | aae7a3ee3765f10fc707a2d0d14446a30608ae4f (diff) | |
download | lcd4linux-cab1c1a4002c5967a26f1ecd1e6384c0f890d454.tar.gz |
driver for TeakLCM by Andreas Thienemann
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1173 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
-rw-r--r-- | Makefile.am | 1 | ||||
-rw-r--r-- | Makefile.in | 48 | ||||
-rw-r--r-- | aclocal.m4 | 15 | ||||
-rw-r--r-- | config.h.in | 3 | ||||
-rwxr-xr-x | configure | 24 | ||||
-rw-r--r-- | drivers.m4 | 14 | ||||
-rw-r--r-- | drv.c | 4 | ||||
-rw-r--r-- | drv_TeakLCM.c | 1024 | ||||
-rw-r--r-- | lcd4linux.conf.sample | 11 | ||||
-rwxr-xr-x | smoketest.sh | 2 |
10 files changed, 1113 insertions, 33 deletions
diff --git a/Makefile.am b/Makefile.am index b1a4ed2..894fbd8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -114,6 +114,7 @@ drv_serdisplib.c \ drv_ShuttleVFD.c \ drv_SimpleLCD.c \ drv_T6963.c \ +drv_TeakLCM.c \ drv_Trefon.c \ drv_ula200.c \ drv_USBHUB.c \ diff --git a/Makefile.in b/Makefile.in index 8f2fb2d..95306f1 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.11.2 from Makefile.am. +# Makefile.in generated by automake 1.11.3 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -97,9 +97,11 @@ DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ - { test ! -d "$(distdir)" \ - || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ - && rm -fr "$(distdir)"; }; } + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best distuninstallcheck_listfiles = find . -type f -print @@ -348,6 +350,7 @@ drv_serdisplib.c \ drv_ShuttleVFD.c \ drv_SimpleLCD.c \ drv_T6963.c \ +drv_TeakLCM.c \ drv_Trefon.c \ drv_ula200.c \ drv_USBHUB.c \ @@ -516,7 +519,7 @@ clean-binPROGRAMS: list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list -lcd4linux$(EXEEXT): $(lcd4linux_OBJECTS) $(lcd4linux_DEPENDENCIES) +lcd4linux$(EXEEXT): $(lcd4linux_OBJECTS) $(lcd4linux_DEPENDENCIES) $(EXTRA_lcd4linux_DEPENDENCIES) @rm -f lcd4linux$(EXEEXT) $(lcd4linux_LINK) $(lcd4linux_OBJECTS) $(lcd4linux_LDADD) $(LIBS) @@ -566,6 +569,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_ShuttleVFD.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_SimpleLCD.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_T6963.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_TeakLCM.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_Trefon.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_USBHUB.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/drv_USBLCD.Po@am__quote@ @@ -782,6 +786,10 @@ dist-bzip2: distdir tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 $(am__remove_distdir) +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz + $(am__remove_distdir) + dist-lzma: distdir tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma $(am__remove_distdir) @@ -818,6 +826,8 @@ distcheck: dist bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.lzma*) \ lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ *.tar.xz*) \ xz -dc $(distdir).tar.xz | $(am__untar) ;;\ *.tar.Z*) \ @@ -1006,20 +1016,20 @@ uninstall-am: uninstall-binPROGRAMS .PHONY: CTAGS GTAGS all all-am am--refresh check check-am clean \ clean-binPROGRAMS clean-generic clean-libtool ctags dist \ - dist-all dist-bzip2 dist-gzip dist-lzma dist-shar dist-tarZ \ - dist-xz dist-zip distcheck distclean distclean-compile \ - distclean-generic distclean-hdr distclean-libtool \ - distclean-tags distcleancheck distdir distuninstallcheck dvi \ - dvi-am html html-am info info-am install install-am \ - install-binPROGRAMS install-data install-data-am install-dvi \ - install-dvi-am install-exec install-exec-am install-html \ - install-html-am install-info install-info-am install-man \ - install-pdf install-pdf-am install-ps install-ps-am \ - install-strip installcheck installcheck-am installdirs \ - maintainer-clean maintainer-clean-generic mostlyclean \ - mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ - pdf pdf-am ps ps-am tags uninstall uninstall-am \ - uninstall-binPROGRAMS + dist-all dist-bzip2 dist-gzip dist-lzip dist-lzma dist-shar \ + dist-tarZ dist-xz dist-zip distcheck distclean \ + distclean-compile distclean-generic distclean-hdr \ + distclean-libtool distclean-tags distcleancheck distdir \ + distuninstallcheck dvi dvi-am html html-am info info-am \ + install install-am install-binPROGRAMS install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags uninstall uninstall-am uninstall-binPROGRAMS # create subversion version @@ -1,4 +1,4 @@ -# generated automatically by aclocal 1.11.2 -*- Autoconf -*- +# generated automatically by aclocal 1.11.3 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, # 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, @@ -10130,7 +10130,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.11' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.11.2], [], +m4_if([$1], [1.11.3], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -10146,7 +10146,7 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.11.2])dnl +[AM_AUTOMAKE_VERSION([1.11.3])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) @@ -11005,7 +11005,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- -# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# Copyright (C) 2004, 2005, 2012 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -11027,10 +11027,11 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # a tarball read from stdin. # $(am__untar) < result.tar AC_DEFUN([_AM_PROG_TAR], -[# Always define AMTAR for backward compatibility. -AM_MISSING_PROG([AMTAR], [tar]) +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) m4_if([$1], [v7], - [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar],, [pax],, [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) diff --git a/config.h.in b/config.h.in index 97c3e84..7fdebfc 100644 --- a/config.h.in +++ b/config.h.in @@ -621,6 +621,9 @@ /* T6963 driver */ #undef WITH_T6963 +/* TeakLCM driver */ +#undef WITH_TEAK_LCM + /* TREFON driver */ #undef WITH_TREFON @@ -1449,7 +1449,7 @@ Optional Packages: Newhaven, Noritake, NULL, Pertelian, PHAnderson, PICGraphic, picoLCD, picoLCDGraphic, PNG, PPM, RouterBoard, Sample, serdisplib, ShuttleVFD, SimpleLCD, st2205, T6963, - Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11 + TeakLCM, Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11 --with-plugins=<list> choose which plugins to compile. type --with-plugins=list for a list of avaible plugins @@ -2821,11 +2821,11 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # We need awk for the "check" target. The system "awk" is bad on # some platforms. -# Always define AMTAR for backward compatibility. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' -AMTAR=${AMTAR-"${am_missing_run}tar"} - -am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' +am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' @@ -6317,6 +6317,7 @@ for driver in $drivers; do SHUTTLEVFD="yes" SIMPLELCD="yes" T6963="yes" + TeakLCM="yes" Trefon="yes" ULA200="yes" USBHUB="yes" @@ -6460,6 +6461,9 @@ for driver in $drivers; do T6963) T6963=$val ;; + TeakLCM) + TeakLCM=$val + ;; Trefon) Trefon=$val ;; @@ -7081,6 +7085,16 @@ $as_echo "$as_me: WARNING: asm/io.h or {linux/parport.h and linux/ppdev.h} not f fi fi +if test "$TeakLCM" = "yes"; then + TEXT="yes" + GPIO="no" + SERIAL="yes" + DRIVERS="$DRIVERS drv_TeakLCM.o" + +$as_echo "#define WITH_TEAK_LCM 1" >>confdefs.h + +fi + if test "$Trefon" = "yes"; then if test "$has_usb" = "true"; then TEXT="yes" @@ -39,7 +39,7 @@ AC_ARG_WITH( [ Newhaven, Noritake, NULL, Pertelian, PHAnderson,] [ PICGraphic, picoLCD, picoLCDGraphic, PNG, PPM, RouterBoard,] [ Sample, serdisplib, ShuttleVFD, SimpleLCD, st2205, T6963,] - [ Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11], + [ TeakLCM, Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11], drivers=$withval, drivers=all ) @@ -105,6 +105,7 @@ for driver in $drivers; do SHUTTLEVFD="yes" SIMPLELCD="yes" T6963="yes" + TeakLCM="yes" Trefon="yes" ULA200="yes" USBHUB="yes" @@ -248,6 +249,9 @@ for driver in $drivers; do T6963) T6963=$val ;; + TeakLCM) + TeakLCM=$val + ;; Trefon) Trefon=$val ;; @@ -752,6 +756,14 @@ if test "$T6963" = "yes"; then fi fi +if test "$TeakLCM" = "yes"; then + TEXT="yes" + GPIO="no" + SERIAL="yes" + DRIVERS="$DRIVERS drv_TeakLCM.o" + AC_DEFINE(WITH_TEAK_LCM,1,[TeakLCM driver]) +fi + if test "$Trefon" = "yes"; then if test "$has_usb" = "true"; then TEXT="yes" @@ -90,6 +90,7 @@ extern DRIVER drv_serdisplib; extern DRIVER drv_ShuttleVFD; extern DRIVER drv_SimpleLCD; extern DRIVER drv_T6963; +extern DRIVER drv_TeakLCM; extern DRIVER drv_Trefon; extern DRIVER drv_ula200; extern DRIVER drv_USBHUB; @@ -236,6 +237,9 @@ DRIVER *Driver[] = { #ifdef WITH_T6963 &drv_T6963, #endif +#ifdef WITH_TEAK_LCM + &drv_TeakLCM, +#endif #ifdef WITH_TREFON &drv_Trefon, #endif diff --git a/drv_TeakLCM.c b/drv_TeakLCM.c new file mode 100644 index 0000000..7517b40 --- /dev/null +++ b/drv_TeakLCM.c @@ -0,0 +1,1024 @@ +/* $Id$ + * $URL$ + * + * TeakLCM lcd4linux driver + * + * Copyright (C) 2005 Michael Reinelt <michael@reinelt.co.at> + * Copyright (C) 2005, 2006, 2007 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net> + * Copyright (C) 2011 Hans Ulrich Niedermann <hun@n-dimensional.de> + * Copyright (C) 2011, 2012 Andreas Thienemann <andreas@bawue.net> + * + * 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. + * + */ + +/* + * + * exported fuctions: + * + * struct DRIVER drv_TeakLCM + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <assert.h> + +#include "event.h" +#include "timer.h" +#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" + +#include "drv_generic_text.h" +#include "drv_generic_serial.h" + + +static char Name[] = "TeakLCM"; + + +static int global_reset_rx_flag = 0; + + +#define HI8(value) ((u_int8_t)(((value)>>8) & 0xff)) +#define LO8(value) ((u_int8_t)((value) & 0xff)) + + +static u_int16_t CRC16(u_int8_t value, u_int16_t crcin) +{ + u_int16_t k = (((crcin >> 8) ^ value) & 255) << 8; + u_int16_t crc = 0; + int bits; + for (bits = 8; bits; --bits) { + if ((crc ^ k) & 0x8000) + crc = (crc << 1) ^ 0x1021; + else + crc <<= 1; + k <<= 1; + } + return ((crcin << 8) ^ crc); +} + + +/** Return a printable character */ +static char printable(const char ch) +{ + if ((32 <= ch) && (ch < 127)) { + return ch; + } else { + return '.'; + } +} + + +static void debug_data_int(const char *prefix, const void *data, const size_t size, const unsigned int delta) +{ + const u_int8_t *b = (const u_int8_t *) data; + size_t y; + assert(delta <= 24); + for (y = 0; y < size; y += delta) { + char buf[100]; + size_t x; + ssize_t idx = 0; + idx += sprintf(&(buf[idx]), "%04x ", y); + for (x = 0; x < delta; x++) { + const size_t i = x + y; + if (i < size) { + idx += sprintf(&(buf[idx]), " %02x", b[i]); + } else { + idx += sprintf(&(buf[idx]), " "); + } + } + idx += sprintf(&buf[idx], " "); + for (x = 0; x < delta; x++) { + const size_t i = x + y; + if (i < size) { + idx += sprintf(&buf[idx], "%c", printable(b[i])); + } else { + idx += sprintf(&buf[idx], " "); + } + } + debug("%s%s", prefix, buf); + } +} + + +static void debug_data(const char *prefix, const void *data, const size_t size) +{ + debug_data_int(prefix, data, size, 16); +} + + +typedef enum { + CMD_CONNECT = 0x05, + CMD_DISCONNECT = 0x06, + CMD_ALARM = 0x07, + CMD_WRITE = 0x08, + CMD_PRINT1 = 0x09, + CMD_PRINT2 = 0x0A, + CMD_ACK = 0x0B, + CMD_NACK = 0x0C, + CMD_CONFIRM = 0x0D, + CMD_RESET = 0x0E, + + LCM_CLEAR = 0x21, + LCM_HOME = 0x22, + LCM_CURSOR_SHIFT_R = 0x23, + LCM_CURSOR_SHIFT_L = 0x24, + LCM_BACKLIGHT_ON = 0x25, + LCM_BACKLIGHT_OFF = 0x26, + LCM_LINE2 = 0x27, + LCM_DISPLAY_SHIFT_R = 0x28, + LCM_DISPLAY_SHIFT_L = 0x29, + LCM_CURSOR_ON = 0x2A, + LCM_CURSOR_OFF = 0x2B, + LCM_CURSOR_BLINK = 0x2C, + LCM_DISPLAY_ON = 0x2D, + LCM_DISPLAY_OFF = 0x2E +} lcm_cmd_t; + + +static +const char *cmdstr(const lcm_cmd_t cmd) +{ + switch (cmd) { +#define D(CMD) case CMD_ ## CMD: return "CMD_" # CMD; break; + D(CONNECT); + D(DISCONNECT); + D(ACK); + D(NACK); + D(CONFIRM); + D(RESET); + D(ALARM); + D(WRITE); + D(PRINT1); + D(PRINT2); +#undef D +#define D(CMD) case LCM_ ## CMD: return "LCM_" # CMD; break; + D(CLEAR); + D(HOME); + D(CURSOR_SHIFT_R); + D(CURSOR_SHIFT_L); + D(BACKLIGHT_ON); + D(BACKLIGHT_OFF); + D(LINE2); + D(DISPLAY_SHIFT_R); + D(DISPLAY_SHIFT_L); + D(CURSOR_ON); + D(CURSOR_OFF); + D(CURSOR_BLINK); + D(DISPLAY_ON); + D(DISPLAY_OFF); +#undef D + } + return "CMD_UNKNOWN"; +} + + +/* + * Magic defines + */ + +#define LCM_FRAME_MASK 0xFF +#define LCM_TIMEOUT 2 +#define LCM_ESC 0x1B + +#define LCM_KEY1 0x31 +#define LCM_KEY2 0x32 +#define LCM_KEY3 0x33 +#define LCM_KEY4 0x34 +#define LCM_KEY12 0x35 +#define LCM_KEY13 0x36 +#define LCM_KEY14 0x37 +#define LCM_KEY23 0x38 +#define LCM_KEY24 0x39 +#define LCM_KEY34 0x3A + + +/****************************************/ +/*** hardware dependant functions ***/ +/****************************************/ + +/* global LCM state machine */ + + +struct _lcm_fsm_t; +typedef struct _lcm_fsm_t lcm_fsm_t; + + +typedef enum { + ST_IDLE, /* mode == 0, IDLE */ + ST_COMMAND, /* mode == 1, COMMAND */ + ST_CONNECTED /* mode == 2, CONNECTED */ +} lcm_state_t; + + +static +const char *state2str(const lcm_state_t state) +{ + switch (state) { + case ST_IDLE: + return "ST_IDLE (0)"; + break; + case ST_COMMAND: + return "ST_COMMAND (1)"; + break; + case ST_CONNECTED: + return "ST_CONNECTED (2)"; + break; + } + return "ST_UNKNOWN"; +} + + +#if 0 +static +void repeat_connect_to_display_callback(void *data); +#endif + +static +void lcm_send_cmd(lcm_cmd_t cmd); + +static +void drv_TeakLCM_clear(void); + + +static +void raw_send_cmd_frame(lcm_cmd_t cmd); + +static +void raw_send_data_frame(lcm_cmd_t cmd, const char *data, const unsigned int len); + + +static +lcm_state_t fsm_get_state(lcm_fsm_t * fsm); + +static +void fsm_handle_cmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd); + +static +void fsm_handle_datacmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const u_int8_t * payload, const unsigned int payload_len); + +static +void try_reset(void); + +static +void fsm_step(lcm_fsm_t * fsm); + +static +void fsm_trans_noop(lcm_fsm_t * fsm, const lcm_state_t next_state); + +static +void fsm_trans_cmd(lcm_fsm_t * fsm, const lcm_state_t next_state, const lcm_cmd_t cmd); + +static +void fsm_trans_data(lcm_fsm_t * fsm, + const lcm_state_t next_state, const lcm_cmd_t cmd, const char *data, const unsigned int len); + + +static +void fsm_handle_bytes(lcm_fsm_t * fsm, u_int8_t * rxbuf, const unsigned int buflen) +{ + if ((buflen >= 3) && (rxbuf[0] == LCM_FRAME_MASK) && (rxbuf[2] == LCM_FRAME_MASK)) { + const lcm_cmd_t cmd = rxbuf[1]; + debug("%s Received cmd frame (cmd=%d=%s)", __FUNCTION__, cmd, cmdstr(cmd)); + fsm_handle_cmd(fsm, cmd); + if (buflen > 3) { + /* recursively handle remaining bytes */ + fsm_handle_bytes(fsm, &rxbuf[3], buflen - 3); + } + return; + } else if ((buflen > 3) && (rxbuf[0] == LCM_FRAME_MASK)) { + unsigned int ri; /* raw indexed */ + unsigned int ci; /* cooked indexed, i.e. after unescaping */ + + debug("%s Received possible data frame", __FUNCTION__); + + /* unescape rxframe data in place */ + u_int16_t crc0 = 0, crc1 = 0, crc2 = 0, crc3 = 0; + for (ri = 1, ci = 1; ri < buflen; ri++) { + switch (rxbuf[ri]) { + case LCM_ESC: + ri++; + /* fall through */ + default: + rxbuf[ci++] = rxbuf[ri]; + crc3 = crc2; + crc2 = crc1; + crc1 = crc0; + crc0 = CRC16(rxbuf[ri], crc0); + break; + } + if ((rxbuf[ci - 1] == LCM_FRAME_MASK) && (rxbuf[ci - 2] == LO8(crc3)) && (rxbuf[ci - 3] == HI8(crc3))) { + /* looks like a complete data frame */ + lcm_cmd_t cmd = rxbuf[1]; + u_int16_t len = (rxbuf[3] << 8) + rxbuf[2]; + assert(ci == (unsigned int) (1 + 1 + 2 + len + 2 + 1)); + fsm_handle_datacmd(fsm, cmd, &rxbuf[4], len); + if (ri + 1 < buflen) { + /* recursively handle remaining bytes */ + fsm_handle_bytes(fsm, &rxbuf[ri + 1], buflen - ri); + } + return; + } + } + + fsm_trans_cmd(fsm, fsm_get_state(fsm), /* TODO: Is this a good next_state value? */ + CMD_NACK); + debug("%s checksum/framemask error", __FUNCTION__); + return; + } else { + debug("%s Received garbage data:", __FUNCTION__); + debug_data(" RXD ", rxbuf, buflen); + return; + } +} + + +static void fsm_handle_cmd(lcm_fsm_t * fsm, lcm_cmd_t cmd) +{ + // debug("fsm_handle_cmd: old state 0x%02x %s", lcm_mode, modestr(lcm_mode)); + const lcm_state_t old_state = fsm_get_state(fsm); + if (CMD_RESET == cmd) { + global_reset_rx_flag = 1; + } + switch (old_state) { + case ST_IDLE: + case ST_COMMAND: + switch (cmd) { + case CMD_CONNECT: + fsm_trans_cmd(fsm, ST_COMMAND, CMD_ACK); + break; + case CMD_ACK: + fsm_trans_cmd(fsm, ST_CONNECTED, CMD_CONFIRM); + break; + case CMD_NACK: + fsm_trans_cmd(fsm, ST_IDLE, CMD_CONFIRM); + break; + case CMD_CONFIRM: + fsm_trans_noop(fsm, ST_CONNECTED); + break; + case CMD_RESET: + fsm_trans_cmd(fsm, ST_COMMAND, CMD_CONNECT); + break; + default: + error("%s: Unhandled cmd %s in state %s", Name, cmdstr(cmd), state2str(old_state)); + fsm_trans_cmd(fsm, ST_IDLE, CMD_NACK); + break; + } + break; + case ST_CONNECTED: /* "if (mode == 2)" */ + switch (cmd) { + case CMD_ACK: + fsm_trans_cmd(fsm, ST_CONNECTED, CMD_CONFIRM); + break; + case CMD_CONNECT: + fsm_trans_cmd(fsm, ST_CONNECTED, CMD_NACK); + break; + case CMD_DISCONNECT: + fsm_trans_cmd(fsm, ST_CONNECTED, CMD_ACK); + break; + case CMD_RESET: + fsm_trans_cmd(fsm, ST_IDLE, CMD_CONNECT); + break; + default: + debug("%s: Ignoring unhandled cmd %s in state %s", Name, cmdstr(cmd), state2str(old_state)); + fsm_trans_noop(fsm, ST_CONNECTED); + break; + } + break; + } + fsm_step(fsm); +} + + +static +void fsm_handle_datacmd(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const u_int8_t * payload, unsigned int payload_len) +{ + const lcm_state_t old_state = fsm_get_state(fsm); + debug("fsm_handle_datacmd: old state 0x%02x %s", old_state, state2str(old_state)); + switch (old_state) { + case ST_CONNECTED: + switch (cmd) { + case CMD_WRITE: + assert(payload_len == 1); + debug("Got a key code 0x%02x", *payload); + fsm_trans_noop(fsm, ST_CONNECTED); + // lcm_send_cmd_frame(CMD_ACK); + break; + default: + debug("Got an unknown data frame: %d=%s", cmd, cmdstr(cmd)); + fsm_trans_noop(fsm, ST_CONNECTED); + // lcm_send_cmd_frame(CMD_NACK); + break; + } + break; + case ST_IDLE: + case ST_COMMAND: + fsm_trans_cmd(fsm, old_state, CMD_NACK); + break; + } + fsm_step(fsm); +} + + +struct _lcm_fsm_t { + lcm_state_t state; + lcm_state_t next_state; + enum { + ACTION_UNINITIALIZED, + ACTION_NOOP, + ACTION_CMD, + ACTION_DATA + } action_type; + union { + struct { + lcm_cmd_t cmd; + } cmd_frame; + struct { + lcm_cmd_t cmd; + const char *data; + unsigned int len; + } data_frame; + } action; +}; + + +static +lcm_state_t fsm_get_state(lcm_fsm_t * fsm) +{ + return fsm->state; +} + + +static +void flush_shadow(void); + + +static +void fsm_step(lcm_fsm_t * fsm) +{ + debug("fsm: old_state=%s new_state=%s", state2str(fsm->state), state2str(fsm->next_state)); + switch (fsm->action_type) { + case ACTION_UNINITIALIZED: + error("Uninitialized LCM FSM action"); + abort(); + break; + case ACTION_NOOP: + break; + case ACTION_CMD: + raw_send_cmd_frame(fsm->action.cmd_frame.cmd); + break; + case ACTION_DATA: + raw_send_data_frame(fsm->action.data_frame.cmd, fsm->action.data_frame.data, fsm->action.data_frame.len); + break; + } + fsm->action_type = ACTION_UNINITIALIZED; + switch (fsm->next_state) { + case ST_IDLE: + case ST_COMMAND: + fsm->state = fsm->next_state; + fsm->next_state = -1; + return; + break; + case ST_CONNECTED: + if (fsm->state != ST_CONNECTED) { + /* going from ST_IDLE or ST_COMMAND into ST_CONNECTED */ + if (!global_reset_rx_flag) { + try_reset(); +#if 0 + int timer_res = timer_add(repeat_connect_to_display_callback, NULL, 50 /*ms */ , 1); + debug("re-scheduled connect callback result: %d", timer_res); + + done by try_reset / fsm_init fsm->state = fsm->next_state; + fsm->next_state = -1; +#endif + return; + } else { + /* properly connected for the first time */ + debug("%s: %s NOW CONNECTED!!!", Name, __FUNCTION__); + + fsm->state = fsm->next_state; + fsm->next_state = -1; + + lcm_send_cmd(LCM_DISPLAY_ON); + flush_shadow(); + lcm_send_cmd(LCM_BACKLIGHT_ON); + return; + } + } else { + debug("no state change in ST_CONNECTED"); + fsm->state = fsm->next_state; + fsm->next_state = -1; + return; + } + error("we should never arrive here"); + abort(); + break; + } + error("LCM FSM: Illegal next_state"); + abort(); +} + + +#if 0 + +#endif + + +static +void fsm_trans_noop(lcm_fsm_t * fsm, const lcm_state_t next_state) +{ + fsm->next_state = next_state; + fsm->action_type = ACTION_NOOP; +} + + +static +void fsm_trans_cmd(lcm_fsm_t * fsm, const lcm_state_t next_state, const lcm_cmd_t cmd) +{ + fsm->next_state = next_state; + fsm->action_type = ACTION_CMD; + fsm->action.cmd_frame.cmd = cmd; +} + + +static +void fsm_trans_data(lcm_fsm_t * fsm, + const lcm_state_t next_state, const lcm_cmd_t cmd, const char *data, const unsigned int len) +{ + fsm->next_state = next_state; + fsm->action_type = ACTION_DATA; + fsm->action.data_frame.cmd = cmd; + fsm->action.data_frame.data = data; + fsm->action.data_frame.len = len; +} + + +static +void fsm_send(lcm_fsm_t * fsm, const lcm_cmd_t cmd) +{ + const lcm_state_t old_state = fsm_get_state(fsm); + switch (old_state) { + case ST_IDLE: + case ST_COMMAND: + debug("%s: %s, ignoring cmd 0x%02x=%s", __FUNCTION__, state2str(old_state), cmd, cmdstr(cmd)); + /* Silently ignore the command to send. */ + /* TODO: Would it be better to queue it and send it later? */ + break; + case ST_CONNECTED: + fsm_trans_cmd(fsm, ST_CONNECTED, cmd); + fsm_step(fsm); + break; + } +} + + +static +void fsm_send_data(lcm_fsm_t * fsm, const lcm_cmd_t cmd, const void *data, const unsigned int len) +{ + const lcm_state_t old_state = fsm_get_state(fsm); + switch (old_state) { + case ST_IDLE: + case ST_COMMAND: + debug("%s: %s, ignoring data cmd 0x%02x=%s", __FUNCTION__, state2str(old_state), cmd, cmdstr(cmd)); + /* Silently ignore the command to send. */ + /* TODO: Would it be better to queue it and send it later? */ + break; + case ST_CONNECTED: + fsm_trans_data(fsm, ST_CONNECTED, cmd, data, len); + fsm_step(fsm); + break; + } +} + + +static lcm_fsm_t lcm_fsm; + + +static +void fsm_init(void) +{ + lcm_fsm.state = ST_IDLE; + lcm_fsm.next_state = -1; + lcm_fsm.action_type = ACTION_UNINITIALIZED; +} + + + +/* Send a command frame to the TCM board */ +static +void raw_send_cmd_frame(lcm_cmd_t cmd) +{ + // lcm_receive_check(); + char cmd_buf[3]; + cmd_buf[0] = LCM_FRAME_MASK; + cmd_buf[1] = cmd; + cmd_buf[2] = LCM_FRAME_MASK; + debug("%s sending cmd frame cmd=0x%02x=%s", __FUNCTION__, cmd, cmdstr(cmd)); + debug_data(" TX ", cmd_buf, 3); + drv_generic_serial_write(cmd_buf, 3); + usleep(100); +#if 0 + usleep(100000); + switch (cmd) { + case CMD_ACK: + //case CMD_CONFIRM: + case CMD_NACK: + lcm_receive_check(); + break; + default: + if (1) { + int i; + for (i = 0; i < 20; i++) { + usleep(100000); + if (lcm_receive_check()) { + break; + } + } + } + break; + } +#endif +} + + +/* Send a data frame to the TCM board */ +static +void raw_send_data_frame(lcm_cmd_t cmd, const char *data, const unsigned int len) +{ + unsigned int di; /* data index */ + unsigned int fi; /* frame index */ + static char frame[32]; + u_int16_t crc = 0; + + frame[0] = LCM_FRAME_MASK; + + frame[1] = cmd; + crc = CRC16(frame[1], crc); + + frame[2] = HI8(len); + crc = CRC16(frame[2], crc); + + frame[3] = LO8(len); + crc = CRC16(frame[3], crc); + +#define APPEND(value) \ + do { \ + const unsigned char v = (value); \ + if ((v == LCM_FRAME_MASK) || (v == LCM_ESC)) { \ + frame[fi++] = LCM_ESC; \ + } \ + frame[fi++] = v; \ + crc = CRC16(v, crc); \ + } while (0) + +#define APPEND_NOCRC(value) \ + do { \ + const unsigned char v = (value); \ + if ((v == LCM_FRAME_MASK) || (v == LCM_ESC)) { \ + frame[fi++] = LCM_ESC; \ + } \ + frame[fi++] = v; \ + } while (0) + + for (fi = 4, di = 0; di < len; di++) { + APPEND(data[di]); + } + + APPEND_NOCRC(HI8(crc)); + APPEND_NOCRC(LO8(crc)); + + frame[fi++] = LCM_FRAME_MASK; + + debug_data(" TXD ", frame, fi); + drv_generic_serial_write(frame, fi); + +#undef APPEND + + usleep(500); +} + + +static +void lcm_send_cmd(lcm_cmd_t cmd) +{ + fsm_send(&lcm_fsm, cmd); +} + + +static +void lcm_event_callback(event_flags_t flags, void *data) +{ + lcm_fsm_t *fsm = (lcm_fsm_t *) data; + debug("%s: flags=%d, data=%p", __FUNCTION__, flags, data); + if (flags & EVENT_READ) { + static u_int8_t rxbuf[32]; + const int readlen = drv_generic_serial_poll((void *) rxbuf, sizeof(rxbuf)); + if (readlen <= 0) { + debug("%s Received no data", __FUNCTION__); + } else { + debug("%s RECEIVED %d bytes", __FUNCTION__, readlen); + debug_data(" RX ", rxbuf, readlen); + fsm_handle_bytes(fsm, rxbuf, readlen); + } + } +} + + +static int drv_TeakLCM_open(const char *section) +{ + /* open serial port */ + /* don't mind about device, speed and stuff, this function will take care of */ + + const int fd = drv_generic_serial_open(section, Name, 0); + if (fd < 0) + return -1; + + return fd; +} + + +static int drv_TeakLCM_close(int fd) +{ + if (fd >= 0) { + event_del(fd); + } + + /* close whatever port you've opened */ + drv_generic_serial_close(); + + return 0; +} + + +/* shadow buffer */ +static +char *shadow; + + +static void debug_shadow(const char *prefix) +{ + debug_data_int(prefix, shadow, DCOLS * DROWS, 20); +} + + +static +void flush_shadow(void) +{ + debug("%s called", __FUNCTION__); + debug_shadow(" shadow "); + usleep(50000); + fsm_send_data(&lcm_fsm, CMD_PRINT1, &shadow[DCOLS * 0], DCOLS); + usleep(50000); + fsm_send_data(&lcm_fsm, CMD_PRINT2, &shadow[DCOLS * 1], DCOLS); + usleep(50000); +} + + +/* text mode displays only */ +static +void drv_TeakLCM_clear(void) +{ + /* do whatever is necessary to clear the display */ + memset(shadow, ' ', DROWS * DCOLS); + flush_shadow(); +} + + +/* text mode displays only */ +static void drv_TeakLCM_write(const int row, const int col, const char *data, int len) +{ + debug("%s row=%d col=%d len=%d data=\"%s\"", __FUNCTION__, row, col, len, data); + + memcpy(&shadow[DCOLS * row + col], data, len); + + debug_shadow(" shadow "); + + fsm_send_data(&lcm_fsm, (row == 0) ? CMD_PRINT1 : CMD_PRINT2, &shadow[DCOLS * row], DCOLS); +} + + +static +void try_reset(void) +{ + debug("%s called", __FUNCTION__); + fsm_init(); + raw_send_cmd_frame(CMD_RESET); +} + + +#if 0 +static +void repeat_connect_to_display_callback(void *data) +{ + static int already_called = 0; + if (!already_called) { + debug("%s(%p): called", __FUNCTION__, data); + + /* reset & initialize display */ + try_reset(); + already_called = 1; + } else { + debug("%s(%p): already called, ignoring", __FUNCTION__, data); + } +} +#endif + + +static +int global_fd = -1; + + +static +void initial_connect_to_display_callback(void *data) +{ + debug("%s(%p): called", __FUNCTION__, data); + + debug("Calling event_add for fd=%d", global_fd); + int ret = event_add(lcm_event_callback, &lcm_fsm, global_fd, 1, 0, 1); + debug("event_add result: %d", ret); + + /* reset & initialize display */ + try_reset(); +} + + +/* start text mode display */ +static int drv_TeakLCM_start(const char *section) +{ + int rows = -1, cols = -1; + char *s; + + s = cfg_get(section, "Size", NULL); + if (s == NULL || *s == '\0') { + error("%s: no '%s.Size' entry from %s", Name, section, cfg_source()); + return -1; + } + if (sscanf(s, "%dx%d", &cols, &rows) != 2 || rows < 1 || cols < 1) { + error("%s: bad %s.Size '%s' from %s", Name, section, s, cfg_source()); + free(s); + return -1; + } + + DROWS = rows; + DCOLS = cols; + shadow = malloc(DROWS * DCOLS); + memset(shadow, ' ', DROWS * DCOLS); + + /* open communication with the display */ + global_fd = drv_TeakLCM_open(section); + if (global_fd < 0) { + return -1; + } + debug("%s: %s opened", Name, __FUNCTION__); + + /* read initial garbage data */ + static u_int8_t rxbuf[32]; + const int readlen = drv_generic_serial_poll((void *) rxbuf, sizeof(rxbuf)); + if (readlen >= 0) { + debug_data(" initial RX garbage ", rxbuf, readlen); + } + + /* We need to do a delayed connect */ + int timer_res = timer_add(initial_connect_to_display_callback, NULL, 10 /*ms */ , 1); + debug("timer_add for connect callback result: %d", timer_res); + + debug("%s: %s done", Name, __FUNCTION__); + return 0; +} + + +/****************************************/ +/*** plugins ***/ +/****************************************/ + + +/****************************************/ +/*** widget callbacks ***/ +/****************************************/ + + +/* using drv_generic_text_draw(W) */ +/* using drv_generic_text_icon_draw(W) */ +/* using drv_generic_text_bar_draw(W) */ + + +/****************************************/ +/*** exported functions ***/ +/****************************************/ + + +/* list models */ +int drv_TeakLCM_list(void) +{ + printf("TeakLCM driver"); + return 0; +} + + +/* initialize driver & display */ +/* use this function for a text display */ +int drv_TeakLCM_init(const char *section, const int quiet) +{ + WIDGET_CLASS wc; + int ret; + + info("%s: %s (quiet=%d)", Name, "$Rev$", quiet); + + /* display preferences */ + XRES = 5; /* pixel width of one char */ + YRES = 8; /* pixel height of one char */ + CHARS = 0; /* number of user-defineable characters */ + CHAR0 = 0; /* ASCII of first user-defineable char */ + GOTO_COST = -1; /* number of bytes a goto command requires */ + + /* real worker functions */ + drv_generic_text_real_write = drv_TeakLCM_write; + + /* start display */ + if ((ret = drv_TeakLCM_start(section)) != 0) + return ret; + + /* initialize generic text driver */ + if ((ret = drv_generic_text_init(section, Name)) != 0) + return ret; + + /* register text widget */ + wc = Widget_Text; + wc.draw = drv_generic_text_draw; + widget_register(&wc); + + /* register plugins */ + + return 0; +} + + +/* close driver & display */ +/* use this function for a text display */ +int drv_TeakLCM_quit(const int quiet) +{ + + info("%s: shutting down. (quiet=%d)", Name, quiet); + + drv_generic_text_quit(); + + /* clear display */ + drv_TeakLCM_clear(); + + lcm_send_cmd(LCM_DISPLAY_OFF); + // lcm_send_cmd_frame(LCM_BACKLIGHT_OFF); + lcm_send_cmd(CMD_DISCONNECT); + + /* FIXME: consume final ack frame */ + usleep(100000); + + debug("closing connection"); + drv_TeakLCM_close(global_fd); + + return (0); +} + + +/* use this one for a text display */ +DRIVER drv_TeakLCM = { + .name = Name, + .list = drv_TeakLCM_list, + .init = drv_TeakLCM_init, + .quit = drv_TeakLCM_quit, +}; + + +/* + * Local Variables: + * c-basic-offset: 4 + * End: + */ diff --git a/lcd4linux.conf.sample b/lcd4linux.conf.sample index 0e4da6c..9c312bd 100644 --- a/lcd4linux.conf.sample +++ b/lcd4linux.conf.sample @@ -27,6 +27,16 @@ Display SerDispLib { } +Display TeakLCM { + Driver 'TeakLCM' + Size '20x2' + Port '/dev/ttyS1' + Speed 38400 + Backlight 1 + Icons 0 +} + + Display Trefon { Driver 'TREFON' Size '16x2' @@ -1313,6 +1323,7 @@ Layout Debug { #Display 'USBLCD' #Display 'BWCT' #Display 'Image' +#Display 'TeakLCD' #Display 'Trefon' #Display 'LCD2USB' #Display 'LPH7508-serdisplib' diff --git a/smoketest.sh b/smoketest.sh index 0077b86..afa92eb 100755 --- a/smoketest.sh +++ b/smoketest.sh @@ -9,7 +9,7 @@ rm -f smoketest.log lcd4linux make distclean ./bootstrap -for driver in ASTUSB BeckmannEgle BWCT CrystalFontz Curses Cwlinux D4D DPF EA232graphic EFN G15 GLCD2USB HD44780 IRLCD LCD2USB LCDLinux LCDTerm LEDMatrix LPH7508 LUIse LW_ABP M50530 MatrixOrbital MatrixOrbitalGX MilfordInstruments Noritake NULL Pertelian PHAnderson picoLCD picoLCDGraphic PNG PPM RouterBoard Sample serdisplib SimpleLCD T6963 Trefon ULA200 USBHUB USBLCD WincorNixdorf X11; do +for driver in ASTUSB BeckmannEgle BWCT CrystalFontz Curses Cwlinux D4D DPF EA232graphic EFN G15 GLCD2USB HD44780 IRLCD LCD2USB LCDLinux LCDTerm LEDMatrix LPH7508 LUIse LW_ABP M50530 MatrixOrbital MatrixOrbitalGX MilfordInstruments Noritake NULL Pertelian PHAnderson picoLCD picoLCDGraphic PNG PPM RouterBoard Sample serdisplib SimpleLCD T6963 TeakLCM Trefon ULA200 USBHUB USBLCD WincorNixdorf X11; do make distclean ./configure --with-drivers=$driver |