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
main(int argc,char * argv[])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
usage(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
do_char(struct lcd_driver * driver,char ch)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
hd44780_output(int type,int data)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
hd44780_input(int type)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
hd44780_prepare(char * devname,char * options)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
hd44780_finish(void)356 hd44780_finish(void)
357 {
358 close(hd_fd);
359 }
360
361 static void
hd44780_command(int cmd)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
hd44780_putc(int c)431 hd44780_putc(int c)
432 {
433 hd44780_output(HD_DATA, c);
434 usleep(40);
435 }
436
437