LCDproc development and user support list

Text archives Help


[Lcdproc] PICL driver


Chronological Thread 
  • From: dpe+lcdproc AT thedotin.net (David Edwards)
  • Subject: [Lcdproc] PICL driver
  • Date: Wed Oct 9 14:23:02 2002

I got a new toy, http://www.earthlcd.com/piclweb/piclindex.htm
Of course, it needed a lcdproc driver. So I wrote one (based on 0.4.3).
I'm also working on one for 0.5 (based on a CVS snapshot from Monday),
but it's not ready to be released upon an unsuspecting world.

Right now, it's very much works-for-me-ware, in a decidedly alpha state,
but better it be out where someone googling for "PICL" will find it,
right? Since 0.4.4 seems to be coming Real Soon Now, I'd suggest leaving
this for 0.4.5 (or 0.4.6, or whenever it seems to be ready for mass
consumption).

What works: What is needed for the v0.4.3 lcdproc client, including line
drawing (see caveat below)

What doesn't work:
Big nums
Downloaded 'custom' characters (Line drawing works, but it uses '#'
characters.)
Heartbeat (though this is trivial to fix, and I'll probably fix it
next time I get a chance)

This is still getting cleaned up, so I welcome suggestions/criticism.
/* This is the LCDproc driver for the PICL serial LCD widget from
EarthLCD (http://www.earthlcd.com)

Quick overview of the PICL: It's a 240x64 graphical LCD display
sitting on a PIC-based controller card with a serial port and
6 buttons. There is support for downloading graphics to the PICL,
but the 'default' is character-mode. There are also provisions for
downloading "custom" code to the PIC through the serial port to
make it act as a 'smart' device. Not bad for (US)$99, and even
includes wall-wart/serial cable...

Advice to anyone playing with one of these: Don't try to use it
while it's lying flat on your bench. Get feet or standoffs or
mount it in a case or..., because otherwise, it ends up resting
on its reset button (DOH!).

By experimentation, I've discovered that the serial port runs at
9600 8N1. (The documentation is a little hit-and-miss...)

Current status: Alpha-quality code... Has not been extensively
tested beyond verifying that the basic lcdproc client works. Does
_not_ support special graphical characters at this point, and may
never do so. Does not currently support heartbeat (hey, what do you
expect from an alpha release?).

Current plan: Will improve and maintain on a time-available basis
until further notice. (Maintainer address: dpe at thedotin dot net)

The PICL supports 3 modes:
Mode 0: 30 x 8 display, 6x8 char cell, with graphics
Mode 1: 40 x 8 display, 6x8 char cell, without graphics
Mode 2: 30 x 8 display, 8x8 char cell, with graphics
(Documentation errata:
Mode 0 and Mode 1 seem to be swapped in the documentation.)

Initial work has focused on getting the Mode 1 stuff to work (40 x 8
display).
Later on, I'll tackle the question of how to get 8x8 (30 x 8) font
working.
Also, hopefully figure out a way to get graphic stuff to the display.

I referred to the CrystalFontz and MtxOrb drivers as examples... Mad
props
to the folks who wrote those drivers! As usual, the brilliant bits were
shamelessly
stolen from other folks, while the bugs, typos, ugliness, and
incompatbilites are
mine alone.

Copyright (C) 2002, David Edwards

This program 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 of the License, or
any later version.

This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 */


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "lcd.h"
#include "picl.h"
#include "render.h"
#include "shared/str.h"
#include "shared/report.h"
#include "server/configfile.h"

// For some reason, this isn't defined on the RH7.2 version of string.h like
// the man page says it should be.
int strnlen(char *, int);

static int fd;
static char* backingstore = NULL;
static char* blankrow = NULL;

static void Picl_linewrap (int on);
static void Picl_autoscroll (int on);
static void Picl_hidecursor ();
static void Picl_reboot ();
static void Picl_heartbeat (int type);

lcd_logical_driver *Picl;

