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