Final version
[liquid-crystal-terminal.git] / terminal / terminal.ino
1 #include <LiquidCrystal.h>
2
3 // LED pins
4 #define LEDS_GRN 15 // Analog 1; Common green anode
5 #define LEDS_RED 16 // Analog 2; Common red anode
6 #define LED1 13 // LED1 cathode
7 #define LED2 12 // LED2 cathode
8 #define LED3 11 // LED3 cathode
9 #define LED4 10 // LED4 cathode
10
11 #define LED_OFF 0x00
12 #define LED_GRN 0x01
13 #define LED_RED 0x02
14 #define LED_BLINK 0x04
15
16 struct leds {
17 byte a:3;
18 byte b:3;
19 byte c:3;
20 byte d:3;
21 unsigned int counter;
22 };
23 struct leds leds;
24
25 // Button pin
26 #define BUTTON 14 // Analog 0; push button
27
28 // LCD pins
29 #define LCD_REGSEL 3 // Register select
30 #define LCD_ENABLE 4 // Chip enable
31 #define LCD_D4 5 // Data line
32 #define LCD_D5 6 // Data line
33 #define LCD_D6 7 // Data line
34 #define LCD_D7 8 // Data line
35 #define LCD_BL 2 // Backlight
36
37 LiquidCrystal lcd(LCD_REGSEL, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
38
39 enum backlight_state {
40 BL_OFF,
41 BL_ON,
42 BL_FLASH
43 };
44 struct backlight {
45 enum backlight_state state;
46 unsigned int timer;
47 };
48 struct backlight backlight;
49
50 #define COLS 40
51 #define ROWS 2
52
53 /**
54 * Special characters.
55 * See https://www.quinapalus.com/hd44780udg.html to make more.
56 * Define which ones are used (max. 8) in setup().
57 * When they need to be changed at runtime, make sure the cursor is disabled
58 * and call setCursor afterwards.
59 */
60 byte char_music[8] = {0x02,0x03,0x02,0x0e,0x1e,0x0c,0x00};
61 byte char_bar_1[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x1f};
62 byte char_bar_2[8] = {0x00,0x00,0x00,0x00,0x00,0x1f,0x1f};
63 byte char_bar_3[8] = {0x00,0x00,0x00,0x00,0x1f,0x1f,0x1f};
64 byte char_bar_4[8] = {0x00,0x00,0x00,0x1f,0x1f,0x1f,0x1f};
65 byte char_bar_5[8] = {0x00,0x00,0x1f,0x1f,0x1f,0x1f,0x1f};
66 byte char_bar_6[8] = {0x00,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f};
67 byte char_bar_7[8] = {0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f};
68 byte char_hbar_start_empty[8] = {0x1f,0x10,0x10,0x10,0x10,0x10,0x1f};
69 byte char_hbar_start_full[8] = {0x1f,0x10,0x17,0x17,0x17,0x10,0x1f};
70 byte char_hbar_inner_empty[8] = {0x1f,0x00,0x00,0x00,0x00,0x00,0x1f};
71 byte char_hbar_inner_half[8] = {0x1f,0x00,0x1c,0x1c,0x1c,0x00,0x1f};
72 byte char_hbar_inner_full[8] = {0x1f,0x00,0x1f,0x1f,0x1f,0x00,0x1f};
73 byte char_hbar_end_empty[8] = {0x1f,0x01,0x01,0x01,0x01,0x01,0x1f};
74 byte char_hbar_end_full[8] = {0x1f,0x01,0x1d,0x1d,0x1d,0x01,0x1f};
75
76 struct terminal {
77 byte x;
78 byte y;
79 char contents[COLS * ROWS];
80 };
81
82 void redraw(struct terminal *term)
83 {
84 lcd.noCursor();
85 lcd.clear();
86 lcd.home();
87 for (byte i = 0; i < COLS * ROWS; i++) {
88 if (i % COLS == 0)
89 lcd.setCursor(i % COLS, i / COLS);
90 lcd.write(term->contents[i]);
91 }
92 lcd.cursor();
93 }
94
95 void scroll_up(struct terminal *term)
96 {
97 for (byte i = 0; i < COLS * (ROWS - 1); i++)
98 term->contents[i] = term->contents[i + COLS];
99 for (byte i = COLS * (ROWS - 1); i < COLS * ROWS; i++)
100 term->contents[i] = ' ';
101 redraw(term);
102 }
103
104 /**
105 * Special character meanings are defined here.
106 */
107 void write(struct terminal *term, char ch)
108 {
109 switch (ch) {
110 case '\r':
111 term->x = 0;
112 lcd.setCursor(term->x, term->y);
113 break;
114 case '\n':
115 if (++term->y >= ROWS) {
116 term->y--;
117 scroll_up(term);
118 }
119 lcd.setCursor(term->x, term->y);
120 break;
121 case 0x7f:
122 if (term->x != 0) {
123 term->x--;
124 term->contents[term->x + term->y * COLS] = 0x00;
125 lcd.setCursor(term->x, term->y);
126 lcd.write(' ');
127 lcd.setCursor(term->x, term->y);
128 }
129 break;
130 default:
131 term->contents[term->x + term->y * COLS] = ch;
132 lcd.write(ch);
133 if (++term->x >= COLS) {
134 term->x = 0;
135 if (++term->y >= ROWS) {
136 term->y--;
137 scroll_up(term);
138 }
139 lcd.setCursor(term->x, term->y);
140 }
141 }
142 }
143
144 struct terminal term;
145
146 char message[] =
147 "Beste Mart, van harte gefeliciteerd! \x00\x00\r\n"
148 "Hier een herprogrammeerbare terminal. ";
149
150 /**
151 * Tasks for the LEDs. Needs to be called every few ms(?) for multiplexing.
152 * When an LED is blinking and in the off period, it is necessary to call
153 * digitalWrite() twice and set the cathode. This ensures that the function
154 * takes equally long to return on every iteration, s.t. the blinking duty
155 * cycle is exactly 50% and LEDs blink equally bright, regardless of the status
156 * of other LEDs.
157 */
158 void led_tasks()
159 {
160 leds.counter++;
161 switch (leds.counter & 0x03) {
162 case 0:
163 digitalWrite(LED4, 1);
164 if ((leds.a & LED_BLINK) && leds.counter & 0x4000) {
165 digitalWrite(LEDS_GRN, 0);
166 digitalWrite(LEDS_RED, 0);
167 } else {
168 digitalWrite(LEDS_GRN, leds.a & 1);
169 digitalWrite(LEDS_RED, leds.a & 2);
170 }
171 digitalWrite(LED1, 0);
172 break;
173 case 1:
174 digitalWrite(LED1, 1);
175 if ((leds.b & LED_BLINK) && leds.counter & 0x4000) {
176 digitalWrite(LEDS_GRN, 0);
177 digitalWrite(LEDS_RED, 0);
178 } else {
179 digitalWrite(LEDS_GRN, leds.b & 1);
180 digitalWrite(LEDS_RED, leds.b & 2);
181 }
182 digitalWrite(LED2, 0);
183 break;
184 case 2:
185 digitalWrite(LED2, 1);
186 if ((leds.c & LED_BLINK) && leds.counter & 0x4000) {
187 digitalWrite(LEDS_GRN, 0);
188 digitalWrite(LEDS_RED, 0);
189 } else {
190 digitalWrite(LEDS_GRN, leds.c & 1);
191 digitalWrite(LEDS_RED, leds.c & 2);
192 }
193 digitalWrite(LED3, 0);
194 break;
195 case 3:
196 digitalWrite(LED3, 1);
197 if ((leds.d & LED_BLINK) && leds.counter & 0x4000) {
198 digitalWrite(LEDS_GRN, 0);
199 digitalWrite(LEDS_RED, 0);
200 } else {
201 digitalWrite(LEDS_GRN, leds.d & 1);
202 digitalWrite(LEDS_RED, leds.d & 2);
203 }
204 digitalWrite(LED4, 0);
205 break;
206 }
207 }
208
209 void button_tasks()
210 {
211 if (digitalRead(BUTTON)) {
212 bl_flash();
213 }
214 }
215
216 void bl_tasks()
217 {
218 if (backlight.state == BL_FLASH)
219 if (!--backlight.timer)
220 backlight.state = BL_OFF;
221
222 switch (backlight.state) {
223 case BL_OFF:
224 digitalWrite(LCD_BL, 0);
225 break;
226 case BL_FLASH:
227 case BL_ON:
228 digitalWrite(LCD_BL, 1);
229 break;
230 }
231 }
232
233 void bl_flash()
234 {
235 backlight.state = BL_FLASH;
236 backlight.timer = (unsigned int) -1;
237 }
238
239 void setup()
240 {
241 lcd.createChar(0, char_music);
242 lcd.createChar(1, char_hbar_start_full);
243 lcd.createChar(2, char_hbar_start_empty);
244 lcd.createChar(3, char_hbar_inner_full);
245 lcd.createChar(4, char_hbar_inner_half);
246 lcd.createChar(5, char_hbar_inner_empty);
247 lcd.createChar(6, char_hbar_end_full);
248 lcd.createChar(7, char_hbar_end_empty);
249
250 lcd.begin(COLS, ROWS);
251 lcd.blink();
252 Serial.begin(115200);
253
254 for (byte i = 0; i < ROWS * COLS; i++)
255 term.contents[i] = ' ';
256
257 for (byte i = 0; i < sizeof(message) - 1; i++)
258 write(&term, message[i]);
259
260 pinMode(LCD_BL, OUTPUT);
261
262 pinMode(LEDS_GRN, OUTPUT);
263 pinMode(LEDS_RED, OUTPUT);
264 pinMode(LED1, OUTPUT);
265 pinMode(LED2, OUTPUT);
266 pinMode(LED3, OUTPUT);
267 pinMode(LED4, OUTPUT);
268
269 pinMode(BUTTON, INPUT);
270
271 leds.a = LED_GRN | LED_BLINK;
272 leds.b = LED_RED | LED_BLINK;
273 leds.c = LED_GRN | LED_BLINK;
274 leds.d = LED_RED | LED_BLINK;
275 }
276
277 enum read_state {
278 S_DEFAULT,
279 S_LED,
280 S_BACKLIGHT
281 };
282 enum read_state read_state;
283
284 void handle_character(char c)
285 {
286 switch (read_state) {
287 case S_DEFAULT:
288 switch (c) {
289 case '\x11':
290 read_state = S_LED;
291 break;
292 case '\x12':
293 read_state = S_BACKLIGHT;
294 break;
295 default:
296 write(&term, c);
297 break;
298 }
299 break;
300
301 case S_LED:
302 switch (c & 0x30) {
303 case 0x00: leds.a = c & 0x0f; break;
304 case 0x10: leds.b = c & 0x0f; break;
305 case 0x20: leds.c = c & 0x0f; break;
306 case 0x30: leds.d = c & 0x0f; break;
307 }
308 read_state = S_DEFAULT;
309 break;
310
311 case S_BACKLIGHT:
312 if (c == 0x02)
313 bl_flash();
314 else
315 backlight.state = (enum backlight_state) c;
316 read_state = S_DEFAULT;
317 break;
318 }
319 }
320
321 void loop()
322 {
323 led_tasks();
324 button_tasks();
325 bl_tasks();
326
327 while (Serial.available())
328 handle_character(Serial.read());
329 }