File: hd44780-piplate.c

package info (click to toggle)
lcdproc 0.5.9-3
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 5,064 kB
  • sloc: ansic: 59,645; sh: 1,740; perl: 681; makefile: 417
file content (373 lines) | stat: -rw-r--r-- 9,693 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/** \file server/drivers/hd44780-piplate.c
 *
 * \c I2C (via MCP23017) connection type of \c hd44780 driver for Hitachi
 * HD44780 based LCD displays connected to the MCP23017 I2C I/O expander
 * (Adafruit Pi Plate, see http://www.adafruit.com/products/1109).
 *
 * The LCD is operated in its 4 bit-mode to be connected to the 8 bit-port
 * of a single MCP23017 that is accessed by the server via the I2C bus.
 */

/*-
 * Copyright (c)  2013 Jonathan Brogdon <jlbrogdon@gmail.com>
 *
 * This file is released under the GNU General Public License. Refer to the
 * COPYING file distributed with this package.
 *
 * Based mostly on the hd44780-i2c module, see there for a complete history.
 */

/*-
 * The connections are:
 * MCP23017     LCD
 * GPA7         Green LCD backlight
 * GPA6         Red LCD backlight
 * GPA4         Left key
 * GPA3         Up key
 * GPA2         Down key
 * GPA1         Right key
 * GPA0         Select key
 * GPB7         RS (4)
 * GPB6         RW (5)
 * GPB5         EN (6)
 * GPB4         D7 (14)
 * GPB3         D6 (13)
 * GPB2         D5 (12)
 * GPB1         D4 (11)
 * GPB0         Blue LCD backlight
 *
 * Configuration:
 * device=/dev/i2c-1   # the device file of the i2c bus
 * port=0x20           # the i2c address of the i2c port expander
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "hd44780-piplate.h"
#include "hd44780-low.h"
#include "shared/report.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#ifdef HAVE_DEV_IICBUS_IIC_H
# include <strings.h>
# include <dev/iicbus/iic.h>
#else				/* HAVE_LINUX_I2C_DEV_H */
# include <linux/i2c-dev.h>
#endif

#define DEFAULT_DEVICE		"/dev/i2c-1"

void i2c_piplate_HD44780_senddata(PrivateData *p,
				  unsigned char displayID, unsigned char flags, unsigned char ch);
void i2c_piplate_HD44780_backlight(PrivateData *p, unsigned char state);
unsigned char i2c_piplate_HD44780_scankeypad(PrivateData *p);
void i2c_piplate_HD44780_close(PrivateData *p);

/* MCP23017 registers */
#define MCP23017_IODIRA 0x00
#define MCP23017_IODIRB 0x01
#define MCP23017_GPPUA  0x0C
#define MCP23017_GPPUB  0x0D
#define MCP23017_GPIOA  0x12
#define MCP23017_GPIOB  0x13

/* GPIOA BITS */
#define G_BIT     (1 << 7)
#define R_BIT     (1 << 6)
#define L_KEY_BIT (1 << 4)
#define U_KEY_BIT (1 << 3)
#define D_KEY_BIT (1 << 2)
#define R_KEY_BIT (1 << 1)
#define S_KEY_BIT (1 << 0)

/* GPIOB BITS */
#define RS_BIT (1 << 7)
#define RW_BIT (1 << 6)
#define EN_BIT (1 << 5)
#define D0_BIT (1 << 4)
#define D1_BIT (1 << 3)
#define D2_BIT (1 << 2)
#define D3_BIT (1 << 1)
#define B_BIT  (1 << 0)

/* Reverses the bits in a 4 bit word */
#define SWIZZLE_4BITS(x) ((((x) & 0x01) << 3) | (((x) & 0x02) << 1) | (((x) & 0x04) >> 1) | (((x) & 0x08) >> 3))

/* Swizzle and offset bits correctly for the PiPlate wiring */
#define DATA_BIT_START_OFFSET 1
#define MAP_TO_DATA_PINS(x) (SWIZZLE_4BITS(x) << DATA_BIT_START_OFFSET)

#define I2C_ADDR_MASK 0x7f

/**
 * Read one of the MCP23017 registers
 * \param p     Pointer to driver private data.
 * \param reg   Address of the register to read.
 * \param val   Pointer to the read buffer.
 * \retval 0    Success.
 * \retval -1   Error.
 */
