1 /*
2 * Control LCD module hung off parallel port using the
3 * ppi 'geek port' interface.
4 *
5 * $FreeBSD: stable/12/share/examples/ppi/ppilcd.c 250460 2013-05-10 16:41:26Z eadler $
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
main(int argc,char * argv[])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
usage(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
do_char(struct lcd_driver * driver,char ch)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
hd44780_output(int type,int data)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
hd44780_input(int type)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
hd44780_prepare(char * devname,char * options)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
hd44780_finish(void)358 hd44780_finish(void)
359 {
360 close(hd_fd);
361 }
362
363 static void
hd44780_command(int cmd)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
hd44780_putc(int c)433 hd44780_putc(int c)
434 {
435 hd44780_output(HD_DATA, c);
436 usleep(40);
437 }
438
439