/////////////////////////////////////////////////////////////////
// Opens com port and sets baud correctly... Mostly cut-and-pasted
// from elsewhere. At the moment, the PICL supports 9600 8N1 only.
//
int
Picl_init (lcd_logical_driver * driver, char *args)
{
struct termios portset;
int tmp, w, h;

int contrast = PICL_DEF_CONTRAST;
char device[200] = PICL_DEF_DEVICE;
int speed = PICL_DEF_SPEED;
char size[200] = PICL_DEF_SIZE;

char buf[256] = "";
char out[4];

Picl = driver;

debug(RPT_INFO, "Picl: init(%p,%s)", driver, args );

#define DriverName "Picl"

strncpy(device, config_get_string ( DriverName , "Device" , 0 ,
PICL_DEF_DEVICE),
sizeof(device));
device[sizeof(device)-1]=0;
report (RPT_INFO,"Picl: Using device: %s", device);

strncpy(size, config_get_string ( DriverName , "Size" , 0 , PICL_DEF_SIZE),
sizeof(size));
size[sizeof(size)-1]=0;
if( sscanf(size , "%dx%d", &w, &h ) != 2
|| (w <= 0) || (w > LCD_MAX_WIDTH)
|| (h <= 0) || (h > LCD_MAX_HEIGHT))
{
report (RPT_WARNING, "Picl: Cannot read size: %s. Using default
value.\n", size);
sscanf( PICL_DEF_SIZE , "%dx%d", &w, &h );
}
driver->wid = w;
driver->hgt = h;

/*Which contrast*/
if (0 <= config_get_int ( DriverName , "Contrast" , 0 , PICL_DEF_CONTRAST)
&&
config_get_int ( DriverName , "Contrast" , 0 , PICL_DEF_CONTRAST)
<= 255)
{
contrast = config_get_int ( DriverName , "Contrast" , 0 ,
PICL_DEF_CONTRAST);
}
else
{
report (RPT_WARNING, "Picl: Contrast must be between 0 and 255. Using
default value.\n");
}

/*Which speed*/
tmp = config_get_int ( DriverName , "Speed" , 0 , PICL_DEF_SPEED);

switch (tmp) {
case 1200:
speed = B1200;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
default:
speed = PICL_DEF_SPEED;
switch (speed) {
case B1200:
strncpy(buf,"1200", sizeof(buf));
break;
case B2400:
strncpy(buf,"2400", sizeof(buf));
break;
case B4800:
strncpy(buf,"4800", sizeof(buf));
break;
case B9600:
strncpy(buf,"9600", sizeof(buf));
break;
case B19200:
strncpy(buf,"19200", sizeof(buf));
break;
}
report (RPT_WARNING , "Picl: Speed must be 1200, 2400, 4800, 9600, or
19200. Using default value of %s baud!", buf);
strncpy(buf,"", sizeof(buf));
}

/* End of config file parsing*/

if (!driver->framebuf)
{
driver->framebuf = malloc (driver->wid * driver->hgt);
memset(driver->framebuf, 0, sizeof(driver->framebuf));

backingstore = malloc (driver->wid * driver->hgt);
memset(backingstore, 0, sizeof(backingstore));

blankrow = malloc (driver->wid);
memset(blankrow, ' ', driver->wid);
}

if (!driver->framebuf)
{
report(RPT_ERR, "Picl: Error: unable to create framebuffer.\n");
return -1;
}

/* Set up io port correctly, and open it...*/
debug( RPT_DEBUG, "Picl: Opening serial device: %s", device);
fd = open (device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
report (RPT_ERR, "Picl: init() failed (%s)\n", strerror (errno));
return -1;
}
else
{
report (RPT_INFO, "Picl: Opened display on %s", device);
}

tcgetattr (fd, &portset);

// We use RAW mode
#ifdef HAVE_CFMAKERAW
// The easy way
cfmakeraw( &portset );
#else
// The hard way
portset.c_iflag &= ~( IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON );
portset.c_oflag &= ~OPOST;
portset.c_lflag &= ~( ECHO | ECHONL | ICANON | ISIG | IEXTEN );
portset.c_cflag &= ~( CSIZE | PARENB | CRTSCTS );
portset.c_cflag |= CS8 | CREAD | CLOCAL ;
#endif

// Set port speed
cfsetospeed (&portset, speed);
cfsetispeed (&portset, B0);

// Do it...
tcsetattr (fd, TCSANOW, &portset);

/* Set display-specific stuff..*/
Picl_hidecursor ();
Picl_linewrap (1);
Picl_autoscroll (0);
Picl_backlight (0);

// Set cursor to non-blinking _
sprintf(out, "\ek1\n");
write(fd, out, 4);

// Set screen mode to mode 1
sprintf(out, "\ef1\n");
write(fd, out, 4);

/* Set the functions the driver supports...*/

driver->daemonize = 1; /* make the server daemonize after initialization*/

driver->clear = Picl_clear;
driver->string = Picl_string;
driver->chr = Picl_chr;
driver->vbar = Picl_vbar;
driver->init_vbar = Picl_init_vbar;
driver->hbar = Picl_hbar;
driver->init_hbar = Picl_init_hbar;
driver->num = Picl_num;

driver->init = Picl_init;
driver->close = Picl_close;
driver->flush = Picl_flush;
driver->flush_box = Picl_flush_box;
driver->contrast = Picl_contrast;
driver->backlight = Picl_backlight;
driver->set_char = Picl_set_char;
driver->icon = Picl_icon;
driver->draw_frame = Picl_draw_frame;
driver->getkey = Picl_getkey;


Picl_contrast (contrast);

driver->cellwid = PICL_DEF_CELL_WIDTH;
driver->cellhgt = PICL_DEF_CELL_HEIGHT;

driver->heartbeat = Picl_heartbeat;

report (RPT_DEBUG, "Picl_init: done\n");

return fd;
}

/////////////////////////////////////////////////////////////////
// Clean-up
//
void
Picl_close ()
{
close (fd);

if (Picl->framebuf)
free (Picl->framebuf);

if (backingstore)
free (backingstore);

if (blankrow)
free (blankrow);

Picl->framebuf = NULL;
backingstore = NULL;
blankrow = NULL;
}

////////////////////////////////////////////////////////////////
// Effectively a no-op
void
Picl_flush ()
{
Picl_draw_frame (Picl->framebuf);
}

/////////////////////////////////////////////////////////////////
// If I read this right, it updates a subset of the display.
// Me, I'd recommend just using Picl_flush(), because I haven't
// even tried to test this yet.
void
Picl_flush_box (int lft, int top, int rgt, int bot)
{
int y;
char out[LCD_MAX_WIDTH];

debug (RPT_DEBUG, "Picl: flush_box (%i,%i)-(%i,%i)\n", lft, top, rgt, bot);

for (y = top; y <= bot; y++)
{
snprintf (out, sizeof(out), "%c%c%c%c", '\e', 'g', lft, y);
write (fd, out, 5);
write (fd, Picl->framebuf + (y * Picl->wid) + lft, rgt - lft + 1);
printf("%s", Picl->framebuf + (y * Picl->wid));
}
}

/////////////////////////////////////////////////////////////////
// Prints a character on the lcd display, at position (x,y). The
// upper-left is (1,1), and the lower right should be (wid,hgt).
//
void
Picl_chr (int x, int y, char c)
{
// Convert to 0-based coordinates
y--;
x--;

// Check to make sure that the coordinates are somewhere on the
// display.
if((x > Picl->wid)||(y > Picl->hgt)||(x < 0)||(y < 0))
return;

// No escape characters, darn it. Also, no 'high' characters until
// I can carefully read the data sheet to see what the display
// character generator does with them.
if (c < 32)
c = '#';

Picl->framebuf[(y * Picl->wid) + x] = c;
}

/////////////////////////////////////////////////////////////////
// Changes screen contrast
//
int
Picl_contrast (int contrast)
{ // Noop - Contrast on the PICL is through a trim pot.
return 0;
}

/////////////////////////////////////////////////////////////////
// Sets the backlight brightness.
// The options are "On" and "Off".
//
void
Picl_backlight (int on)
{
static int current = -1;
int realbacklight = -1;
char out[4];

if (on == current)
return;

if(0 == on)
realbacklight = 0;
else
realbacklight = 1;

snprintf (out, sizeof(out), "\eb%c", realbacklight);
write (fd, out, 4);
}

/////////////////////////////////////////////////////////////////
// Toggle the built-in linewrapping feature
//
static void
Picl_linewrap (int on)
{// Noop - Haven't figured out how to disable this yet.
// But I'm also whacking any attempt at wrapping a string,
// so it shouldn't be an issue.
}

/////////////////////////////////////////////////////////////////
// Toggle the built-in automatic scrolling feature
//
static void
Picl_autoscroll (int on)
{// Noop - Haven't figured out how to enable this yet.
}

/////////////////////////////////////////////////////////////////
// Hide the cursor.
// The docs say this should work. I have my doubts.,,
//
static void
Picl_hidecursor ()
{
char out[4];
snprintf (out, sizeof(out), "\ek0\n");
write (fd, out, 4);
}

/////////////////////////////////////////////////////////////////
// Reset the display
// Just clears the screen. No idea how to reset the sucker...
//
static void
Picl_reboot ()
{
char out[4];
snprintf (out, sizeof(out), "\ec");
write (fd, out, 3);
}

/////////////////////////////////////////////////////////////////
// Sets up for vertical bars. Call before Picl->vbar()
//
void
Picl_init_vbar ()
{ // Currently a no-op. No idea how to create characters...
}

/////////////////////////////////////////////////////////////////
// Inits horizontal bars...
//
void
Picl_init_hbar ()
{ // Currently a no-op. No idea how to create characters...
}

/////////////////////////////////////////////////////////////////
// Draws a vertical bar...
//
void
Picl_vbar (int x, int len)
{
char map[9] = { '.', '.', '+', '+', '|', '|', '|', '|', '|' };

int y;
for (y = Picl->hgt; y > 0 && len > 0; y--)
{
if (len >= Picl->cellhgt)
Picl_chr (x, y, map[8]);
else
Picl_chr (x, y, map[len]);

len -= Picl->cellhgt;
}
}

/////////////////////////////////////////////////////////////////
// Draws a horizontal bar to the right.
//
void
Picl_hbar (int x, int y, int len)
{
char map[8] = { '-', '-', '-', '-', '-', '-', '-', '-' };

for (; x <= Picl->wid && len > 0; x++)
{
if (len >= Picl->cellwid)
Picl_chr (x, y, map[7]);
else
Picl_chr (x, y, map[len]);
len -= Picl->cellwid;
}
}


/////////////////////////////////////////////////////////////////
// Writes a big number.
//
void
Picl_num (int x, int num)
{ // No-op, mostly because I have no idea what it's _supposed_ to do.
// printf("Picl_num(%i, %i)\n", x, num);
}

/////////////////////////////////////////////////////////////////
// Sets a custom character from 0-7...
//
// For input, values > 0 mean "on" and values <= 0 are "off".
//
// The input is just an array of characters...
//
void
Picl_set_char (int n, char *dat)
{// Noop
}

void
Picl_icon (int which, char dest)
{ // Noop
}

/////////////////////////////////////////////////////////////
// Transfer a single "frame" from memory to the LCD display.
//
void
Picl_draw_frame (char *dat)
{
char out[5];
char * display_row;
char * backing_row;
int i;
char bufferChanged = 0;

if (!dat)
return;

for (i = 0; i < Picl->hgt; i++)
{
display_row = dat + (Picl->wid * i);
backing_row = backingstore + (Picl->wid * i);

if(memcmp(backing_row, display_row, Picl->wid))
{

bufferChanged++;
// Position at the beginning of the appropriate line...
snprintf(out, sizeof(out), "%c%c%c%c", '\e', 'g', 0, i);
write(fd, out, 5);

//memcpy(backingstore + (Picl->wid * i), display_row, Picl->wid);
write(fd, display_row, Picl->wid);
fdatasync(fd);
}
}

// The "obvious" thing to do is to just copy the changed line
// to the backing store. However, since most "smart" compilers
// will allocate memory on double-word boundries, and then
// do "double word" memory copies of alligned memory, this
// is probably actually faster.
memcpy(backingstore, dat, Picl->wid * Picl->hgt);
/*
if(bufferChanged)
Picl_dumpFramebuffer();
*/
}

/////////////////////////////////////////////////////////////////
// Clears the LCD screen
//
void
Picl_clear ()
{
memset (Picl->framebuf, ' ', Picl->wid * Picl->hgt);
}

/////////////////////////////////////////////////////////////////
// Prints a string on the lcd display, at position (x,y). The
// upper-left is (1,1), and the lower right should be (wid,hgt).
//
void
Picl_string (int x, int y, char string[])
{
int i, len;
char * pDisplayOffset;

// Check for 0-length string.
len = strnlen(string, Picl->wid);
if(0 == len)
return;

// Check coordinates fall within the viewing area
if((x >= Picl->wid) || (y >= Picl->hgt))
return;

// Convert to 0-based coords.
x--; y--;

// Check that the string to be displayed is not longer than
// the line it is to be displayed on.
if((x + len) > Picl->wid)
return;

// Check for "illegal" characters. Some displays don't like
// "negative" characters. others have non-alphanumeric 'escape'
// characters to trigger screen control sequences.
for(i = 0; i < len; i++)
{
if(string[i] < 32)
string[i] = '#';
};

// This bit of ugliness sets pDisplayOffset to point at the
// 'correct' place in the framebuffer for the string to be added.
pDisplayOffset = Picl->framebuf + (y * Picl->wid) + x;
memcpy(pDisplayOffset, string, len);
}

/////////////////////////////////////////////////////////////
// Does the heartbeat...
//
static void
Picl_heartbeat (int type)
{ // Noop
}


/////////////////////////////////////////////////////////////////
// A handy debugging utility function. Will dump the contents of
// the framebuffer to whatever console LCDd is running on. Should
// not be used in "production" code.
void
Picl_dumpFramebuffer()
{
int i;
printf("Picl_dumpFramebuffer() (wid: %i, hgt: %i)\n", Picl->wid, Picl->hgt);
for(i = 0; i < (Picl->wid * Picl->hgt); i++)
{
printf("%c", Picl->framebuf[i]);
if(((i + 1) % Picl->wid) == 0)
{
printf("\n");
}
}
printf("Picl_dumpFramebuffer() returning.\n");
return;
}

/////////////////////////////////////////////////////////////////
// returns one character from the keypad, 1-6 on success, 0 on failure...
static char
Picl_getkey ()
{
char in[3] = {0, 0, 0};

read (fd, &in, 2);

if (in[0] == 'b')
{ // PICL sends button presses up in the form bX, where X is 1-6.
return in[1];
}
else
{
return 0;
}
}
/* This is the LCDproc driver for the PICL serial LCD widget from
EarthLCD (http://www.earthlcd.com)

I did refer to the CFontz and MtxOrb drivers a fairish bit when
writing this... Mad props to the original authors!

Copyright (C) 2002, David Edwards

This program 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 of the License, or
any later version.

This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 */

#ifndef PICL_H
#define PICL_H

extern lcd_logical_driver *Picl;

int Picl_init (lcd_logical_driver * driver, char *device);
void Picl_close ();
void Picl_flush ();
void Picl_flush_box (int lft, int top, int rgt, int bot);
void Picl_chr (int x, int y, char c);
int Picl_contrast (int contrast);
void Picl_backlight (int on);
void Picl_init_vbar ();
void Picl_init_hbar ();
void Picl_vbar (int x, int len);
void Picl_hbar (int x, int y, int len);
void Picl_init_num ();
void Picl_num (int x, int num);
void Picl_set_char (int n, char *dat);
void Picl_icon (int which, char dest);
void Picl_draw_frame (char *dat);
void Picl_clear (void);
void Picl_string (int x, int y, char string[]);
void Picl_dumpFramebuffer();
static char Picl_getkey ();

#define PICL_DEF_CELL_WIDTH 6
#define PICL_DEF_CELL_HEIGHT 8
#define PICL_DEF_CONTRAST 140
#define PICL_DEF_DEVICE "/dev/ttyS1"
#define PICL_DEF_SPEED B9600
#define PICL_DEF_SIZE "40x8"

#endif



Archive powered by MHonArc 2.6.18.

Top of page