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