// 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 . // // 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 } } //============================================================================