forked from Traumflug/Teacup_Firmware
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parallel-4bit.c
206 lines (160 loc) · 5.19 KB
/
parallel-4bit.c
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
/** \file
\brief Parallel 4-bit subsystem
This implements this custom interface often seen on LCD displays which
uses 4 data lines (D0..D3) and 3 control lines:
RS = Register Select: High for data input, Low for instruction input.
RW = Read/Write: High for read, Low for write.
E = Enable: A line for triggering a read or a write. Its detailed
usage is a bit complicated.
This implementation was written in the hope to be exchangeable with using
I2C or SPI as display bus for the same display.
Other than the I2C implementation, this one uses no send buffer. Bytes can
be sent quickly enough to allow sending them immediately.
*/
#include "parallel-4bit.h"
#ifdef DISPLAY_BUS_4BIT
#include "delay.h"
#include "pinio.h"
// Check for the necessary pins.
#ifndef DISPLAY_RS_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_RS_PIN.
#endif
#ifndef DISPLAY_RW_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_RW_PIN.
#endif
#ifndef DISPLAY_E_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_E_PIN.
#endif
#ifndef DISPLAY_D4_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D4_PIN.
#endif
#ifndef DISPLAY_D5_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D5_PIN.
#endif
#ifndef DISPLAY_D6_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D6_PIN.
#endif
#ifndef DISPLAY_D7_PIN
#error DISPLAY_BUS_4BIT defined, but not DISPLAY_D7_PIN.
#endif
static void e_pulse(void) {
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
WRITE(DISPLAY_E_PIN, 0);
}
/**
Inititalise the subsystem.
*/
void parallel_4bit_init(void) {
SET_OUTPUT(DISPLAY_RS_PIN);
WRITE(DISPLAY_RS_PIN, 0);
SET_OUTPUT(DISPLAY_RW_PIN);
WRITE(DISPLAY_RW_PIN, 0);
SET_OUTPUT(DISPLAY_E_PIN);
WRITE(DISPLAY_E_PIN, 0);
/**
Perform a reset.
*/
SET_OUTPUT(DISPLAY_D4_PIN);
SET_OUTPUT(DISPLAY_D5_PIN);
SET_OUTPUT(DISPLAY_D6_PIN);
SET_OUTPUT(DISPLAY_D7_PIN);
// Initial write is 8 bit.
WRITE(DISPLAY_D4_PIN, 1);
WRITE(DISPLAY_D5_PIN, 1);
WRITE(DISPLAY_D6_PIN, 0);
WRITE(DISPLAY_D7_PIN, 0);
e_pulse();
delay_ms(5); // Delay, busy flag can't be checked here.
// Repeat last command.
e_pulse();
delay_us(100); // Delay, busy flag can't be checked here.
// Repeat last command a third time.
e_pulse();
delay_us(100); // Delay, busy flag can't be checked here.
// Now configure for 4 bit mode.
WRITE(DISPLAY_D4_PIN, 0);
e_pulse();
delay_us(100); // Some displays need this additional delay.
}
/**
Read a byte from the bus. Doing so is e.g. required to detect wether the
display is busy.
\param rs 1: Read data.
0: Read busy flag / address counter.
\return Byte read from LCD controller.
*/
static uint8_t parallel_4bit_read(uint8_t rs) {
uint8_t data;
if (rs)
WRITE(DISPLAY_RS_PIN, 1); // Read data.
else
WRITE(DISPLAY_RS_PIN, 0); // Read busy flag.
WRITE(DISPLAY_RW_PIN, 1); // Read mode.
SET_INPUT(DISPLAY_D4_PIN);
SET_INPUT(DISPLAY_D5_PIN);
SET_INPUT(DISPLAY_D6_PIN);
SET_INPUT(DISPLAY_D7_PIN);
data = 0;
// Read high nibble.
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
if (READ(DISPLAY_D4_PIN)) data |= 0x10;
if (READ(DISPLAY_D5_PIN)) data |= 0x20;
if (READ(DISPLAY_D6_PIN)) data |= 0x40;
if (READ(DISPLAY_D7_PIN)) data |= 0x80;
WRITE(DISPLAY_E_PIN, 0);
delay_us(1);
// Read low nibble.
WRITE(DISPLAY_E_PIN, 1);
delay_us(1);
if (READ(DISPLAY_D4_PIN)) data |= 0x01;
if (READ(DISPLAY_D5_PIN)) data |= 0x02;
if (READ(DISPLAY_D6_PIN)) data |= 0x04;
if (READ(DISPLAY_D7_PIN)) data |= 0x08;
WRITE(DISPLAY_E_PIN, 0);
return data;
}
/**
Report wether the bus or the connected display is busy.
\return Wether the bus is busy, which means that eventual new transactions
would have to wait.
*/
uint8_t parallel_4bit_busy(void) {
uint8_t status;
status = parallel_4bit_read(0);
return status & 0x80;
}
/**
Send a byte to the bus partner.
\param data The byte to be sent.
\param rs 1 = parallel_4bit_data: Write data.
0 = parallel_4bit_instruction: Write instruction.
Other than other bus implementations we do not buffer here. Writing a byte
takes just some 3 microseconds and there is nothing supporting such writes in
hardware, so the overhead of buffering is most likely not worth the effort.
*/
void parallel_4bit_write(uint8_t data, enum rs_e rs) {
// Wait for the display to become ready.
while (parallel_4bit_busy());
// Setup for writing.
WRITE(DISPLAY_RS_PIN, rs); // Write data / instruction.
WRITE(DISPLAY_RW_PIN, 0); // Write mode.
SET_OUTPUT(DISPLAY_D4_PIN);
SET_OUTPUT(DISPLAY_D5_PIN);
SET_OUTPUT(DISPLAY_D6_PIN);
SET_OUTPUT(DISPLAY_D7_PIN);
// Output high nibble.
WRITE(DISPLAY_D4_PIN, (data & 0x10) ? 1 : 0);
WRITE(DISPLAY_D5_PIN, (data & 0x20) ? 1 : 0);
WRITE(DISPLAY_D6_PIN, (data & 0x40) ? 1 : 0);
WRITE(DISPLAY_D7_PIN, (data & 0x80) ? 1 : 0);
e_pulse();
// Output low nibble.
WRITE(DISPLAY_D4_PIN, (data & 0x01) ? 1 : 0);
WRITE(DISPLAY_D5_PIN, (data & 0x02) ? 1 : 0);
WRITE(DISPLAY_D6_PIN, (data & 0x04) ? 1 : 0);
WRITE(DISPLAY_D7_PIN, (data & 0x08) ? 1 : 0);
e_pulse();
}
#endif /* DISPLAY_BUS_4BIT */