1219 lines
41 KiB
C++
1219 lines
41 KiB
C++
// vi:ts=4
|
|
// ---------------------------------------------------------------------------
|
|
// hd44780_I2Cexp.h - hd44780_I2Cexp i/o subclass for hd44780 library
|
|
// Copyright (c) 2013-2020 Bill Perry
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// This file is part of the hd44780 library
|
|
//
|
|
// hd44780_I2Cexp 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_I2Cexp 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_I2Cexp. If not, see <http://www.gnu.org/licenses/>.
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
//
|
|
// It implements all the hd44780 library i/o methods to control an LCD based
|
|
// on the Hitachi HD44780 and compatible chipsets using I2C extension
|
|
// backpacks that use a simple I2C i/o expander chip.
|
|
// Currently the PCF8574 or the MCP23008 are supported.
|
|
//
|
|
// The API functionality provided by this library class is compatible
|
|
// with the API functionality of the Arduino LiquidCrystal library.
|
|
//
|
|
// The hd44780_I2Cexp constructor can specify all the parameters or let the
|
|
// library auto configure itself.
|
|
// hd44780_I2Cexp constructor can specifiy expander output bit assignments
|
|
// or use pre-defined backpack board types
|
|
// Some parameters can be left off when not used or to auto detect.
|
|
//
|
|
// examples:
|
|
// hd44780_I2Cexp lcd; // autolocate/autoconfigure everything
|
|
// hd44780_I2Cexp lcd(0x20); // autoconfigure for lcd at i2c address 0x20
|
|
//
|
|
// hd44780_I2Cexp lcdA; // autolocate/autoconfigure for lcd instance 0
|
|
// hd44780_I2Cexp lcdB; // autolocate/autoconfigure for lcd instance 1
|
|
//
|
|
// hd44780_I2Cexp lcd(addr, chiptype, rs,[rw],en,d4,d5,d6,d7,bl,blLevel);
|
|
// hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008,1,2,3,4,5,6,7,HIGH); // no rw support
|
|
// hd44780_I2Cexp lcd(0x27, I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH); // with rw support
|
|
//
|
|
// hd44780_I2Cexp lcd(addr, canned-entry);
|
|
// hd44780_I2Cexp lcd(0x20, I2Cexp_BOARD_ADAFRUIT292); // specific backpack at 0x20
|
|
// hd44780_I2Cexp lcd(0x38, I2Cexp_BOARD_SYDZ); // specific backpack at 0x38
|
|
//
|
|
// hd44780_I2Cexp lcd(canned-entry);
|
|
// hd44780_I2Cexp lcd(I2Cexp_BOARD_SYDZ); // locate specific backpack
|
|
//
|
|
//
|
|
// NOTES:
|
|
// It is best to use autoconfigure if possible.
|
|
// Intermixing autolocate and specific i2c addresss can create conflicts.
|
|
// The library cannot autoconfigure the SYDZ backpack.
|
|
// It will correctly identify the pin mapping but incorrectly determine
|
|
// the backlight active level control.
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
// History
|
|
//
|
|
// 2020.06.16 bperrybap - tweak to MCP23008 auto config for Adafruit #292 board
|
|
// 2020.06.13 bperrybap - fixed constructor issue for MCP23008 canned entries
|
|
// 2018.08.06 bperrybap - removed TinyWireM work around (TinyWireM was fixed)
|
|
// 2017.12.23 bperrybap - added LiquidCrystal_I2C compatible constructor
|
|
// 2017.05.12 bperrybap - now requires IDE 1.0.1 or newer
|
|
// This is to work around TinyWireM library bugs
|
|
// 2017.01.07 bperrybap - unknown address is now an address of zero
|
|
// 2016.12.26 bperrybap - update comments for constructor usage
|
|
// 2016.12.25 bperrybap - new constructor for canned entry with no address for auto locate
|
|
// 2016.10.29 bperrybap - added sunrom canned entry
|
|
// updated pcf8574 autoconfig comments
|
|
// 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 ioread()
|
|
// 2016.07.27 bperrybap - added return status for iosend()
|
|
// 2016.07.21 bperrybap - merged all class code into header
|
|
// 2016.07.20 bperrybap - merged into hd44780 library
|
|
// 2016.06.15 bperrybap - added i2c probing delay for chipkit i2c issue
|
|
// 2016.06.15 bperrybap - added getProp() diagnostic function
|
|
// 2016.06.09 bperrybap - changed name from hd44780_IICexp to hd44780_I2Cexp
|
|
// 2016.06.08 bperrybap - removed pre IDE 1.0 support
|
|
// 2016.06.03 bperrybap - added smart execution delays
|
|
// 2016.05.14 bperrybap - added support for multiple unknown address objects
|
|
// 2016.04.01 bperrybap - added auto config & auto detect pin mapping
|
|
// 2014.02.15 bperrybap - changed to use hd44780 base class library
|
|
// 2013.06.01 bperrybap - initial creation
|
|
//
|
|
// @author Bill Perry - bperrybap@opensource.billsworld.billandterrie.com
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#ifndef hd44780_I2Cexp_h
|
|
#define hd44780_I2Cexp_h
|
|
|
|
// A bug in TinyWireM is that requestFrom() returns incorrect status
|
|
// so its return status can't be used. Instead the code will check the return
|
|
// from Wire.read() which will return -1 if there no data was transfered.
|
|
|
|
#if (ARDUINO < 101) && !defined(MPIDE)
|
|
#error hd44780_I2Cexp i/o class requires Arduino 1.0.1 or later
|
|
#endif
|
|
|
|
// canned i2c board/backpack parameters
|
|
// allows using:
|
|
// hd44780_I2Cexp lcd(I2Cexp_BOARD_XXX); // auto locate
|
|
// hd44780_I2Cexp lcd(i2c_address, I2Cexp_BOARD_XXX); // explicit i2c address
|
|
// instead of specifying all individual parameters.
|
|
// Note: some boards tie the LCD r/w line directly to ground
|
|
// boards that have control of the LCD r/w line will be able to do reads from lcd.
|
|
//
|
|
// The underlying hd4480_I2Cexp constructors support
|
|
// with or without r/w control, and with and without backlight control.
|
|
// - If r/w control is not desired, simply leave off the r/w pin from the constructor.
|
|
// - If backlight control is not desired/supported, simply leave off backlight pin and active level
|
|
//
|
|
// Since the library has to drive all 8 output pins, the boards that have
|
|
// r/w tied to ground should use an unused output pin for the r/w signal
|
|
// which will be set to LOW but ignored by the LCD on those boards.
|
|
// This means that the unused pin on the i/o expander cannot be used as an input
|
|
//
|
|
// expType, rs[,rw],en,d4,d5,d6,d7[,bl, blLevel]
|
|
#define I2Cexp_BOARD_LCDXIO I2Cexp_PCF8574, 4,5,6,0,1,2,3 // ElectroFun default (no backlight control)
|
|
#define I2Cexp_BOARD_LCDXIOnBL I2Cexp_PCF8574, 4,5,6,0,1,2,3,7,LOW // Electrofun & PNP transistor for BL
|
|
|
|
#define I2Cexp_BOARD_MJKDZ I2Cexp_PCF8574, 6,5,4,0,1,2,3,7,LOW // mjkdz backpack
|
|
#define I2Cexp_BOARD_GYI2CLCD I2Cexp_PCF8574, 6,5,4,0,1,2,3,7,LOW // GY-I2CLCD backpack
|
|
|
|
#define I2Cexp_BOARD_LCM1602 I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,LOW // Robot Arduino LCM1602 backpack
|
|
// (jumper forces backlight on)
|
|
|
|
// these boards are all the same
|
|
// and match/work with the hardcoded Arduino LiquidCrystal_I2C class
|
|
#define I2Cexp_BOARD_YWROBOT I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
#define I2Cexp_BOARD_DFROBOT I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
#define I2Cexp_BOARD_SAINSMART I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
#define I2Cexp_BOARD_FUNDUINO I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
#define I2Cexp_BOARD_SUNROM I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
// http://www.sunrom.com/p/i2c-lcd-backpack-pcf8574
|
|
// not recommended
|
|
#define I2Cexp_BOARD_SYDZ I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // YwRobot/DFRobot/SainSmart/funduino backpack
|
|
// SYDZ backpacks have a broken backlight circuit design.
|
|
// the backlight active level can not be auto detected
|
|
// It hooks the BL anode to the emitter rather than
|
|
// hook the collector to the BL cathode.
|
|
// The pullup on the base wont' be pulled low enough by
|
|
// the backlight so the P3 pin will read high instead of low.
|
|
// This breaks the autodetection.
|
|
|
|
#define I2Cexp_BOARD_SY1622 I2Cexp_PCF8574, 0,1,2,4,5,6,7,3,HIGH // This board uses a FET for backlight control
|
|
// This breaks the autodetection.
|
|
|
|
// MCP23008 based boards
|
|
// Currently r/w control is disabled since most boards either can't do it, or have it disabled.
|
|
#define I2Cexp_BOARD_ADAFRUIT292 I2Cexp_MCP23008,1,2,3,4,5,6,7,HIGH // Adafruit #292 i2c/SPI backpack in i2c mode (lcd RW grounded)
|
|
// GP0 not connected to r/w so no ability to do LCD reads
|
|
|
|
#define I2Cexp_BOARD_WIDEHK I2Cexp_MCP23008,4,7,0,1,2,3,6,HIGH // WIDE.HK mini backpack (lcd r/w hooked to GP5)
|
|
|
|
#define I2Cexp_BOARD_LCDPLUG I2Cexp_MCP23008,4,6,0,1,2,3,7,HIGH // JeeLabs LCDPLUG (NOTE: NEVER use the SW jumper)
|
|
// GP5 is hooked to s/w JP1 jumper, LCD RW is hardwired to gnd
|
|
// So no ability to do LCD reads.
|
|
|
|
#define I2Cexp_BOARD_EFREAK I2Cexp_MCP23008,7,6,5,4,3,2,1,HIGH // elecfreaks backpack. (lcd RW grounded)
|
|
// very similar design to Adafruit board but uses different pin mapping
|
|
// http://www.elecfreaks.com/store/i2ctwi-lcd1602-moduleblack-on-green-p-314.html
|
|
// http://elecfreaks.com/store/download/datasheet/lcd/Char/IICshematic.pdf
|
|
// http://www.elecfreaks.com/wiki/index.php?title=I2C/TWI_LCD1602_Module
|
|
|
|
|
|
|
|
#define I2Cexp_BOARD_MLTBLUE I2Cexp_MCP23008,1,3,4,5,6,7,0,HIGH // i2c LCD MLT group "Blue Board" backpack
|
|
// http://www.mlt-group.com/I2C-LCD-Blue-Board-for-Arduino
|
|
// There is jumper on the board jp6 that controls how the
|
|
// the board drives r/w.
|
|
// It looks like by defualt r/w is wired to gnd and
|
|
// can be changed by changing the solder jumper jp6.
|
|
// however it isn't clear if that changes to GP2 or to Vcc.
|
|
// It sounds like it changes to vcc with is REALLY dumb!
|
|
|
|
|
|
//FIXME these can't go in the class unless they are referenced using the classname
|
|
|
|
enum I2CexpType { I2Cexp_UNKNOWN, I2Cexp_PCF8574, I2Cexp_MCP23008 };
|
|
|
|
class hd44780_I2Cexp : public hd44780
|
|
{
|
|
public:
|
|
// ====================
|
|
// === constructors ===
|
|
// ====================
|
|
|
|
// -- Automagic / auto-detect constructors --
|
|
|
|
// Auto find next instance and auto config pin mapping
|
|
hd44780_I2Cexp(){ _addr = 0; _expType = I2Cexp_UNKNOWN;}
|
|
|
|
// Auto config specific i2c addr
|
|
hd44780_I2Cexp(uint8_t addr){ _addr = addr; _expType = I2Cexp_UNKNOWN;}
|
|
|
|
// Auto locate but with explicit config with r/w control and backlight control
|
|
hd44780_I2Cexp(I2CexpType type, uint8_t rs, uint8_t rw, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
|
|
uint8_t bl, uint8_t blLevel)
|
|
{
|
|
config(0, type, rs, rw, en, d4, d5, d6, d7, bl, blLevel); // auto locate i2c address
|
|
}
|
|
|
|
// Auto locate but with explicit config no r/w control with backlight control
|
|
hd44780_I2Cexp(I2CexpType type, uint8_t rs, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
|
|
uint8_t bl, uint8_t blLevel)
|
|
{
|
|
config(0, type, rs, 0xff, en, d4, d5, d6, d7, bl, blLevel); // auto locate i2c address
|
|
}
|
|
|
|
// Auto locate but with explicit config no r/w control with no backlight control
|
|
hd44780_I2Cexp(I2CexpType type, uint8_t rs, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7)
|
|
{
|
|
config(0, type, rs, 0xff, en, d4, d5, d6, d7); // auto locate i2c address
|
|
}
|
|
|
|
|
|
|
|
|
|
// -- undocumented LiquidCrystal_I2C compatible constructor
|
|
// Note: auto locate i2c address is also supported by using address 0 (zero)
|
|
// The init() function is also supported
|
|
hd44780_I2Cexp(uint8_t addr, uint8_t cols, uint8_t rows) :
|
|
hd44780(cols, rows), _addr(addr), _expType(I2Cexp_UNKNOWN) {}
|
|
|
|
|
|
// -- Explicit constructors, specify address & pin mapping information --
|
|
|
|
// constructor with r/w control without backlight control
|
|
hd44780_I2Cexp(uint8_t i2c_addr, I2CexpType type, uint8_t rs, uint8_t rw, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7 )
|
|
{
|
|
config(i2c_addr, type, rs, rw, en, d4, d5, d6, d7);
|
|
}
|
|
|
|
// Constructor with r/w control with backlight control
|
|
hd44780_I2Cexp(uint8_t i2c_addr, I2CexpType type, uint8_t rs, uint8_t rw, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
|
|
uint8_t bl, uint8_t blLevel)
|
|
{
|
|
config(i2c_addr, type, rs, rw, en, d4, d5, d6, d7, bl, blLevel);
|
|
}
|
|
|
|
// Constructor without r/w control without backlight control
|
|
hd44780_I2Cexp(uint8_t i2c_addr, I2CexpType type, uint8_t rs, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7 )
|
|
{
|
|
config(i2c_addr, type, rs, 0xff, en, d4, d5, d6, d7);
|
|
}
|
|
|
|
// Constructor without r/w control with backlight control
|
|
hd44780_I2Cexp(uint8_t i2c_addr, I2CexpType type, uint8_t rs, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
|
|
uint8_t bl, uint8_t blLevel)
|
|
{
|
|
config(i2c_addr, type, rs, 0xff, en, d4, d5, d6, d7, bl, blLevel);
|
|
}
|
|
|
|
|
|
|
|
// ============================================
|
|
// === library specific diagnostic function ===
|
|
// ============================================
|
|
|
|
enum I2CexpProp
|
|
{
|
|
Prop_addr,
|
|
Prop_expType,
|
|
Prop_rs,
|
|
Prop_rw,
|
|
Prop_en,
|
|
Prop_d4,
|
|
Prop_d5,
|
|
Prop_d6,
|
|
Prop_d7,
|
|
Prop_bl,
|
|
Prop_blLevel,
|
|
};
|
|
|
|
int mask2bit(uint8_t mask)
|
|
{
|
|
for(uint8_t bit = 0; bit < 8; bit++)
|
|
if(mask & (1 << bit))
|
|
return(bit);
|
|
|
|
// didn't find it, return error
|
|
return(hd44780::RV_ENXIO);
|
|
}
|
|
|
|
int getProp(I2CexpProp propID)
|
|
{
|
|
switch(propID)
|
|
{
|
|
case Prop_addr:
|
|
return(_addr);
|
|
case Prop_expType:
|
|
return((int)_expType);
|
|
case Prop_rs:
|
|
return(mask2bit(_rs));
|
|
case Prop_rw:
|
|
return(mask2bit(_rw));
|
|
case Prop_en:
|
|
return(mask2bit(_en));
|
|
case Prop_d4:
|
|
return(mask2bit(_d4));
|
|
case Prop_d5:
|
|
return(mask2bit(_d5));
|
|
case Prop_d6:
|
|
return(mask2bit(_d6));
|
|
case Prop_d7:
|
|
return(mask2bit(_d7));
|
|
case Prop_bl:
|
|
return(mask2bit(_bl));
|
|
case Prop_blLevel:
|
|
return(_blLevel);
|
|
default:
|
|
return(hd44780::RV_EINVAL);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// ====================
|
|
// === private data ===
|
|
// ====================
|
|
|
|
// expander pin mapping & state information
|
|
uint8_t _addr; // I2C Address of the IO expander
|
|
I2CexpType _expType; // I2C chip type used on the IO expander
|
|
uint8_t _rs; // I2C chip IO pin mask for Register Select pin
|
|
uint8_t _rw; // I2C chip IO pin mask for r/w pin
|
|
uint8_t _en; // I2C chip IO pin mask for enable pin
|
|
uint8_t _d4; // I2C chip IO pin mask for data d4 pin
|
|
uint8_t _d5; // I2C chip IO pin mask for data d5 pin
|
|
uint8_t _d6; // I2C chip IO pin mask for data d6 pin
|
|
uint8_t _d7; // I2C chip IO pin mask for data d7 pin
|
|
uint8_t _bl; // I2C chip IO pin mask for Backlight
|
|
uint8_t _blLevel; // backlight active control level HIGH/LOW
|
|
uint8_t _blCurState; // Current IO pin state mask for Backlight
|
|
|
|
// ==================================================
|
|
// === hd44780 i/o subclass virtual i/o functions ===
|
|
// ==================================================
|
|
|
|
// ioinit() - initialize the h/w
|
|
// Returns non zero if initialization failed.
|
|
//
|
|
// can't be used from constructors because not everything is initalized yet.
|
|
// (maybe interrupts or other library constructors)
|
|
// if Wire library is used in constructor, it will hang.
|
|
int ioinit()
|
|
{
|
|
int status = 0;
|
|
// auto instance tracts inst number when creating multiple auto locate objects
|
|
// this is static since it is for the entire class not per object.
|
|
static uint8_t AutoInst;
|
|
|
|
/*
|
|
* First, initialize the i2c (Wire) library.
|
|
* This really shouldn't be here
|
|
* because Wire.begin() should only be called once, but
|
|
* unfortunately, there is no way to know if anybody
|
|
* else has called this.
|
|
* I believe that it is unreasonable to require the the user
|
|
* sketch code to do it, because all that should change between
|
|
* hd44780 i/o interfaces should be the constructor
|
|
* So we go ahead and call it here.
|
|
*/
|
|
Wire.begin();
|
|
|
|
// auto locate i2c expander and magically detect pin mappings
|
|
|
|
if(!_addr) // locate next instance
|
|
{
|
|
_addr = LocateDevice(AutoInst++);
|
|
}
|
|
else
|
|
{
|
|
// check to see if device at specified address is really there
|
|
Wire.beginTransmission(_addr);
|
|
if(Wire.endTransmission())
|
|
return(hd44780::RV_ENXIO);
|
|
}
|
|
|
|
if(!_addr) // locate next instance
|
|
_addr = LocateDevice(AutoInst++);
|
|
|
|
if(!_addr) // if we couldn't locate it, return error
|
|
return(hd44780::RV_ENXIO);
|
|
|
|
if(_expType == I2Cexp_UNKNOWN) // figure out expander chip if not told
|
|
{
|
|
_expType = IdentifyIOexp(_addr);
|
|
|
|
if(_expType == I2Cexp_UNKNOWN) // coudn't figure it out?, return error
|
|
return(hd44780::RV_EIO);
|
|
|
|
if(_expType == I2Cexp_PCF8574)
|
|
status = autocfg8574(); // go auto configure the pin mappings
|
|
else
|
|
status = autocfgMCP23008(); // go auto configure the pin mappings
|
|
if(status)
|
|
return(status);
|
|
}
|
|
|
|
// initialize IO expander chip
|
|
|
|
Wire.beginTransmission(_addr);
|
|
|
|
if(_expType == I2Cexp_MCP23008)
|
|
{
|
|
/*
|
|
* First make sure to put chip into BYTE mode
|
|
* BYTE mode is used to make a MCP23008 work more like PCF8574
|
|
* In BYTE mode the address register does not increment so that
|
|
* once you point it to GPIO you can write to it over and over again
|
|
* within the same i2c connection by simply sending more bytes.
|
|
* This is necessary as the code uses back to back writes to perform
|
|
* the nibble updates as well as the toggling the enable signal.
|
|
* This methodology offers significant performance gains.
|
|
*/
|
|
Wire.write(5); // point to IOCON
|
|
Wire.write(0x20);// disable sequential mode (enables BYTE mode)
|
|
Wire.endTransmission();
|
|
|
|
/*
|
|
* Now set up output port
|
|
*/
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write((uint8_t)0); // point to IODIR
|
|
Wire.write((uint8_t)0); // all pins output
|
|
Wire.endTransmission();
|
|
|
|
/*
|
|
* point chip to GPIO
|
|
*/
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(9); // point to GPIO
|
|
|
|
}
|
|
Wire.write((uint8_t)0); // Set the entire output port to LOW
|
|
if( (status = Wire.endTransmission()) ) // assignment
|
|
status = hd44780::RV_EIO;
|
|
|
|
return ( status );
|
|
}
|
|
|
|
// ioread(type) - read a byte from LCD DDRAM
|
|
//
|
|
// returns:
|
|
// success: 8 bit value read
|
|
// failure: negative value: error or read not supported
|
|
int ioread(hd44780::iotype type)
|
|
{
|
|
uint8_t gpioValue = _blCurState;
|
|
uint8_t data = 0;
|
|
int iodata;
|
|
int rval = hd44780::RV_EIO;
|
|
|
|
// If no address or expander type is unknown, then abort read w/error
|
|
if(!_addr || _expType == I2Cexp_UNKNOWN)
|
|
return(hd44780::RV_ENXIO);
|
|
|
|
// reads for MCP23008 not yet supported
|
|
if(_expType == I2Cexp_MCP23008)
|
|
{
|
|
return(hd44780::RV_ENOTSUP);
|
|
}
|
|
|
|
// check if reads supported
|
|
if(!_rw)
|
|
return(hd44780::RV_ENOTSUP);
|
|
|
|
/*
|
|
* ensure that previous LCD instruction finished.
|
|
* There is a 45us offset since there will be at least 2 bytes
|
|
* (the i2c address and the i/o expander data) transmitted over i2c
|
|
* before the i/o expander i/o pins could be seen by the LCD.
|
|
* (3 bytes on the MCP23008)
|
|
* At 400Khz (max rate supported by the i/o expanders) 16 bits plus start
|
|
* and stop bits is 45us.
|
|
* So there is at least 45us of time overhead in the physical interface.
|
|
*/
|
|
|
|
waitReady(-45);
|
|
|
|
// put all the expander LCD data pins into input mode.
|
|
// PCF8574 psuedo inputs use pullups so setting them to 1
|
|
// makes them suitible for inputs.
|
|
|
|
gpioValue |= _d4|_d5|_d6|_d7;
|
|
|
|
// set RS based on type of read (data or status/cmd)
|
|
if(type == hd44780::HD44780_IOdata)
|
|
{
|
|
gpioValue |= _rs; // RS high to read data reg
|
|
}
|
|
|
|
gpioValue |= _rw; // r/w high for reading
|
|
|
|
// write all the bits to the expander port
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(gpioValue); // d4-d7 are inputs, RS, r/w high, E LOW
|
|
if(Wire.endTransmission())
|
|
goto returnStatus;
|
|
|
|
|
|
// raise E to read the data.
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(gpioValue | _en); // Raises E
|
|
if(Wire.endTransmission())
|
|
goto returnStatus;
|
|
|
|
// read the expander port to get the upper nibble of the byte
|
|
Wire.requestFrom((int)_addr, 1);
|
|
iodata = Wire.read();
|
|
if(iodata < 0) // did we not receive a byte?
|
|
goto returnStatus;
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(gpioValue); // lower E after reading nibble
|
|
if(Wire.endTransmission())
|
|
goto returnStatus;
|
|
|
|
// map i/o expander port bits into upper nibble of byte
|
|
if(iodata & _d4)
|
|
data |= (1 << 4);
|
|
|
|
if(iodata & _d5)
|
|
data |= (1 << 5);
|
|
|
|
if(iodata & _d6)
|
|
data |= (1 << 6);
|
|
|
|
if(iodata & _d7)
|
|
data |= (1 << 7);
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(gpioValue | _en); // Raise E to read next nibble
|
|
if(Wire.endTransmission())
|
|
goto returnStatus;
|
|
|
|
// read the expander port to get the lower nibble of the byte
|
|
// We can't look at the return value from requestFrom() on the TineyWireM
|
|
// library as it doesn't work like it is supposed to.
|
|
// So we look at the return status from read() instead.
|
|
Wire.requestFrom((int)_addr, 1);
|
|
|
|
iodata = Wire.read();
|
|
|
|
if(iodata < 0) // did we not receive a byte?
|
|
goto returnStatus;
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(gpioValue); // lower E after reading nibble
|
|
if(Wire.endTransmission())
|
|
goto returnStatus;
|
|
|
|
// map i/o expander port bits into lower nibble of byte
|
|
if(iodata & _d4)
|
|
data |= (1 << 0);
|
|
|
|
if(iodata & _d5)
|
|
data |= (1 << 1);
|
|
|
|
if(iodata & _d6)
|
|
data |= (1 << 2);
|
|
|
|
if(iodata & _d7)
|
|
data |= (1 << 3);
|
|
|
|
rval = data;
|
|
|
|
returnStatus:
|
|
|
|
// try to put gpio port back to all outputs state with WR signal low for writes
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(_blCurState); // with E LOW
|
|
if(Wire.endTransmission())
|
|
rval = hd44780::RV_EIO;
|
|
|
|
return(rval);
|
|
}
|
|
|
|
// iowrite(type, value) - send either command or data byte to lcd
|
|
// returns zero on success, non zero on failure
|
|
int iowrite(hd44780::iotype type, uint8_t value)
|
|
{
|
|
// If no address or expander type is unknown, then drop data
|
|
if(!_addr || _expType == I2Cexp_UNKNOWN)
|
|
return(hd44780::RV_ENXIO);
|
|
|
|
/*
|
|
* ensure that previous LCD instruction finished.
|
|
* There is a 45us offset since there will be at least 2 bytes
|
|
* (the i2c address and the i/o expander data) transmitted over i2c
|
|
* before the i/o expander i/o pins could be seen by the LCD.
|
|
* (3 bytes on the MCP23008)
|
|
* At 400Khz (max rate supported by the i/o expanders) 16 bits plus start
|
|
* and stop bits is 45us.
|
|
* So there is at least 45us of time overhead in the physical interface.
|
|
*/
|
|
|
|
waitReady(-45);
|
|
|
|
// grab i2c bus
|
|
Wire.beginTransmission(_addr);
|
|
if(_expType == I2Cexp_MCP23008)
|
|
{
|
|
Wire.write(9); // point to GPIO
|
|
}
|
|
// send both nibbles in same i2c connection
|
|
write4bits( (value >> 4), type ); // upper nibble
|
|
|
|
/*
|
|
* "4 bit commands" are special.
|
|
* They are used only during initalization and
|
|
* are used to reliably get the LCD and host in nibble sync
|
|
* with each other and to get the LCD into 4 bit mode.
|
|
* When sending a "4 bit command" only the upper nibble of
|
|
* of the 8 bit byte will presented on LCD signals D4-D7.
|
|
*/
|
|
if(type != hd44780::HD44780_IOcmd4bit)
|
|
{
|
|
write4bits( (value & 0x0F), type); // lower nibble, if not 4bit cmd
|
|
}
|
|
if(Wire.endTransmission()) // send buffered bytes to the expander
|
|
return(hd44780::RV_EIO);
|
|
|
|
return(hd44780::RV_ENOERR);
|
|
}
|
|
|
|
// iosetBacklight() - set backlight brightness
|
|
// Since dimming is not supported, any non zero value
|
|
// will turn on the backlight.
|
|
int iosetBacklight(uint8_t dimvalue)
|
|
{
|
|
if(!_bl) // backlight control?
|
|
return(hd44780::RV_ENOTSUP); // not backlight control support
|
|
|
|
// dimvalue 0 is backlight off any other dimvalue is backlight on
|
|
// configure backlight state mask according to active level
|
|
if(((dimvalue) && (_blLevel == HIGH)) ||
|
|
((dimvalue == 0) && (_blLevel == LOW)))
|
|
{
|
|
_blCurState = _bl;
|
|
}
|
|
else
|
|
{
|
|
_blCurState = 0;
|
|
}
|
|
Wire.beginTransmission(_addr);
|
|
if(_expType == I2Cexp_MCP23008)
|
|
{
|
|
Wire.write(9); // point to GPIO
|
|
}
|
|
Wire.write( _blCurState );
|
|
if(Wire.endTransmission())
|
|
return(hd44780::RV_EIO);
|
|
|
|
return(hd44780::RV_ENOERR); // all is good
|
|
}
|
|
|
|
// ================================
|
|
// === internal class functions ===
|
|
// ================================
|
|
|
|
// config() - save constructor parameters
|
|
void config(uint8_t i2c_addr, I2CexpType i2c_type, uint8_t rs, uint8_t rw, uint8_t en,
|
|
uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7,
|
|
uint8_t bl=0xff, uint8_t blLevel=0xff )
|
|
{
|
|
// Save away config data into object
|
|
_expType = i2c_type;
|
|
_addr = i2c_addr;
|
|
|
|
_rs = ( 1 << rs );
|
|
|
|
if(rw < 8)
|
|
_rw = (1 << rw);
|
|
else
|
|
_rw = 0; // no r/w control
|
|
|
|
_en = ( 1 << en );
|
|
|
|
// Initialise pin mapping
|
|
_d4 = ( 1 << d4 );
|
|
_d5 = ( 1 << d5 );
|
|
_d6 = ( 1 << d6 );
|
|
_d7 = ( 1 << d7 );
|
|
|
|
if(bl < 8)
|
|
_bl = ( 1 << bl );
|
|
else
|
|
_bl = 0; // no backlight control
|
|
_blLevel = blLevel;
|
|
|
|
// set default bl state to backlight on
|
|
// if no _bl control, the _blCurState values will also be set to zero
|
|
// so it doesn't turn on any other pins.
|
|
//
|
|
|
|
if(_bl && (blLevel == HIGH))
|
|
_blCurState = _bl;
|
|
else
|
|
_blCurState = 0;
|
|
}
|
|
|
|
// LocateDevice() - Locate I2C expander device instance
|
|
uint8_t LocateDevice(uint8_t instance)
|
|
{
|
|
uint8_t error, address;
|
|
uint8_t locinst = 0;
|
|
|
|
// 8 addresses for PCF8574 or MCP23008
|
|
for(address = 0x20; address <= 0x27; address++ )
|
|
{
|
|
Wire.beginTransmission(address);
|
|
error = Wire.endTransmission();
|
|
// chipkit stuff screws up if you do beginTransmission() too fast
|
|
// after an endTransmission()
|
|
// below 20us will cause it to fail
|
|
// ESP8286 needs to make sure WDT doesn't fire so we use delay()
|
|
// The delay(1) is overkill and not needed for other chips, but it won't
|
|
// hurt and the loop is only 8 addresses.
|
|
|
|
delay(1);
|
|
if(error == 0) // if no error we found something
|
|
{
|
|
if(locinst == instance)
|
|
{
|
|
#if 0
|
|
if(IdentifyIOexp(address) == I2Cexp_UNKNOWN) // if we can't identify it, keep looking
|
|
continue;
|
|
#endif
|
|
return(address);
|
|
}
|
|
locinst++;
|
|
}
|
|
}
|
|
// 8 addresses for PCF8574A
|
|
for(address = 0x38; address <= 0x3f; address++ )
|
|
{
|
|
Wire.beginTransmission(address);
|
|
error = Wire.endTransmission();
|
|
// chipkit stuff screws up if you do beginTransmission() too fast
|
|
// after an endTransmission()
|
|
// below 20us will cause it to fail.
|
|
// ESP8286 needs to make sure WDT doesn't fire so we use delay()
|
|
// The delay(1) is overkill and not needed for other chips, but it won't
|
|
// hurt and the loop is only 8 addresses.
|
|
|
|
delay(1);
|
|
if(error == 0) // if no error we found something
|
|
{
|
|
if(locinst == instance)
|
|
{
|
|
#if 0
|
|
if(IdentifyIOexp(address) == I2Cexp_UNKNOWN) // if we can't identify it, keep looking
|
|
continue;
|
|
#endif
|
|
return(address);
|
|
}
|
|
locinst++;
|
|
}
|
|
}
|
|
return(0); // could not locate expander instance
|
|
}
|
|
|
|
|
|
// IdentifyIOexp() - Identify I2C i/o expander device type
|
|
// Currently PCF8574 or MCP23008
|
|
I2CexpType IdentifyIOexp(uint8_t address)
|
|
{
|
|
int data;
|
|
I2CexpType chiptype;
|
|
|
|
/*
|
|
* Identify PCF8574 vs MCP23008
|
|
* On a PCF8574 1 bits turn on pullups and make the pin an input.
|
|
* and 0 bits set the output pin to drive 0.
|
|
* And a read always reads the port pins.
|
|
*
|
|
* Strategy:
|
|
* - Write 0xff to MCP23008 IODIR reg (location 0) puts pins in input mode
|
|
* - Point MCP23008 to IODIR register (location 0)
|
|
* - Read 1 byte
|
|
*
|
|
* On a MCP23008 the read will return 0xff because it will read the
|
|
* IODIR we just wrote
|
|
* On a PCF8574 we should read a 0 since we last wrote zeros to all the
|
|
* PORT bits
|
|
*
|
|
* NOTE:
|
|
* The values 0xff and zero were carefully chosen so as to not create a bus
|
|
* collision with the LCD data lines.
|
|
* On a PCF8574 the 0xff will put all the port pins into input mode and the
|
|
* the value 0x0 will ensure that the LCD is write mode even if the R/W
|
|
* pin is connected to a PCF8574 pin both of these will ensure that the
|
|
* PCF8574 is not ever driving a pin that
|
|
* is connected to pin that the LCD is also driving.
|
|
*
|
|
* On a MCP23008, the write of 0xff to IODIR will put the port into input
|
|
* mode.
|
|
*/
|
|
|
|
/*
|
|
* First try to write 0xff to MCP23008 IODIR
|
|
* On a PCF8574 this will end up writing 0 and then ff to output port
|
|
*/
|
|
Wire.beginTransmission(address);
|
|
Wire.write((uint8_t) 0); // try to point to MCP23008 IODR
|
|
Wire.write((uint8_t) 0xff); // try to write to MCP23008 IODR
|
|
Wire.endTransmission();
|
|
|
|
/*
|
|
* Now try to point MCP23008 to IODIR for read
|
|
* On a PCF8574 this will end up writing a 0 to the output port
|
|
*/
|
|
|
|
Wire.beginTransmission(address);
|
|
Wire.write((uint8_t) 0); // try to point to MCP23008 IODR
|
|
Wire.endTransmission();
|
|
|
|
/*
|
|
* Now read a byte
|
|
* On a MCP23008 we should read the 0xff we wrote to IODIR
|
|
* On a PCF8574 we should read 0 since the output port was set to 0
|
|
*/
|
|
Wire.requestFrom((int)address, 1);
|
|
data = Wire.read();
|
|
|
|
if(data == 0xff)
|
|
{
|
|
chiptype = I2Cexp_MCP23008;
|
|
}
|
|
else if(data == 0x00)
|
|
{
|
|
chiptype = I2Cexp_PCF8574;
|
|
}
|
|
else
|
|
{
|
|
chiptype = I2Cexp_UNKNOWN; // this shouldn't happen
|
|
}
|
|
return(chiptype);
|
|
}
|
|
|
|
/*
|
|
* autocfg8574() - automagically figure out the pcf8574 pin mappings
|
|
*
|
|
* This code can auto detect 6 different 8574 backpack pin mappings.
|
|
* This is all the known pin mappings.
|
|
* Note: it does fail on the SYDZ bapack by incorrectly picking the backlight
|
|
* active level because that backpack uses a pullup on the backlight transistor base.
|
|
* SYDZ backpacks will have to be manually configured.
|
|
*
|
|
* Overview of how this works:
|
|
*
|
|
* When you set the i/o port to input (set port to 0xff) and read
|
|
* the port, that the expander input pins with the en, rs, & di connections
|
|
* will all float high from the expander input pullups.
|
|
*
|
|
* Also,
|
|
* When there is no pullup/pulldown resistor on the backlight transistor base,
|
|
* the 8574 bl pin as an input will be pulled to the direction of the emitter.
|
|
* for active low backlights, the bl input pin will be high
|
|
* for active high backlights, the bl input pin will be low.
|
|
* When there is a pullup on the NPN base, it will still be pulled down low enough
|
|
* to read as a low as long as the emitter is wired directly to ground.
|
|
*
|
|
* NOTE: While normally wiring an i/o pin directly to a NPN base when the emitter
|
|
* is connected directly to ground would be bad when the i/o pin is driven high (it shorts),
|
|
* It is not an issue with the PCF8574 since the PCF8574 does not drive i/o pins.
|
|
* The PCF8574 will enable a pullup when the pin is set to "high". The path through
|
|
* the transistor base, out the emitter to ground will pull the signal down to read as a low.
|
|
*
|
|
* All known 8574 backpacks appear to use either the upper or the lower
|
|
* nibble for the data.
|
|
* All the devices that use the upper nibble use the same bits
|
|
* for rs (0), rw (1), and en (2), and backlight is bit 3
|
|
*
|
|
* The MAGIC is knowing how to use all this information to differenciate
|
|
* between backpacks.
|
|
*
|
|
* The key is to attempt to do a bit of intelligent probing & "guessing"
|
|
* We have to combine some initial observations of the pins when all signals
|
|
* have a weak pullup on them and then
|
|
* we have to actually guess which bit controls E, to try to
|
|
* to get the LCD to stop driving the data lines. If the guess was correct,
|
|
* since the 8574 has weak pullups, each data line will pull up.
|
|
* If not, then we continue on with some other probing checks & guesses.
|
|
*
|
|
* Also of importance is that if RS should go low, the read will pull from
|
|
* the status register instead of the data memory.
|
|
* So if RS was lowered instead of E, then the 4 data bits will not all
|
|
* float to 1s.
|
|
*
|
|
* So the summary:
|
|
* If bits 0-2 are all 1s and bits 4-7 are 1s when bit 2 is off,
|
|
* this determines that the upper 4 bits are lcd data lines and the lower 3 bits
|
|
* are the lcd control lines with bit 2 being the E signal.
|
|
* this means rs=0,rw=1,en=2,d4=4,d5=5,d6=6,d7=7,bl=3
|
|
*
|
|
* If bits 4-6 are all 1s and bits 0-3 are 1s when bit 4 is off
|
|
* this determines that the lower 4 bits are lcd data lines and the upper 3 bits
|
|
* are the lcd control lins with bit 4 being the E signal.
|
|
* this means rs=6,rw=5,en=4,d4=0,d5=1,d6=2,d7=3,bl=7
|
|
*
|
|
* If bits 4-6 are all 1s and bits 0-3 are 1s when bit 6 is off
|
|
* this determines that the lower 4 bits are lcd data lines and the upper 3 bits
|
|
* are the lcd control lins with bit 6 being the E signal.
|
|
* this means rs=4,rw=5,en=6,d4=0,d5=1,d6=2,d7=3,bl=7
|
|
*
|
|
* Anyting else, is "undetermined" - error
|
|
*
|
|
* Determiing backlight active level is easy as mentioned above.
|
|
*
|
|
* Auto active level detect fails when a FET is used.
|
|
* Auto active level detect will also fail when a pullup resistor is used on
|
|
* a NPN transistor base *AND* the BL anode is wired to the emitter instead wiring
|
|
* the BL cathode to the colector.
|
|
* With an FET there is no load betweent the 8574 bl pin and the FET gate,
|
|
* so there is no way to distinguish this from a PNP emitter pulling up a
|
|
* base pin.
|
|
* When using a NPN transistor with a pullup, the pullup on the base will
|
|
* not allow the base to be pulled in the direction of the emitter if the BL
|
|
* anode is wired to the emitter.
|
|
*
|
|
*
|
|
* As of now, the only known auto config failure is the SYDZ board since
|
|
* it uses a pullup on the base and wired the backlight to the emitter instead
|
|
* of the collector.
|
|
* Because of this, the code will incorrecly pick active low backlight control.
|
|
* Modifying the h/w to disable the pullup can be done, but is difficult due
|
|
* to the way R1 resistors are used and the PCB layout.
|
|
* It requires cuting a trace and dead bugging
|
|
* a new resistor to connect to the base of the transistor.
|
|
*
|
|
* As a result the SYDZ backpack cannot use autoconfiguration.
|
|
*/
|
|
|
|
int autocfg8574()
|
|
{
|
|
uint8_t data, data2;
|
|
uint8_t rs, rw, en, d4, d5, d6, d7, bl, blLevel;
|
|
|
|
// First put a 0xff in the output port
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write((uint8_t) 0xff);
|
|
Wire.endTransmission();
|
|
|
|
// now read back from the port
|
|
|
|
Wire.requestFrom((int)_addr, 1);
|
|
data = Wire.read();
|
|
|
|
// Turn off bit2 to attempt to see if en is bit 2,
|
|
// if it is, it should change all lcd data bits to 1s
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write((uint8_t) (~(1 << 2)) );
|
|
Wire.endTransmission();
|
|
|
|
// read back data
|
|
Wire.requestFrom((int)_addr, 1);
|
|
data2 = Wire.read();
|
|
|
|
// If lower 3 bits are high and all 4 upper bits are high after clearing bit 2
|
|
// then lower bits are control bits and upper bits are data bits
|
|
// and bit2 was en.
|
|
|
|
if( ((data & 0x7) == 7) && ((data2 & 0xf0) == 0xf0))
|
|
{
|
|
rs = 0; rw = 1; en = 2; d4 = 4; d5 = 5; d6 = 6; d7 = 7; bl = 3;
|
|
|
|
if(data & 0x8) // bit 3 high so active low
|
|
{
|
|
// LCM1602
|
|
// 0,1,2,4,5,6,7,3, LOW
|
|
blLevel = LOW;
|
|
}
|
|
else
|
|
{
|
|
// YwRobot/DFRobot/SainSmart/funduino
|
|
// 0,1,2,4,5,6,7,3, HIGH
|
|
blLevel = HIGH;
|
|
}
|
|
}
|
|
else if((data & 0x70) == 0x70)
|
|
{
|
|
|
|
// now we have either Electro fun LCDXIO or MJKDZ board
|
|
// both use bit 7 for backlight control
|
|
|
|
// Turn off the en bit which should change the data bits
|
|
// bit 4 on the mjkdz is en so we try that bit
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write((uint8_t) (~(1 << 4)) );
|
|
Wire.endTransmission();
|
|
|
|
// read back data
|
|
Wire.requestFrom((int)_addr, 1);
|
|
data2 = Wire.read();
|
|
|
|
// look at data bits and see if they changed
|
|
// if they changed to 0xf, then en was bit 4 and it is mjdkz
|
|
// this will happen since when E is low the LCD is not
|
|
// driving the bus pins so they will read as 1s since the
|
|
// pullups in the PCF8574 will pull them up.
|
|
if((data2 & 0xf) == 0xf)
|
|
{
|
|
// mjkdz
|
|
rs = 6; rw = 5; en = 4; d4 = 0; d5 = 1; d6 = 2; d7 = 3; bl = 7;
|
|
}
|
|
else
|
|
{
|
|
// electrofun LCDXIO
|
|
rs = 4; rw = 5; en = 6; d4 = 0; d5 = 1; d6 = 2; d7 = 3; bl = 7;
|
|
}
|
|
if(data & 0x80) // bit 7 high so active low
|
|
{
|
|
blLevel = LOW;
|
|
}
|
|
else
|
|
{
|
|
blLevel = HIGH;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// couldn't figure it out
|
|
return(hd44780::RV_ENOTSUP);
|
|
}
|
|
config(_addr, _expType, rs, rw, en, d4, d5, d6, d7, bl, blLevel);
|
|
return(0);
|
|
}
|
|
|
|
/*
|
|
* autocfgMCP23008() - automagically figure out the mcp23008 pin mappings
|
|
*
|
|
* - BOARD_ADAFRUIT292 - adafruit #292 I2C/"SPI"
|
|
* - BOARD_WIDEHK - WideHK
|
|
* - BOARD_LCDPLUG - Jeelabs LCDPlug
|
|
* - BOARD_XXX - unknown vendor
|
|
*
|
|
* WARNING WARNING WARNING:
|
|
* Because the MCP23008 has high drive and sink capabability on its i/o port
|
|
* pins, it is possible that if this auto detection fails, that there could
|
|
* be a bus contention between the MCP23008 and the LCD data lines.
|
|
* Should this occur, it is possible that either one or both could be damaged.
|
|
*
|
|
* currently this code will not detect the LCDPLUG as it is similar to
|
|
* adafruit #292 board.
|
|
*
|
|
* On the #292 board, the combination of the pullups with the 595 SR chip on
|
|
* the data bus, and the r/w line hard wired to gnd, the data lines are all
|
|
* pulled high, when the port is in input mode with pullups enabled.
|
|
* GP0 is not stable without pullups as it is not connected to anything.
|
|
* GP7 will pull down from the emitter of the NPN transistor.
|
|
* It has been seen that the combination of some #292 backpacks and LCD panels
|
|
* have a backlight that will alter the voltage to the p7 pin just eough that
|
|
* it doesn't read as a low when the pullup is enabled.
|
|
* So at least for now, the pullup on bit 7 will not be enabled
|
|
* during the probing.
|
|
*
|
|
* On the WIDEKH board,
|
|
* GP6 should pull down from the emitter of the NPN transistor.
|
|
* GP5 is wired to LCD RW signal
|
|
*
|
|
* On the BOARD_XXXX,
|
|
* GP1 should pull down from the emitter of the NPN transistor.
|
|
*
|
|
* On the LCDPLUG,
|
|
* GP7 will pull down from the emitter of the NPN transistor.
|
|
* LCD RW signal is hardwired to ground.
|
|
*
|
|
* Given that the LCDPLUG is the only other board that uses GP7 for the
|
|
* backlight control and all the known boards active HIGH control and GP7
|
|
* is never used for a data line,
|
|
* it is safe to assume that if GP7 is low then the board is either
|
|
* LCDPLUG or ADAFRUIT292.
|
|
* For now, if GP7 is low then assume the board is ADAFRUIT292
|
|
*
|
|
* Other untested probes:
|
|
* If GP6 is low then the board is a WIDEHK
|
|
* if GP1 is low then the board is the BOARD_XXX board
|
|
* The BOARD_XXX may not work as GP1 is used for D5 on WIDEHK and LCDPLUG
|
|
*
|
|
*
|
|
*/
|
|
int autocfgMCP23008()
|
|
{
|
|
uint8_t data;
|
|
uint8_t rs, en, d4, d5, d6, d7, bl;
|
|
uint8_t blLevel;
|
|
|
|
/*
|
|
* Now set up output port
|
|
* Make no assumptions as to the state of IOCON BYTE mode
|
|
*/
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write((uint8_t)0); // point to IODIR
|
|
Wire.write(0xff); // all pins inputs
|
|
Wire.endTransmission();
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(6); // point to GPPU
|
|
// turn on pullups, except bit 7 which is backlight transistor on #292
|
|
Wire.write(0x7f);
|
|
Wire.endTransmission();
|
|
|
|
/*
|
|
* read from the GPIO port
|
|
*/
|
|
|
|
Wire.beginTransmission(_addr);
|
|
Wire.write(9); // point to GPIO
|
|
Wire.endTransmission();
|
|
Wire.requestFrom((int)_addr, 1);
|
|
data = Wire.read();
|
|
|
|
blLevel = HIGH; // known boards are active HIGH bl
|
|
|
|
if(data == 0x7f) // bit 7 low, and other control and data lines high
|
|
{
|
|
// board is either ADAFRUIT292 or LCDPLUG
|
|
// for now, assume ADAFRUIT292
|
|
rs = 1;
|
|
// rw = 0; // not used
|
|
en = 2;
|
|
d4 = 3;
|
|
d5 = 4;
|
|
d6 = 5;
|
|
d7 = 6;
|
|
bl = 7;
|
|
|
|
} else if(!(data & (1<<6))) // bit 6 low (untested)
|
|
{
|
|
// WIDEHK
|
|
rs = 4;
|
|
// rw = 5; // not used
|
|
en = 7;
|
|
d4 = 0;
|
|
d5 = 1;
|
|
d6 = 2;
|
|
d7 = 3;
|
|
bl = 6;
|
|
|
|
} else if(!(data & (1<<1))) // bit 1 low (untested)
|
|
{
|
|
// BOARD_XXX
|
|
rs = 7;
|
|
// rw = 0; // not used
|
|
en = 6;
|
|
d4 = 5;
|
|
d5 = 4;
|
|
d6 = 3;
|
|
d7 = 2;
|
|
bl = 1;
|
|
|
|
}
|
|
else
|
|
{
|
|
// could not identify board
|
|
return(hd44780::RV_ENOTSUP);
|
|
}
|
|
// currently writes are disabled for all MCP23008 devices
|
|
config(_addr, _expType, rs, 0xff, en, d4, d5, d6, d7, bl, blLevel);
|
|
return(0);
|
|
}
|
|
|
|
|
|
// write4bits - send a nibble to the LCD through i/o expander port
|
|
void write4bits(uint8_t value, hd44780::iotype type )
|
|
{
|
|
uint8_t gpioValue = _blCurState;
|
|
|
|
// convert the value to an i/o expander port value
|
|
// based on pin mappings
|
|
if(value & (1 << 0))
|
|
gpioValue |= _d4;
|
|
|
|
if(value & (1 << 1))
|
|
gpioValue |= _d5;
|
|
|
|
if(value & (1 << 2))
|
|
gpioValue |= _d6;
|
|
|
|
if(value & (1 << 3))
|
|
gpioValue |= _d7;
|
|
|
|
|
|
if(type == hd44780::HD44780_IOdata)
|
|
{
|
|
gpioValue |= _rs; // set RS high to send to data reg
|
|
}
|
|
|
|
// Cheat here by raising E at the same time as setting control lines
|
|
// This violates the spec but seems to work realiably.
|
|
Wire.write(gpioValue |_en); // with E HIGH
|
|
Wire.write(gpioValue); // with E LOW
|
|
}
|
|
|
|
}; // end of class definition
|
|
|
|
#endif
|