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