static int
i2c_read_reg(PrivateData *p, unsigned char reg, unsigned char *val)
{
	/* Set the address to be read */
	if (write(p->fd, &reg, 1) != 1) {
		return -1;
	}

	/* Read the value */
	if (read(p->fd, val, 1) != 1) {
		return -1;
	}

	return 0;
}

/**
 * Write one of the MCP23017 registers
 * \param p     Pointer to driver private data.
 * \param reg   Address of the register to write.
 * \param val   Value to be written.
 * \retval 0    Success.
 * \retval -1   Error.
 */
static int
i2c_write_reg(PrivateData *p, unsigned char reg, unsigned char val)
{
	unsigned char buf[2];

	buf[0] = reg;
	buf[1] = val;
	if (write(p->fd, buf, sizeof(buf)) != sizeof(buf)) {
		return -1;
	}
	return 0;
}

/**
 * Initialize the driver.
 * \param drvthis  Pointer to driver structure.
 * \retval 0       Success.
 * \retval -1      Error.
 */
int
hd_init_i2c_piplate(Driver *drvthis)
{
	PrivateData *p = (PrivateData *) drvthis->private_data;
	HD44780_functions *hd44780_functions = p->hd44780_functions;
	char device[256] = DEFAULT_DEVICE;
#ifdef HAVE_DEV_IICBUS_IIC_H
	struct iiccmd cmd;
	bzero(&cmd, sizeof(cmd));
#endif

	/* Get serial device to use */
	strncpy(device, drvthis->config_get_string(drvthis->name, "Device", 0, DEFAULT_DEVICE), sizeof(device));
	device[sizeof(device) - 1] = '\0';
	report(RPT_INFO, "HD44780: piplate: Using device '%s' and address 0x%02X for a MCP23017",
	       device, p->port & I2C_ADDR_MASK);

	/* Open the I2C device */
	p->fd = open(device, O_RDWR);
	if (p->fd < 0) {
		report(RPT_ERR, "HD44780: piplate: open i2c device '%s' failed: %s",
		       device, strerror(errno));
		return -1;
	}

	/* Set I2C address */
#ifdef HAVE_DEV_IICBUS_IIC_H
	cmd.slave = (p->port & I2C_ADDR_MASK) << 1;
	cmd.last = 0;
	cmd.count = 0;
	if (ioctl(p->fd, I2CRSTCARD, &cmd) < 0) {
		report(RPT_ERR, "HD44780: piplate: reset bus failed: %s", strerror(errno));
		return -1;
	}
	if (ioctl(p->fd, I2CSTART, &cmd) < 0) {
		report(RPT_ERR, "HD44780: piplate: set address to 0x%02X: %s",
		       p->port & I2C_ADDR_MASK, strerror(errno));
		return -1;
	}
#else				/* HAVE_LINUX_I2C_DEV_H */
	if (ioctl(p->fd, I2C_SLAVE, p->port & I2C_ADDR_MASK) < 0) {
		report(RPT_ERR, "HD44780: piplate: set address to 0x%02X: %s",
		       p->port & I2C_ADDR_MASK, strerror(errno));
		return -1;
	}
#endif

	/* IODIRA - keys as input, all other as output */
	i2c_write_reg(p, MCP23017_IODIRA,
		      (unsigned char) (L_KEY_BIT | U_KEY_BIT | D_KEY_BIT | R_KEY_BIT | S_KEY_BIT) & ~(G_BIT | R_BIT));

	/* IODIRB - LCD control/data bus signals as outputs */
	i2c_write_reg(p, MCP23017_IODIRB,
		      (unsigned char) ~(RS_BIT | RW_BIT | EN_BIT | D0_BIT | D1_BIT | D2_BIT | D3_BIT | B_BIT));

	/* GPPUA - Pullups for key switch inputs */
	i2c_write_reg(p, MCP23017_GPPUA,
		      (unsigned char) (L_KEY_BIT | U_KEY_BIT | D_KEY_BIT | R_KEY_BIT | S_KEY_BIT) & ~(G_BIT | R_BIT));

	/* GPPUB - Pullups disabled */
	i2c_write_reg(p, MCP23017_GPPUB, (unsigned char) 0x00);

	/* Set driver functions */
	hd44780_functions->senddata = i2c_piplate_HD44780_senddata;
	hd44780_functions->backlight = i2c_piplate_HD44780_backlight;
	hd44780_functions->scankeypad = i2c_piplate_HD44780_scankeypad;
	hd44780_functions->close = i2c_piplate_HD44780_close;

	/* Initialize the LCD */
	hd44780_functions->senddata(p, 0, RS_INSTR, 0x33);
	hd44780_functions->uPause(p, 1);
	hd44780_functions->senddata(p, 0, RS_INSTR, 0x32);
	hd44780_functions->uPause(p, 1);
	common_init(p, IF_4BIT);

	report(RPT_INFO, "HD44780: piplate: initialized!");

	return 0;
}

