// vi:ts=4 // ---------------------------------------------------------------------------- // I2Cexpdiag - i2c LCD i/o expander backpack diagnostic tool // Created by Bill Perry 2016-06-17 // Copyright 2016-2020 - Under GPL v3 // // ---------------------------------------------------------------------------- // // This file is part of the hd44780 library // // I2Cexpdiag 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, either version 3 of the License, or // (at your option) any later version. // // I2Cexpdiag 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 I2Cexpdiag. If not, see . // // ----------------------------------------------------------------------- // Description: // ----------------------------------------------------------------------- // Sketch to verify configuation & operation of hd44780 LCDs based // on the Hitachi HD44780 and compatible chipsets using I2C extension // backpacks that use a simple I2C i/o expander chip. // // WARNING: // Use caution when using 3v only processors like arm and ESP8266 processors // when interfacing with 5v modules as not doing proper level shifting or // incorrectly hooking things up can damage the processor. // // It requires the hd44780 library which can be installed using the // Arduino IDE library manager starting with IDE 1.6.2 or can found here: // https://github.com/duinoWitchery/hd44780 // // It is recommended to run the sketch with a serial monitor // to be able to see the diagnostic messages, in case there are any issues. // // While the sketch will work without a serial monitor connection, // on some USB based boards like the Teensy or Leonardo, // if there is no USB serial port connection, // the code will wait up to 3 seconds for a USB serial connection to be // established before starting. // // The serial port is configured to 9600 baud. // // The sketch will perform checks on the i2c bus as well as mutiple LCD // expander based devices if multiple devices are connected to the i2c bus. // Information is provided on the LCDs as well as sent to the serial monitor. // // ----------------------------------------------------------------------- // To use this sketch: // ----------------------------------------------------------------------- // // 1. install hd44780 library // the hd44780 library can be installed using the Arduino IDE library manager // starting with IDE 1.6.2 or can found here: // https://github.com/duinoWitchery/hd44780 // // 2. Hookup up i2c i/o expander backpack based LCD devices // and only the i2c LCD backpack(s) if possible. // Currently supports PCF8574 or MCP32008 devices // (can test more than one LCD device at a time) // WARNING: 3v only systems like ARM/Teensy3/ESP8266 devices will need to take // precautions on SDA and SCL connections if using 5V I2C devices. // Level shifters are recommended and should be used. // It is possible to cheat and hook the pullups to 3v instead of 5v. // If doing this, there can be no other pullups that are conected to 5V // on any device that is connected to the bus. // Many of the backpacks ind other devices nclude 5v pullups on them // so if pullups to 3v are being used to cheat, the backpack or other // device will likely require hardware modification. // Failure to do this 3v pullup "cheat" correctly can damage the processor // which is why using actual level shifters is recommended. // // 3. Connect to the board using the IDE serial monitor; set the baud rate to 9600 // While using the serial monitor is not required, additional information // will be sent to the serial monitor. // NOTE: If you have IDE older than 1.6.6 you will have to connect to the // serial monitor *after* the upload completes as the upload will // not reconnect after the upload completes. // // It is recommended to connect to the serial monitor *before* you upload the // sketch so that the serial monitor is reconnected as soon as the sketch is // uploaded to avoid character loss. // This is particularly helpful on boards that use a USB virtual serial port // like leonardo. // // 4. compile and upload sketch // Note: // if you have a slow LCD display you may need to modify the // execution times to be longer than the hd44780 defaults. // Scroll down to modify the defines: LCD_CHEXECTIME, LCD_INSEXECTIME // // ----------------------------------------------------------------------- // Expected behavior // ----------------------------------------------------------------------- // Highlights of sketch behavior // - display information about the enviroment on the serial monitor. // - probe the i2c bus to check for external pullup resistors // - scan the i2c bus and show all devices found // NOTE: Arduino 2560 boards have 10k external pullups on the arduino board // Arduino DUE V3 boards have 1.5k external pullups on the arduino board // - attempt to initalize each LCD device detected // - attempt to blink backlight of each initalized LCD 3 times // - display information about each each initialized LCD device // this includes i2c address and configuration information // and information about missing pullups. // Note that pins work differently for some of the ESP8266 boards. // The ESP8266 core use GPIO bit numbers and a few boards use Dn or Pn defines // to do do pin # mapping to bit numbers. // The code attempts to accurately report that information. // - test internal LCD display memory // LCD expander must be able to control r/w line // - perform a backlight blink test // - drop into a loop and display the system uptime on each display // *P on the display indicates missing external pullups // // If the sketch cannot determine any usable LCD devices // the code will fall into a fatal error and blink out an error code: // Error codes: // [1] no i2c devices found on i2c bus // [2] no working LCD devices // [3] i2c bus is not usable // // ----------------------------------------------------------------------- // Also note: // With respect to the the jumper on certain backpacks: // On the bacpacks Ive seen so far, it controls the backlight control. // Depending on the backpack, it can // - force the backlight on // - force the backlight off // - allow backlight control by PCF8574 // // So you may have to experiment with the jumper in/out to be able // to see anything on the display if you have a display that uses // light pixels on a dark background. // // ----------------------------------------------------------------------- // NOTE: // This sketch is a diagnostic tool, as such, it is not an example of // library usage. // It uses internal library information and APIs that are not needed // and should not normally used by sketches. // Sketches that use these undocumented capabilities may not work correctly // in future releases // ----------------------------------------------------------------------- // // History // 2020.06.16 bperrybap - report begin() status when it fails // 2020.06.03 bperrybap - added SDA/SCL pin output decodes for STM32 platform // 2020.05.18 bperrybap - reduced defualt max displays to 4 to save memory // 2020.05.18 bperrybap - hack workaround for RogerClarks STM32 cores // 2020.05.14 bperrybap - check for F_CPU define // 2020.05.13 bperrybap - removed ifdef check for INPUT_PULLUP // 2020.03.28 bperrybap - tweak for ESP32 core // 2019.07.28 bperrybap - clarified define to disable ESP8266 specific pin // decoding // 2018.10.16 bperrybap - better shorted pin testing // 2018.06.17 bperrybap - check for SDA and SCL shorted together // 2018.03.23 bperrybap - bumped default instruction time to 38us // 2016.12.25 bperrybap - updates for ESP8266 // 2016.08.07 bperrybap - added lcd memory tests // 2016.07.27 bperrybap - added defines for setting execution times // 2016.06.17 bperrybap - initial creation // // @author Bill Perry - bperrybap@opensource.billsworld.billandterrie.com // ----------------------------------------------------------------------- #include #include #include // i2c expander i/o class header // ============================================================================ // user configurable options below this point // ============================================================================ // Uncomment and use this line instead of the one below if you have a SYDZ backpack //hd44780_I2Cexp lcd[1]={{I2Cexp_ADDR_UNKNOWN, I2Cexp_BOARD_SYDZ}}; // to run on a single SYDZ based backpack // set maximum number of displays to auto locate & configure // this can be any number between 1 and 16 // this was reduced from 16 to allow running on AVRs with smaller RAM hd44780_I2Cexp lcd[4]; // All displays will be assumed to be 16x2 // Even if display is larger the sketch should still work correctly const int LCD_ROWS = 2; const int LCD_COLS = 16; // If code has issues compiling for ESP8266 / ESP32 cores, // and/or breaks in printDigitalPin() function, // turn on this define to disable // ESP specific pin decoding //#define I2CEXPDIAG_CFG_NO_DECODE_ESPXXXXPINS // if you have slow displays uncomment these defines // to override the default execution times. // CHEXECTIME is the execution time for clear and home commands // INSEXECTIME is the execution time for everything else; cmd/data // times are in Us // NOTE: if using, you must enable both // Although each display can have seperate times, these values will be used // on all displays. //#define LCD_CHEXECTIME 2000 //#define LCD_INSEXECTIME 38 // ============================================================================ // End of user configurable options // ============================================================================ // for now create SDA and SCL defines for chipkit boards, as it is missing // note: this should continue to work if they eventually add these defines/const values #if !defined(SDA) || !defined(SCL) #if defined(_DTWI0_SDA_PIN) && defined(_DTWI0_SCL_PIN) #define SDA _DTWI0_SDA_PIN #define SCL _DTWI0_SCL_PIN #endif #endif // Create SDA and SCL defines for RogerClark's STM32 platform. // https://github.com/rogerclarkmelbourne/Arduino_STM32 // Note, this is total BULLSHIT as I offered a fix for this and he refused to // even accept this as an issue. // https://github.com/rogerclarkmelbourne/Arduino_STM32/issues/777 // Roger's platform does have SDA and SCL defines but they are currently // in SoftWire.h so they only exist when SoftWire.h is included. // While they should always exist, I made an alternative // suggestion to move them to utility/WireBase.h so at least they // would exist when either Wire.h or SoftWire.h was included. // // This hack includes SoftWire.h on that platform just to get the symbols // NOTE: // there is no Wire library support in Roger's STM32F2 core #if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) #include #endif /* * Nasty kludges for @#@#@ AVR progmem CRAP * PSTR() macro uses static char instead of const char * This matters on C++ so we can't use const on our progmem strings * because you can't mix const and non const in the same section. * It could be declared as "static" instead of "const" or * alternatively you can use a different section. * But we still need to redefine PROGMEM to keep from getting warnings * for for each and every use of F() and PSTR() macros on older versions of the IDE. */ #if defined(__AVR__) #include #ifdef PROGMEM #undef PROGMEM #define PROGMEM __attribute__((section(".progmem.data"))) #endif #define PROGMEMDIAG __attribute__((section(".progmem.diag"))) #define P(name) const char name[] PROGMEMDIAG // declare a const string in AVR Progmem #else // The rest of the world is so much simpler and normal #define P(name) const char name[] // declare a const string #endif /* * declare some horizonal 'line' strings in program memory * so they aren't duplicated on each use. */ P(_hline) = "--------------------------------------------------------------------"; P(_hstar) = "********************************************************************"; P(_PASSED) = "PASSED"; P(_FAILED) = "FAILED"; #ifndef BIT #define BIT(_bitnum) (1 << _bitnum) #endif #ifdef __AVR__ #define hline (const __FlashStringHelper *)_hline #define hstar (const __FlashStringHelper *)_hstar #define PASSED (const __FlashStringHelper *)_PASSED #define FAILED (const __FlashStringHelper *)_FAILED #else #define hline _hline #define hstar _hstar #define PASSED _PASSED #define FAILED _FAILED #endif #define MAX_ERRORS 16 #define DEFPROMPT ((const char *) 0) int NumLcd; // number of LCD displays found. uint16_t WorkingLCD = 0; // bitmap of LCDs that are "working" // macros to process working lcd info #define isWorkingLCD(_n) (WorkingLCD & BIT(_n)) #define setWorkingLCD(_n) WorkingLCD |= BIT(_n) #define clrWorkingLCD(_n) WorkingLCD &= ~BIT(_n) #define anyWorkingLCD (WorkingLCD) // non zero if there are any working LCDs // convert a define to a string #define define2str(s) _str(s) #define _str(...) #__VA_ARGS__ void setup() { int nopullups; // give lots of time to allow the main power to ramp up and // allow all the hardware time settle // this is because the procesors often start running before full VCC // has been achieved. delay(100); Serial.begin(9600); #if (ARDUINO > 101) do { // wait on serial port to be ready but timout out after 5 seconds // this is for sytems that use virtual serial over USB. if(millis() > 5000) // millis starts at 0 after reset break; delay(10); // easy way to allow some cores to call yield() } while(!Serial); #endif Serial.println(); Serial.println(hstar); Serial.println(F("Serial Initialized")); Serial.println(hline); Serial.println(F("I2CexpDiag - i2c LCD i/o expander backpack diagnostic tool")); #ifdef HD44780_VERSIONSTR Serial.println(hline); Serial.print(F("hd44780 lib version: ")); Serial.println(HD44780_VERSIONSTR); #endif #if ARDUINO < 10605 // wait 3 seconds on older IDEs // to allow users some time to manually start monitor delay(3000); #endif Serial.println(hline); showSystemConfig(); Serial.println(hline); nopullups = i2cpulluptest(); if(nopullups < 0) { Serial.println(F("I2C bus not usable")); fatalError(3); // this never returns } Serial.println(hline); Wire.begin(); if(!showI2Cdevices()) // show all i2c devices on bus { Serial.println(F("No I2C devices found")); fatalError(1); // this never returns } Serial.println(hline); Serial.print(F("Scanning i2c bus for all lcd displays (")); Serial.print(sizeof(lcd)/sizeof(hd44780_I2Cexp)); Serial.println(F(" max)")); /* * Locate all the displays by attempting to intialize each one */ for(NumLcd = 0; NumLcd < (int) (sizeof(lcd)/sizeof(hd44780_I2Cexp)); NumLcd++) { char buf[16]; int status; // set custom exectution times if configured #if defined(LCD_CHEXECTIME) && defined(LCD_INSEXECTIME) lcd[NumLcd].setExecTimes(LCD_CHEXECTIME, LCD_INSEXECTIME); #endif // If begin fails, then assume we have no more displays if((status = lcd[NumLcd].begin(LCD_ROWS, LCD_COLS)) != 0) { if(NumLcd == 0) { Serial.print("LCD 0 begin() failed: "); Serial.println(status); } break; } setWorkingLCD(NumLcd); // mark LCD as "working" Serial.print(F(" LCD at address: ")); Serial.print(F("0x")); Serial.print(lcd[NumLcd].getProp(hd44780_I2Cexp::Prop_addr), HEX); Serial.print(F(" | config: ")); Serial.print(lcdConfigStr(buf, lcd[NumLcd])); Serial.print(F(" | R/W control: ")); // it takes r/w control to read LCD status // assume if reading status fails, no r/w control if(lcd[NumLcd].status() < 0) Serial.print(F("No")); else Serial.print(F("Yes")); Serial.println(); // attempt to blink backlight 3 times for(int i = 0; i < 3; i++) { lcd[NumLcd].noBacklight(); // turn off backlight delay(150); lcd[NumLcd].backlight(); // turn on backlight delay(200); } } if(!NumLcd) { Serial.println(F("No working LCD devices")); fatalError(2); // this never returns } Serial.print(F("Total LCD devices found: ")); Serial.println(NumLcd); Serial.println(hline); Serial.println(F("LCD Display Memory Test")); for(int n = 0; n < NumLcd; n++) { int errors, lcdstatus; Serial.print(F("Display: ")); Serial.println(n); // check for r/w control // by attempting to read lcd status if((lcdstatus = lcd[n].status()) >= 0) { if(lcdstatus & 0x80) // check for stuck BUSY status { Serial.println(F(" LCD stuck BUSY status")); clrWorkingLCD(n); // mark LCD as no longer "working" continue; } Serial.print(F(" Walking 1s data test:\t")); // try a few different locations which also tests addressing errors = lcdw1test(lcd[n], 0); errors += lcdw1test(lcd[n], 0x40); errors += lcdw1test(lcd[n], 0x10); errors += lcdw1test(lcd[n], 0x50); if(errors) { Serial.print(FAILED); } else { Serial.print(PASSED); } Serial.println(); Serial.print(F(" Address line test:\t")); errors = lcdAddrLineTest(lcd[n], 0x00, 0x27); // 1st block of memory errors += lcdAddrLineTest(lcd[n], 0x40, 0x67); // 2nd block of memory if(errors) { Serial.print(FAILED); clrWorkingLCD(n); // mark LCD as no longer "working" } else { Serial.print(PASSED); } #if 0 Serial.println(); // quick/short test of DDRAM // note: avoid and in value range since those // are currently dropped by write() // Also, the hd44780 has 80 bytes of ram but it is not contiguous. // The 1st 40 bytes is 0x00 - 0x27 // the 2nd 40 bytes is 0x40 - 0x67 // attempting to use 0x28 - 0x3f or 0x68-0x7f will fail as there // technically is no memory there so it maps internally to other // locations and a memory test would fail. // // this quick test will test a few values on the 2nd chunk of memory. // Serial.print(F(" Quick DDRAM memory test: ")); errors = lcdDDRAMtest(lcd[n], 0x40, 0x67, '0', '9'); if(errors) Serial.print(FAILED); else Serial.print(PASSED); #endif if(errors) { Serial.println(); Serial.println(F("Memory test failures are usually due to poor solder connections")); Serial.println(F("Most common cause is poor solder joints on pins soldered to the LCD")); } } else { Serial.print(F(" (R/W control not supported)")); } Serial.println(); } Serial.println(hline); if(!anyWorkingLCD) { Serial.println(F("No working LCD devices")); fatalError(2); // this never returns } for(int n = 0; n < NumLcd; n++) { char buf[16]; if(!(isWorkingLCD(n))) continue; //skip over non working LCDs //showLCDconfig(Serial, lcd[n]); /* * Label the display with its instance number * i2c address and config info on 2nd line */ lcd[n].clear(); lcd[n].setCursor(0, 0); lcd[n].print(F("LCD:")); lcd[n].print(n); if(nopullups) { lcd[n].setCursor(5,0); lcd[n].print(F(" NoPullups")); } lcd[n].setCursor(0, 1); lcd[n].print(F("0x")); lcd[n].print(lcd[n].getProp(hd44780_I2Cexp::Prop_addr), HEX); lcd[n].print(','); lcd[n].print(lcdConfigStr(buf, lcd[n])); } Serial.println(F("Each working display should have its backlight on")); Serial.println(F("and be displaying its #, address, and config information")); Serial.println(F("If all pixels are on, or no pixels are showing, but backlight is on, try adjusting contrast pot")); Serial.println(F("If backlight is off, wait for next test")); delay(10000); Serial.println(hline); Serial.println(F("Blinking backlight test: to verify BL level autodetection")); Serial.println(F("If backlight is mostly off but")); Serial.println(F("you briefly see \"BL Off\" on display with backlight on,")); Serial.println(F("then the library autodetected incorrect BL level")); Serial.println(F("and the library cannot autoconfigure the device")); delay(2000); // blink display backlight 3 times // print "BL Off" on TOP line when display is off // A SYDZ board will light up when the display backlight is turned off // for(int i = 0; i < 3; i++) { // turn off backlight and print "BL Off" on all displays // i2c boards like SYDZ will turn on backlight as library // cann't properly determine backlight level on that backpack for(int n = 0; n < NumLcd; n++) { lcd[n].noBacklight(); lcd[n].setCursor(5, 0); lcd[n].print(F(" BL Off ")); } delay(2000); // erase "BL Off" text from on all displays for(int n = 0; n < NumLcd; n++) { lcd[n].setCursor(5, 0); if(nopullups) { lcd[n].print(F(" NoPullups")); } else { lcd[n].print(F(" ")); } } // wait some time for pixels to turn off // before turning backlights back on. // (liqudcrystal is actually really slow at transitions) delay(250); for(int n = 0; n < NumLcd; n++) { lcd[n].backlight(); } delay(500); } Serial.println(hline); // relabel all displays with their i2c address and pullup status // and erase 2nd line on all displays for(int n = 0; n < NumLcd; n++) { if(!isWorkingLCD(n)) continue; lcd[n].setCursor(0,0); lcd[n].print(F("LCD:")); lcd[n].print(n); lcd[n].print(F(" (0x")); lcd[n].print(lcd[n].getProp(hd44780_I2Cexp::Prop_addr), HEX); lcd[n].print(')'); if(nopullups) { lcd[n].print(F(" *P")); } lcd[n].setCursor(0, 1); for(int c= 0; c< LCD_COLS; c++) lcd[n].write(' '); } Serial.println(F("Displaying 'uptime' on all displays")); Serial.println(hline); } void loop() { static unsigned long lastsecs = -1; // pre-initialize with non zero value unsigned long secs; secs = millis() / 1000; // see if 1 second has passed // so the display is only updated once per second if(secs != lastsecs) { lastsecs = secs; // keep track of last seconds // write the 'uptime' to each working display for(int n = 0; n < NumLcd; n++) { if(!isWorkingLCD(n)) continue; // skip over non working displays // set the cursor to column 0, line 1 // (note: line 1 is the second row, counting begins with 0): if(lcd[n].setCursor(0, 1)) { clrWorkingLCD(n); // mark display as no longer working // output uptime and error message to serial port PrintUpTime(Serial, secs); Serial.print(F(" - Error on Display: ")); Serial.println(n); } else { // print uptime on lcd device: (time since last reset) PrintUpTime(lcd[n], secs); } if(!anyWorkingLCD) { Serial.println(hstar); Serial.println(F("No working LCD devices")); PrintUpTime(Serial, secs); Serial.print(" - Fatal error: "); Serial.println(2); fatalError(2); // this never returns } } } } // PrintUpTime(outdev, secs) - print uptime in HH:MM:SS format // outdev - the device to send output // secs - the total number of seconds uptime void PrintUpTime(Print &outdev, unsigned long secs) { unsigned int hr, mins, sec; // convert total seconds to hours, mins, seconds mins = secs / 60; // how many total minutes hr = mins / 60; // how many total hours mins = mins % 60; // how many minutes within the hour sec = secs % 60; // how many seconds within the minute // print uptime in HH:MM:SS format // Print class does not support fixed width formatting // so insert a zero if number smaller than 10 if(hr < 10) outdev.write('0'); outdev.print((int)hr); outdev.write(':'); if(mins < 10) outdev.write('0'); outdev.print((int)mins); outdev.write(':'); if(sec < 10) outdev.write('0'); outdev.print((int)sec); } // printDigitalPin(outdev, pin) - print digital pin # // outdev - the device to send output // pin - pin number // // This function will also print the digital pin number "symbol name" as // it can be different from the naked constant. // So far the only core where this happens is the ESP8266. // It is messy and ugly and won't be 100% accurate at // detecting the use of Dn or Pn mappings used by the ESP8266 variants. // For ESP8266 modules it will print GPIO# and if it detects a variant // that uses Dn or Pn mapping will print the associated symbol. // If more than a single symbol is used for the pin value, it will print // all symbols associated with the pin value. // // Details: // // ESP8266 core does not use naked pin#s as Arduino pin numbers like other // cores. // in the ESP8266 core, naked constants are bit numbers in the GPIO output // port register. While this makes things MUCH faster, This can make things // confusing since some variants (notably WeMos D1 and NodeMCU) decided to include // Dn defines to do pin to bit mapping. // This means that using pin N is the not same as using Dn with those // variants. // i.e. D5 may not be the same pin as 5 // // This makes things very difficult as there is no direct way to map the // naked constant values back to the Dn symbol names for those particular // variants. This is very unfortunate since boards that use Dn pin mapping // print Dn numbers on them rather than the GPIO bit# and for most pins, // Dn is the not the same as N. // This mismatched mapping is usually the case for Pn mapping variants for the i2c // pins. // Most variants use GPIO 4 for SDA and GPIO 5 for SCL and do not use or/ // include Dn pin mapping defines. // The Wemos D1 and NodeMCU board variants have really made a mess of things. // Those variants include Dn pin mappings and D4 is sometimes 4 and D5 is // not 5. // And some boards have a D14 and D15 labels on the I2C SDA and SCL header // pins that map to GPIO 4 and GPIO 5. On those boards D14 is the same as // D4 and D15 is the same as D3 // void printDigitalPin(Print &outdev, int pin) { // On all cores, print the pin value outdev.print(pin); #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) // print GPIO# for all ESP8266 & ESP32 boards since those cores uses GPIO bit numbers // as the pin number. outdev.print(F(" (GPIO")); outdev.print(pin); outdev.print(')'); #endif #if !defined(I2CEXPDIAG_CFG_NO_DECODE_ESPXXXXPINS) // this next part is ugly. // It is trying to convert the GPIO bit number back to a pin define name // It is dependent on the boards.txt file which is subject to change // it also makes certain assumptions about the symbols in the variant // pins_arduino.h file // based on the board define in the boards.txt file all of which is // also subject to change. // In older ESP8266 cores, two different WeMos boards // "WeMos D1 R1" and "WeMos D1 R1 & mini" use the same board define // ESP8266_WEMOS_D1MINI so there is no way to tell them apart. // Bug filed: https://github.com/esp8266/Arduino/issues/4303 // and fixed. New define is ESP8266_WEMOS_D1R1 // When compiled on older ESP8266 cores, this code will end up only look for // Dn pins 0-8 on D1R1 boards. // luckily i2c pins fall inside of this range on these variants. // These boards have D0 to D8 symbols #if defined(ARDUINO_ESP8266_WEMOS_D1MINI) || defined(ARDUINO_ESP8266_WEMOS_D1MINIPRO) || \ defined(ARDUINO_ESP8266_WEMOS_D1MINILITE) || defined(ARDUINO_ESP8266_NODEMCU) || \ defined(ARDUINO_ESP8266_WEMOS_D1R1) if(pin == D0) outdev.print(F(" D0")); if(pin == D1) outdev.print(F(" D1")); if(pin == D2) outdev.print(F(" D2")); if(pin == D3) outdev.print(F(" D3")); if(pin == D4) outdev.print(F(" D4")); if(pin == D5) outdev.print(F(" D5")); if(pin == D6) outdev.print(F(" D6")); if(pin == D7) outdev.print(F(" D7")); if(pin == D8) outdev.print(F(" D8")); #endif // boards that support D9 and D10 // Note that older ESP8266 cores don't define ARDUINO_ESP8266_WEMOS_D1R1 // so boards using older cores won't see these pins. #if defined(ARDUINO_ESP8266_NODEMCU) || defined(ARDUINO_ESP8266_WEMOS_D1R1) if(pin == D9) outdev.print(F(" D9")); if(pin == D10) outdev.print(F(" D10")); #endif // Boards that support D10 to D15 // Note that older ESP8266 cores don't define ARDUINO_ESP8266_WEMOS_D1R1 // so those boards won't see these pins. #if defined(ARDUINO_ESP8266_WEMOS_D1R1) if(pin == D11) outdev.print(F(" D11")); if(pin == D12) outdev.print(F(" D12")); if(pin == D13) outdev.print(F(" D13")); if(pin == D14) outdev.print(F(" D14")); if(pin == D15) outdev.print(F(" D15")); #endif // This define is for the DigiStump Oak board that stupidly uses Pn names #if defined(ARDUINO_ESP8266_OAK) if(pin == P0) outdev.print(F(" P0")); if(pin == P1) outdev.print(F(" P1")); if(pin == P2) outdev.print(F(" P2")); if(pin == P3) outdev.print(F(" P3")); if(pin == P4) outdev.print(F(" P4")); if(pin == P5) outdev.print(F(" P5")); if(pin == P6) outdev.print(F(" P6")); if(pin == P7) outdev.print(F(" P7")); if(pin == P8) outdev.print(F(" P8")); if(pin == P9) outdev.print(F(" P9")); if(pin == P10) outdev.print(F(" P10")); if(pin == P10) outdev.print(F(" P10")); #endif #endif // I2CEXPDIAG_CFG_NO_DECODE_ESPXXXXPINS // Special ugly code for Roger's maple/stm32duino cores // They don't define SDA or SCL at all but code // above gets them from SoftWire // Since they are #define symbols that reverence other PXX symbols // we will print define string instead of its value #if defined(ARDUINO_ARCH_STM32F1) || defined(ARDUINO_ARCH_STM32F4) if(pin == SDA) outdev.print(F(" " define2str(SDA))); // print define string not its final value if(pin == SCL) outdev.print(F(" " define2str(SCL))); // print define string not its final value #endif ////////////////////////////////////////////////////////////////////////// // code for STM32 platform defines #if defined(ARDUINO_ARCH_STM32) #if defined(PA0) if(pin == PA0) outdev.print(F(" PA0")); #endif #if defined(PA1) if(pin == PA1) outdev.print(F(" PA1")); #endif #if defined(PA2) if(pin == PA2) outdev.print(F(" PA2")); #endif #if defined(PA3) if(pin == PA3) outdev.print(F(" PA3")); #endif #if defined(PA4) if(pin == PA4) outdev.print(F(" PA4")); #endif #if defined(PA5) if(pin == PA5) outdev.print(F(" PA5")); #endif #if defined(PA6) if(pin == PA6) outdev.print(F(" PA6")); #endif #if defined(PA7) if(pin == PA7) outdev.print(F(" PA7")); #endif #if defined(PA8) if(pin == PA8) outdev.print(F(" PA8")); #endif #if defined(PA9) if(pin == PA9) outdev.print(F(" PA9")); #endif #if defined(PA10) if(pin == PA10) outdev.print(F(" PA10")); #endif #if defined(PA11) if(pin == PA11) outdev.print(F(" PA11")); #endif #if defined(PA12) if(pin == PA12) outdev.print(F(" PA12")); #endif #if defined(PA13) if(pin == PA13) outdev.print(F(" PA13")); #endif #if defined(PA14) if(pin == PA14) outdev.print(F(" PA14")); #endif #if defined(PA15) if(pin == PA15) outdev.print(F(" PA15")); #endif #if defined(PB0) if(pin == PB0) outdev.print(F(" PB0")); #endif #if defined(PB1) if(pin == PB1) outdev.print(F(" PB1")); #endif #if defined(PB2) if(pin == PB2) outdev.print(F(" PB2")); #endif #if defined(PB3) if(pin == PB3) outdev.print(F(" PB3")); #endif #if defined(PB4) if(pin == PB4) outdev.print(F(" PB4")); #endif #if defined(PB5) if(pin == PB5) outdev.print(F(" PB5")); #endif #if defined(PB6) if(pin == PB6) outdev.print(F(" PB6")); #endif #if defined(PB7) if(pin == PB7) outdev.print(F(" PB7")); #endif #if defined(PB8) if(pin == PB8) outdev.print(F(" PB8")); #endif #if defined(PB9) if(pin == PB9) outdev.print(F(" PB9")); #endif #if defined(PB10) if(pin == PB10) outdev.print(F(" PB10")); #endif #if defined(PB11) if(pin == PB11) outdev.print(F(" PB11")); #endif #if defined(PB12) if(pin == PB12) outdev.print(F(" PB12")); #endif #if defined(PB13) if(pin == PB13) outdev.print(F(" PB13")); #endif #if defined(PB14) if(pin == PB14) outdev.print(F(" PB14")); #endif #if defined(PB15) if(pin == PB15) outdev.print(F(" PB15")); #endif #if defined(PC0) if(pin == PC0) outdev.print(F(" PC0")); #endif #if defined(PC1) if(pin == PC1) outdev.print(F(" PC1")); #endif #if defined(PC2) if(pin == PC2) outdev.print(F(" PC2")); #endif #if defined(PC3) if(pin == PC3) outdev.print(F(" PC3")); #endif #if defined(PC4) if(pin == PC4) outdev.print(F(" PC4")); #endif #if defined(PC5) if(pin == PC5) outdev.print(F(" PC5")); #endif #if defined(PC6) if(pin == PC6) outdev.print(F(" PC6")); #endif #if defined(PC7) if(pin == PC7) outdev.print(F(" PC7")); #endif #if defined(PC8) if(pin == PC8) outdev.print(F(" PC8")); #endif #if defined(PC9) if(pin == PC9) outdev.print(F(" PC9")); #endif #if defined(PC10) if(pin == PC10) outdev.print(F(" PC10")); #endif #if defined(PC11) if(pin == PC11) outdev.print(F(" PC11")); #endif #if defined(PC12) if(pin == PC12) outdev.print(F(" PC12")); #endif #if defined(PC13) if(pin == PC13) outdev.print(F(" PC13")); #endif #if defined(PC14) if(pin == PC14) outdev.print(F(" PC14")); #endif #if defined(PC15) if(pin == PC15) outdev.print(F(" PC15")); #endif #endif // ARDUINO_ARCH_STM32 ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // print the analog pin if it matches the pin # #if defined(A0) || defined(PIN_A0) if(pin == A0) outdev.print(F(" A0")); #endif #if defined(A1) || defined(PIN_A1) if(pin == A1) outdev.print(F(" A1")); #endif #if defined(A2) || defined(PIN_A2) if(pin == A2) outdev.print(F(" A2")); #endif #if defined(A3) || defined(PIN_A3) if(pin == A3) outdev.print(F(" A3")); #endif #if defined(A4) || defined(PIN_A4) if(pin == A4) outdev.print(F(" A4")); #endif #if defined(A5) || defined(PIN_A5) if(pin == A5) outdev.print(F(" A5")); #endif #if defined(A6) || defined(PIN_A6) if(pin == A6) outdev.print(F(" A6")); #endif #if defined(A7) || defined(PIN_A7) if(pin == A7) outdev.print(F(" A7")); #endif #if defined(A8) || defined(PIN_A8) if(pin == A8) outdev.print(F(" A8")); #endif #if defined(A9) || defined(PIN_A9) if(pin == A9) outdev.print(F(" A9")); #endif #if defined(A10) || defined(PIN_A10) if(pin == A10) outdev.print(F(" A10")); #endif #if defined(A11) || defined(PIN_A11) if(pin == A11) outdev.print(F(" A11")); #endif #if defined(A12) || defined(PIN_A12) if(pin == A12) outdev.print(F(" A12")); #endif #if defined(A13) || defined(PIN_A13) if(pin == A13) outdev.print(F(" A13")); #endif #if defined(A14) || defined(PIN_A14) if(pin == A14) outdev.print(F(" A14")); #endif #if defined(A15) || defined(PIN_A15) if(pin == A15) outdev.print(F(" A15")); #endif ////////////////////////////////////////////////////////////////////////// return; } /* * dump everthying we know about the system environment */ void showSystemConfig(void) { #ifdef ARDUINO Serial.print(F("Reported Arduino Revision: ")); #if ARDUINO > 158 // ARDUINO rev format changed after 1.5.8 to #.##.## (breaks after 3.x.x for 16 int bit calc) Serial.print(ARDUINO/10000); Serial.print('.'); Serial.print((ARDUINO%10000)/100); Serial.print('.'); Serial.println((ARDUINO%10000)%100); #elif ARDUINO >= 100 // 1.0.0 to 1.5.8 uses rev format #.#.# Serial.print(ARDUINO/100); Serial.print('.'); Serial.print((ARDUINO%100)/10); Serial.print('.'); Serial.println((ARDUINO%100)%10); #else Serial.print(F("0.")); Serial.println(define2str(ARDUINO)); // print the raw string vs as a number #endif #endif // only print board name if platform hands it to us #if defined(ARDUINO_BOARD) || defined(BOARD_NAME) Serial.print(F("Arduino Board: ")); #if defined(ARDUINO_BOARD) Serial.print(ARDUINO_BOARD); #if defined(ARDUINO_VARIANT) Serial.print(F(" Arduino Variant: ")); Serial.print(ARDUINO_VARIANT); #endif #elif defined(BOARD_NAME) Serial.print(BOARD_NAME); #else Serial.print(F("unknown")); #endif Serial.println(); #endif #if defined(__AVR__) Serial.print(F("CPU ARCH: AVR - ")); #elif defined(__arm__) Serial.print(F("CPU ARCH: arm - ")); #elif defined(__PIC32MX__) Serial.print(F("CPU ARCH: pic32 - ")); #elif defined(ARDUINO_ARCH_ESP8266) Serial.print(F("CPU ARCH: ESP8266 - ")); #elif defined(ARDUINO_ARCH_ESP32) Serial.print(F("CPU ARCH: ESP32 - ")); #elif defined(ARDUINO_ARCH_STM32) Serial.print(F("CPU ARCH: STM32 - ")); #endif Serial.print(F("F_CPU: ")); // just in case the core does not define this // (like cores in RogerClark's STM32 platform) #ifdef F_CPU Serial.println(F_CPU); #else Serial.println("undefined"); #endif Serial.println(hline); Serial.print(F("SDA digital pin: ")); printDigitalPin(Serial, SDA); Serial.println(); Serial.print(F("SCL digital pin: ")); printDigitalPin(Serial, SCL); Serial.println(); } /* * Test for external pullup on pin * returns less than zero if pin appears to be driven low * returns zero if pin appears to have external pullup on it * returns greather than zero if pin appears to NOT have a pullup on it * * i.e. zero means pullup exists and non zero means there is no pullup. * and positive/negative indicates more information */ int pullupOnPin(uint8_t pin) { int status; // test to see if pin is pulled/stuck low // Arduino 1.0 didn't support INPUT_PULLUP // it was added in the next release 1.0.1 // the code used to check for INPUT_PULLUP macro // and work around it // but many cores didn't use a macro so this check caused issues // now the code will simply fail to compile ungracefully if the symbol doesn't exist pinMode(pin, INPUT_PULLUP); delay(20); if(digitalRead(pin) == LOW) { status = -1; // pin appears to be driven low goto leave; } // test to see if high is an external pullup pinMode(pin, OUTPUT); digitalWrite(pin, LOW); delay(20); pinMode(pin,INPUT); delayMicroseconds(10); if(digitalRead(pin) == HIGH) status = 0; // pin appears to have external pullup else status = 1; // pin appears to NOT have an external pullup leave: pinMode(pin,INPUT); // ensure pin is left in input mode return(status); } /* * test of two pins are shorted together * returns > zero if pins are shorted * returns zero if pins are not shorted * returns < zero if indeterminate */ int pinsShorted(uint8_t p1, uint8_t p2) { int rval = 0; // assume not shorted pinMode(p1, INPUT_PULLUP); pinMode(p2, INPUT_PULLUP); delay(150); // this needs quite a while for chipkit/pic32 to let signals rise up // check to see if both pins are high if((digitalRead(p1) != HIGH) || (digitalRead(p2) != HIGH)) return(-1); // can't determine if pins are shorted pinMode(p1, OUTPUT); digitalWrite(p1, LOW); delay(5); if(digitalRead(p2) == LOW) rval = 1; // put back into input state pinMode(p1, INPUT); pinMode(p2, INPUT); return(rval); } /* * Test for external pullups on i2c signals * * returns 0 if both pullups appear ok * returns positive if no pullup exist on either pin (soft error on AVR) * returns negative if I2C cannot function * -1 if either pin is driven low * -2 if pins are shorted together */ int i2cpulluptest() { int rval = 0; int s; Serial.print(F("Checking for required external I2C pull-up on SDA - ")); if ( (s = pullupOnPin(SDA)) ) { if(s > 0) { rval = 1; Serial.println(F("NO")); } else { Serial.println(F("STUCK LOW")); rval = -1; } } else { Serial.println(F("YES")); } Serial.print(F("Checking for required external I2C pull-up on SCL - ")); if ( (s = pullupOnPin(SCL)) ) { if(s > 0) { if(!rval) rval = 1; Serial.println(F("NO")); } else { Serial.println(F("STUCK LOW")); rval = -1; } } else { Serial.println(F("YES")); } // check for short between SDA and SCL if no pins stuck low if(rval >= 0) { Serial.print(F("Checking for I2C pins shorted together - ")); s = pinsShorted(SDA, SCL); if(!s) { Serial.println(F("Not Shorted")); } else if(s > 0) { Serial.println(F("Shorted")); rval = -2; } else { Serial.println(F("Undetermined")); } } if(rval) { Serial.println(hstar); if(rval > 0) { Serial.println(F("WARNING: I2C requires external pullups for proper operation")); Serial.println(F("It may appear to work without them, but may be unreliable and slower")); Serial.println(F("Do not be surprised if it fails to work correctly")); Serial.println(F("Install external pullup resistors to ensure proper I2C operation")); } else { if(rval == -1) Serial.println(F("ERROR: SDA or SCL stuck pin")); else Serial.println(F("ERROR: SDA and SCL shorted together")); } Serial.println(hstar); } return(rval); } #ifdef LATER // i2cexpPinsTest - test for shorts on expander port pins // FIXME currenly only works with PCF8574 // Also, the pin with the backlight can fail as the backlight // circuitry can pull the pin low on active HIGH backlight circuits. // so not sure that test can ever be made to work as desired. int i2cexpPinsTest(uint8_t addr) { int bitdiffs = 0; int rval; uint8_t wdata, rdata; // to test for shorted/broken/stuck pin: // - set all pins but one to outputs and low // - set single pin an input with pullup enabled // - read back the port register. (all 8 pins) // // If the input pin is not a high there is an issue // note: // r/w will be low even if En goes high, so the port should read back // what was written. // An active high backlight curcuit will create false positives since // the backlight transistor will pull the input pin low. // for(int pin = 0; pin < 8; pin++) { wdata = (1 << pin); // convert pin# to bit position mask Wire.beginTransmission(addr); Wire.write(wdata); Wire.endTransmission(); Wire.requestFrom((int)addr, 1); rval = Wire.read(); rdata = uint8_t (rval); Wire.endTransmission(); if (rdata != wdata) { bitdiffs |= (rdata ^ wdata); } } if(bitdiffs) { Serial.print(F("i2cExpander port error: ")); Serial.print(F("Pins/Bits:")); for(uint8_t bit=0; bit < 8; bit++) { if(bitdiffs & (1<< bit)) { Serial.print(' '); Serial.print(bit); } } Serial.println(); } return bitdiffs; } #endif /* * Returns number of i2c devices found */ int showI2Cdevices(void) { uint8_t error, address; int devcount = 0; Serial.println(F("Scanning i2c bus for devices..")); /* * Note: * Addresses below 8 are reserved for special use * Addresses above 0x77 are reserved for special use */ for(address = 8; address <= 0x77; address++ ) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { devcount++; Serial.print(F(" i2c device found at address 0x")); if (address<16) Serial.print('0'); Serial.println(address,HEX); } else if (error==4) { Serial.print(F(" Unknown error at address 0x")); if (address<16) Serial.print('0'); Serial.println(address,HEX); } // 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. // normally yield() would be used to prevent WDT, but yield() doesn't // exist on older IDEs so we use delay(1) which calls yield() // The delay(1) is overkill for chipkit and not needed for other chips, // but it won't hurt and keeps things compatible with older IDEs. delay(1); } Serial.print(F("Total I2C devices found: ")); Serial.println(devcount); return(devcount); } /* * lcdw1est() * Walk a bit through a single memory location to see if * basic reads/writes work. */ uint8_t lcdw1test(hd44780 &lcd, uint8_t addr) { uint8_t errors = 0; int rdata; for(uint8_t pat = 1; pat != 0; pat <<= 1) { lcd.setCursor(addr,0); lcd.write(pat); lcd.setCursor(addr,0); rdata = lcd.read(); if(rdata < 0) { Serial.print(F(" Read Error after writing ")); Serial.println((unsigned int)pat, HEX); errors++; delay(1); // easy way to allow some cores to call yield() } else if((rdata != pat)) { if(!errors) Serial.println(); Serial.print(F("\tCompare error: addr: ")); Serial.print(addr, HEX); Serial.print(F(" read ")); Serial.print((unsigned int)rdata, HEX); Serial.print(F(" != wrote ")); Serial.println((unsigned int)pat, HEX); errors++; delay(1); // easy way to allow some cores to call yield() } } return(errors); } /* * lcdAddrLineTest() - address line test * writes the memory addres to each locaton and verifies contents. * * This will verify that all memory is being addressed correctly. */ int lcdAddrLineTest(hd44780 &lcd, uint8_t saddr, uint8_t eaddr) { int errors = 0; int rdata; // first write to all the memory locations for(uint8_t addr = saddr; addr <= eaddr; addr++) { // ugly hack, skip over addresses of codes for and // since write() currently drops and characters if(addr == '\n' || addr == '\r') continue; lcd.setCursor(addr,0); if(lcd.write(addr) != 1) { if(!errors) Serial.println(); Serial.print(F("\tRead Error addr: ")); Serial.println((unsigned int)addr, HEX); errors++; delay(1); // easy way to allow some cores to call yield() } } // now go back and verify that each memory location has the // proper contents. for(uint8_t addr = saddr; addr <= eaddr; addr++) { // ugly hack, skip over addresses of codes for and // since write() currently drops and characters if(addr == '\n' || addr == '\r') continue; lcd.setCursor(addr,0); rdata = lcd.read(); if(rdata < 0) { if(!errors) Serial.println(); Serial.print(F("\tRead Error addr: ")); Serial.println((unsigned int)addr, HEX); errors++; delay(1); // easy way to allow some cores to call yield() } else if((rdata != addr)) { if(!errors) Serial.println(); Serial.print(F("\tCompare error: addr: ")); Serial.print(addr, HEX); Serial.print(F(" read ")); Serial.print((unsigned int)rdata, HEX); Serial.print(F(" != wrote ")); Serial.println((unsigned int)addr, HEX); errors++; delay(1); // easy way to allow some cores to call yield() } } return(errors); } /* * Walk incrementing values through incrementing memory locations. * * A value starting at sval ending at eval will be walked through memory. * The starting address will be filled in with sval and the value will * incremented through all locations to be tested. Values are written through * incrementing addresses. * * All the values are read and compared to expected values. * * Then process starts over again by incrementing the starting value. * This repeats until the starting value reaches the ending value. * * Each memory location will tested with an incrementing value * eval-sval+1 times. * * If sval is 0 and eval is 255, * every memory location will be tested for every value. * */ int lcdDDRAMtest(hd44780 &lcd, uint8_t saddr, uint8_t eaddr, uint8_t sval, uint8_t eval) { uint8_t addr; int data; int rdata; uint8_t errors = 0; uint8_t lval = sval; /* * perform each interation of test across memory with * an incrementing pattern * starting at sval and bumping sval each iteration. */ do { /* * write sequentially through all addresses */ data = lval; // use serCursor to set initial DDRAM address // writes will bump it lcd.setCursor(saddr, 0); for(addr = saddr; addr <= eaddr; addr++) { lcd.write((uint8_t)data); if(++data > eval) data = sval; } /* * Now go back and verify the pages */ data = lval; // use serCursor to set initial DDRAM address // reads will bump it lcd.setCursor(saddr, 0); for(addr = saddr; addr <= eaddr; addr++) { rdata = lcd.read(); if(rdata < 0) { Serial.print(F(" Read Error, addr: ")); Serial.print((unsigned int)addr, HEX); Serial.print(F(" sval: ")); Serial.print(sval); Serial.print(F(" expected data: ")); Serial.print(data); Serial.println(); if(++errors > MAX_ERRORS) return(errors); } else if(data != rdata) { Serial.print(F(" Verify error: (")); Serial.print((unsigned int) addr); Serial.print(F(") read ")); Serial.print((unsigned int)rdata, HEX); Serial.print(F(" != wrote ")); Serial.print((unsigned int)data, HEX); Serial.println(); if(++errors > MAX_ERRORS) return(errors); } if(++data > eval) data = sval; } } while(lval++ != eval); return(errors); } // create a LCD configuration string // requires being handed a 16 byte buffer to hold the string // returns the original buffer pointer for convenience. char * lcdConfigStr(char *str, hd44780_I2Cexp &lcd) { int rv; char *p = str; #if 1 switch(lcd.getProp(hd44780_I2Cexp::Prop_expType)) { case I2Cexp_PCF8574: *p++ = 'P'; break; case I2Cexp_MCP23008: *p++ = 'M'; break; default: *p++ = 'U'; } #else *p++ = lcd.getProp(hd44780_I2Cexp::Prop_expType + '0'; #endif *p++ = lcd.getProp(hd44780_I2Cexp::Prop_rs) + '0'; // r/w support may or may not be enabled. rv = lcd.getProp(hd44780_I2Cexp::Prop_rw); if((unsigned int) rv <= 7) // check if r/w is supported *p++ = rv + '0'; *p++ = lcd.getProp(hd44780_I2Cexp::Prop_en) + '0'; *p++ = lcd.getProp(hd44780_I2Cexp::Prop_d4) + '0'; *p++ = lcd.getProp(hd44780_I2Cexp::Prop_d5) + '0'; *p++ = lcd.getProp(hd44780_I2Cexp::Prop_d6) + '0'; *p++ = lcd.getProp(hd44780_I2Cexp::Prop_d7) + '0'; rv = lcd.getProp(hd44780_I2Cexp::Prop_bl); if((unsigned int) rv <= 7) // check if bl control is supported { *p++ = rv + '0'; #if 1 if(lcd.getProp(hd44780_I2Cexp::Prop_blLevel) == HIGH) *p++ = 'H'; else *p++ = 'L'; #else if(lcd.getProp(hd44780_I2Cexp::Prop_blLevel) == HIGH) *p++ = '1'; else *p++ = '0'; #endif } *p = 0; // terminate string return(str); } // fatalError() - loop & blink an error code void fatalError(int ecode) { hd44780::fatalError(ecode); // does not return } void waitinput(const char *prompt) { if(prompt) Serial.print(prompt); else Serial.print(F(" or click [Send] to Continue>")); while(Serial.available()) Serial.read(); // swallow all input while(!Serial.available()){} // wait on serial input Serial.println(); }