Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
HD44780 (or compatible) LCD driver in C for Parallax Propeller, hacked together from PropWare's implementation
// WHY THIS MONSTROSITY EXISTS:
// PropWare has an excellent HD44780 LCD driver written in C++.
// I wanted something that was simpler, without the dependencies, an in C.
// So I took the basic implementation from PropWare and threw this
// together. I removed 8-bit support (4-bit mode is usually all you need) and
// it assumes 16x2 (although that is easy to change - see HD44780_init()).
// This is actually 3 files, you can see the big separator below.
// First is the header, then the library C code, then a demo program.
// You can just copy and paste what you need right
// into SimpleIDE or other Propeller C build setup and it will
// basically "just work". I'm saving it here as a gist because I've
// had to hack together an HD44780 driver in C at least 3 times now
// and I'm tired of making the same thing over and over; and too lazy
// to clean up all nice and put it as it's own project
// on GitHub or OBEX.
// May your LCD output be bright and merry!
/////////////////////////////////////////////////////////////////////////
// HD44780.h
// QUICK AND DIRTY PORT OF PropWare/hmi/output/hd44780.h to C (from C++) by Brad Peabody
#ifndef __HD44780_H__
#define __HD44780_H__
/**
* @name Commands
* @note Must be combined with arguments below to create a parameter
* for the HD44780
*/
#define HD44780_CLEAR (1 << 0)
#define HD44780_RET_HOME (1 << 1)
#define HD44780_ENTRY_MODE_SET (1 << 2)
#define HD44780_DISPLAY_CTRL (1 << 3)
#define HD44780_SHIFT (1 << 4)
#define HD44780_FUNCTION_SET (1 << 5)
#define HD44780_SET_CGRAM_ADDR (1 << 6)
#define HD44780_SET_DDRAM_ADDR (1 << 7)
/**@}*/
/**
* @name Entry mode arguments
* @{
*/
#define HD44780_SHIFT_INC (1 << 1)
#define HD44780_SHIFT_EN (1 << 0)
/**@}*/
/**
* @name Display control arguments
* @{
*/
#define HD44780_DISPLAY_PWR (1 << 2)
#define HD44780_CURSOR (1 << 1)
#define HD44780_BLINK (1 << 0)
/**@}*/
/**
* @name Cursor/display shift arguments
* @{
*/
#define HD44780_SHIFT_DISPLAY (1 << 3) // 0 = shift cursor
#define HD44780_SHIFT_RIGHT (1 << 2) // 0 = shift left
/**@}*/
/**
* @name Function set arguments
* @{
*/
/**
* 0 = 4-bit mode
*/
// #define HD44780_FUNC_8BIT_MODE (1 << 4)
/**
* 0 = "1-line" mode - use 2-line mode for 2- and 4-line displays
*/
#define HD44780_FUNC_2LINE_MODE (1 << 3)
/**
* 0 = 5x8 dot mode
*/
#define HD44780_FUNC_5x10_CHAR (1 << 2)
/**@}*/
#define HD44780_MILLISECOND ((uint32_t) (CLKFREQ / 1000))
#define HD44780_MICROSECOND ((uint32_t) (HD44780_MILLISECOND / 1000))
#define HD44780_TAB_WIDTH 4
typedef struct {
uint8_t m_rs;
uint8_t m_rw;
uint8_t m_en;
uint8_t m_dataPinStart;
uint8_t m_curPos_row;
uint8_t m_curPos_col;
/** How many characters can be displayed on a single row */
uint8_t m_memMap_charRows;
/** How many characters can be displayed in a single column */
uint8_t m_memMap_charColumns;
/**
* How many contiguous bytes of memory per visible character row
*/
uint8_t m_memMap_ddramCharRowBreak;
/** Last byte of memory used in each DDRAM line */
uint8_t m_memMap_ddramLineEnd;
} HD44780;
void HD44780_init(HD44780 *this, uint8_t lsbDataPin, uint8_t rs, uint8_t rw, uint8_t en);
void HD44780_start(HD44780 *this);
void HD44780_clear(HD44780 *this);
void HD44780_move(HD44780 *this, const uint8_t row, const uint8_t col);
void HD44780_puts(HD44780 *this, const char string[]);
void HD44780_put_char(HD44780 *this, const char c);
void HD44780_cmd(HD44780 *this, const uint8_t command);
void HD44780_write(HD44780 *this, const uint8_t val);
void HD44780_clock_pulse(HD44780 *this);
#endif
/////////////////////////////////////////////////////////////////////////
// HD44780.c
// QUICK AND DIRTY PORT OF PropWare/hmi/output/hd44780.h to C (from C++) by Brad Peabody
/**
* @file PropWare/hmi/output/hd44780.h
*
* @author David Zemon
* @author Collin Winans
*
* @copyright
* The MIT License (MIT)<br>
* <br>Copyright (c) 2013 David Zemon<br>
* <br>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:<br>
* <br>The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.<br>
* <br>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.
*/
#include <simpletools.h>
#include "HD44780.h"
/************************
*** Public Functions ***
************************/
/**
* @brief Construct an LCD object
*
* @param[in] lsbDataPin Pin mask for the least significant pin of the data port
* @param[in] rs Pin mask connected to the `register select` control pin of the LCD driver
* @param[in] rw Pin mask connected to the `read/write` control pin of the LCD driver
* @param[in] en Pin mask connected to the `enable` control pin of the LCD driver
* @param[in] bitMode Select between whether the parallel bus is using 4 or 8 pins
* @param[in] dimensions Dimensions of your LCD device. Most common is HD44780::DIM_16x2
*/
void HD44780_init(HD44780 *this, uint8_t lsbDataPin, uint8_t rs, uint8_t rw, uint8_t en) {
this->m_dataPinStart = lsbDataPin;
set_directions(this->m_dataPinStart, this->m_dataPinStart+3, 0xF); // data pins to output
set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, 0x0); // data pins low
this->m_rs = rs;
set_direction(this->m_rs, 1);
low(this->m_rs);
this->m_rw = rw;
set_direction(this->m_rw, 1);
low(this->m_rw);
this->m_en = en;
set_direction(this->m_en, 1);
low(this->m_en);
this->m_curPos_row = 0;
this->m_curPos_col = 0;
// Save the modes
this->m_memMap_charRows = 2;
this->m_memMap_charColumns = 16;
this->m_memMap_ddramCharRowBreak = 16;
this->m_memMap_ddramLineEnd = 16;
}
/**
* @brief Initialize an HD44780 LCD display
*
* @note A 250 ms delay is called while the LCD does internal
* initialization
*
* @return Returns 0 upon success, otherwise error code
*/
void HD44780_start(HD44780 *this) {
uint8_t arg;
// Wait for a couple years until the LCD has finished internal initialization
waitcnt(250 * HD44780_MILLISECOND + CNT);
// Begin init routine:
// if (BusWidth::WIDTH8 == this->m_bitMode)
// arg = 0x30;
// else
// /* Implied: "if (HD44780::WIDTH4 == this->m_bitMode)" */
// 4-bit mode
arg = 0x3;
set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, arg);
HD44780_clock_pulse(this);
waitcnt(100 * HD44780_MILLISECOND + CNT);
HD44780_clock_pulse(this);
waitcnt(100 * HD44780_MILLISECOND + CNT);
HD44780_clock_pulse(this);
waitcnt(10 * HD44780_MILLISECOND + CNT);
// if (BusWidth::WIDTH4 == this->m_bitMode) {
set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, 0x2);
HD44780_clock_pulse(this);
// }
// Default functions during initialization
arg = HD44780_FUNCTION_SET;
// if (BusWidth::WIDTH8 == this->m_bitMode)
// arg |= HD44780_FUNC_8BIT_MODE;
arg |= HD44780_FUNC_2LINE_MODE;
HD44780_cmd(this, arg);
// Turn off display shift (set cursor shift) and leave default of
// shift-left
arg = HD44780_SHIFT;
HD44780_cmd(this, arg);
// Turn the display on; Leave cursor off and not blinking
arg = HD44780_DISPLAY_CTRL
| HD44780_DISPLAY_PWR;
HD44780_cmd(this, arg);
// Set cursor to auto-increment upon writing a character
arg = HD44780_ENTRY_MODE_SET
| HD44780_SHIFT_INC;
HD44780_cmd(this, arg);
HD44780_clear(this);
}
/**
* @brief Clear the LCD display and return cursor to home
*/
void HD44780_clear(HD44780 *this) {
HD44780_cmd(this, HD44780_CLEAR);
this->m_curPos_row = 0;
this->m_curPos_col = 0;
waitcnt(1530 * HD44780_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 HD44780_move(HD44780 *this, const uint8_t row, const uint8_t col) {
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;
}
HD44780_cmd(this, addr | HD44780_SET_DDRAM_ADDR);
this->m_curPos_row = row;
this->m_curPos_col = col;
}
void HD44780_puts(HD44780 *this, const char string[]) {
const char *s = (char *) string;
while (*s) {
HD44780_put_char(this, *s);
++s;
}
}
void HD44780_put_char(HD44780 *this, 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;
HD44780_move(this, this->m_curPos_row, this->m_curPos_col);
} else if ('\t' == c) {
do {
HD44780_put_char(this, ' ');
} while (this->m_curPos_col % HD44780_TAB_WIDTH);
} else if ('\r' == c)
HD44780_move(this, this->m_curPos_row, 0);
// And for everything else...
else {
//set RS to data and RW to write
high(this->m_rs);
HD44780_write(this, (const uint8_t) c);
// Insert a line wrap if necessary
++this->m_curPos_col;
if (this->m_memMap_charColumns == this->m_curPos_col)
HD44780_put_char(this, '\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)
HD44780_move(this, 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 HD44780_cmd(HD44780 *this, const uint8_t command) {
//set RS to command mode and RW to write
low(this->m_rs);
HD44780_write(this, command);
}
/***************************
*** Protected Functions ***
***************************/
/**
* @brief Write a single byte to the LCD - instruction or data
*
* @param[in] val Value to be written
*/
void HD44780_write(HD44780 *this, const uint8_t val) {
// Clear RW to signal write value
low(this->m_rw);
// if (BusWidth::WIDTH4 == this->m_bitMode) {
// shift out the high nibble
set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, val >> 4);
HD44780_clock_pulse(this);
// Shift out low nibble
set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, val);
// }
// Shift remaining four bits out
// else /* Implied: if (HD44780::8BIT == this->m_bitMode) */{
// set_outputs(this->m_dataPinStart, this->m_dataPinStart+3, val);
// }
HD44780_clock_pulse(this);
}
/**
* @brief Toggle the enable pin, inducing a write to the LCD's register
*/
void HD44780_clock_pulse(HD44780 *this) {
high(this->m_en);
waitcnt(HD44780_MILLISECOND + CNT);
low(this->m_en);
}
/////////////////////////////////////////////////////////////////////////
// LCDDemo.c - example of usage
#include <simpletools.h>
#include "HD44780.h"
int main(void)
{
print("Starting LCD...\n");
HD44780 lcd;
HD44780_init(&lcd,
7, // data start
4, // rs
6, // rw
5 // en
);
HD44780_start(&lcd);
HD44780_move(&lcd, 0, 0);
while (1) {
pause(2000);
HD44780_clear(&lcd);
pause(2000);
HD44780_move(&lcd, 0, 0);
HD44780_puts(&lcd, "Oh crap it works");
HD44780_move(&lcd, 1, 0);
HD44780_puts(&lcd, "Yes indeed itduz");
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.