| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193 |
- // vi:ts=4
- // ---------------------------------------------------------------------------
- // hd44780_I2Cexp.h - hd44780_I2Cexp i/o subclass for hd44780 library
- // Copyright (c) 2013-2018 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,blpol);
- // 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 lcdcanned-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
- //
- // 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, blpol]
- #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
- }
- // -- 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 ); // send 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())
- 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.
- *
- * 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
- Wire.write(0xff); // turn on pullups
- 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
|