/**
 * Close the device.
 * \param p          Pointer to driver's private data structure.
 */
void
i2c_piplate_HD44780_close(PrivateData *p)
{
	if (p->fd >= 0) {
		close(p->fd);
	}
}

/**
 * Send data or commands to the display.
 * \param p          Pointer to driver's private data structure.
 * \param displayID  ID of the display (or 0 for all) to send data to.
 * \param flags      Defines whether to end a command or data.
 * \param ch         The value to send.
 */
void
i2c_piplate_HD44780_senddata(PrivateData *p,
			     unsigned char displayID, unsigned char flags, unsigned char ch)
{
	unsigned char nibble[2];
	unsigned char write_val = 0;
	int nibble_idx = 0;

	/* Split the val byte into nibbles */
	nibble[0] = (ch >> 4);
	nibble[1] = (ch & 0x0f);

	for (nibble_idx = 0; nibble_idx < 2; nibble_idx++) {
		/* Adjust for 4-bit data pins */
		write_val = MAP_TO_DATA_PINS(nibble[nibble_idx]);

		/* Set the RS bit */
		if (flags == RS_INSTR)
			write_val &= ~RS_BIT;
		else
			write_val |= RS_BIT;

		/* Set the blue backlight bit if backlight is on */
		if (p->backlightstate == BACKLIGHT_OFF)
			write_val |= B_BIT;

		/* Set enable bit */
		i2c_write_reg(p, MCP23017_GPIOB, write_val | EN_BIT);

		p->hd44780_functions->uPause(p, 1);

		/* Clear enable bit */
		i2c_write_reg(p, MCP23017_GPIOB, write_val & ~EN_BIT);

	}			/* for each nibble */

	p->hd44780_functions->uPause(p, 1);
}

/**
 * Turn display backlight on or off. For the Pi Plate this means a read /
 * modify / write cycle is required, as the bits are shared with other signals
 * on the same byte.
 *
 * \param p      Pointer to driver's private data structure.
 * \param state  New backlight status.
 */
void
i2c_piplate_HD44780_backlight(PrivateData *p, unsigned char state)
{
	unsigned char gpioa = 0;
	unsigned char gpiob = 0;

	/* Read the RG bits */
	if (i2c_read_reg(p, MCP23017_GPIOA, &gpioa) != 0) {
		return;
	}

	/* Read the B bit */
	if (i2c_read_reg(p, MCP23017_GPIOB, &gpiob) != 0) {
		return;
	}

	/* Set or clear the RGB bits. Backlight is ON if the bits are CLEAR */
	if (state == BACKLIGHT_ON) {
		gpioa &= ~(R_BIT | G_BIT);
		gpiob &= ~(B_BIT);
	}
	else {
		gpioa |= (R_BIT | G_BIT);
		gpiob |= (B_BIT);
	}

	/* Write the RGB bits */
	i2c_write_reg(p, MCP23017_GPIOA, gpioa);
	i2c_write_reg(p, MCP23017_GPIOB, gpiob);
}

/**
 * Scan the LCD keys
 * \param p      Pointer to driver's private data structure.
 * \retval key   Key number
 */
unsigned char
i2c_piplate_HD44780_scankeypad(PrivateData *p)
{
	unsigned char gpioa = 0;

	/* Read the key bits */
	if (i2c_read_reg(p, MCP23017_GPIOA, &gpioa) != 0)
		return 0;

	/* Select key */
	if ((gpioa & S_KEY_BIT) == 0)
		return 1;

	/* Up key */
	if ((gpioa & U_KEY_BIT) == 0)
		return 2;

	/* Down key */
	if ((gpioa & D_KEY_BIT) == 0)
		return 3;

	/* Left key */
	if ((gpioa & L_KEY_BIT) == 0)
		return 4;

	/* Right key */
	if ((gpioa & R_KEY_BIT) == 0)
		return 5;

	return 0;
}