Files
Arduino/libraries/hd44780/hd44780.cpp
Jérôme Delacotte 7b30d6e298 first commit
2025-03-06 11:15:32 +01:00

971 lines
34 KiB
C++

// vi:ts=4
// -----------------------------------------------------------------------
// hd44780.cpp - hd44780 base class
// Copyright (c) 2014-2020 Bill Perry
// (Derivative work of arduino.cc IDE LiquidCrystal library)
// Note:
// Original Copyrights for LiquidCrystal are a mess as originally none were
// specified, but in Nov 2015 these have appeared so they are included:
//
// Copyright (C) 2006-2008 Hans-Christoph Steiner. All rights reserved.
// Copyright (c) 2010 Arduino LLC. All right reserved.
//
// See licenseInfo.txt for a history of the copyrights of LiquidCrystal
// ------------------------------------------------------------------------
//
// This file is part of the hd44780 library
//
// hd44780 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation version 3 of the License.
//
// hd44780 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with hd44780. If not, see <http://www.gnu.org/licenses/>.
//
// See the license.txt file for further licensing & copyright details.
// -----------------------------------------------------------------------
//
// hd44780 is an extensible LCD library for hd44780 based LCD displays.
// The library consists of a hd44780 base class combined with one or more
// i/o subclasses to perform the i/o communication between the host and the
// hd44780 display interface.
//
// The API functionality provided by the hd44780 base class, when combined
// with an hd44780 library i/o subclass, is compatible with the API
// functionality of the Arduino LiquidCrystal library as well as compatibilty
// with most of the LCD API 1.0 Specification (nearly obsolete).
//
// The hd44780 API also provides some addtional extensions and all the API
// functions provided by hd44780 are common across all i/o subclasses.
//
// -----------------------------------------------------------------------
// History
//
// 2020-11-14 bperrybap - fixed timing issue in begin() on VERY fast processors like ESP using pinIO
// 2019.08.11 bperrybap - updates for reinitalization using begin() & init() and use of "new"
// 2019.05.30 bperrybap - updates to support use of "new" for lcd objects
// 2019.02.03 bperrybap - fatalError(errcode), accept negative errcode values
// 2017.12.23 bperrybap - added LCD API 1.0 init() function
// 2017.05.11 bperrybap - setCursor() wraps when auto linewrap enabled
// and col beyond end of line
// 2017.05.11 bperrybap - linewrap tweak for better visual cursor position
// 2017.05.11 bperrybap - added auto linewrap functionality
// 2017.01.07 bperrybap - added blinkLED() and fatalError()
// 2016.12.26 bperrybap - new constructors
// 2016.10.17 bperrybap - corrected DDRAM address mask in createChar()
// 2016.10.15 bperrybap - createChar() restores DDRAM location when possible
// 2016.09.08 bperrybap - changed param order of iowrite() to match ioread()
// 2016.08.06 bperrybap - changed iosend() to iowrite()
// 2016.08.06 bperrybap - added status() and read()
// 2016.07.27 bperrybap - added return status for command() and iosend()
// 2016.07.20 bperrybap - merged hd44780 base class and i/o classes into a
// single library.
// 2016.06.08 bperrybap - removed pre IDE 1.0 support
// 2016.06.03 bperrybap - added smart execution delays
// 2016.05.14 bperrybap - added LCD 1.0 API functions
// 2016.05.05 bperrybap - added support for 8 bit mode
// 2014.02.15 bperrybap - initial creation
//
// @author Bill Perry - bperrybap@opensource.billsworld.billandterrie.com
//
// ------------------------------------------------------------------------
#include "hd44780.h"
// Unfortunately, it cannot be assumed that the hd44780 object is zereod out since
// users can create the lcd object using "new".
// Because of this, the constructor code must initalize certain variables
//
// One oddity is that constructors zero out the row/line offset/address table
// of addresses for each row/line
// This is intentional to allow these capabilities:
// - user can call setRowOffsets() *before* begin()
// - user can call init() at any time to reinitialize and
// and the row offsets will be preserved.
// To support this, begin() will look at the row offsets and assign defaults if
// they have not been assigned. (still zero)
//
hd44780::hd44780() : _cols(0), _rows(0)
{
/*
* Set up default execution times for clear/home and
* all other instructions and data writes.
* Setting exec times here
* allows application to call setExecTimes() before begin()
* should it be necessary to override defaults.
*/
setExecTimes(HD44780_CHEXECTIME, HD44780_INSEXECTIME);
// clear row offset addresses, will be set in begin()
setRowOffsets(0,0,0,0);
noLineWrap(); // no linewrap as default
markStart(0); // initialize last start time to 'now'
}
hd44780::hd44780(uint8_t cols, uint8_t rows) : _cols(cols), _rows(rows)
{
// clear row offset addresses, will be set in begin()
setRowOffsets(0,0,0,0);
noLineWrap(); // no linewrap as default
setExecTimes(HD44780_CHEXECTIME, HD44780_INSEXECTIME);
markStart(0); // initialize last start time to 'now'
}
hd44780::hd44780(uint8_t cols, uint8_t rows, uint32_t chExecTimeUs, uint32_t insExecTimeus) :
_cols(cols), _rows(rows), _chExecTime(chExecTimeUs), _insExecTime(insExecTimeus)
{
// clear row offset addresses, will be set in begin()
setRowOffsets(0,0,0,0);
noLineWrap(); // no linewrap as default
markStart(0); // initialize last start time to 'now'
}
// undocumented init() function to allow compatibilty with certain
// other "liquidcrystal" libraries like LiquidCrystal_I2C
//
// It is different than the IDE bundled LiquidCrystal library in that
// it takes no parameters to be consistent across all i/o classes.
//
// This function can be called at any time to re-initialize the LCD
// LCD rowoffset table values and the execution timings are preserved
//
// Note: default rows and cols is 16x2
// which is different than LiquidCryal which is 16x1
int hd44780::init()
{
if(!_cols)
_cols = 16;
if(!_rows)
_rows = 2;
return(begin(_cols, _rows));
}
//
// initialize the LCD
// Note: The hd44780 spec uses the term "line" whereas the
// IDE LiquidCrystal library uses the term "row" so this
// library (hd44780) will continue using the term "row"
// rather than "line".
// Other than name, they are the same.
//
// Returns 0 on success, non zero on initialization failure
//
int hd44780::begin(uint8_t cols, uint8_t rows, uint8_t dotsize)
{
int rval = 0;
/*
* Limit lines/rows to max in the row offset table
*/
if(rows > sizeof(_rowOffsets) / sizeof(_rowOffsets[0]))
rows = sizeof(_rowOffsets) / sizeof(_rowOffsets[0]);
/*
* create default row/line offset table of addresses for each row/line
* if not already set by user using setRowOffsets() before begin() is called.
* OR
* if the user is changing the number of columns on the display since a previous
* initialization
*
* See here for further explanation of lcd memory addressing:
* http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
* This default will handle:
* 40x2, 20x4, 20x2, 16x2, 16x1 (type 2), 16x4
* displays not supported by this:
* - 16x1 (type 1), This uses a discontigous memory for the single line
* (This requires ugly mods to the library to make work)
* - 40x4 is dual 40x2 displays using dual E signals which is not suppo
*
* users can override this at any time by calling setRowOffsets(r0,r1,r2,r3)
*/
if((!_rowOffsets[0] && !_rowOffsets[1] && !_rowOffsets[2] && !_rowOffsets[3])
|| (_cols && (_cols != cols)))
{
setRowOffsets(0x00, 0x40, 0x00 + cols, 0x40 + cols);
}
/*
* Save rows/cols
*/
_rows = rows;
_cols = cols;
/*
* SEE PAGE 45/46 of Hitachi HD44780 spec FOR INITIALIZATION SPECIFICATION.
* according to datasheet,we need at least 40ms after power rises above 2.7V
* before sending commands.The Arduino AVR can turn on and start running way
* before 4.5V so we'll wait 100ms to add some additional margin
*/
delay(100);
// default to 4 bit mode (can be overridded by i/o class library)
_displayfunction = HD44780_4BITMODE;
// call h/w i/o class init function
if((rval = ioinit())) // intentional assignment
{
return(rval);
}
if (_rows > 1)
{
_displayfunction |= HD44780_2LINE;
}
// for some 1 line displays you can select a 10 pixel high font
if ((dotsize != HD44780_5x8DOTS) && (rows == 1))
{
_displayfunction |= HD44780_5x10DOTS;
}
/*
* ========================================================================
* The init sequence below is shown in the hitachi HD44780 datasheet
* figures 23 & 24, pages 45 & 46
*
* NOTE:
* IMO, most explanations and comments about this sequence are wrong as the
* code authors don't really understand what it is actually doing.
* Adding to the confusion is that the hitachi HD44780 datasheet does
* a very poor job explaining this sequence.
* Also, adding to the confusion is the note about FunctionSet on page 27:
* ----------------------------------------------------------------------
* Note: Perform the function at the head of the program before executing
* any instructions (except for the read busy flag and address instruction)
* From this point, the function set instruction cannot be executed unless
* the interface data length is changed.
* ----------------------------------------------------------------------
*
* It isn't clear what information this note is trying to convey.
*
* It appears to be implying that FunctionSet must be done first and that it
* cannot be executed later unless the data length is changed.
* If that is what it saying...
* This is in direct conflict with the comand sequence in the
* Initialize by Instruction in figures 23 & 24, particulary in
* figure 23 as the 4th command sent will be a FunctionSet command
* setting data length to 8 bits as well as the prefered the N and F bits
* when the data length is guaranteed to already be 8 bits.
*
* But since sending a FunctionSet command with DL=1 along with setting N
* and F bits works when the display is already in 8 bit mode, my conclusion
* is that the note on page 27 is obviously incorrect and can be ignored.
*
* --
*
* When using 4 bit length mode, the LCD expects the nibbles on d4-d7 and
* expects the upper nibble of the 8 bits first.
* Therefore, when using 4 bit mode, it is critical that the host and
* the LCD are in sync on the nibbles.
*
* The command sequences in figures 23 and 24 are not simple retries and
* there is nothing magical about the timing.
* They are specifically chosen to ensure that after completion,
* the LCD is in the desired interface mode (8 bit or 4 bit).
* The two sequences start off with an identical 3 command sequence of
* FunctionSet commands which are used to reliably put the LCD into
* 8 bit length mode regardless of what mode or state the LCD happends
* to be in and regardless of whether the host is controlling all 8 data
* pin signals or just d4-d7.
* The only difference between the two sequences is that the 4 bit
* initializaion (figure 24) puts the LCD interface into 4 bit mode
* after the LCD is assured to be in 8 bit mode.
*
*
* Below is a detailed explanation of the hd44780 initalization sequence
*
* --- bperrybap
*
* The overall methodology is to carefully use a command sequence that can
* first reliably put the LCD back into 8 bit mode and then if using 4 bit
* mode, a command can be issued to put the LCD into 4 bit mode.
* This sequence is not reseting the display but rather re-initalizing the
* host interface mode (8 bit or 4 bit)
* It also ensures that a host that can only control data lines D4-D7 is
* able to reliably get into nibble sync with the LCD and put the LCD into
* 4 bit mode regardless of the what state the LCD is currently in.
*
* This special initailization command sequence is required beause there is
* no way to actully reset the LCD to its power up default state and
* there is no way of knowing if the LCD is currently in 4bit or 8bit mode.
* Even if the h/w inteface is 8 bits, the LCD could potentially be in
* 4 bit mode.
* Also, if the LCD is in 4 bit mode, the LCD could be in the middle of
* receiving nibbles. i.e. the system was reset after only a single
* nibble was sent to the LCD.
* When the LCD is in 4 bit mode and out of nibble sync with the host:
* the LCD has received the upper/1st nibble of a command/data byte and
* is waiting for the lower/2nd nibble.
* After the 2nd nibble is recieved, the LCD will go off and execute an
* unknown command. This is why the firt delay is so long. The host has to
* accomodate the longest possible command execution time.
* The next two FunctionSet commands will be interpreted
* as a single FunctionSet command by the LCD to put the display back into
* 8 bit mode.
*
* It appears that all delays specified in figures 23 & 24 are 2.7x the
* execution times in table 6. It appears that these values were chosen to
* accomodate LCDs that are running chips clocked slower than the 270kHz
* reference used in table 6
* A 2.7x factor would allow the Initialize by Instruction sequence to work
* with chips clocked down to 100Khz.
*
* In each of the 3 steps, EN is strobed once as the host will be
* treating the LCD h/w interface as if it is 8 bits wide regardless of
* whether the actual h/w interface to the LCD may only be 4 bits.
* d4-d7 signals on the hd44780 interface are set for
* a function set "goto 8bit mode" command.
* Also, d0-d3 signals on the physical hd44780 interface are either set to
* low, or not driven in the case of 4 bit only h/w host interfaces.
*
* Here is the command sequence shown seperately for each of 3 states the
* LCD can be in.
* This will demonstrate why there must be a command sequence
* of three "goto 8 bit mode" followed by one "goto to 4 bit mode" if
* using 4 bit mode.
*
* ========================================================================
* LCD in 8 BIT mode:
* =================
* 1) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* 8bit host: LCD interprets command 0x30
* since lower 4 bits will be low and read as zeros.
* 4bit host: LCD interprets command 0x3X
* since lower 4 bits will be unpredictable
* FUNCTION_SET & 8BIT are set, so it gets interpreted as a
* "goto 8bit mode" and so this is essentially a NOP as the
* display is already 8 bit mode
*
* 2) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* 8bit host: LCD interprets command 0x30
* since lower 4 bits will be low and read as zeros.
* 4bit host: LCD interprets command 0x3X
* since lower 4 bits will be unpredictable
* FUNCTION_SET & 8BIT are set, so it gets interpreted as a
* "goto 8bit mode" and so this is essentially a NOP as the
* display is already 8 bit mode
*
* 3) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* 8bit host: LCD interprets command 0x30
* since lower 4 bits will be low and read as zeros.
* 4bit host: LCD interprets command 0x3X
* since lower 4 bits will be unpredictable
* FUNCTION_SET & 8BIT are set, so it gets interpreted as a
* "goto 8bit mode" and so this is essentially a NOP as the
* display is already 8 bit mode
*
* LCD in 4 bit mode and expecting upper/1st nibble:
* =================================================
* 1) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* LCD pulls in nibble as upper 4 bits of 8 bit command
*
* 2) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* LCD pulls in nibble as lower 4 bits to complete 8 bits of command
* LCD interprets command 0x33.
* the LCD sees a Goto 8bit mode command and changes to 8 bit mode.
*
* 3) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* LCD interprets command 0x30 on 8bit hosts since
* lower 4 bits will be low and read as zeros.
* LCD interprets command 0x3X on 4 bit hosts since
* lower 4 bits will be unpredictable
* since FUNCTION_SET & 8BIT are set, it gets interpreted as
* a "goto 8bit mode" and so
* this is essentially a NOP as the display is already 8 bit mode
*
* LCD in 4 bit mode and expecting lower/2nd nibble:
* (out of nibble sync with host)
* ========================================================================
* 1) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* LCD pulls in nibble as lower 4 bits to complete 8 bits of command
* LCD interprets command with upper nibble (unknown) and lower
* nibble 0x3
* There is no telling what this command does as the upper nibble is
* unknown. And THAT is why the first delay in the sequence must be so
* long. It isn't that the the first "function set" takes that long it
* is because there is no telling what command might now be executing
* and BUSY cannot be used yet.
* Therefore, the host must blind wait the worst case amount of time.
* A 4.1ms delay appears to be 2.7x the worst case instruction
* execution time (clear & home are 1.52ms) at a 270kHz LCD clock rate.
* (4.1ms would accomodate a chip clocked at 100kHz instead of 270 kHz)
*
* 2) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* If LCD happened to go into 8BIT mode in step 1
* LCD interprets this command as 0x30
* since d0-d3 lower 4 bits will be read as zeros in 8 bit mode
* This is essentially a NOP as the display is already in
* 8 bit mode
*
* If LCD still in 4 bit mode,
* then this is upper nibble of next command.
*
* 3) Set the upper 4 bits (d4-d7) of Functionset: FUNCTION_SET|8BIT = 0x3
* If LCD happened to go into 8BIT mode in step 1
* LCD interprets this command as 0x30
* since d0-d3 lower 4 bits will be read as zeros in 8 bit mode
* This is essentially a NOP as the display is already in
* 8 bit mode
*
* If LCD still in 4 bit mode
* LCD pulls in nibble as lower 4 bits to complete 8 bits of cmd
* LCD interprets command 0x33.
* the LCD sees a Goto 8bit mode command and changes to 8 bit mode.
*
*
* =======================================================================
* Once the display in 8 bit mode, if the interface is to be in 4 bit mode:
* =======================================================================
* - Set the upper 4 bits of Functionset: with 8BIT clear
* this puts the LCD in 4 bit and it now expects upper nibble
*/
/*
* Note:
* The initialization sequence below uses special hd44780 internal API
* for 4 bit commands.
* Internal 4 bit commands are commands that are sent to the LCD using
* only a single strobe of EN even if the host h/w interface is 4 bit only.
* In other words it is indicating that only the upper 4 bits need to be
* sent to the display - so effectively the host must ensure that the
* upper four bits are placed on D4-d7 and that EN is stobed only once
* including if the host h/w interface is only 4 bits wide.
*
* 8bit only i/o interface libraries can ignore this as the bits for these
* special commands are in the proper d4-d7 bit locations so the full
* 8 bit byte can be presented to the LCD.
* i.e.
* - 8 bit interfaces can treat 4bit commands the same as regular commands
* - 4 bit only interfaces will send just the upper 4 bits (d4-d7)
*
* Additional Note:
* The Initiatlization by Instruction figures 23 & 24 of the hitachi spec
* seems to use delays that are 2.7x the specified instruction times based
* on a 270khz clock from table 6
* i.e. 4.1ms is 2.7x 1.52ms of clear/home commands
* and 100us is 2.7x 37us for all the other instructions (cmd & data)
* The delay values appear to be accomodating LCDs clocked down to 100kHz
* rather than at the 270Khz reference.
* This library will wait a little bit longer given the delay functions
* used. However, since iowrite() honors execution times,
* the delays here are in addition to the configured execution times
* and technically should not be needed.
* So if the application called setExecTimes() with excution
* values prior to begin() those will be in addition to these
* delays so everthing should work no matter how slow the LCD is.
*
* Note that delay() is used here vs delayMicroSeconds() as delay()
* on later versions of Arduino core code calls some scheduling functions
* like yield() to potentially allow other code to execute during this time period.
*
* delay() can be used because this code is never called from a constructor
*
*/
command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE);
delay(5); // wait 5ms vs 4.1ms, some are slower than spec
command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE);
delay(1); // wait 1ms vs 100us
command4bit(HD44780_FUNCTIONSET|HD44780_8BITMODE);
delay(1); // wait 1ms vs 100us
/*
* At this point the LCD is guaranteed to be in 8 bit mode
*
* If the interface needs to be put into 4 bit mode, the
* goto 4bit mode command is sent as another special internal
* "4bit" command because the 4bit only host talks 4 bits and the LCD
* isn't in 4 bit mode yet.
* Note: there is no need for a delay *after* command4bit() since
* it uses markStart(_insExecTime) to indicate the instruction time
* and will be honored by waitReady() used in the i/o class.
*/
if(!(_displayfunction & HD44780_8BITMODE))
command4bit(HD44780_FUNCTIONSET|HD44780_4BITMODE);
/*
* At this point the LCD is in 8 bit mode for 8 bit host interfaces,
* and in 4 bit mode for 4bit only host interfaces so we can
* now use the "normal" library command() & API interface functions
*/
// set # lines, font size, etc.
command(HD44780_FUNCTIONSET | _displayfunction);
// turn the display on with no cursor or blinking default
_displaycontrol = HD44780_DISPLAYON | HD44780_CURSOROFF | HD44780_BLINKOFF;
display();
clear(); // clear display
// Initialize to default text direction (for romance languages)
_displaymode = HD44780_ENTRYLEFT2RIGHT;
// set the entry mode
rval = command(HD44780_ENTRYMODESET | _displaymode);
// FIXME
#ifdef LATER
// quick check to see if BUSY is working on devices that support reads
{
int lcdstatus = status();
if(lcdstatus >= 0)
{
// if BUSY bit is set then there is a problem
if(lcdstatus & 0x80)
return(RV_EBUSY);
}
}
#endif
backlight(); // turn on the backlight, if supported
return(rval);
}
int hd44780::clear()
{
return(command(HD44780_CLEARDISPLAY)); // clear display, set cursor to 0,0
}
int hd44780::home()
{
return(command(HD44780_RETURNHOME)); // set cursor position to 0,0
}
// setRowOffsets:
//
// Create a row/line offset table of addresses for each row/line
// See here for further explanation of lcd memory addressing:
// http://web.alfredstate.edu/weimandn/lcd/lcd_addressing/lcd_addressing_index.html
int hd44780::setRowOffsets(int row0, int row1, int row2, int row3)
{
_rowOffsets[0] = row0;
_rowOffsets[1] = row1;
_rowOffsets[2] = row2;
_rowOffsets[3] = row3;
return(RV_ENOERR);
}
int hd44780::setCursor(uint8_t col, uint8_t row)
{
// while this could return RV_EINVAL for invalid parameters
// it is going to adjust them to maintain compability with existing LiquidCrystal
// library and to allow a back door way to set the ddram address - see below
if ( row >= _rows )
{
// set to max line (rows/lines start at 0, _rows is 1 based)
row = _rows-1;
}
/*
* if col is too large, it can't corrupt the command since
* SETDDRRAMADDR is bit 7 of the byte
* It is intionally not checked to allow allow:
* - positioning off the end of the visable line
* - if line 0 is used, it is a backdoor to do SETDDRAMADDR to any address
*
* NOTE:
* Things are handled differently if auto linewrap is enabled:
* position takes into consideration line wrapping.
* i.e. positioning to col 16, row 0 on a 16x2 display
* will position the cursor to col 0 row 1.
*/
if(_wraplines)
{
// wrap while requested col > toal cols
while(col >= _cols)
{
col -= _cols;
if(++row >= _rows)
row = 0; // wrap back to top line
}
// save position
_curcol = col;
_currow = row;
}
#ifdef later
// in right to left mode the cursor position wil be incorrect.
// however, things like home() and clear() will not work as expected either.
// home() positions back to DDRAMADDR 0
// clear() positions back to DDRAMADDR 0 and removes the right to left mode
// It isn't clear how home() and clear() should be handled.
// if they position to (0,0) then they have to expcility do it as well as
// restore rightToLeft() mode when being used.
//
if(!(_displaymode & HD44780_ENTRYLEFT2RIGHT))
return(command(HD44780_SETDDRAMADDR | (_cols - col -1 + _rowOffsets[row])));
else
#endif
return(command(HD44780_SETDDRAMADDR | (col + _rowOffsets[row])));
}
// turn off display pixels
int hd44780::noDisplay()
{
_displaycontrol &= ~HD44780_DISPLAYON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// turn on display pixels
int hd44780::display()
{
_displaycontrol |= HD44780_DISPLAYON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// Turns off underline cursor
int hd44780::noCursor()
{
_displaycontrol &= ~HD44780_CURSORON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// Turn on underline cursor
int hd44780::cursor()
{
_displaycontrol |= HD44780_CURSORON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// Turn on off the blinking cursor
int hd44780::noBlink()
{
_displaycontrol &= ~HD44780_BLINKON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// Turn on the blinking cursor
int hd44780::blink()
{
_displaycontrol |= HD44780_BLINKON;
return(command(HD44780_DISPLAYCONTROL | _displaycontrol));
}
// These API functions scroll/shift the display contents without changing the RAM
int hd44780::scrollDisplayLeft(void)
{
return(command(HD44780_CURDISPSHIFT | HD44780_DISPLAYMOVE | HD44780_MOVELEFT));
}
int hd44780::scrollDisplayRight(void)
{
return(command(HD44780_CURDISPSHIFT | HD44780_DISPLAYMOVE | HD44780_MOVERIGHT));
}
// This is for text that flows Left to Right
int hd44780::leftToRight(void)
{
_displaymode |= HD44780_ENTRYLEFT2RIGHT;
return(command(HD44780_ENTRYMODESET | _displaymode));
}
// This is for text that flows Right to Left
int hd44780::rightToLeft(void)
{
_displaymode &= ~HD44780_ENTRYLEFT2RIGHT;
return(command(HD44780_ENTRYMODESET | _displaymode));
}
// This moves the cursor one space to the right
int hd44780::moveCursorRight(void)
{
return(command(HD44780_CURDISPSHIFT | HD44780_CURSORMOVE | HD44780_MOVERIGHT));
}
// This moves the cursor one space to the left
int hd44780::moveCursorLeft(void)
{
return(command(HD44780_CURDISPSHIFT | HD44780_CURSORMOVE | HD44780_MOVELEFT));
}
// This will enable autoshifting display as new characters are written.
// If mode is left to right, shift is left
// if mode is right to left, shift is right
int hd44780::autoscroll(void)
{
_displaymode |= HD44780_ENTRYAUTOSHIFT;
return(command(HD44780_ENTRYMODESET | _displaymode));
}
// This will disable autoshifting when new characters are written
int hd44780::noAutoscroll(void)
{
_displaymode &= ~HD44780_ENTRYAUTOSHIFT;
return(command(HD44780_ENTRYMODESET | _displaymode));
}
// Allows filling in the first 8 CGRAM locations
// with custom characters
// note that it is not possible to support more than 8 locations
// since SETCGRAMADDR instruction is bit 6 and there no room
// for any more address bits.
// (location is bits 3-5 and data row is bits 0-2 of the instruction)
int hd44780::createChar(uint8_t location, uint8_t charmap[])
{
int rval;
int ddramaddr;
location &= 0x7; // we only have 8 locations 0-7
ddramaddr = status(); // fetch status which includes ddram address
if(ddramaddr < 0) // status() failed, so just set address to 0
ddramaddr = 0;
else
ddramaddr &= 0x7f; // strip off BUSY bit
rval = command(HD44780_SETCGRAMADDR | (location << 3));
if(rval)
return(rval);
for (int i=0; i<8; i++)
{
if(_write(charmap[i]) != 1) // use raw write to avoid line processing
return(RV_EIO);
}
// put LCD back into DDRAM mode so write() works
return(setCursor(ddramaddr , 0));
}
// special version of code to support wimpy AVR parts that can't directly access
// const data in flash like all the other processors
// Note that most other Arduino cores have adopted to support the
// AVR proprietary PROGMEM macros to be compatible with AVR specific code.
// Since the macro PROGMEM is defined when proprietary AVR progmem support is
// implememented (even on non AVR cores),
// this code will look for that macro to enable special code to deal with it.
// While at this time, this is only needed for AVR parts, it will work on
// non AVR parts that have implemented support for the proprietary
// AVR progmem crap.
//
#if defined (PROGMEM)
int hd44780::createChar(uint8_t location, const uint8_t *charmap)
{
uint8_t buf[8];
// fetch/read the full 8 byte glyph data into RAM
for(int i= 0; i< 8; i++)
{
buf[i] = pgm_read_byte(charmap++);
}
// call the RAM based function to actually send it to the LCD
return(createChar(location, buf));
}
#else
int hd44780::createChar(uint8_t location, const uint8_t *charmap)
{
return(createChar(location, (uint8_t *)charmap));
}
#endif
// turn on backlight at full intensity
int hd44780::backlight(void)
{
return(iosetBacklight(-1)); // max brightness
}
// turn off backlight
int hd44780::noBacklight(void)
{
return(iosetBacklight(0));
}
// turn on pixels and backlight
int hd44780::on ( void )
{
int rval;
rval = display();
backlight(); // ignore any issues for backlight control
return(rval);
}
// turn off pixels and backlight
int hd44780::off ( void )
{
noBacklight(); // ignore any issues for backlight control
return(noDisplay());
}
// command() - send hd44780 command byte to lcd
//
// Returns 0 on success, non zero if command failed
//
inline int hd44780::command(uint8_t value)
{
int status;
status = iowrite(HD44780_IOcmd, value);
// executime time depends on command
if((value == HD44780_CLEARDISPLAY) || (value == HD44780_RETURNHOME))
{
_curcol = 0;
_currow = 0;
markStart(_chExecTime);
}
else
{
markStart(_insExecTime);
}
return(status);
}
// status() - read status byte from LCD
// returns:
// success: 8 bit hd44780 status byte (busy flag & address)
// failure: neagative value (error or read/status not supported by i/o subclass
int hd44780::status()
{
int rvalue = ioread(HD44780_IOcmd);
// markStart() is not called here as status reads do not
// require any execution time.
// setting the start time to now, which is 0, can potentially erase
// the execution time of a previous command if status reads
// are not supported therefore, just leave the previous start time.
return(rvalue);
}
// read() - read a data byte from LCD
// returns:
// success: 8 bit value read
// failure: neagative value (error or read not supported by i/o subclass
int hd44780::read()
{
int rvalue = ioread(HD44780_IOdata);
// it apears that even though the read operation actually completed
// when the data has been read, i.e. ioread() returns,
// that the chip cannot take another instruction
// until after the normal instruction execution time.
// See Table 6 page 25 of Hitachi hd44780 datasheet
markStart(_insExecTime);
return(rvalue);
}
// write() - process data character byte to lcd
// returns number of bytes successfully written to device
// i.e. 1 if success or 0 if no character was processed (error)
size_t hd44780::write(uint8_t value)
{
size_t rval;
rval = _write(value);
if(_wraplines)
{
// currently only works for left to right mode
if(++_curcol >= _cols)
{
_curcol = 0;
_currow++;
if(_currow >= _rows)
_currow = 0;
setCursor(_curcol, _currow);
}
}
return (rval);
}
// _write() - send raw data byte to lcd
// returns 1 if success or 0 if no byte was processed (error)
size_t hd44780::_write(uint8_t value)
{
int status = 1; //assume success
if(iowrite(HD44780_IOdata, value))
status = 0; // write was unsuccessful
markStart(_insExecTime);
return status;
}
//============================================================================
// A couple of functions that really shouldn't be here.
// blinkLED() and fatalError()
//
// NOTE: I REALLY do not like having these functions here and they definitely
// do not belong in this class; HOWEVER.... blinking a built in LED in arduino
// is not as easy as it should be to ensure that works across many different
// cores and boards.
// Some cores didn't correclty define their LED_BUILTIN define, and some boards
// use use an active LOW LED.
// To make things simpler for the hd44780 examples and ensure consistency as
// well as making maintenance much easier,
// I held my nose and moved those two functions to here.
// -- bperrybap
// blinkLED() - blink builtin LED
// blinks a built in LED if there is one.
// returns zero if successful
//
// helper macros to turn on built in LED as some boards like the ESP8266 use
// active low LEDs
#if defined(ARDUINO_ARCH_ESP8266)
#define ledBuiltinOn() digitalWrite(LED_BUILTIN, LOW)
#define ledBuiltinOff() digitalWrite(LED_BUILTIN, HIGH)
#else
#define ledBuiltinOn() digitalWrite(LED_BUILTIN, HIGH)
#define ledBuiltinOff() digitalWrite(LED_BUILTIN, LOW)
#endif
int hd44780::blinkLED(int blinks)
{
#ifdef LED_BUILTIN
pinMode(LED_BUILTIN, OUTPUT);
// blink out error code
for(int i = 0; i< blinks; i++)
{
ledBuiltinOn();
delay(100);
ledBuiltinOff();
delay(250);
}
return(0);
#else
// No built in LED, so do "nothing"
if(blinks){} // "nop" if to eliminate warning, will be optimized out
return(RV_ENOTSUP);
#endif
}
// fatalError() - loop & blink an error code
void hd44780::fatalError(int errcode)
{
if(errcode < 0)
errcode = -errcode;
while(1)
{
blinkLED(errcode); // blink LED if possible
delay(1500); // using delay() ensures watchdogs don't trip
}
}
//============================================================================