first commit
This commit is contained in:
970
libraries/hd44780/hd44780.cpp
Normal file
970
libraries/hd44780/hd44780.cpp
Normal file
@@ -0,0 +1,970 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
//============================================================================
|
||||
Reference in New Issue
Block a user