Last active
October 21, 2019 13:44
-
-
Save bradleypeabody/56080ea360a5d5fe4e7a6dce4b9c3a9f to your computer and use it in GitHub Desktop.
HD44780 (or compatible) LCD driver in C for Parallax Propeller, hacked together from PropWare's implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// WHY THIS MONSTROSITY EXISTS: | |
// PropWare has an excellent HD44780 LCD driver written in C++. | |
// I wanted something that was simpler, without the dependencies, and 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