// 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();
}