--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief LM044L type LCD hardware module (impl.)
+ *
+ * \version $Id$
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 18:00:42 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.2 2005/06/14 14:43:43 bernie
+ *#* Add DevLib headers.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*
+ *#* Revision 1.9 2005/05/09 21:58:53 batt
+ *#* Fix doxygen tags.
+ *#*
+ *#* Revision 1.8 2005/05/09 12:52:46 batt
+ *#* lcd_dataRead(): Avoid bus collision; Add back *UNTESTED* 8bit bus support.
+ *#*
+ *#* Revision 1.7 2005/05/09 12:24:13 batt
+ *#* lcd_putc(): Fix latent bug; lcd_hw_init(): Extend timings.
+ *#*/
+
+#include "lcd_hd44.h"
+#include "lcd_bus_pz.h"
+#include <arch_config.h>
+#include <drv/timer.h>
+
+#if defined(LCD_READ_H) && defined(LCD_READ_L) && defined(LCD_WRITE_H) && defined(LCD_WRITE_L)
+ #define CONFIG_LCD_4BIT 1
+#elif defined(LCD_READ) && defined(LCD_WRITE)
+ #define CONFIG_LCD_4BIT 0
+#else
+ #error Incomplete or missing LCD_READ/LCD_WRITE macros
+#endif
+
+/*! Flag di stato del display */
+#define LCDF_BUSY BV(7)
+
+/*!
+ * Addresses of LCD display character positions, expanded
+ * for faster access (DB7 = 1).
+ */
+static const uint8_t lcd_address[] =
+{
+ /* row 0 */
+ 0x80, 0x81, 0x82, 0x83,
+ 0x84, 0x85, 0x86, 0x87,
+ 0x88, 0x89, 0x8A, 0x8B,
+ 0x8C, 0x8D, 0x8E, 0x8F,
+#if LCD_COLS > 16
+ 0x90, 0x91, 0x92, 0x93,
+#endif
+
+ /* row 1 */
+ 0xC0, 0xC1, 0xC2, 0xC3,
+ 0xC4, 0xC5, 0xC6, 0xC7,
+ 0xC8, 0xC9, 0xCA, 0xCB,
+ 0xCC, 0xCD, 0xCE, 0xCF,
+#if LCD_COLS > 16
+ 0xD0, 0xD1, 0xD2, 0xD3,
+#endif
+
+#if LCD_ROWS > 2
+ /* row 2 */
+ 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9A, 0x9B,
+ 0x9C, 0x9D, 0x9E, 0x9F,
+ 0xA0, 0xA1, 0xA2, 0xA3,
+#if LCD_COLS > 16
+ 0xA4, 0xA5, 0xA6, 0xA7,
+#endif
+
+ /* row 3 */
+ 0xD4, 0xD5, 0xD6, 0xD7,
+ 0xD8, 0xD9, 0xDA, 0xDB,
+ 0xDC, 0xDD, 0xDE, 0xDF,
+ 0xE0, 0xE1, 0xE2, 0xE3,
+#if LCD_COLS > 16
+ 0xE4, 0xE5, 0xE6, 0xE7,
+#endif
+
+#endif /* LCD_ROWS > 2 */
+};
+
+STATIC_ASSERT(countof(lcd_address) == LCD_ROWS * LCD_COLS);
+
+/*!
+ * Current display position. We remember this to optimize
+ * LCD output by avoiding to set the address every time.
+ */
+static lcdpos_t lcd_current_addr;
+
+
+#if !defined(ARCH_EMUL) || !(ARCH & ARCH_EMUL)
+/* __________________
+ * RS
+ *
+ * R/W __________________
+ * _______
+ * ENA _____/ \____
+ *
+ * DATA -<================
+ */
+INLINE void lcd_dataWrite(uint8_t data)
+{
+#if CONFIG_LCD_4BIT
+ /* Write high nibble */
+ LCD_WRITE_H(data);
+ LCD_SET_E;
+ LCD_DELAY_WRITE;
+ LCD_CLR_E;
+ LCD_DELAY_WRITE;
+
+ /* Write low nibble */
+ LCD_WRITE_L(data);
+ LCD_SET_E;
+ LCD_DELAY_WRITE;
+ LCD_CLR_E;
+ LCD_DELAY_WRITE;
+
+#else /* !CONFIG_LCD_4BIT */
+
+ /* Write data */
+ LCD_WRITE(data);
+ LCD_SET_E;
+ LCD_DELAY_WRITE;
+ LCD_CLR_E;
+ LCD_DELAY_WRITE;
+
+#endif /* !CONFIG_LCD_4BIT */
+}
+
+/* __________________
+ * RS
+ * ____________
+ * R/W __/ \__
+ * _______
+ * ENA _____/ \____
+ * ______ ____
+ * DATA X/ \====/
+ */
+INLINE uint8_t lcd_dataRead(void)
+{
+ uint8_t data;
+
+ LCD_SET_RD;
+ LCD_DB_IN; /* Set bus as input! */
+ LCD_DELAY_READ;
+
+#if CONFIG_LCD_4BIT
+
+ /* Read high nibble */
+ LCD_SET_E;
+ LCD_DELAY_READ;
+ data = LCD_READ_H;
+ LCD_CLR_E;
+ LCD_DELAY_READ;
+
+ /* Read low nibble */
+ LCD_SET_E;
+ LCD_DELAY_READ;
+ data |= LCD_READ_L;
+ LCD_CLR_E;
+ LCD_DELAY_READ;
+
+#else /* !CONFIG_LCD_4BIT */
+
+ /* Read data */
+ LCD_SET_E;
+ LCD_DELAY_READ;
+ data |= LCD_READ;
+ LCD_CLR_E;
+ LCD_DELAY_READ;
+
+#endif /* !CONFIG_LCD_4BIT */
+
+ LCD_CLR_RD;
+ LCD_DB_OUT; /* Reset bus as output! */
+
+ return data;
+}
+
+/* ___ __
+ * RS \___________/
+ *
+ * READ __________________
+ * _______
+ * ENA _____/ \____
+ *
+ * DATA --<===============
+ */
+INLINE void lcd_regWrite(uint8_t data)
+{
+ LCD_CLR_RS;
+ lcd_dataWrite(data);
+ LCD_SET_RS;
+}
+
+/* __ _
+ * RS \_____________/
+ * ___________
+ * READ ___/ \__
+ * _______
+ * ENA _____/ \____
+ * ______ ____
+ * DATA X/ \====/
+ */
+INLINE uint8_t lcd_regRead(void)
+{
+ uint8_t data;
+
+ LCD_CLR_RS;
+ data = lcd_dataRead();
+ LCD_SET_RS;
+ return data;
+}
+
+#if CONFIG_LCD_4BIT
+
+INLINE void lcd_mode4Bit(void)
+{
+ LCD_CLR_RS;
+
+ LCD_WRITE_H(LCD_CMD_SETFUNC);
+ LCD_SET_E;
+ LCD_DELAY_WRITE;
+ LCD_CLR_E;
+ LCD_DELAY_WRITE;
+
+ LCD_SET_RS;
+}
+
+#endif /* CONFIG_LCD_4BIT */
+
+#else /* ARCH_EMUL */
+
+extern void Emul_LCDWriteReg(uint8_t d);
+extern uint8_t Emul_LCDReadReg(void);
+extern void Emul_LCDWriteData(uint8_t d);
+extern uint8_t Emul_LCDReadData(void);
+
+#define lcd_regWrite(d) Emul_LCDWriteReg(d)
+#define lcd_regRead(d) Emul_LCDReadReg()
+#define lcd_dataWrite(d) Emul_LCDWriteData(d)
+#define lcd_dataRead(d) Emul_LCDReadData()
+
+#endif /* ARCH_EMUL */
+
+
+/*!
+ * Wait until the LCD busy flag clears.
+ */
+void lcd_waitBusy(void)
+{
+ for (;;)
+ {
+ uint8_t val = lcd_regRead();
+ if (!(val & LCDF_BUSY))
+ break;
+ }
+}
+
+
+/*!
+ * Move the cursor to \a addr, only if not already there.
+ */
+void lcd_moveTo(uint8_t addr)
+{
+ if (addr != lcd_current_addr)
+ {
+ lcd_waitBusy();
+ lcd_regWrite(lcd_address[addr]);
+ lcd_current_addr = addr;
+ }
+}
+
+
+/*!
+ * Write a value in LCD data register, waiting for the busy flag.
+ */
+void lcd_setReg(uint8_t val)
+{
+ lcd_waitBusy();
+ lcd_regWrite(val);
+}
+
+#include <cfg/debug.h>
+/*!
+ * Write the character \a c on display address \a addr.
+ *
+ * NOTE: argh, the HD44 lcd type is a bad beast: our
+ * move/write -> write optimization requires this mess
+ * because display lines are interleaved!
+ */
+void lcd_putc(uint8_t addr, uint8_t c)
+{
+ if (addr != lcd_current_addr)
+ lcd_setReg(lcd_address[addr]);
+
+ lcd_waitBusy();
+ lcd_dataWrite(c);
+ lcd_current_addr = addr + 1;
+
+ /* If we are at end of display wrap the address to 0 */
+ if (lcd_current_addr == LCD_COLS * LCD_ROWS)
+ lcd_current_addr = 0;
+
+ /* If we are at the end of a row put the cursor at the beginning of the next */
+ if (!(lcd_current_addr % LCD_COLS))
+ lcd_setReg(lcd_address[lcd_current_addr]);
+}
+
+
+/*!
+ * Remap the glyph of a character.
+ *
+ * glyph - bitmap of 8x8 bits.
+ * code - must be 0-7 for the Hitachi LCD-II controller.
+ */
+void lcd_remapChar(const char *glyph, char code)
+{
+ int i;
+
+ /* Set CG RAM address */
+ lcd_setReg((uint8_t)((1<<6) | (code << 3)));
+
+ /* Write bitmap data */
+ for (i = 0; i < 8; i++)
+ {
+ lcd_waitBusy();
+ lcd_dataWrite(glyph[i]);
+ }
+
+ /* Move back to original address */
+ lcd_setReg(lcd_address[lcd_current_addr]);
+}
+
+
+#if 0 /* unused */
+void lcd_remapfont(void)
+{
+ static const char lcd_glyphs[8] =
+ {
+ 0x04, 0x0E, 0x15, 0x04, 0x04, 0x04, 0x04, 0x00 /* up arrow */
+ };
+ int i;
+
+ for (i = 0; i < 15; i++)
+ lcd_remapChar(i, bernie_char);
+
+
+ lcd_setAddr(lcd_DefLayer, 0);
+ for (i = 0; i < 80; i++)
+ lcd_putCharUnlocked(i);
+}
+#endif /* unused */
+
+void lcd_hw_init(void)
+{
+ lcd_bus_init();
+
+ timer_delay(50);
+
+#if CONFIG_LCD_4BIT
+ lcd_mode4Bit();
+ timer_delay(2);
+#endif /* CONFIG_LCD_4BIT */
+
+ lcd_regWrite(LCD_CMD_SETFUNC);
+ timer_delay(2);
+
+ lcd_regWrite(LCD_CMD_DISPLAY_ON);
+ timer_delay(2);
+
+ lcd_regWrite(LCD_CMD_CLEAR);
+ timer_delay(2);
+
+ //lcd_regWrite(LCD_CMD_RESET_DDRAM); 4 bit mode doesn't allow char reprogramming
+
+ lcd_regWrite(LCD_CMD_DISPLAYMODE);
+ timer_delay(2);
+}
+
+#if CONFIG_TEST
+
+void lcd_hw_test(void)
+{
+ lcd_regWrite(LCD_CMD_SET_DDRAMADDR | 3);
+ timer_delay(1);
+ kprintf("3 -> %02X\n", lcd_regRead());
+ timer_delay(1);
+
+ for (int i = 0; i < 10; i++)
+ {
+ lcd_dataWrite('c');
+ timer_delay(1);
+ kprintf("addr = %02X\n", lcd_regRead());
+ timer_delay(1);
+ }
+
+ lcd_regWrite(LCD_CMD_SET_DDRAMADDR | 0x4a);
+ timer_delay(1);
+ kprintf("4A -> %02X\n", lcd_regRead());
+ timer_delay(1);
+
+ lcd_regWrite(LCD_CMD_SET_DDRAMADDR | 0x52);
+ timer_delay(1);
+ kprintf("52 -> %02X\n", lcd_regRead());
+ timer_delay(1);
+
+ lcd_regWrite(LCD_CMD_SET_DDRAMADDR | 0x1F);
+ timer_delay(1);
+ kprintf("1F -> %02X\n", lcd_regRead());
+ timer_delay(1);
+}
+
+#endif /* CONFIG_TEST */
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief Hitachi HD44780 and clones LCD module (interface)
+ *
+ * \version $Id$
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 18:00:42 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.2 2005/06/14 14:43:43 bernie
+ *#* Add DevLib headers.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*
+ *#* Revision 1.3 2005/04/22 13:14:58 batt
+ *#* Better lcd rows and cols definition.
+ *#*
+ *#* Revision 1.2 2005/04/17 22:41:39 bernie
+ *#* lcd_test(): Rename to lcd_hw_test() and conditionalize on CONFIG_TEST.
+ *#*
+ *#* Revision 1.1 2005/04/16 19:48:35 aleph
+ *#* Rename lcd driver
+ *#*/
+
+#ifndef DRV_LCD_HD44_H
+#define DRV_LCD_HD44_H
+
+#include <cfg/compiler.h> /* For stdint types */
+
+/*!
+ * \name Display dimensions (in chars)
+ * \{
+ */
+#define LCD_ROWS 2
+#define LCD_COLS 16
+/* \} */
+
+/*!
+ * \name Hitachi HD44 commands.
+ * \{
+ */
+#define LCD_CMD_DISPLAY_INI 0x30
+//#define LCD_CMD_SETFUNC 0x38 /*!< 8 bits, 2 lines, 5x7 dots */
+#define LCD_CMD_SETFUNC 0x28 /*!< 4 bits, 2 lines, 5x7 dots */
+#define LCD_CMD_DISPLAY_ON 0x0F /*!< Switch on display */
+#define LCD_CMD_DISPLAY_OFF 0x08 /*!< Switch off display */
+#define LCD_CMD_CLEAR 0x01 /*!< Clear display */
+#define LCD_CMD_CURSOR_BLOCK 0x0D /*!< Show cursor (block) */
+#define LCD_CMD_CURSOR_LINE 0x0F /*!< Show cursor (line) */
+#define LCD_CMD_CURSOR_OFF 0x0C /*!< Hide cursor */
+#define LCD_CMD_DISPLAYMODE 0x06
+#define LCD_CMD_SET_CGRAMADDR 0x40
+#define LCD_CMD_RESET_DDRAM 0x80
+#define LCD_CMD_SET_DDRAMADDR 0x80
+#define LCD_CMD_DISPLAY_SHIFT 0x18
+#define LCD_CMD_MOVESHIFT_LEFT 0x00
+#define LCD_CMD_MOVESHIFT_RIGHT 0x04
+/*\}*/
+
+/*! Type for combined LCD cursor position (x,y). */
+typedef uint8_t lcdpos_t;
+
+void lcd_waitBusy(void);
+void lcd_moveTo(uint8_t addr);
+void lcd_setReg(uint8_t val);
+void lcd_putc(uint8_t a, uint8_t c);
+void lcd_remapChar(const char *glyph, char code);
+void lcd_hw_init(void);
+void lcd_hw_test(void);
+
+#endif /* DRV_LCD_HD44_H */
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief Generic text LCD driver (impl.).
+ *
+ * \version $Id$
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 18:00:42 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.11 2005/06/14 14:43:43 bernie
+ *#* Add DevLib headers.
+ *#*
+ *#* Revision 1.10 2005/06/06 17:41:57 batt
+ *#* Add lcd_layerSet function.
+ *#*
+ *#* Revision 1.9 2005/06/01 16:40:07 batt
+ *#* Remove debug string.
+ *#*
+ *#* Revision 1.8 2005/06/01 16:38:04 batt
+ *#* Adapt to changes in mware/list.h.
+ *#*
+ *#* Revision 1.7 2005/06/01 10:45:22 batt
+ *#* lcd_setAddr(): Bugfix boundary condition; Misc cleanup.
+ *#*
+ *#* Revision 1.6 2005/06/01 10:36:23 batt
+ *#* Layer: Rename member variables and Doxygenize.
+ *#*
+ *#* Revision 1.5 2005/05/27 11:05:58 batt
+ *#* Do not write on lcd if layer is hidden.
+ *#*/
+
+#include "lcd_text.h"
+#include "lcd_hd44.h"
+#include <drv/timer.h> // timer_delay()
+#include <mware/formatwr.h> // _formatted_write()
+#include <cfg/macros.h> // BV()
+#include <cfg/debug.h>
+
+#include <string.h> // strlen()
+
+
+/*! Maximum number of layers. */
+#define LCD_LAYERS 6
+
+#if CONFIG_KERNEL
+ /*! Semaphore to arbitrate access to the display. */
+ static struct Semaphore lcd_semaphore;
+ #define LOCK_LCD sem_obtain(&lcd_semaphore)
+ #define UNLOCK_LCD sem_release(&lcd_semaphore)
+#else /* !CONFIG_KERNEL */
+ #define LOCK_LCD do {} while (0)
+ #define UNLOCK_LCD do {} while (0)
+#endif /* !CONFIG_KERNEL */
+
+DECLARE_LIST_TYPE(Layer);
+
+Layer *lcd_DefLayer;
+static Layer lcd_LayersPool[LCD_LAYERS];
+static LIST_TYPE(Layer) lcd_Layers;
+static LIST_TYPE(Layer) lcd_FreeLayers;
+
+/*!
+ * Current cursor status.
+ *
+ * One of LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK or LCD_CMD_CURSOR_LINE.
+ */
+static uint8_t lcd_CursorStatus;
+
+/*! Current cursor position, encoded as a Cursor position and status. */
+static lcdpos_t lcd_CursorAddr;
+
+
+void lcd_setAddr(Layer *layer, lcdpos_t addr)
+{
+ /* Sanity check: wrap around to display limits */
+ while (addr >= LCD_ROWS * LCD_COLS)
+ addr -= LCD_ROWS * LCD_COLS;
+
+ layer->addr = addr;
+}
+
+#if CONFIG_KERNEL
+
+void lcd_lock(void)
+{
+ LOCK_LCD;
+}
+
+
+void lcd_unlock(void)
+{
+ UNLOCK_LCD;
+}
+
+#endif /* CONFIG_KERNEL */
+
+
+/*!
+ * Write one character to the display at the current
+ * cursor prosition, then move the cursor right. The
+ * cursor is wrapped to the next line when it moves
+ * beyond the end of the current line.
+ *
+ * \note Does _NOT_ lock the display semaphore.
+ */
+static void lcd_putCharUnlocked(char c, Layer *layer)
+{
+ Layer *l2;
+ lcdpos_t addr = layer->addr;
+
+ /* Store character in layer buffer */
+ layer->buf[addr] = c;
+
+ /* Move to next character */
+ if (++layer->addr >= LCD_COLS * LCD_ROWS)
+ layer->addr = 0;
+
+ /* Do not write on LCD if layer is hidden. */
+ if (layer->pri == LAYER_HIDDEN)
+ return;
+
+ /*
+ * Check if this location is obscured by
+ * other layers above us.
+ */
+ for (l2 = layer->pred; l2->pred; l2 = l2->pred)
+ {
+ if (l2->buf[addr])
+ {
+ /* DB(kprintf("layer %04x obs %04x at %d\n", l2, layer, addr);) */
+ return;
+ }
+ }
+
+ /* Write character */
+ if (c)
+ lcd_putc(addr, c);
+ else
+ /* FIXME: should look for layers beneath! */
+ lcd_putc(addr, ' ');
+}
+
+
+void lcd_putChar(char c, Layer *layer)
+{
+ LOCK_LCD;
+ lcd_putCharUnlocked(c, layer);
+ UNLOCK_LCD;
+}
+
+void lcd_layerSet(Layer *layer, char c)
+{
+ int i;
+
+ LOCK_LCD;
+ lcd_setAddr(layer, 0);
+ for (i = 0; i < LCD_COLS * LCD_ROWS; i++)
+ lcd_putCharUnlocked(c, layer);
+ UNLOCK_LCD;
+}
+
+
+void lcd_clear(Layer *layer)
+{
+ lcd_layerSet(layer, 0);
+}
+
+
+void lcd_clearLine(Layer *layer, int y)
+{
+ int i;
+
+ LOCK_LCD;
+ lcd_setAddr(layer, LCD_POS(0, y));
+ for (i = 0; i < LCD_COLS; i++)
+ lcd_putCharUnlocked(0, layer);
+ UNLOCK_LCD;
+}
+
+
+void lcd_moveCursor(lcdpos_t addr)
+{
+ LOCK_LCD;
+ lcd_moveTo(addr);
+ UNLOCK_LCD;
+}
+
+
+char lcd_setCursor(char mode)
+{
+ static const char cursor_cmd[3] =
+ {
+ LCD_CMD_CURSOR_OFF, LCD_CMD_CURSOR_BLOCK, LCD_CMD_CURSOR_LINE
+ };
+ char oldmode = lcd_CursorStatus;
+
+ LOCK_LCD;
+ lcd_CursorStatus = mode;
+ lcd_setReg(cursor_cmd[(int)mode]);
+ if (mode)
+ lcd_moveCursor(lcd_CursorAddr);
+ UNLOCK_LCD;
+
+ return oldmode;
+}
+
+
+int lcd_vprintf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, va_list ap)
+{
+ int len;
+
+ LOCK_LCD;
+
+ /*
+ * Se il cursore era acceso, spegnilo durante
+ * l'output per evitare che salti alla posizione
+ * in cui si scrive.
+ */
+ if (lcd_CursorStatus)
+ lcd_setReg(LCD_CMD_CURSOR_OFF);
+
+ /* Spostamento del cursore */
+ lcd_setAddr(layer, addr);
+
+ if (mode & LCD_CENTER)
+ {
+ int pad;
+
+ /*
+ * NOTE: calculating the string lenght BEFORE it gets
+ * printf()-formatted. Real lenght may differ.
+ */
+ pad = (LCD_COLS - strlen(format)) / 2;
+ while (pad--)
+ lcd_putCharUnlocked(' ', layer);
+ }
+
+ len = _formatted_write(format, (void (*)(char, void *))lcd_putCharUnlocked, layer, ap);
+
+ if (mode & (LCD_FILL | LCD_CENTER))
+ while (layer->addr % LCD_COLS)
+ lcd_putCharUnlocked(' ', layer);
+
+ /*
+ * Riaccendi il cursore e riportalo alla
+ * vecchia posizione
+ */
+ if (lcd_CursorStatus)
+ lcd_setCursor(lcd_CursorStatus);
+
+ UNLOCK_LCD;
+
+ return len;
+}
+
+
+int lcd_printf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, ...)
+{
+ int len;
+ va_list ap;
+
+ va_start(ap, format);
+ len = lcd_vprintf(layer, addr, mode, format, ap);
+ va_end(ap);
+
+ return len;
+}
+
+
+/*!
+ * Internal function to move a layer betweet two positions.
+ *
+ * \note The layer must be *already* enqueued in some list.
+ * \note The display must be already locked!
+ */
+static void lcd_enqueueLayer(Layer *layer, char pri)
+{
+ Layer *l2;
+
+ /* Remove layer from whatever list it was in before */
+ REMOVE(layer);
+
+ layer->pri = pri;
+
+ /*
+ * Search for the first layer whose priority
+ * is less or equal to the layer we are adding.
+ */
+ FOREACHNODE(l2, &lcd_Layers)
+ if (l2->pri <= pri)
+ break;
+
+ /* Enqueue layer */
+ INSERTBEFORE(layer, l2);
+}
+
+Layer *lcd_newLayer(char pri)
+{
+ Layer *layer;
+
+ LOCK_LCD;
+
+ if (ISLISTEMPTY(&lcd_FreeLayers))
+ {
+ UNLOCK_LCD;
+ //ASSERT(false);
+ return NULL;
+ }
+
+ layer = (Layer *)LIST_HEAD(&lcd_FreeLayers);
+ layer->addr = 0;
+ memset(layer->buf, 0, LCD_ROWS * LCD_COLS);
+
+ lcd_enqueueLayer(layer, pri);
+
+ UNLOCK_LCD;
+ return layer;
+}
+
+/*!
+ * Redraw the display (internal).
+ *
+ * \note The display must be already locked.
+ */
+static void lcd_refresh(void)
+{
+ lcdpos_t addr;
+ Layer *l;
+
+ for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
+ {
+ FOREACHNODE(l, &lcd_Layers)
+ {
+ //kprintf("%d %x %p\n", addr, l->buf[0], l);
+ if (l->pri == LAYER_HIDDEN)
+ break;
+
+ if (l->buf[addr])
+ {
+ /* Refresh location */
+ lcd_putc(addr, l->buf[addr]);
+ goto done;
+ }
+ }
+
+ /* Draw background */
+ lcd_putc(addr, ' ');
+ done:
+ ;
+ }
+}
+
+/*!
+ * Rearrange layer depth and refresh display accordingly.
+ *
+ * \note Setting a priority of LAYER_HIDDEN makes the layer invisible.
+ */
+void lcd_setLayerDepth(Layer *layer, char pri)
+{
+ if (pri != layer->pri)
+ {
+ LOCK_LCD;
+ lcd_enqueueLayer(layer, pri);
+ /* Vile but simple */
+ lcd_refresh();
+ UNLOCK_LCD;
+ }
+}
+
+void lcd_deleteLayer(Layer *layer)
+{
+ LOCK_LCD;
+
+/* We use lcd_refresh() instead. Much simpler than this mess, but slower. */
+#if 0
+ Layer *l2;
+ lcdpos_t addr;
+
+ /* Repair damage on underlaying layers */
+ for (addr = 0; addr < LCD_ROWS * LCD_COLS; ++addr)
+ {
+ /* If location was covered by us */
+ if (layer->buf[addr])
+ {
+ /* ...and it wasn't covered by others above us... */
+ for (l2 = layer->pred; l2->pred; l2 = l2->pred)
+ if (l2->buf[addr])
+ /* can't just break here! */
+ goto not_visible;
+
+ /* ...scan underlaying layers to repair damage */
+ for (l2 = layer->succ; l2->succ; l2 = l2->succ)
+ if (l2->buf[addr])
+ {
+ /* Refresh character */
+ lcd_putc(addr, l2->buf[addr]);
+
+ /* No need to search on deeper layers */
+ break;
+ }
+
+ not_visible:
+ ;
+ }
+ }
+#endif
+
+ // Remove layer from lcd_Layers list.
+ REMOVE(layer);
+
+ /* Put layer back into free list */
+ ADDHEAD(&lcd_FreeLayers, layer);
+
+ lcd_refresh();
+
+ UNLOCK_LCD;
+}
+
+
+static void lcd_setDefLayer(Layer *layer)
+{
+ lcd_DefLayer = layer;
+}
+
+#include <cfg/debug.h>
+void lcd_init(void)
+{
+ int i;
+
+ LIST_INIT(&lcd_Layers);
+ LIST_INIT(&lcd_FreeLayers);
+ for (i = 0; i < LCD_LAYERS; ++i)
+ ADDHEAD(&lcd_FreeLayers, &lcd_LayersPool[i]);
+
+ lcd_setDefLayer(lcd_newLayer(0));
+
+ lcd_hw_init();
+
+ lcd_setCursor(0);
+}
+
+#if CONFIG_TEST
+void lcd_test(void)
+{
+ int i;
+
+ for (i = 0; i < LCD_ROWS * LCD_COLS; ++i)
+ {
+ lcd_putCharUnlocked('0' + (i % 10), lcd_DefLayer);
+ timer_delay(100);
+ }
+}
+#endif /* CONFIG_TEST */
+
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief Generic text LCD driver (interface).
+ *
+ * \version $Id$
+ * \author Bernardo Innocenti <bernie@develer.com>
+ * \author Stefano Fedrigo <aleph@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 18:00:42 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.5 2005/06/14 14:43:43 bernie
+ *#* Add DevLib headers.
+ *#*
+ *#* Revision 1.4 2005/06/06 17:41:57 batt
+ *#* Add lcd_layerSet function.
+ *#*
+ *#* Revision 1.3 2005/06/01 10:36:23 batt
+ *#* Layer: Rename member variables and Doxygenize.
+ *#*
+ *#* Revision 1.2 2005/05/26 08:31:23 batt
+ *#* Add layer hiding/showing.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*/
+
+#ifndef DRV_LCD_H
+#define DRV_LCD_H
+
+#include "lcd_hd44.h"
+
+#include <cfg/macros.h>
+#include <cfg/compiler.h>
+#include <mware/list.h>
+
+#include <stdarg.h> // vprintf()
+
+
+/* flags for lcd_printf() */
+#define LCD_NORMAL 0 /* Scrittura normale */
+#define LCD_FILL BV(0) /* Fill rest of line with spaces */
+#define LCD_CENTER BV(1) /* Center string in line */
+#define LCD_NOCURSOR BV(2) /* Scrittura senza spostamento cursore */
+
+/*! Special priority value for lcd_setLayerDepth(). */
+#define LAYER_HIDDEN -128
+
+/* Compute LCD address from x/y coordinates */
+#define LCD_POS(x,y) ((lcdpos_t)((uint8_t)(x) + (uint8_t)(y) * (uint8_t)LCD_COLS))
+#define LCD_ROW0 (LCD_COLS * 0)
+#define LCD_ROW1 (LCD_COLS * 1)
+#define LCD_ROW2 (LCD_COLS * 2)
+#define LCD_ROW3 (LCD_COLS * 3)
+
+/*!
+ * Overwrapping layer context.
+ */
+typedef struct _Layer
+{
+ /*!
+ * Active layers are linked together in a list
+ * sorted in top to bottom order.
+ */
+ DECLARE_NODE_ANON(struct _Layer)
+
+ /*! Current XY address into this layer, for write operations. */
+ lcdpos_t addr;
+
+ /*! Priority of this layer (greater in front of lesser). */
+ char pri;
+
+ /*!
+ * Buffer per il salvataggio ed il ripristino del
+ * contenuto del display. Le posizioni che contengono
+ * il carattere '\0' sono trasparenti.
+ */
+ char buf[LCD_COLS * LCD_ROWS];
+} Layer;
+
+
+/* Global variables */
+extern Layer *lcd_DefLayer;
+
+/* Function prototypes */
+extern void lcd_init(void);
+extern void lcd_test(void);
+
+extern void lcd_moveCursor(lcdpos_t addr);
+extern char lcd_setCursor(char state);
+
+extern void lcd_setAddr(Layer *layer, lcdpos_t addr);
+extern void lcd_putChar(char c, Layer *layer);
+extern int lcd_vprintf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, va_list ap) FORMAT(printf, 4, 0);
+extern int lcd_printf(Layer *layer, lcdpos_t addr, uint8_t mode, const char *format, ...) FORMAT(printf, 4, 5);
+extern void lcd_clear(Layer *layer);
+extern void lcd_layerSet(Layer *layer, char c);
+extern void lcd_clearLine(Layer *layer, int y);
+
+extern void lcd_setLayerDepth(Layer *layer, char pri);
+extern Layer *lcd_newLayer(char pri);
+extern void lcd_deleteLayer(Layer *layer);
+extern void lcd_lock(void);
+extern void lcd_unlock(void);
+
+#endif /* DRV_LCD_H */
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2004, 2005 Develer S.r.l. (http://www.de+veler.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief Driver for NTC (reads a temperature through an ADC)
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ * \author Francesco Sacchi <batt@develer.com>
+ *
+ *
+ * This module handles an external NTC bound to an AD converter. As usual,
+ * it relies on a low-level API (ntc_hw_*) (see below):
+ *
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 17:59:47 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*/
+
+#include <drv/ntc.h>
+#include <hw_ntc.h>
+#include <ntc_map.h>
+
+#include <cfg/debug.h>
+
+DB(bool ntc_initialized;)
+
+/*!
+ * Find in a table of values \a orig_table of size \a size, the index which
+ * value is less or equal to \a val.
+ *
+ * \retval 0 When \a val is higher than the first table entry.
+ * \retval size When \a val is lower than the last table entry.
+ * \retval 1..size-1 When \a val is within the table.
+ */
+static size_t upper_bound(const res_t *orig_table, size_t size, res_t val)
+{
+ const res_t *table = orig_table;
+
+ while (size)
+ {
+ size_t pos = size / 2;
+ if (val > table[pos])
+ size = pos;
+ else
+ {
+ table += pos+1;
+ size -= pos+1;
+ }
+ }
+
+ return table - orig_table;
+}
+
+
+/*!
+ * Read the temperature for the NTC channel \a dev.
+ * First read the resistence of the NTC through ntc_hw_read(), then,
+ * for the conversion from resistance to temperature, since the formula
+ * varies from device to device, we implemented a generic system using
+ * a table of data which maps temperature (index) to resistance (data).
+ * The range of the table (min/max temperature) and the step
+ * (temperature difference between two consecutive elements of the table)
+ * is variable and can be specified. Notice that values inbetween the
+ * table elements are still possible as the library does a linear
+ * interpolation using the actual calculated resistance to find out
+ * the exact temperature.
+ *
+ * The low-level API provides a function to get access to a description
+ * of the NTC (ntc_hw_getInfo()), including the resistance table.
+ *
+ */
+deg_t ntc_read(NtcDev dev)
+{
+ const NtcHwInfo* hw = ntc_hw_getInfo(dev);
+ const res_t* r = hw->resistances;
+
+ float rx;
+ size_t i;
+ deg_t degrees;
+
+ rx = ntc_hw_read(dev);
+
+
+ i = upper_bound(r, hw->num_resistances, rx);
+ ASSERT(i <= hw->num_resistances);
+
+ if (i >= hw->num_resistances)
+ return NTC_SHORT_CIRCUIT;
+ else if (i == 0)
+ return NTC_OPEN_CIRCUIT;
+
+ /*
+ * Interpolated value in 0.1 degrees multiplied by 10:
+ * delta t step t
+ * ---------- = ----------------
+ * (rx - r[i]) (r[i-1] - r [i])
+ */
+ float tmp;
+ tmp = 10 * hw->degrees_step * (rx - r[i]) / (r[i - 1] - r[i]);
+
+ /*
+ * degrees = integer part corresponding to the superior index
+ * in the table multiplied by 10
+ * - decimal part interpolated (already multiplied by 10)
+ */
+ degrees = (i * hw->degrees_step + hw->degrees_min) * 10 - (int)(tmp);
+
+ //kprintf("dev= %d, I=%d, degrees = %d\n", dev, i , degrees);
+
+ return degrees;
+}
+
+
+/*!
+ * Init NTC hardware.
+ */
+void ntc_init(void)
+{
+ NTC_HW_INIT;
+ DB(ntc_initialized = true;)
+}
+
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2004, 2005 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief Driver for NTC (reads a temperature through an ADC)
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ * \author Francesco Sacchi <batt@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 17:59:47 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.3 2005/06/10 08:56:47 batt
+ *#* Avoid calling DEG_T_TO_DEG().
+ *#*
+ *#* Revision 1.2 2005/06/10 08:54:58 batt
+ *#* Rename deg_t conversion macros to accomplish coding standard.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*/
+
+#ifndef DRV_NTC_H
+#define DRV_NTC_H
+
+#include <ntc_map.h>
+#include <cfg/debug.h>
+#include <cfg/compiler.h>
+
+#define NTC_OPEN_CIRCUIT -32768
+#define NTC_SHORT_CIRCUIT 32767
+
+typedef int16_t deg_t; /*! type for celsius degrees deg_t = °C * 10 */
+
+/*! Macro for converting from deg to deg_t type */
+#define DEG_TO_DEG_T(x) ((deg_t)((x) * 10))
+
+/*! Macro for converting from deg_t to celsius degrees (returns only the integer part) */
+#define DEG_T_TO_INTDEG(x) ((x) / 10)
+
+/*! Macro for converting from deg_t to celsius degrees (returns only the decimal part) */
+#define DEG_T_TO_DECIMALDEG(x) ((x) % 10)
+
+/*! Macro for converting from deg_t to celsius degrees (returns type is float) */
+#define DEG_T_TO_FLOATDEG(x) ((x) / 10.0)
+
+
+typedef uint32_t res_t; /*! type for resistor res_t = Ohm * 100 */
+typedef float amp_t; /*! type for defining amplifications amp_t = A, where A is a pure number */
+
+DB(extern bool ntc_initialized;)
+
+
+/*! Describe a NTC chip */
+typedef struct NtcHwInfo
+{
+ const res_t *resistances; //!< resistances of the NTC (ohms * 100)
+ size_t num_resistances; //!< number of resistances
+ deg_t degrees_min; //!< degrees corresponding to the first entry in the table (celsius * 10)
+ deg_t degrees_step; //!< difference in degrees between two consecutive elements in the table (celsius * 10)
+} NtcHwInfo;
+
+/*! Initialize the NTC module */
+void ntc_init(void);
+
+/*! Read a single temperature value from the NTC */
+deg_t ntc_read(NtcDev dev);
+
+#endif /* DRV_NTC_H */
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief Thermo-control driver
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ * \author Francesco Sacchi <batt@develer.com>
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 17:59:47 bernie
+ *#* Import into DevLib.
+ *#*
+ *#*/
+#include <thermo_map.h>
+#include <hw_thermo.h>
+
+#include <drv/thermo.h>
+#include <drv/timer.h>
+#include <drv/ntc.h>
+
+#include <cfg/macros.h>
+#include <cfg/debug.h>
+
+
+/*! Interval at which thermo control is performed. */
+#define THERMO_INTERVAL_MS 100
+
+/*! Number of different samples we interpolate over to get the hifi temperature. */
+#define THERMO_HIFI_NUM_SAMPLES 10
+
+/*! Timer for thermo-regulation. */
+static Timer thermo_timer;
+
+typedef struct ThermoControlDev
+{
+ deg_t hifi_samples[THERMO_HIFI_NUM_SAMPLES];
+ deg_t cur_hifi_sample;
+ deg_t target;
+ thermostatus_t status;
+ ticks_t expire;
+} ThermoControlDev;
+
+/*! Array of thermo-devices. */
+ThermoControlDev devs[THERMO_CNT];
+
+
+/*!
+ * Return the status of the specific \a dev thermo-device.
+ */
+thermostatus_t thermo_status(ThermoDev dev)
+{
+ return devs[dev].status;
+}
+
+
+/*!
+ * Do a single thermo control for device \a dev.
+ */
+static void thermo_do(ThermoDev index)
+{
+ ThermoControlDev* dev = &devs[index];
+ deg_t cur_temp;
+ deg_t tolerance = thermo_hw_tolerance(index);
+
+ cur_temp = thermo_hw_read(index);
+
+ // Store the sample into the hifi FIFO buffer for later interpolation
+ dev->hifi_samples[dev->cur_hifi_sample] = cur_temp;
+ if (++dev->cur_hifi_sample == THERMO_HIFI_NUM_SAMPLES)
+ dev->cur_hifi_sample = 0;
+
+ cur_temp = thermo_read_temperature(index);
+
+ if (cur_temp == NTC_SHORT_CIRCUIT || cur_temp == NTC_OPEN_CIRCUIT)
+ {
+ if (cur_temp == NTC_SHORT_CIRCUIT)
+ {
+ #ifdef _DEBUG
+ if (!(dev->status & THERMOERRF_NTCSHORT))
+ kprintf("dev[%d], thermo_do: NTC_SHORT\n",index);
+ #endif
+ dev->status |= THERMOERRF_NTCSHORT;
+ }
+ else
+ {
+ #ifdef _DEBUG
+ if (!(dev->status & THERMOERRF_NTCOPEN))
+ kprintf("dev[%d], thermo_do: NTC_OPEN\n", index);
+ #endif
+ dev->status |= THERMOERRF_NTCOPEN;
+ }
+
+ /* Reset timeout when there is an ntc error */
+ dev->expire = thermo_hw_timeout(index) + timer_clock();
+ thermo_hw_off(index);
+ return;
+ }
+ dev->status &= ~(THERMOERRF_NTCOPEN | THERMOERRF_NTCSHORT);
+
+ if ((cur_temp < dev->target - tolerance) || (cur_temp > dev->target + tolerance))
+ {
+ dev->status &= ~THERMO_TGT_REACH;
+
+ /* Check for timeout */
+ if (timer_clock() - dev->expire > 0)
+ {
+ dev->status |= THERMOERRF_TIMEOUT;
+ kprintf("dev[%d], thermo_do: TIMEOUT\n", index);
+ }
+ }
+ else /* In target */
+ {
+ /* Clear errors */
+ dev->status &= ~THERMO_ERRMASK;
+ dev->status |= THERMO_TGT_REACH;
+
+ /* Reset timeout in case we go out of target in the future */
+ dev->expire = thermo_hw_timeout(index) + timer_clock();
+ }
+
+ if (cur_temp < dev->target)
+ dev->status = (dev->status | THERMO_HEATING) & ~THERMO_FREEZING;
+ else
+ dev->status = (dev->status & ~THERMO_HEATING) | THERMO_FREEZING;
+
+ thermo_hw_set(index, dev->target, cur_temp);
+
+}
+
+
+/*!
+ * Thermo soft interrupt.
+ */
+static void thermo_softint(void)
+{
+ int i;
+ for (i = 0; i < THERMO_CNT; ++i)
+ if (devs[i].status & THERMO_ACTIVE)
+ thermo_do((ThermoDev)i);
+
+ timer_add(&thermo_timer);
+}
+
+
+/*!
+ * Set the target temperature \a temperature for a specific \a dev thermo-device.
+ */
+void thermo_setTarget(ThermoDev dev, deg_t temperature)
+{
+ ASSERT(dev < THERMO_CNT);
+ devs[dev].target = temperature;
+ devs[dev].expire = timer_clock() + thermo_hw_timeout(dev);
+
+ kprintf("setTarget dev[%d], T[%d.%d]\n", dev, temperature / 10, temperature % 10);
+}
+
+/*!
+ * Starts a thermo-regulation for channel \a dev.
+ */
+void thermo_start(ThermoDev dev)
+{
+ int i;
+ deg_t temp;
+
+ ASSERT(dev < THERMO_CNT);
+
+ devs[dev].status |= THERMO_ACTIVE;
+
+ /* Initialize the hifi FIFO with a constant value (the current temperature) */
+ temp = thermo_hw_read(dev);
+ for (i = 0; i < THERMO_HIFI_NUM_SAMPLES; ++i)
+ devs[dev].hifi_samples[i] = temp;
+ devs[dev].cur_hifi_sample = 0;
+
+ /* Reset timeout */
+ devs[dev].expire = timer_clock() + thermo_hw_timeout(dev);
+}
+
+/*!
+ * Stops a thermo-regulation for channel \a dev.
+ */
+void thermo_stop(ThermoDev dev)
+{
+ ASSERT(dev < THERMO_CNT);
+
+ devs[dev].status &= ~THERMO_ACTIVE;
+ thermo_hw_off(dev);
+}
+
+
+/*!
+ * Clear errors for channel \a dev.
+ */
+void thermo_clearErrors(ThermoDev dev)
+{
+ ASSERT(dev < THERMO_CNT);
+ devs[dev].status &= ~(THERMO_ERRMASK);
+}
+
+
+/*!
+ * Read the temperature of the thermo-device \a dev using mobile mean.
+ */
+deg_t thermo_read_temperature(ThermoDev dev)
+{
+ int i;
+ long accum = 0;
+
+ for (i = 0; i < THERMO_HIFI_NUM_SAMPLES; i++)
+ accum += devs[dev].hifi_samples[i];
+
+ return (deg_t)(accum / THERMO_HIFI_NUM_SAMPLES);
+}
+
+
+/*!
+ * Init thermo-control and associated hw.
+ */
+void thermo_init(void)
+{
+ THERMO_HW_INIT;
+
+ /* Set all status to off */
+ for (int i = 0; i < THERMO_CNT; i++)
+ devs[i].status = THERMO_OFF;
+
+ timer_setDelay(&thermo_timer, ms_to_ticks(THERMO_INTERVAL_MS));
+ timer_set_event_softint(&thermo_timer, (Hook)thermo_softint, 0);
+ timer_add(&thermo_timer);
+}
--- /dev/null
+/*!
+ * \file
+ * <!--
+ * Copyright 2004, 2005 Develer S.r.l. (http://www.develer.com/)
+ * This file is part of DevLib - See README.devlib for information.
+ * -->
+ *
+ * \brief Thermo-control driver
+ *
+ * \version $Id$
+ *
+ * \author Giovanni Bajo <rasky@develer.com>
+ * \author Francesco Sacchi <batt@develer.com>
+ *
+ * This module implements multiple thermo controls, which is the logic needed to try
+ * keeping the temperature of a device constant. For this module, a "device" is a black box
+ * whose temperature can be measured, and which has a mean to make it hotter or colder.
+ * For instance, a device could be the combination of a NTC (analog temperature reader) and
+ * a Peltier connected to the same physic block.
+ *
+ * This module relies on a low-level driver to communicate with the device (implementation
+ * of the black box). This low-level driver also controls the units in which the temperature
+ * is expressed: thermo control treats it just as a number.
+ *
+ */
+
+/*#*
+ *#* $Log$
+ *#* Revision 1.1 2005/11/04 17:59:47 bernie
+ *#* Import into DevLib.
+ *#*
+ *#* Revision 1.2 2005/06/14 10:13:36 batt
+ *#* Better thermo errors handling.
+ *#*
+ *#* Revision 1.1 2005/05/24 09:17:58 batt
+ *#* Move drivers to top-level.
+ *#*
+ *#* Revision 1.4 2005/05/10 16:55:10 batt
+ *#* Add timeout to thermo-regulator; better thermo control handling; change thermo_getStatus() to thermo_status().
+ *#*
+ *#* Revision 1.3 2005/05/10 09:26:54 batt
+ *#* Add thermo_getStatus for getting status/errors of thermo control.
+ *#*
+ *#* Revision 1.2 2005/05/09 19:18:40 batt
+ *#* Remove old logs.
+ *#*
+ *#* Revision 1.1 2005/05/09 16:40:44 batt
+ *#* Add thermo-control driver
+ *#*/
+
+
+#ifndef DRV_THERMO_H
+#define DRV_THERMO_H
+
+#include <drv/ntc.h>
+#include <thermo_map.h>
+
+void thermo_init(void);
+
+
+/*!
+ * Set the target temperature at which a given device should be kept.
+ *
+ * \param dev Device
+ * \param temperature Target temperature
+ */
+void thermo_setTarget(ThermoDev dev, deg_t temperature);
+
+/*! Start thermo control for a certain device \a dev */
+void thermo_start(ThermoDev dev);
+
+/*! Stop thermo control for a certain device \a dev */
+void thermo_stop(ThermoDev dev);
+
+/*! Clear errors for channel \a dev */
+void thermo_clearErrors(ThermoDev dev);
+
+/*! Return the status of the specific \a dev thermo-device. */
+thermostatus_t thermo_status(ThermoDev dev);
+
+/*!
+ * Return the current temperature of a device currently under thermo
+ * control.
+ *
+ * \param dev Device
+ * \return Current temperature (Celsius degrees * 10)
+ */
+deg_t thermo_read_temperature(ThermoDev dev);
+
+
+#endif /* DRV_THERMO_H */