/**
* @file PropWare/hd44780.h
*
* @author David Zemon
* @author Collin Winans
*
* @copyright
* The MIT License (MIT)
*
Copyright (c) 2013 David Zemon
*
Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include
#include
#include
#include
#include
namespace PropWare {
/**
* @brief Support for the common "character LCD" modules using the HD44780
* controller for the Parallax Propeller
*
* @note Does not natively support 40x4 or 24x4 character displays
*/
class HD44780 : public PrintCapable {
public:
/**
* @brief LCD databus width
*/
typedef enum {
/** 4-bit mode */BM_4 = 4,
/** 8-bit mode */BM_8 = 8,
} Bitmode;
/**
* @brief Supported LCD dimensions; Used for determining cursor placement
*
* @note There are two variations of 16x1 character LCDs; if you're
* unsure which version you have, try 16x1_1 first, it is more
* common. 16x1_1 uses both DDRAM lines of the controller,
* 8-characters on each line; 16x1_2 places all 16 characters
* are a single line of DDRAM.
*/
typedef enum {
/** 8x1 */ DIM_8x1,
/** 8x2 */ DIM_8x2,
/** 8x2 */ DIM_8x4,
/** 16x1 mode 1 */DIM_16x1_1,
/** 16x1 mode 2 */DIM_16x1_2,
/** 16x2 */ DIM_16x2,
/** 16x2 */ DIM_16x4,
/** 20x1 */ DIM_20x1,
/** 20x2 */ DIM_20x2,
/** 20x2 */ DIM_20x4,
/** 24x1 */ DIM_24x1,
/** 24x2 */ DIM_24x2,
/** 40x1 */ DIM_40x1,
/** 40x2 */ DIM_40x2,
} Dimensions;
/** Number of allocated error codes for HD44780 */
#define HD44780_ERRORS_LIMIT 16
/** First HD44780 error code */
#define HD44780_ERRORS_BASE 48
/**
* Error codes - Proceeded by SD, SPI
*/
typedef enum {
/** No error */ NO_ERROR = 0,
/** First HD44780 error */BEG_ERROR = HD44780_ERRORS_BASE,
/** HD44780 Error 0 */ INVALID_CTRL_SGNL = HD44780::BEG_ERROR,
/** HD44780 Error 1 */ INVALID_DIMENSIONS,
/** Last HD44780 error */END_ERROR = HD44780::INVALID_DIMENSIONS
} ErrorCode;
protected:
/**
* Store metadata on the LCD device to determine when line-wraps should
* and shouldn't occur
*/
typedef struct {
/** How many characters can be displayed on a single row */
uint8_t charRows;
/** How many characters can be displayed in a single column */
uint8_t charColumns;
/**
* How many contiguous bytes of memory per visible character row
*/
uint8_t ddramCharRowBreak;
/** Last byte of memory used in each DDRAM line */
uint8_t ddramLineEnd;
} MemMap;
public:
/** Number of spaces inserted for '\\t' */
static const uint8_t TAB_WIDTH = 4;
/**
* @name Commands
* @note Must be combined with arguments below to create a parameter
* for the HD44780
*/
static const uint8_t CLEAR = BIT_0;
static const uint8_t RET_HOME = BIT_1;
static const uint8_t ENTRY_MODE_SET = BIT_2;
static const uint8_t DISPLAY_CTRL = BIT_3;
static const uint8_t SHIFT = BIT_4;
static const uint8_t FUNCTION_SET = BIT_5;
static const uint8_t SET_CGRAM_ADDR = BIT_6;
static const uint8_t SET_DDRAM_ADDR = BIT_7;
/**@}*/
/**
* @name Entry mode arguments
* @{
*/
static const uint8_t SHIFT_INC = BIT_1;
static const uint8_t SHIFT_EN = BIT_0;
/**@}*/
/**
* @name Display control arguments
* @{
*/
static const uint8_t DISPLAY_PWR = BIT_2;
static const uint8_t CURSOR = BIT_1;
static const uint8_t BLINK = BIT_0;
/**@}*/
/**
* @name Cursor/display shift arguments
* @{
*/
static const uint8_t SHIFT_DISPLAY = BIT_3; // 0 = shift cursor
static const uint8_t SHIFT_RIGHT = BIT_2; // 0 = shift left
/**@}*/
/**
* @name Function set arguments
* @{
*/
static const uint8_t FUNC_8BIT_MODE = BIT_4; // 0 = 4-bit mode
static const uint8_t FUNC_2LINE_MODE = BIT_3; // 0 = "1-line" mode - use 2-line mode for 2- and 4-line displays
static const uint8_t FUNC_5x10_CHAR = BIT_2; // 0 = 5x8 dot mode
/**@}*/
public:
/************************
*** Public Functions ***
************************/
HD44780 () {
this->m_curPos = &(this->m_bogus);
this->m_curPos->row = 0;
this->m_curPos->col = 0;
}
/**
* @brief Initialize an HD44780 LCD display
*
* @note A 250 ms delay is called while the LCD does internal
* initialization
*
* @param[in] lsbDataPin Pin mask for the least significant pin of the data port
* @param[in] rs, rw, en PropWare::Pin::Mask instances for each of the RS, RW, and EN signals
* @param[in] bitmode Select between HD44780::BM_4 and HD44780::BM_8 modes to determine whether you
* will need 4 data wires or 8 between the Propeller and your LCD device
* @param[in] dimensions Dimensions of your LCD device. Most common is HD44780::DIM_16x2
*
* @return Returns 0 upon success, otherwise error code
*/
void start (const PropWare::Pin::Mask lsbDataPin, const Pin rs, const Pin rw, const Pin en,
const HD44780::Bitmode bitmode, const HD44780::Dimensions dimensions) {
uint8_t arg;
// Wait for a couple years until the LCD has done internal initialization
waitcnt(250 * MILLISECOND + CNT);
// Save all control signal pin masks
this->m_rs = rs;
this->m_rw = rw;
this->m_en = en;
this->m_rs.set_dir(PropWare::Pin::OUT);
this->m_rw.set_dir(PropWare::Pin::OUT);
this->m_en.set_dir(PropWare::Pin::OUT);
this->m_rs.clear();
this->m_rw.clear();
this->m_en.clear();
// Save data port
this->m_dataPort.set_mask(lsbDataPin, bitmode);
this->m_dataPort.set_dir(PropWare::Pin::OUT);
// Save the modes
this->generate_mem_map(dimensions);
this->m_bitmode = bitmode;
// Begin init routine:
if (HD44780::BM_8 == bitmode)
arg = 0x30;
else
/* Implied: "if (HD44780::BM_4 == bitmode)" */
arg = 0x3;
this->m_dataPort.write(arg);
this->clock_pulse();
waitcnt(100 * MILLISECOND + CNT);
this->clock_pulse();
waitcnt(100 * MILLISECOND + CNT);
this->clock_pulse();
waitcnt(10 * MILLISECOND + CNT);
if (PropWare::HD44780::BM_4 == bitmode) {
this->m_dataPort.write(0x2);
this->clock_pulse();
}
// Default functions during initialization
arg = PropWare::HD44780::FUNCTION_SET;
if (PropWare::HD44780::BM_8 == bitmode)
arg |= PropWare::HD44780::FUNC_8BIT_MODE;
arg |= PropWare::HD44780::FUNC_2LINE_MODE;
this->cmd(arg);
// Turn off display shift (set cursor shift) and leave default of
// shift-left
arg = PropWare::HD44780::SHIFT;
this->cmd(arg);
// Turn the display on; Leave cursor off and not blinking
arg = PropWare::HD44780::DISPLAY_CTRL
| PropWare::HD44780::DISPLAY_PWR;
this->cmd(arg);
// Set cursor to auto-increment upon writing a character
arg = PropWare::HD44780::ENTRY_MODE_SET
| PropWare::HD44780::SHIFT_INC;
this->cmd(arg);
this->clear();
}
/**
* @brief Clear the LCD display and return cursor to home
*/
void clear (void) {
this->cmd(PropWare::HD44780::CLEAR);
this->m_curPos->row = 0;
this->m_curPos->col = 0;
waitcnt(1530 * MICROSECOND + CNT);
}
/**
* @brief Move the cursor to a specified column and row
*
* @param[in] row Zero-indexed row to place the cursor
* @param[in] col Zero indexed column to place the cursor
*/
void move (const uint8_t row, const uint8_t col) const {
uint8_t ddramLine, addr = 0;
// Handle weird special case where a single row LCD is split across
// multiple DDRAM lines (i.e., 16x1 type 1)
if (this->m_memMap.ddramCharRowBreak > this->m_memMap.ddramLineEnd) {
ddramLine = col / this->m_memMap.ddramLineEnd;
if (ddramLine)
addr = 0x40;
addr |= col % this->m_memMap.ddramLineEnd;
} else if (4 == this->m_memMap.charRows) {
// Determine DDRAM line
if (row % 2)
addr = 0x40;
if (row / 2)
addr += this->m_memMap.ddramCharRowBreak;
addr += col % this->m_memMap.ddramCharRowBreak;
} else /* implied: "if (2 == memMap.charRows)" */{
if (row)
addr = 0x40;
addr |= col;
}
this->cmd(addr | PropWare::HD44780::SET_DDRAM_ADDR);
this->m_curPos->row = row;
this->m_curPos->col = col;
}
void puts (const char string[]) {
const char *s = (char *) string;
while (*s) {
this->put_char(*s);
++s;
}
}
void put_char (const char c) {
// For manual new-line characters...
if ('\n' == c) {
this->m_curPos->row++;
if (this->m_curPos->row == this->m_memMap.charRows)
this->m_curPos->row = 0;
this->m_curPos->col = 0;
this->move(this->m_curPos->row, this->m_curPos->col);
} else if ('\t' == c) {
do {
this->put_char(' ');
} while (this->m_curPos->col % PropWare::HD44780::TAB_WIDTH);
} else if ('\r' == c)
this->move(this->m_curPos->row, 0);
// And for everything else...
else {
//set RS to data and RW to write
this->m_rs.set();
this->write((const uint8_t) c);
// Insert a line wrap if necessary
++this->m_curPos->col;
if (this->m_memMap.charColumns == this->m_curPos->col)
this->put_char('\n');
// Handle weird special case where a single row LCD is split
// across multiple DDRAM lines (i.e., 16x1 type 1)
if (this->m_memMap.ddramCharRowBreak
> this->m_memMap.ddramLineEnd)
this->move(this->m_curPos->row, this->m_curPos->col);
}
}
/**
* @brief Send a control command to the LCD module
*
* @param[in] command 8-bit command to send to the LCD
*/
void cmd (const uint8_t command) const {
//set RS to command mode and RW to write
this->m_rs.clear();
this->write(command);
}
static void print_error_str (const Printer *printer, const HD44780::ErrorCode err) {
char str[] = "HD44780 Error %u: %s\n";
switch (err) {
case PropWare::HD44780::INVALID_CTRL_SGNL:
printer->printf(str, err - PropWare::HD44780::BEG_ERROR, "invalid control signal");
break;
case PropWare::HD44780::INVALID_DIMENSIONS:
printer->printf(str, err - PropWare::HD44780::BEG_ERROR,
"invalid LCD dimension; please choose from the HD44780::Dimensions type");
break;
default:
break;
}
}
protected:
/***************************
*** Protected Functions ***
***************************/
/**
* @brief Write a single byte to the LCD - instruction or data
*
* @param[in] val Value to be written
*/
void write (const uint8_t val) const {
// Clear RW to signal write value
this->m_rw.clear();
if (PropWare::HD44780::BM_4 == this->m_bitmode) {
// shift out the high nibble
this->m_dataPort.write(val >> 4);
this->clock_pulse();
// Shift out low nibble
this->m_dataPort.write(val);
}
// Shift remaining four bits out
else /* Implied: if (HD44780::8BIT == this->m_bitmode) */{
this->m_dataPort.write(val);
}
this->clock_pulse();
}
/**
* @brief Toggle the enable pin, inducing a write to the LCD's
* register
*/
void clock_pulse (void) const {
this->m_en.set();
waitcnt(MILLISECOND + CNT);
this->m_en.clear();
}
/**
* @brief The memory map is used to determine where line wraps should
* and shouldn't occur
*/
void generate_mem_map (const HD44780::Dimensions dimensions) {
// TODO: Make this a look-up table instead of a switch-case
switch (dimensions) {
case PropWare::HD44780::DIM_8x1:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 8;
this->m_memMap.ddramCharRowBreak = 8;
this->m_memMap.ddramLineEnd = 8;
break;
case PropWare::HD44780::DIM_8x2:
this->m_memMap.charRows = 2;
this->m_memMap.charColumns = 8;
this->m_memMap.ddramCharRowBreak = 8;
this->m_memMap.ddramLineEnd = 8;
break;
case PropWare::HD44780::DIM_8x4:
this->m_memMap.charRows = 4;
this->m_memMap.charColumns = 8;
this->m_memMap.ddramCharRowBreak = 8;
this->m_memMap.ddramLineEnd = 16;
break;
case PropWare::HD44780::DIM_16x1_1:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 16;
this->m_memMap.ddramCharRowBreak = 8;
this->m_memMap.ddramLineEnd = 8;
break;
case PropWare::HD44780::DIM_16x1_2:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 16;
this->m_memMap.ddramCharRowBreak = 16;
this->m_memMap.ddramLineEnd = 16;
break;
case PropWare::HD44780::DIM_16x2:
this->m_memMap.charRows = 2;
this->m_memMap.charColumns = 16;
this->m_memMap.ddramCharRowBreak = 16;
this->m_memMap.ddramLineEnd = 16;
break;
case PropWare::HD44780::DIM_16x4:
this->m_memMap.charRows = 4;
this->m_memMap.charColumns = 16;
this->m_memMap.ddramCharRowBreak = 16;
this->m_memMap.ddramLineEnd = 32;
break;
case PropWare::HD44780::DIM_20x1:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 20;
this->m_memMap.ddramCharRowBreak = 20;
this->m_memMap.ddramLineEnd = 20;
break;
case PropWare::HD44780::DIM_20x2:
this->m_memMap.charRows = 2;
this->m_memMap.charColumns = 20;
this->m_memMap.ddramCharRowBreak = 20;
this->m_memMap.ddramLineEnd = 20;
break;
case PropWare::HD44780::DIM_20x4:
this->m_memMap.charRows = 4;
this->m_memMap.charColumns = 20;
this->m_memMap.ddramCharRowBreak = 20;
this->m_memMap.ddramLineEnd = 40;
break;
case PropWare::HD44780::DIM_24x1:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 24;
this->m_memMap.ddramCharRowBreak = 24;
this->m_memMap.ddramLineEnd = 24;
break;
case PropWare::HD44780::DIM_24x2:
this->m_memMap.charRows = 2;
this->m_memMap.charColumns = 24;
this->m_memMap.ddramCharRowBreak = 24;
this->m_memMap.ddramLineEnd = 24;
break;
case PropWare::HD44780::DIM_40x1:
this->m_memMap.charRows = 1;
this->m_memMap.charColumns = 40;
this->m_memMap.ddramCharRowBreak = 40;
this->m_memMap.ddramLineEnd = 40;
break;
case PropWare::HD44780::DIM_40x2:
this->m_memMap.charRows = 2;
this->m_memMap.charColumns = 40;
this->m_memMap.ddramCharRowBreak = 40;
this->m_memMap.ddramLineEnd = 40;
break;
}
}
private:
typedef struct {
uint8_t row;
uint8_t col;
} Position;
protected:
HD44780::MemMap m_memMap;
private:
// Horrible bad hack so that methods can be const
Position m_bogus;
Position *m_curPos;
Pin m_rs, m_rw, m_en;
SimplePort m_dataPort;
HD44780::Bitmode m_bitmode;
};
}