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