xref: /freebsd/share/examples/ppi/ppilcd.c (revision 380a989b3223d455375b4fae70fd0b9bdd43bafb)
1 /*
2  * Control LCD module hung off parallel port using the
3  * ppi 'geek port' interface.
4  *
5  * $Id$
6  */
7 
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ctype.h>
12 #include <fcntl.h>
13 #include <unistd.h>
14 #include <err.h>
15 #include <sysexits.h>
16 
17 /* XXX should be in <machine/> */
18 #include "ppbconf.h"
19 #include "ppi.h"
20 
21 #define debug(lev, fmt, args...)	if (debuglevel >= lev) fprintf(stderr, fmt "\n" , ## args);
22 
23 static void	usage(void);
24 static char	*progname;
25 
26 #define	DEFAULT_DEVICE	"/dev/ppi0"
27 
28 /* Driver functions */
29 static void	hd44780_prepare(char *devname, char *options);
30 static void	hd44780_finish(void);
31 static void	hd44780_command(int cmd);
32 static void	hd44780_putc(int c);
33 
34 /*
35  * Commands
36  * Note that unrecognised command escapes are passed through with
37  * the command value set to the ASCII value of the escaped character.
38  */
39 #define CMD_RESET	0
40 #define CMD_BKSP	1
41 #define CMD_CLR		2
42 #define CMD_NL		3
43 #define CMD_CR		4
44 #define CMD_HOME	5
45 
46 #define MAX_DRVOPT	10	/* maximum driver-specific options */
47 
48 struct lcd_driver
49 {
50     char	*l_code;
51     char	*l_name;
52     char	*l_options[MAX_DRVOPT];
53     void	(* l_prepare)(char *name, char *options);
54     void	(* l_finish)(void);
55     void	(* l_command)(int cmd);
56     void	(* l_putc)(int c);
57 };
58 
59 static struct lcd_driver lcd_drivertab[] = {
60     {
61 	"hd44780",
62 	"Hitachi HD44780 and compatibles",
63 	{
64 	    "Reset options:",
65 	    "    1     1-line display (default 2)",
66 	    "    B     Cursor blink enable",
67 	    "    C     Cursor enable",
68 	    "    F     Large font select",
69 	    NULL
70 	},
71 	hd44780_prepare,
72 	hd44780_finish,
73 	hd44780_command,
74 	hd44780_putc
75     },
76     {
77 	NULL,
78 	NULL,
79 	{
80 	    NULL
81 	},
82 	NULL,
83 	NULL
84     }
85 };
86 
87 static void	do_char(struct lcd_driver *driver, char ch);
88 
89 int	debuglevel = 0;
90 int	vflag = 0;
91 
92 void
93 main(int argc, char *argv[])
94 {
95     extern char		*optarg;
96     extern int		optind;
97     struct lcd_driver	*driver = &lcd_drivertab[0];
98     char		*drivertype, *cp;
99     char		*devname = DEFAULT_DEVICE;
100     char		*drvopts = NULL;
101     int			ch, i;
102 
103     if ((progname = strrchr(argv[0], '/'))) {
104 	progname++;
105     } else {
106 	progname = argv[0];
107     }
108 
109     drivertype = getenv("LCD_TYPE");
110 
111     while ((ch = getopt(argc, argv, "Dd:f:o:v")) != EOF) {
112 	switch(ch) {
113 	case 'D':
114 	    debuglevel++;
115 	    break;
116 	case 'd':
117 	    drivertype = optarg;
118 	    break;
119 	case 'f':
120 	    devname = optarg;
121 	    break;
122 	case 'o':
123 	    drvopts = optarg;
124 	    break;
125 	case 'v':
126 	    vflag = 1;
127 	    break;
128 	default:
129 	    usage();
130 	}
131     }
132     argc -= optind;
133     argv += optind;
134 
135     /* If an LCD type was specified, look it up */
136     if (drivertype != NULL) {
137 	driver = NULL;
138 	for (i = 0; lcd_drivertab[i].l_code != NULL; i++) {
139 	    if (!strcmp(drivertype, lcd_drivertab[i].l_code)) {
140 		driver = &lcd_drivertab[i];
141 		break;
142 	    }
143 	}
144 	if (driver == NULL) {
145 	    warnx("LCD driver '%s' not known", drivertype);
146 	    usage();
147 	}
148     }
149     debug(1, "Driver selected for %s", driver->l_name);
150     driver->l_prepare(devname, drvopts);
151     atexit(driver->l_finish);
152 
153     if (argc > 0) {
154 	debug(2, "reading input from %d argument%s", argc, (argc > 1) ? "s" : "");
155 	for (i = 0; i < argc; i++)
156 	    for (cp = argv[i]; *cp; cp++)
157 		do_char(driver, *cp);
158     } else {
159 	debug(2, "reading input from stdin");
160 	setvbuf(stdin, NULL, _IONBF, 0);
161 	while ((ch = fgetc(stdin)) != EOF)
162 	    do_char(driver, (char)ch);
163     }
164     exit(EX_OK);
165 }
166 
167 static void
168 usage(void)
169 {
170     int		i, j;
171 
172     fprintf(stderr, "usage: %s [-v] [-d drivername] [-f device] [-o options] [args...]\n", progname);
173     fprintf(stderr, "   -D      Increase debugging\n");
174     fprintf(stderr, "   -f      Specify device, default is '%s'\n", DEFAULT_DEVICE);
175     fprintf(stderr, "   -d      Specify driver, one of:\n");
176     for (i = 0; lcd_drivertab[i].l_code != NULL; i++) {
177 	fprintf(stderr, "              %-10s (%s)%s\n",
178 		lcd_drivertab[i].l_code, lcd_drivertab[i].l_name, (i == 0) ? " *default*" : "");
179 	if (lcd_drivertab[i].l_options[0] != NULL) {
180 
181 	    for (j = 0; lcd_drivertab[i].l_options[j] != NULL; j++)
182 		fprintf(stderr, "                  %s\n", lcd_drivertab[i].l_options[j]);
183 	}
184     }
185     fprintf(stderr, "  -o       Specify driver option string\n");
186     fprintf(stderr, "  args     Message strings.  Embedded escapes supported:\n");
187     fprintf(stderr, "                  \\b	Backspace\n");
188     fprintf(stderr, "                  \\f	Clear display, home cursor\n");
189     fprintf(stderr, "                  \\n	Newline\n");
190     fprintf(stderr, "                  \\r	Carriage return\n");
191     fprintf(stderr, "                  \\R	Reset display\n");
192     fprintf(stderr, "                  \\v	Home cursor\n");
193     fprintf(stderr, "                  \\\\	Literal \\\n");
194     fprintf(stderr, "           If args not supplied, strings are read from standard input\n");
195     exit(EX_USAGE);
196 }
197 
198 static void
199 do_char(struct lcd_driver *driver, char ch)
200 {
201     static int	esc = 0;
202 
203     if (esc) {
204 	switch(ch) {
205 	case 'b':
206 	    driver->l_command(CMD_BKSP);
207 	    break;
208 	case 'f':
209 	    driver->l_command(CMD_CLR);
210 	    break;
211 	case 'n':
212 	    driver->l_command(CMD_NL);
213 	    break;
214 	case 'r':
215 	    driver->l_command(CMD_CR);
216 	    break;
217 	case 'R':
218 	    driver->l_command(CMD_RESET);
219 	    break;
220 	case 'v':
221 	    driver->l_command(CMD_HOME);
222 	    break;
223 	case '\\':
224 	    driver->l_putc('\\');
225 	    break;
226 	default:
227 	    driver->l_command(ch);
228 	    break;
229 	}
230 	esc = 0;
231     } else {
232 	if (ch == '\\') {
233 	    esc = 1;
234 	} else {
235 	    if (vflag || isprint(ch))
236 		driver->l_putc(ch);
237 	}
238     }
239 }
240 
241 
242 /******************************************************************************
243  * Driver for the Hitachi HD44780.  This is probably *the* most common driver
244  * to be found on one- and two-line alphanumeric LCDs.
245  *
246  * This driver assumes the following connections :
247  *
248  * Parallel Port	LCD Module
249  * --------------------------------
250  * Strobe (1)		Enable (6)
251  * Data (2-9)		Data (7-14)
252  * Select(13)		RS (4)
253  * Auto Feed (14)	R/W (5)
254  *
255  * In addition, power must be supplied to the module, normally with
256  * a circuit similar to this:
257  *
258  * VCC (+5V) O------o-------o--------O Module pin 2
259  *                  |       | +
260  *                  /      ---
261  *                  \      --- 1uF
262  *                  /       | -
263  *                  \ <-----o--------O Module pin 3
264  *                  /
265  *                  \
266  *                  |
267  * GND       O------o----------------O Module pin 1
268  *
269  * The ground line should also be connected to the parallel port, on
270  * one of the ground pins (eg. pin 25).
271  *
272  * Note that the pinning on some LCD modules has the odd and even pins
273  * arranged as though reversed; check carefully before conecting a module
274  * as it is possible to toast the HD44780 if the power is reversed.
275  */
276 
277 static int	hd_fd;
278 static u_int8_t	hd_cbits;
279 static int	hd_lines = 2;
280 static int	hd_blink = 0;
281 static int 	hd_cursor = 0;
282 static int	hd_font = 0;
283 
284 #define HD_COMMAND	SELECTIN
285 #define HD_DATA		0
286 #define HD_READ		0
287 #define HD_WRITE	AUTOFEED
288 
289 #define HD_BF		0x80		/* internal busy flag */
290 #define HD_ADDRMASK	0x7f		/* DDRAM address mask */
291 
292 #define hd_sctrl(v)	{u_int8_t _val; _val = hd_cbits | v; ioctl(hd_fd, PPISCTRL, &_val);}
293 #define hd_sdata(v)	{u_int8_t _val; _val = v; ioctl(hd_fd, PPISDATA, &_val);}
294 #define hd_gdata(v)	ioctl(hd_fd, PPIGDATA, &v)
295 
296 static void
297 hd44780_output(int type, int data)
298 {
299     debug(3, "%s -> 0x%02x", (type == HD_COMMAND) ? "cmd " : "data", data);
300     hd_sctrl(type | HD_WRITE | STROBE);	/* set direction, address */
301     hd_sctrl(type | HD_WRITE);		/* raise E */
302     hd_sdata((u_int8_t) data);		/* drive data */
303     hd_sctrl(type | HD_WRITE | STROBE);	/* lower E */
304 }
305 
306 static int
307 hd44780_input(int type)
308 {
309     u_int8_t	val;
310 
311     hd_sctrl(type | HD_READ | STROBE);	/* set direction, address */
312     hd_sctrl(type | HD_READ);		/* raise E */
313     hd_gdata(val);			/* read data */
314     hd_sctrl(type | HD_READ | STROBE);	/* lower E */
315 
316     debug(3, "0x%02x -> %s", val, (type == HD_COMMAND) ? "cmd " : "data");
317     return(val);
318 }
319 
320 static void
321 hd44780_prepare(char *devname, char *options)
322 {
323     char	*cp = options;
324 
325     if ((hd_fd = open(devname, O_RDWR, 0)) == -1)
326 	err(EX_OSFILE, "can't open '%s'", devname);
327 
328     /* parse options */
329     while (cp && *cp) {
330 	switch (*cp++) {
331 	case '1':
332 	    hd_lines = 1;
333 	    break;
334 	case 'B':
335 	    hd_blink = 1;
336 	    break;
337 	case 'C':
338 	    hd_cursor = 1;
339 	    break;
340 	case 'F':
341 	    hd_font = 1;
342 	    break;
343 	default:
344 	    errx(EX_USAGE, "hd44780: unknown option code '%c'", *(cp-1));
345 	}
346     }
347 
348     /* Put LCD in idle state */
349     if (ioctl(hd_fd, PPIGCTRL, &hd_cbits))		/* save other control bits */
350 	err(EX_IOERR, "ioctl PPIGCTRL failed (not a ppi device?)");
351     hd_cbits &= ~(STROBE | SELECTIN | AUTOFEED);	/* set strobe, RS, R/W low */
352     debug(2, "static control bits 0x%x", hd_cbits);
353     hd_sctrl(STROBE);
354     hd_sdata(0);
355 
356 }
357 
358 static void
359 hd44780_finish(void)
360 {
361     close(hd_fd);
362 }
363 
364 static void
365 hd44780_command(int cmd)
366 {
367     u_int8_t	val;
368 
369     switch (cmd) {
370     case CMD_RESET:	/* full manual reset and reconfigure as per datasheet */
371 	debug(1, "hd44780: reset to %d lines, %s font,%s%s cursor",
372 	      hd_lines, hd_font ? "5x10" : "5x7", hd_cursor ? "" : " no", hd_blink ? " blinking" : "");
373 	val = 0x30;
374 	if (hd_lines == 2)
375 	    val |= 0x08;
376 	if (hd_font)
377 	    val |= 0x04;
378 	hd44780_output(HD_COMMAND, val);
379 	usleep(10000);
380 	hd44780_output(HD_COMMAND, val);
381 	usleep(1000);
382 	hd44780_output(HD_COMMAND, val);
383 	usleep(1000);
384 	val = 0x08;				/* display off */
385 	hd44780_output(HD_COMMAND, val);
386 	usleep(1000);
387 	val |= 0x04;				/* display on */
388 	if (hd_cursor)
389 	    val |= 0x02;
390 	if (hd_blink)
391 	    val |= 0x01;
392 	hd44780_output(HD_COMMAND, val);
393 	usleep(1000);
394 	hd44780_output(HD_COMMAND, 0x06);	/* shift cursor by increment */
395 	usleep(1000);
396 	/* FALLTHROUGH */
397 
398     case CMD_CLR:
399 	hd44780_output(HD_COMMAND, 0x01);
400 	usleep(2000);
401 	break;
402 
403     case CMD_BKSP:
404 	hd44780_output(HD_DATA, 0x10);		/* shift cursor left one */
405 	break;
406 
407     case CMD_NL:
408 	if (hd_lines == 2)
409 	    hd44780_output(HD_COMMAND, 0xc0);	/* beginning of second line */
410 	break;
411 
412     case CMD_CR:
413 	/* XXX will not work in 4-line mode, or where readback fails */
414 	val = hd44780_input(HD_COMMAND) & 0x3f;	/* mask character position, save line pos */
415 	hd44780_output(HD_COMMAND, 0x80 | val);
416 	break;
417 
418     case CMD_HOME:
419 	hd44780_output(HD_COMMAND, 0x02);
420 	usleep(2000);
421 	break;
422 
423     default:
424 	if (isprint(cmd)) {
425 	    warnx("unknown command %c", cmd);
426 	} else {
427 	    warnx("unknown command 0x%x", cmd);
428 	}
429     }
430     usleep(40);
431 }
432 
433 static void
434 hd44780_putc(int c)
435 {
436     hd44780_output(HD_DATA, c);
437     usleep(40);
438 }
439 
440