all: Added -Wall to the build
[simavr] / examples / parts / hd44780.c
1 /*
2         hd44780.c
3
4         Copyright Luki <humbell@ethz.ch>
5         Copyright 2011 Michel Pollet <buserror@gmail.com>
6
7         This file is part of simavr.
8
9         simavr is free software: you can redistribute it and/or modify
10         it under the terms of the GNU General Public License as published by
11         the Free Software Foundation, either version 3 of the License, or
12         (at your option) any later version.
13
14         simavr is distributed in the hope that it will be useful,
15         but WITHOUT ANY WARRANTY; without even the implied warranty of
16         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17         GNU General Public License for more details.
18
19         You should have received a copy of the GNU General Public License
20         along with simavr.  If not, see <http://www.gnu.org/licenses/>.
21  */
22
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include "sim_time.h"
27
28 #include "hd44780.h"
29
30 void
31 hd44780_print(
32                 hd44780_t *b)
33 {
34         printf("/******************\\\n");
35         const uint8_t offset[] = { 0, 0x40, 0x20, 0x60 };
36         for (int i = 0; i < b->h; i++) {
37                 printf("| ");
38                 fwrite(b->vram + offset[i], 1, b->w, stdout);
39                 printf(" |\n");
40         }
41         printf("\\******************/\n");
42 }
43
44
45 static void
46 _hd44780_reset_cursor(
47                 hd44780_t *b)
48 {
49         b->cursor = 0;
50         hd44780_set_flag(b, HD44780_FLAG_DIRTY, 1);
51         avr_raise_irq(b->irq + IRQ_HD44780_ADDR, b->cursor);
52 }
53
54 static void
55 _hd44780_clear_screen(
56                 hd44780_t *b)
57 {
58         memset(b->vram, ' ', 80);
59         hd44780_set_flag(b, HD44780_FLAG_DIRTY, 1);
60         avr_raise_irq(b->irq + IRQ_HD44780_ADDR, b->cursor);
61 }
62
63
64
65 /*
66  * This is called when the delay between operation is triggered
67  * without the AVR firmware 'reading' the status byte. It
68  * automatically clears the BUSY flag for the next command
69  */
70 static avr_cycle_count_t
71 _hd44780_busy_timer(
72                 struct avr_t * avr,
73         avr_cycle_count_t when, void * param)
74 {
75         hd44780_t *b = (hd44780_t *) param;
76 //      printf("%s called\n", __FUNCTION__);
77         hd44780_set_flag(b, HD44780_FLAG_BUSY, 0);
78         avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 0);
79         return 0;
80 }
81
82 static void
83 hd44780_kick_cursor(
84         hd44780_t *b)
85 {
86         if (hd44780_get_flag(b, HD44780_FLAG_I_D)) {
87                 if (b->cursor < 79)
88                         b->cursor++;
89                 else if (b->cursor < 80+64-1)
90                         b->cursor++;
91         } else {
92                 if (b->cursor < 80 && b->cursor)
93                         b->cursor--;
94                 else if (b->cursor > 80)
95                         b->cursor--;
96                 hd44780_set_flag(b, HD44780_FLAG_DIRTY, 1);
97                 avr_raise_irq(b->irq + IRQ_HD44780_ADDR, b->cursor);
98         }
99 }
100
101 /*
102  * current data byte is ready in b->datapins
103  */
104 static uint32_t
105 hd44780_write_data(
106                 hd44780_t *b)
107 {
108         uint32_t delay = 37; // uS
109         b->vram[b->cursor] = b->datapins;
110         printf("hd44780_write_data %02x\n", b->datapins);
111         if (hd44780_get_flag(b, HD44780_FLAG_S_C)) {    // display shift ?
112                 // TODO display shift
113         } else {
114                 hd44780_kick_cursor(b);
115         }
116         hd44780_set_flag(b, HD44780_FLAG_DIRTY, 1);
117         return delay;
118 }
119
120 /*
121  * current command is ready in b->datapins
122  */
123 static uint32_t
124 hd44780_write_command(
125                 hd44780_t *b)
126 {
127         uint32_t delay = 37; // uS
128         int top = 7;    // get highest bit set'm
129         while (top)
130                 if (b->datapins & (1 << top))
131                         break;
132                 else top--;
133         printf("hd44780_write_command %02x\n", b->datapins);
134
135         switch (top) {
136                 // Set  DDRAM address
137                 case 7:         // 1 ADD ADD ADD ADD ADD ADD ADD
138                         b->cursor = b->datapins & 0x7f;
139                         break;
140                 // Set  CGRAM address
141                 case 6:         // 0 1 ADD ADD ADD ADD ADD ADD ADD
142                         b->cursor = 64 + (b->datapins & 0x3f);
143                         break;
144                 // Function     set
145                 case 5: {       // 0 0 1 DL N F x x
146                         int four = !hd44780_get_flag(b, HD44780_FLAG_D_L);
147                         hd44780_set_flag(b, HD44780_FLAG_D_L, b->datapins & 16);
148                         hd44780_set_flag(b, HD44780_FLAG_N, b->datapins & 8);
149                         hd44780_set_flag(b, HD44780_FLAG_F, b->datapins & 4);
150                         if (!four && !hd44780_get_flag(b, HD44780_FLAG_D_L)) {
151                                 printf("%s activating 4 bits mode\n", __FUNCTION__);
152                                 hd44780_set_flag(b, HD44780_FLAG_LOWNIBBLE, 0);
153                         }
154                 }       break;
155                 // Cursor display shift
156                 case 4:         // 0 0 0 1 S/C R/L x x
157                         hd44780_set_flag(b, HD44780_FLAG_S_C, b->datapins & 8);
158                         hd44780_set_flag(b, HD44780_FLAG_R_L, b->datapins & 4);
159                         break;
160                 // Display on/off control
161                 case 3:         // 0 0 0 0 1 D C B
162                         hd44780_set_flag(b, HD44780_FLAG_D, b->datapins & 4);
163                         hd44780_set_flag(b, HD44780_FLAG_C, b->datapins & 2);
164                         hd44780_set_flag(b, HD44780_FLAG_B, b->datapins & 1);
165                         hd44780_set_flag(b, HD44780_FLAG_DIRTY, 1);
166                         break;
167                 // Entry mode set
168                 case 2:         // 0 0 0 0 0 1 I/D S
169                         hd44780_set_flag(b, HD44780_FLAG_I_D, b->datapins & 2);
170                         hd44780_set_flag(b, HD44780_FLAG_S, b->datapins & 1);
171                         break;
172                 // Return home
173                 case 1:         // 0 0 0 0 0 0 1 x
174                         _hd44780_reset_cursor(b);
175                         delay = 1520;
176                         break;
177                 // Clear display
178                 case 0:         // 0 0 0 0 0 0 0 1
179                         _hd44780_clear_screen(b);
180                         break;
181         }
182         return delay;
183 }
184
185 /*
186  * the E pin went low, and it's a write
187  */
188 static uint32_t
189 hd44780_process_write(
190                 hd44780_t *b )
191 {
192         uint32_t delay = 0; // uS
193         int four = !hd44780_get_flag(b, HD44780_FLAG_D_L);
194         int comp = four && hd44780_get_flag(b, HD44780_FLAG_LOWNIBBLE);
195         int write = 0;
196
197         if (four) { // 4 bits !
198                 if (comp)
199                         b->datapins = (b->datapins & 0xf0) | ((b->pinstate >>  IRQ_HD44780_D4) & 0xf);
200                 else
201                         b->datapins = (b->datapins & 0xf) | ((b->pinstate >>  (IRQ_HD44780_D4-4)) & 0xf0);
202                 write = comp;
203                 b->flags ^= (1 << HD44780_FLAG_LOWNIBBLE);
204         } else {        // 8 bits
205                 b->datapins = (b->pinstate >>  IRQ_HD44780_D0) & 0xff;
206                 write++;
207         }
208         avr_raise_irq(b->irq + IRQ_HD44780_DATA_IN, b->datapins);
209
210         // write has 8 bits to process
211         if (write) {
212                 if (hd44780_get_flag(b, HD44780_FLAG_BUSY)) {
213                         printf("%s command %02x write when still BUSY\n", __FUNCTION__, b->datapins);
214                 }
215                 if (b->pinstate & (1 << IRQ_HD44780_RS))        // write data
216                         delay = hd44780_write_data(b);
217                 else                                                                            // write command
218                         delay = hd44780_write_command(b);
219         }
220         return delay;
221 }
222
223 static uint32_t
224 hd44780_process_read(
225                 hd44780_t *b )
226 {
227         uint32_t delay = 0; // uS
228         int four = !hd44780_get_flag(b, HD44780_FLAG_D_L);
229         int comp = four && hd44780_get_flag(b, HD44780_FLAG_LOWNIBBLE);
230         int done = 0;   // has something on the datapin we want
231
232         if (comp) {
233                 // ready the 4 final bits on the 'actual' lcd pins
234                 b->readpins <<= 4;
235                 done++;
236                 b->flags ^= (1 << HD44780_FLAG_LOWNIBBLE);
237         }
238
239         if (!done) { // new read
240
241                 if (b->pinstate & (1 << IRQ_HD44780_RS)) {      // read data
242                         delay = 37;
243                         b->readpins = b->vram[b->cursor];
244                         hd44780_kick_cursor(b);
245                 } else {        // read 'command' ie status register
246                         delay = 0;      // no raising busy when reading busy !
247
248                         // low bits are the current cursor
249                         b->readpins = b->cursor < 80 ? b->cursor : b->cursor-64;
250                         int busy = hd44780_get_flag(b, HD44780_FLAG_BUSY);
251                         b->readpins |= busy ? 0x80 : 0;
252
253                 //      if (busy) printf("Good boy, guy's reading status byte\n");
254                         // now that we're read the busy flag, clear it and clear
255                         // the timer too
256                         hd44780_set_flag(b, HD44780_FLAG_BUSY, 0);
257                         avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 0);
258                         avr_cycle_timer_cancel(b->avr, _hd44780_busy_timer, b);
259                 }
260                 avr_raise_irq(b->irq + IRQ_HD44780_DATA_OUT, b->readpins);
261
262                 done++;
263                 if (four)
264                         b->flags |= (1 << HD44780_FLAG_LOWNIBBLE); // for next read
265         }
266
267         // now send the prepared output pins to send as IRQs
268         if (done) {
269                 avr_raise_irq(b->irq + IRQ_HD44780_ALL, b->readpins >> 4);
270                 for (int i = four ? 4 : 0; i < 8; i++)
271                         avr_raise_irq(b->irq + IRQ_HD44780_D0 + i, (b->readpins >> i) & 1);
272         }
273         return delay;
274 }
275
276 static avr_cycle_count_t
277 _hd44780_process_e_pinchange(
278                 struct avr_t * avr,
279         avr_cycle_count_t when, void * param)
280 {
281         hd44780_t *b = (hd44780_t *) param;
282
283         hd44780_set_flag(b, HD44780_FLAG_REENTRANT, 1);
284
285 #if 0
286         uint16_t touch = b->oldstate ^ b->pinstate;
287         printf("LCD: %04x %04x %c %c %c %c\n", b->pinstate, touch,
288                         b->pinstate & (1 << IRQ_HD44780_RW) ? 'R' : 'W',
289                         b->pinstate & (1 << IRQ_HD44780_RS) ? 'D' : 'C',
290                         hd44780_get_flag(b, HD44780_FLAG_LOWNIBBLE) ? 'L' : 'H',
291                         hd44780_get_flag(b, HD44780_FLAG_BUSY) ? 'B' : ' ');
292 #endif
293         int delay = 0; // in uS
294
295         if (b->pinstate & (1 << IRQ_HD44780_RW))        // read !?!
296                 delay = hd44780_process_read(b);
297         else                                                                            // write
298                 delay = hd44780_process_write(b);
299
300         if (delay) {
301                 hd44780_set_flag(b, HD44780_FLAG_BUSY, 1);
302                 avr_raise_irq(b->irq + IRQ_HD44780_BUSY, 1);
303                 avr_cycle_timer_register_usec(b->avr, delay,
304                         _hd44780_busy_timer, b);
305         }
306 //      b->oldstate = b->pinstate;
307         hd44780_set_flag(b, HD44780_FLAG_REENTRANT, 0);
308         return 0;
309 }
310
311 static void
312 hd44780_pin_changed_hook(
313                 struct avr_irq_t * irq,
314                 uint32_t value,
315         void *param)
316 {
317         hd44780_t *b = (hd44780_t *) param;
318
319         uint16_t old = b->pinstate;
320
321         switch (irq->irq) {
322                 /*
323                  * Update all the pins in one go by calling ourselves
324                  * This is a shortcut for firmware that respects the conventions
325                  */
326                 case IRQ_HD44780_ALL:
327                         for (int i = 0; i < 4; i++)
328                                 hd44780_pin_changed_hook(b->irq + IRQ_HD44780_D4 + i,
329                                                 ((value >> i) & 1), param);
330                         hd44780_pin_changed_hook(b->irq + IRQ_HD44780_RS, (value >> 4), param);
331                         hd44780_pin_changed_hook(b->irq + IRQ_HD44780_E, (value >> 5), param);
332                         hd44780_pin_changed_hook(b->irq + IRQ_HD44780_RW, (value >> 6), param);
333                         return; // job already done!
334                 case IRQ_HD44780_D0 ... IRQ_HD44780_D7:
335                         // don't update these pins in read mode
336                         if (hd44780_get_flag(b, HD44780_FLAG_REENTRANT))
337                                 return;
338                         break;
339         }
340         b->pinstate = (b->pinstate & ~(1 << irq->irq)) | (value << irq->irq);
341         int eo = old & (1 << IRQ_HD44780_E);
342         int e = b->pinstate & (1 << IRQ_HD44780_E);
343         // on the E pin rising edge, do stuff otherwise just exit
344         if (!eo && e)
345                 avr_cycle_timer_register(b->avr, 1, _hd44780_process_e_pinchange, b);
346 }
347
348 static const char * irq_names[IRQ_HD44780_COUNT] = {
349         [IRQ_HD44780_ALL] = "7=hd44780.pins",
350         [IRQ_HD44780_RS] = "<hd44780.RS",
351         [IRQ_HD44780_RW] = "<hd44780.RW",
352         [IRQ_HD44780_E] = "<hd44780.E",
353         [IRQ_HD44780_D0] = "=hd44780.D0",
354         [IRQ_HD44780_D1] = "=hd44780.D1",
355         [IRQ_HD44780_D2] = "=hd44780.D2",
356         [IRQ_HD44780_D3] = "=hd44780.D3",
357         [IRQ_HD44780_D4] = "=hd44780.D4",
358         [IRQ_HD44780_D5] = "=hd44780.D5",
359         [IRQ_HD44780_D6] = "=hd44780.D6",
360         [IRQ_HD44780_D7] = "=hd44780.D7",
361
362         [IRQ_HD44780_BUSY] = ">hd44780.BUSY",
363         [IRQ_HD44780_ADDR] = "7>hd44780.ADDR",
364         [IRQ_HD44780_DATA_IN] = "8>hd44780.DATA_IN",
365         [IRQ_HD44780_DATA_OUT] = "8>hd44780.DATA_OUT",
366 };
367
368 void
369 hd44780_init(
370                 struct avr_t *avr,
371                 struct hd44780_t * b,
372                 int width,
373                 int height )
374 {
375         memset(b, 0, sizeof(*b));
376         b->avr = avr;
377         b->w = width;
378         b->h = height;
379         /*
380          * Register callbacks on all our IRQs
381          */
382         b->irq = avr_alloc_irq(&avr->irq_pool, 0, IRQ_HD44780_COUNT, irq_names);
383         for (int i = 0; i < IRQ_HD44780_INPUT_COUNT; i++)
384                 avr_irq_register_notify(b->irq + i, hd44780_pin_changed_hook, b);
385
386         _hd44780_reset_cursor(b);
387         _hd44780_clear_screen(b);
388
389         printf("LCD: %duS is %d cycles for your AVR\n",
390                         37, (int)avr_usec_to_cycles(avr, 37));
391         printf("LCD: %duS is %d cycles for your AVR\n",
392                         1, (int)avr_usec_to_cycles(avr, 1));
393 }
394