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