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