mirror of
https://github.com/netfun2000/lcd4linux.git
synced 2026-02-27 09:44:34 +08:00
cab1c1a400
git-svn-id: https://ssl.bulix.org/svn/lcd4linux/trunk@1173 3ae390bd-cb1e-0410-b409-cd5a39f66f1f
1025 lines
22 KiB
C
1025 lines
22 KiB
C
/* $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:
|
|
*/
|