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