2dbd4132887963fadad5661b56ef3797687b227a
[bertos.git] / drv / lcd_text.c
1 /**
2  * \file
3  * <!--
4  * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
5  * This file is part of DevLib - See README.devlib for information.
6  * -->
7  *
8  * \brief Generic text LCD driver (impl.).
9  *
10  * \version $Id$
11  * \author Bernardo Innocenti <bernie@develer.com>
12  * \author Stefano Fedrigo <aleph@develer.com>
13  */
14
15 /*#*
16  *#* $Log$
17  *#* Revision 1.4  2006/09/20 20:02:43  marco
18  *#* Replaced ISLISTEMPTY with LIST_EMPTY
19  *#*
20  *#* Revision 1.3  2006/07/19 12:56:25  bernie
21  *#* Convert to new Doxygen style.
22  *#*
23  *#* Revision 1.2  2006/02/24 00:27:14  bernie
24  *#* Use new naming convention for list macros.
25  *#*
26  *#* Revision 1.1  2005/11/04 18:00:42  bernie
27  *#* Import into DevLib.
28  *#*
29  *#* Revision 1.11  2005/06/14 14:43:43  bernie
30  *#* Add DevLib headers.
31  *#*
32  *#* Revision 1.10  2005/06/06 17:41:57  batt
33  *#* Add lcd_layerSet function.
34  *#*
35  *#* Revision 1.9  2005/06/01 16:40:07  batt
36  *#* Remove debug string.
37  *#*
38  *#* Revision 1.8  2005/06/01 16:38:04  batt
39  *#* Adapt to changes in mware/list.h.
40  *#*
41  *#* Revision 1.7  2005/06/01 10:45:22  batt
42  *#* lcd_setAddr(): Bugfix boundary condition; Misc cleanup.
43  *#*
44  *#* Revision 1.6  2005/06/01 10:36:23  batt
45  *#* Layer: Rename member variables and Doxygenize.
46  *#*
47  *#* Revision 1.5  2005/05/27 11:05:58  batt
48  *#* Do not write on lcd if layer is hidden.
49  *#*/
50
51 #include "lcd_text.h"
52 #include "lcd_hd44.h"
53 #include <drv/timer.h> // timer_delay()
54 #include <mware/formatwr.h> // _formatted_write()
55 #include <mware/list.h> // LIST_EMPTY()
56 #include <cfg/macros.h> // BV()
57 #include <cfg/debug.h>
58
59 #include <string.h> // strlen()
60
61
62 /** Maximum number of layers. */
63 #define LCD_LAYERS 6
64
65 #if CONFIG_KERNEL
66         /** Semaphore to arbitrate access to the display. */
67         static struct Semaphore lcd_semaphore;
68         #define LOCK_LCD        sem_obtain(&lcd_semaphore)
69         #define UNLOCK_LCD      sem_release(&lcd_semaphore)
70 #else /* !CONFIG_KERNEL */
71         #define LOCK_LCD        do {} while (0)
72         #define UNLOCK_LCD      do {} while (0)
73 #endif /* !CONFIG_KERNEL */
74
75 DECLARE_LIST_TYPE(Layer);
76
77 Layer *lcd_DefLayer;
78 static Layer lcd_LayersPool[LCD_LAYERS];
79 static LIST_TYPE(Layer) lcd_Layers;
80 static LIST_TYPE(Layer) lcd_FreeLayers;
81
82 /**
83  * Current cursor status.
84  *
85  * One of LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK or LCD_CMD_CURSOR_LINE.
86  */
87 static uint8_t lcd_CursorStatus;
88
89 /** Current cursor position, encoded as a Cursor position and status. */
90 static lcdpos_t lcd_CursorAddr;
91
92
93 void lcd_setAddr(Layer *layer, lcdpos_t addr)
94 {
95         /* Sanity check: wrap around to display limits */
96         while (addr >= LCD_ROWS * LCD_COLS)
97                 addr -= LCD_ROWS * LCD_COLS;
98
99         layer->addr = addr;
100 }
101
102 #if CONFIG_KERNEL
103
104 void lcd_lock(void)
105 {
106         LOCK_LCD;
107 }
108
109
110 void lcd_unlock(void)
111 {
112         UNLOCK_LCD;
113 }
114
115 #endif /* CONFIG_KERNEL */
116
117
118 /**
119  * Write one character to the display at the current
120  * cursor prosition, then move the cursor right. The
121  * cursor is wrapped to the next line when it moves
122  * beyond the end of the current line.
123  *
124  * \note Does _NOT_ lock the display semaphore.
125  */
126 static void lcd_putCharUnlocked(char c, Layer *layer)
127 {
128         Layer *l2;
129         lcdpos_t addr = layer->addr;
130
131         /* Store character in layer buffer */
132         layer->buf[addr] = c;
133
134         /* Move to next character */
135         if (++layer->addr >= LCD_COLS * LCD_ROWS)
136                 layer->addr = 0;
137
138         /* Do not write on LCD if layer is hidden. */
139         if (layer->pri == LAYER_HIDDEN)
140                 return;
141
142         /*
143          * Check if this location is obscured by
144          * other layers above us.
145          */
146         for (l2 = layer->pred; l2->pred; l2 = l2->pred)
147         {
148                 if (l2->buf[addr])
149                 {
150                         /* DB(kprintf("layer %04x obs %04x at %d\n", l2, layer, addr);) */
151                         return;
152                 }
153         }
154
155         /* Write character */
156         if (c)
157                 lcd_putc(addr, c);
158         else
159                 /* FIXME: should look for layers beneath! */
160                 lcd_putc(addr, ' ');
161 }
162
163
164 void lcd_putChar(char c, Layer *layer)
165 {
166         LOCK_LCD;
167         lcd_putCharUnlocked(c, layer);
168         UNLOCK_LCD;
169 }
170
171 void lcd_layerSet(Layer *layer, char c)
172 {
173         int i;
174
175         LOCK_LCD;
176         lcd_setAddr(layer, 0);
177         for (i = 0; i < LCD_COLS * LCD_ROWS; i++)
178                 lcd_putCharUnlocked(c, layer);
179         UNLOCK_LCD;
180 }
181
182
183 void lcd_clear(Layer *layer)
184 {
185         lcd_layerSet(layer, 0);
186 }
187
188
189 void lcd_clearLine(Layer *layer, int y)
190 {
191         int i;
192
193         LOCK_LCD;
194         lcd_setAddr(layer, LCD_POS(0, y));
195         for (i = 0; i < LCD_COLS; i++)
196                 lcd_putCharUnlocked(0, layer);
197         UNLOCK_LCD;
198 }
199
200
201 void lcd_moveCursor(lcdpos_t addr)
202 {
203         LOCK_LCD;
204         lcd_moveTo(addr);
205         UNLOCK_LCD;
206 }
207
208
209 char lcd_setCursor(char mode)
210 {
211         static const char cursor_cmd[3] =
212         {
213                 LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK, LCD_CMD_CURSOR_LINE
214         };
215         char oldmode = lcd_CursorStatus;
216
217         LOCK_LCD;
218         lcd_CursorStatus = mode;
219         lcd_setReg(cursor_cmd[(int)mode]);
220         if (mode)
221                 lcd_moveCursor(lcd_CursorAddr);
222         UNLOCK_LCD;
223
224         return oldmode;
225 }
226
227
228 int lcd_vprintf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, va_list ap)
229 {
230         int len;
231
232         LOCK_LCD;
233
234         /*
235          * Se il cursore era acceso, spegnilo durante
236          * l'output per evitare che salti alla posizione
237          * in cui si scrive.
238          */
239         if (lcd_CursorStatus)
240                 lcd_setReg(LCD_CMD_CURSOR_OFF);
241
242         /* Spostamento del cursore */
243         lcd_setAddr(layer, addr);
244
245         if (mode & LCD_CENTER)
246         {
247                 int pad;
248
249                 /*
250                  * NOTE: calculating the string lenght BEFORE it gets
251                  * printf()-formatted. Real lenght may differ.
252                  */
253                 pad = (LCD_COLS - strlen(format)) / 2;
254                 while (pad--)
255                         lcd_putCharUnlocked(' ', layer);
256         }
257
258         len = _formatted_write(format, (void (*)(char, void *))lcd_putCharUnlocked, layer, ap);
259
260         if (mode & (LCD_FILL | LCD_CENTER))
261                 while (layer->addr % LCD_COLS)
262                         lcd_putCharUnlocked(' ', layer);
263
264         /*
265          * Riaccendi il cursore e riportalo alla
266          * vecchia posizione
267          */
268         if (lcd_CursorStatus)
269                 lcd_setCursor(lcd_CursorStatus);
270
271         UNLOCK_LCD;
272
273         return len;
274 }
275
276
277 int lcd_printf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, ...)
278 {
279         int len;
280         va_list ap;
281
282         va_start(ap, format);
283         len = lcd_vprintf(layer, addr, mode, format, ap);
284         va_end(ap);
285
286         return len;
287 }
288
289
290 /**
291  * Internal function to move a layer between two positions.
292  *
293  * \note The layer must be *already* enqueued in some list.
294  * \note The display must be already locked!
295  */
296 static void lcd_enqueueLayer(Layer *layer, char pri)
297 {
298         Layer *l2;
299
300         /* Remove layer from whatever list it was in before */
301         REMOVE(layer);
302
303         layer->pri = pri;
304
305         /*
306          * Search for the first layer whose priority
307          * is less or equal to the layer we are adding.
308          */
309         FOREACH_NODE(l2, &lcd_Layers)
310                 if (l2->pri <= pri)
311                         break;
312
313         /* Enqueue layer */
314         INSERT_BEFORE(layer, l2);
315 }
316
317 Layer *lcd_newLayer(char pri)
318 {
319         Layer *layer;
320
321         LOCK_LCD;
322
323         if (LIST_EMPTY(&lcd_FreeLayers))
324         {
325                 UNLOCK_LCD;
326                 //ASSERT(false);
327                 return NULL;
328         }
329
330         layer = (Layer *)LIST_HEAD(&lcd_FreeLayers);
331         layer->addr = 0;
332         memset(layer->buf, 0, LCD_ROWS * LCD_COLS);
333
334         lcd_enqueueLayer(layer, pri);
335
336         UNLOCK_LCD;
337         return layer;
338 }
339
340 /**
341  * Redraw the display (internal).
342  *
343  * \note The display must be already locked.
344  */
345 static void lcd_refresh(void)
346 {
347         lcdpos_t addr;
348         Layer *l;
349
350         for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
351         {
352                 FOREACH_NODE(l, &lcd_Layers)
353                 {
354                         //kprintf("%d %x %p\n", addr, l->buf[0], l);
355                         if (l->pri == LAYER_HIDDEN)
356                                 break;
357
358                         if (l->buf[addr])
359                         {
360                                 /* Refresh location */
361                                 lcd_putc(addr, l->buf[addr]);
362                                 goto done;
363                         }
364                 }
365
366                 /* Draw background */
367                 lcd_putc(addr, ' ');
368         done:
369                 ;
370         }
371 }
372
373 /**
374  * Rearrange layer depth and refresh display accordingly.
375  *
376  * \note Setting a priority of LAYER_HIDDEN makes the layer invisible.
377  */
378 void lcd_setLayerDepth(Layer *layer, char pri)
379 {
380         if (pri != layer->pri)
381         {
382                 LOCK_LCD;
383                 lcd_enqueueLayer(layer, pri);
384                 /* Vile but simple */
385                 lcd_refresh();
386                 UNLOCK_LCD;
387         }
388 }
389
390 void lcd_deleteLayer(Layer *layer)
391 {
392         LOCK_LCD;
393
394 /* We use lcd_refresh() instead.  Much simpler than this mess, but slower. */
395 #if 0
396         Layer *l2;
397         lcdpos_t addr;
398
399         /* Repair damage on underlaying layers */
400         for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
401         {
402                 /* If location was covered by us */
403                 if (layer->buf[addr])
404                 {
405                         /* ...and it wasn't covered by others above us... */
406                         for (l2 = layer->pred; l2->pred; l2 = l2->pred)
407                                 if (l2->buf[addr])
408                                         /* can't just break here! */
409                                         goto not_visible;
410
411                         /* ...scan underlaying layers to repair damage */
412                         for (l2 = layer->succ; l2->succ; l2 = l2->succ)
413                                 if (l2->buf[addr])
414                                 {
415                                         /* Refresh character */
416                                         lcd_putc(addr, l2->buf[addr]);
417
418                                         /* No need to search on deeper layers */
419                                         break;
420                                 }
421
422                         not_visible:
423                                 ;
424                 }
425         }
426 #endif
427
428         // Remove layer from lcd_Layers list.
429         REMOVE(layer);
430
431         /* Put layer back into free list */
432         ADDHEAD(&lcd_FreeLayers, layer);
433
434         lcd_refresh();
435
436         UNLOCK_LCD;
437 }
438
439
440 static void lcd_setDefLayer(Layer *layer)
441 {
442         lcd_DefLayer = layer;
443 }
444
445 #include <cfg/debug.h>
446 void lcd_init(void)
447 {
448         int i;
449
450         LIST_INIT(&lcd_Layers);
451         LIST_INIT(&lcd_FreeLayers);
452         for (i = 0; i < LCD_LAYERS; ++i)
453                 ADDHEAD(&lcd_FreeLayers, &lcd_LayersPool[i]);
454
455         lcd_setDefLayer(lcd_newLayer(0));
456
457         lcd_hw_init();
458
459         lcd_setCursor(0);
460 }
461
462 #if CONFIG_TEST
463 void lcd_test(void)
464 {
465         int i;
466
467         for (i = 0; i < LCD_ROWS * LCD_COLS; ++i)
468         {
469                 lcd_putCharUnlocked('0' + (i % 10), lcd_DefLayer);
470                 timer_delay(100);
471         }
472 }
473 #endif /* CONFIG_TEST */
474