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