5880acf140a1ea4764f0bbcbe6d00b192736de59
[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_smile[8] = {0x00,0x00,0x0a,0x00,0x11,0x0e,0x00};
62 byte char_bar_1[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x1f};
63 byte char_bar_2[8] = {0x00,0x00,0x00,0x00,0x00,0x1f,0x1f};
64 byte char_bar_3[8] = {0x00,0x00,0x00,0x00,0x1f,0x1f,0x1f};
65 byte char_bar_4[8] = {0x00,0x00,0x00,0x1f,0x1f,0x1f,0x1f};
66 byte char_bar_5[8] = {0x00,0x00,0x1f,0x1f,0x1f,0x1f,0x1f};
67 byte char_bar_6[8] = {0x00,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f};
68 byte char_bar_7[8] = {0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f};
69 byte char_hbar_start_empty[8] = {0x1f,0x10,0x10,0x10,0x10,0x10,0x1f};
70 byte char_hbar_start_full[8] = {0x1f,0x10,0x17,0x17,0x17,0x10,0x1f};
71 byte char_hbar_inner_empty[8] = {0x1f,0x00,0x00,0x00,0x00,0x00,0x1f};
72 byte char_hbar_inner_half[8] = {0x1f,0x00,0x1c,0x1c,0x1c,0x00,0x1f};
73 byte char_hbar_inner_full[8] = {0x1f,0x00,0x1f,0x1f,0x1f,0x00,0x1f};
74 byte char_hbar_end_empty[8] = {0x1f,0x01,0x01,0x01,0x01,0x01,0x1f};
75 byte char_hbar_end_full[8] = {0x1f,0x01,0x1d,0x1d,0x1d,0x01,0x1f};
76
77 struct terminal {
78 byte x;
79 byte y;
80 char contents[COLS * ROWS];
81 };
82
83 void redraw(struct terminal *term)
84 {
85 lcd.noCursor();
86 lcd.clear();
87 lcd.home();
88 for (byte i = 0; i < COLS * ROWS; i++) {
89 if (i % COLS == 0)
90 lcd.setCursor(i % COLS, i / COLS);
91 lcd.write(term->contents[i]);
92 }
93 lcd.cursor();
94 }
95
96 void scroll_up(struct terminal *term)
97 {
98 for (byte i = 0; i < COLS * (ROWS - 1); i++)
99 term->contents[i] = term->contents[i + COLS];
100 for (byte i = COLS * (ROWS - 1); i < COLS * ROWS; i++)
101 term->contents[i] = ' ';
102 redraw(term);
103 }
104
105 /**
106 * Special character meanings are defined here.
107 */
108 void write(struct terminal *term, char ch)
109 {
110 switch (ch) {
111 case '\r':
112 term->x = 0;
113 lcd.setCursor(term->x, term->y);
114 break;
115 case '\n':
116 if (++term->y >= ROWS) {
117 term->y--;
118 scroll_up(term);
119 }
120 lcd.setCursor(term->x, term->y);
121 break;
122 case 0x7f:
123 if (term->x != 0) {
124 term->x--;
125 term->contents[term->x + term->y * COLS] = 0x00;
126 lcd.setCursor(term->x, term->y);
127 lcd.write(' ');
128 lcd.setCursor(term->x, term->y);
129 }
130 break;
131 default:
132 term->contents[term->x + term->y * COLS] = ch;
133 lcd.write(ch);
134 if (++term->x >= COLS) {
135 term->x = 0;
136 if (++term->y >= ROWS) {
137 term->y--;
138 scroll_up(term);
139 }
140 lcd.setCursor(term->x, term->y);
141 }
142 }
143 }
144
145 struct terminal term;
146
147 char message[] =
148 "Beste Mart, van harte gefeliciteerd! \x01\x00\r\n"
149 "Hier een herprogrammeerbare terminal. ";
150
151 /**
152 * Tasks for the LEDs. Needs to be called every few ms(?) for multiplexing.
153 * When an LED is blinking and in the off period, it is necessary to call
154 * digitalWrite() twice and set the cathode. This ensures that the function
155 * takes equally long to return on every iteration, s.t. the blinking duty
156 * cycle is exactly 50% and LEDs blink equally bright, regardless of the status
157 * of other LEDs.
158 */
159 void led_tasks()
160 {
161 leds.counter++;
162 switch (leds.counter & 0x03) {
163 case 0:
164 digitalWrite(LED4, 1);
165 if ((leds.a & LED_BLINK) && leds.counter & 0x4000) {
166 digitalWrite(LEDS_GRN, 0);
167 digitalWrite(LEDS_RED, 0);
168 } else {
169 digitalWrite(LEDS_GRN, leds.a & 1);
170 digitalWrite(LEDS_RED, leds.a & 2);
171 }
172 digitalWrite(LED1, 0);
173 break;
174 case 1:
175 digitalWrite(LED1, 1);
176 if ((leds.b & LED_BLINK) && leds.counter & 0x4000) {
177 digitalWrite(LEDS_GRN, 0);
178 digitalWrite(LEDS_RED, 0);
179 } else {
180 digitalWrite(LEDS_GRN, leds.b & 1);
181 digitalWrite(LEDS_RED, leds.b & 2);
182 }
183 digitalWrite(LED2, 0);
184 break;
185 case 2:
186 digitalWrite(LED2, 1);
187 if ((leds.c & LED_BLINK) && leds.counter & 0x4000) {
188 digitalWrite(LEDS_GRN, 0);
189 digitalWrite(LEDS_RED, 0);
190 } else {
191 digitalWrite(LEDS_GRN, leds.c & 1);
192 digitalWrite(LEDS_RED, leds.c & 2);
193 }
194 digitalWrite(LED3, 0);
195 break;
196 case 3:
197 digitalWrite(LED3, 1);
198 if ((leds.d & LED_BLINK) && leds.counter & 0x4000) {
199 digitalWrite(LEDS_GRN, 0);
200 digitalWrite(LEDS_RED, 0);
201 } else {
202 digitalWrite(LEDS_GRN, leds.d & 1);
203 digitalWrite(LEDS_RED, leds.d & 2);
204 }
205 digitalWrite(LED4, 0);
206 break;
207 }
208 }
209
210 void button_tasks()
211 {
212 if (digitalRead(BUTTON)) {
213 bl_flash();
214 }
215 }
216
217 void bl_tasks()
218 {
219 if (backlight.state == BL_FLASH)
220 if (!--backlight.timer)
221 backlight.state = BL_OFF;
222
223 switch (backlight.state) {
224 case BL_OFF:
225 digitalWrite(LCD_BL, 0);
226 break;
227 case BL_FLASH:
228 case BL_ON:
229 digitalWrite(LCD_BL, 1);
230 break;
231 }
232 }
233
234 void bl_flash()
235 {
236 backlight.state = BL_FLASH;
237 backlight.timer = (unsigned int) -1;
238 }
239
240 void setup()
241 {
242 lcd.createChar(0, char_music);
243 lcd.createChar(1, char_smile);
244
245 lcd.begin(COLS, ROWS);
246 lcd.blink();
247 Serial.begin(115200);
248
249 for (byte i = 0; i < ROWS * COLS; i++)
250 term.contents[i] = ' ';
251
252 for (byte i = 0; i < sizeof(message) - 1; i++)
253 write(&term, message[i]);
254
255 pinMode(LCD_BL, OUTPUT);
256
257 pinMode(LEDS_GRN, OUTPUT);
258 pinMode(LEDS_RED, OUTPUT);
259 pinMode(LED1, OUTPUT);
260 pinMode(LED2, OUTPUT);
261 pinMode(LED3, OUTPUT);
262 pinMode(LED4, OUTPUT);
263
264 pinMode(BUTTON, INPUT);
265
266 leds.a = LED_GRN | LED_BLINK;
267 leds.b = LED_RED | LED_BLINK;
268 leds.c = LED_GRN | LED_BLINK;
269 leds.d = LED_RED | LED_BLINK;
270 }
271
272 enum read_state {
273 S_DEFAULT,
274 S_LED,
275 S_BACKLIGHT
276 };
277 enum read_state read_state;
278
279 void handle_character(char c)
280 {
281 switch (read_state) {
282 case S_DEFAULT:
283 switch (c) {
284 case '\x11':
285 read_state = S_LED;
286 break;
287 case '\x12':
288 read_state = S_BACKLIGHT;
289 break;
290 default:
291 write(&term, c);
292 break;
293 }
294 break;
295
296 case S_LED:
297 switch (c & 0x30) {
298 case 0x00: leds.a = c & 0x0f; break;
299 case 0x10: leds.b = c & 0x0f; break;
300 case 0x20: leds.c = c & 0x0f; break;
301 case 0x30: leds.d = c & 0x0f; break;
302 }
303 read_state = S_DEFAULT;
304 break;
305
306 case S_BACKLIGHT:
307 if (c == 0x02)
308 bl_flash();
309 else
310 backlight.state = (enum backlight_state) c;
311 read_state = S_DEFAULT;
312 break;
313 }
314 }
315
316 void loop()
317 {
318 led_tasks();
319 button_tasks();
320 bl_tasks();
321
322 while (Serial.available())
323 handle_character(Serial.read());
324 }