Hi apologies for breaking the rule. I am not quiet sure how i can explain the code as it is divided into multiple (.h) files but the main files which use the LCD header are listed below with their code. Hope I have not made any mistakes.
The code under BarGraph.h
#pragma once
#include <LiquidCrystal_I2C.h>
#include "Debug.h"
#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
// BarGraph ----------------------------------------------
// Draw a bargraph on the LCD display
class BarGraph
{
protected:
const uint8_t COL_PER_CHAR = 5;
const uint8_t ROW_PER_CHAR = 8;
const uint8_t LCD_BLOCK_CHAR = 0xff;
const uint8_t LCD_BLANK_CHAR = ' ';
LiquidCrystal_I2C *_lcd;
uint8_t _row, _colStart;
int8_t _colLen;
uint32_t _valueMin, _valueMax;
uint8_t _charLCD;
public:
BarGraph(LiquidCrystal_I2C *lcd, uint8_t row, uint8_t colStart, int8_t colLen) :
_lcd(lcd), _row(row), _colStart(colStart), _colLen(colLen), _charLCD(0)
{
setRange(0, 100);
};
void begin(void) {};
inline void setChar(uint8_t charLCD) { if (_charLCD < 8) _charLCD = charLCD; };
inline void setRange(uint32_t valueMin, uint32_t valueMax) { _valueMin = valueMin; _valueMax = valueMax; };
void show(uint32_t value)
// Build a string with the graph characters and then display the string.
// Assume the graph is being display L to R and then reverse the string
// if it is to be displyed R to L. Just need to ensure that the end
// character of the bar graph is built in the correct direction.
{
uint8_t charMap[ROW_PER_CHAR]; // LCD user defined character
char *szGraph = (char *)malloc((abs(_colLen) + 1) * sizeof(char));
uint8_t lenGraph; // size of the graph in pixel columns
uint8_t barValue;
uint8_t c;
// Can't do much if we couldn't get RAM
if (szGraph == NULL) return;
// work out what value display means
lenGraph = abs(_colLen) * COL_PER_CHAR;
if (value > _valueMax) barValue = lenGraph;
else if (value < _valueMin) barValue = 0;
else barValue = map(value, _valueMin, _valueMax, 0, lenGraph);
// create the correct orientation for the user defined character
// create the first row, then copy it to the others
c = barValue % COL_PER_CHAR; // number of extra columns
charMap[0] = 0;
for (uint8_t i = 0; i<c; i++)
bitSet(charMap[0], (_colLen < 0) ? i : COL_PER_CHAR - i);
memset(&charMap[1], charMap[0], sizeof(charMap) - sizeof(charMap[0]));
_lcd->createChar(_charLCD, charMap);
// create the graph string as if it was being displayed L to R
{
uint8_t i; // retain loop index within the code block
c = barValue / COL_PER_CHAR; // number of whole characters
for (i = 0; i<c; i++) // full block characters
szGraph[i] = LCD_BLOCK_CHAR;
szGraph[i++] = _charLCD; // end of graph special character
for (; i<abs(_colLen); i++)
szGraph[i] = LCD_BLANK_CHAR; // blanks where no blocks
szGraph[abs(_colLen)] = '\0'; // end the string properly
}
// prepare to display
if (_colLen > 0)
{
// just set the cursor at specified position - string is already correct
_lcd->setCursor(_colStart, _row);
}
else
{
// need to reverse the string and set a different starting cursor position
strrev(szGraph);
_lcd->setCursor(_colStart + _colLen + 1, _row);
}
// display it and release the string memory
_lcd->print(szGraph);
free(szGraph);
}
};
The code under BigNum.h
#pragma once
#include <LiquidCrystal_I2C.h>
#include "Debug.h"
#define ORIGINAL_FONT 0
// BigNum -----------------------------------------------
// Draw a large character over 2 lines on the LCD display
class BigNum
{
private:
#if ORIGINAL_FONT
// Definitions for the number of characters and their size
static const uint8_t NUM_ELEMENTS = 8; // elements required
static const uint8_t ELEMENT_SIZE = 8; // bytes per element
static const PROGMEM uint8_t fontSingle[NUM_ELEMENTS][ELEMENT_SIZE];
static const PROGMEM uint8_t fontDouble[NUM_ELEMENTS][ELEMENT_SIZE];
#else
// Definitions for the number of characters and their size
static const uint8_t NUM_ELEMENTS = 7; // elements required
static const uint8_t ELEMENT_SIZE = 8; // bytes per element
static const PROGMEM uint8_t fontSingle[NUM_ELEMENTS][ELEMENT_SIZE];
#endif
protected:
LiquidCrystal_I2C *_lcd;
public:
BigNum(LiquidCrystal_I2C *lcd) : _lcd(lcd)
{ };
void begin(void)
{
setSingleFont();
}
void setSingleFont(void)
{
uint8_t c[NUM_ELEMENTS];
for (uint8_t i = 0; i < NUM_ELEMENTS; i++)
{
memcpy_P(c, &fontSingle[i][0], ELEMENT_SIZE*sizeof(fontSingle[0][0]));
_lcd->createChar(i, c);
}
};
void setDoubleFont(void)
{
uint8_t c[NUM_ELEMENTS];
for (uint8_t i = 0; i < NUM_ELEMENTS; i++)
{
#if ORIGINAL_FONT
memcpy_P(c, &fontDouble[i][0], ELEMENT_SIZE*sizeof(fontDouble[0][0]));
#else
memcpy_P(c, &fontSingle[i][0], ELEMENT_SIZE*sizeof(fontSingle[0][0]));
#endif
_lcd->createChar(i, c);
}
};
void writeNumber(uint8_t row, uint8_t col, uint16_t num, uint8_t digits, bool leadZero = false)
// write number num at top left coordinate (row, col) in a space 3 digits in size
// with leading zero if requested
{
for (int8_t i = digits; i > 0; i--)
{
uint8_t d = num % 10;
// draw the digit depeding on zero or not and, if zero, position in the number or not
if ((d != 0) || leadZero || (d == 0 && i == digits) || (d == 0 && num != 0))
writeDigit(row, col + i - 1, d);
else
{
_lcd->setCursor(col, row);
_lcd->write(' ');
_lcd->setCursor(col, row + 1);
_lcd->write(' ');
}
num /= 10;
}
}
void writeDigit(uint8_t row, uint8_t col, uint8_t digit)
{
// Define the elements for the top and bottom for each digit
#if ORIGINAL_FONT
static const uint8_t top[10] = { 1, 0, 7, 7, 3, 4, 4, 7, 1, 1 };
static const uint8_t bot[10] = { 3, 0, 4, 2, 0, 6, 3, 0, 5, 2 };
#else
static const uint8_t top[10] = { 0, 5, 2, 2, 1, 3, 3, 6, 4, 4 };
static const uint8_t bot[10] = { 1, 5, 3, 2, 6, 2, 4, 5, 4, 2 };
#endif // ORIGINAL_FONT
if (digit > 9) return;
_lcd->setCursor(col, row);
_lcd->write((char)top[digit]);
_lcd->setCursor(col, row + 1);
_lcd->write((char)bot[digit]);
};
};
// PROGMEM Font data for big numbers
//
#if ORIGINAL_FONT
// Character elements defined in the user chars in numerical order
// 0: 1: _ 2: _ 3: 4: _ 5: _ 6: 7: _
// | | | _| |_| |_ |_| _| |
// Two types of fonts are defined - single and double stroke widths
// Single width strokes
const PROGMEM uint8_t BigNum::fontSingle[NUM_ELEMENTS][ELEMENT_SIZE] =
{
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 0
{ 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 1
{ 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 2
{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 3
{ 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f }, // 4
{ 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 5
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 6
{ 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 } // 7
};
const PROGMEM uint8_t BigNum::fontDouble[NUM_ELEMENTS][ELEMENT_SIZE] =
{
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 0
{ 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 1
{ 0x1f, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x1f }, // 2
{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f }, // 3
{ 0x1f, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x1f }, // 4
{ 0x1f, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x1f, 0x1f }, // 5
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x0f, 0x1f }, // 6
{ 0x1f, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 } // 7
};
#else
// Character elements defined in the user chars in numerical order
// 0: _ 1: 2: _ 3: _ 4: _ 5: 6: _ 7: not used
// | | |_| _| |_ |_| | |
// Only one font is defined but it takes on less user defined character
// Single width strokes
const PROGMEM uint8_t BigNum::fontSingle[NUM_ELEMENTS][ELEMENT_SIZE] =
{
{ 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11 }, // 0
{ 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 1
{ 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f }, // 2
{ 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f }, // 3
{ 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1f }, // 4
{ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 5
{ 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }, // 6
};
#endif
The code under LCDDisplay.h
#pragma once
#include <LiquidCrystal_I2C.h>
#include "Hardware.h"
#include "BarGraph.h"
#include "BigNum.h"
#include "Debug.h"
// LCD Display -------------------------------------------
// Manage the LCD display for the different modes of operation.
class LCD_Display : protected LiquidCrystal_I2C
{
private:
// Assumes a 2 line 16 character LCD module display.
static const uint8_t LCD_ROWS = 2;
static const uint8_t LCD_COLS = 16;
// There are different types of displays, defined by the fields
struct fieldDef_t { uint8_t row, col, len; };
//
// * MAIN DISPLAY *
//
// +----------------+ D Double height current value
// |DDDo M SSS XXXXX| S SP for current value
// |DDDU GGGGGGGGGGG| M Control mode (Power or Temperature)
// +----------------+ X Mode of Operation
// 0123456789012345 G Bar graph of power applied
// o degree symbol
// C units of temperature
//
const fieldDef_t MDF_CV = { 0, 0, 3 };
const fieldDef_t MDF_DEG = { 0, 3, 1 };
const fieldDef_t MDF_UOM = { 1, 3, 1 };
const fieldDef_t MDF_PT = { 0, 5, 1 };
const fieldDef_t MDF_SP = { 0, 7, 3 };
const fieldDef_t MDF_MODE = { 0, 11, 5 };
const fieldDef_t MDF_BG = { 1, 5, 11 };
//
// * CALIBRATE *
// +----------------+
// |DDDo LLLLLLLLLLL| L Label for the parameter
// |DDDU SSSSSSSSSSS| S value for setpoint/value
// +----------------+
// 0123456789012345
//
const fieldDef_t CDF_CV = { 0, 0, 3 };
const fieldDef_t CDF_DEG = { 0, 3, 1 };
const fieldDef_t CDF_UOM = { 1, 3, 1 };
const fieldDef_t CDF_LBL = { 0, 5, LCD_COLS };
const fieldDef_t CDF_DTA = { 1, 5, LCD_COLS };
//
// * ERROR *
// +----------------+
// |MMMMMMMMMMMMMMMM| M Message Line 1 (Label)
// |NNNNNNNNNNNNNNNN| N Message LIne 2 (Error)
// +----------------+
// 0123456789012345
//
const fieldDef_t EDF_1 = { 0, 0, LCD_COLS };
const fieldDef_t EDF_2 = { 1, 0, LCD_COLS };
// Variables
uint16_t _pctGauge;
uint16_t _CV, _SP;
char _szLabel[LCD_COLS + 1];
char _szError[LCD_COLS + 1];
BarGraph *_bg;
BigNum *_bn;
enum dispMode { DM_MAIN, DM_CONFIG, DM_ERROR } _dispMode;
enum opMode { OM_READY, OM_HEAT, OM_COOL } _opMode;
enum tempMode { TM_DEGC, TM_DEGF, TM_RAW } _tempMode;
enum ctlMode { CM_TEMP, CM_PWR, CM_NONE } _ctlMode;
public:
LCD_Display(uint8_t DAT, uint8_t CLK) : LiquidCrystal_I2C(DAT, CLK)
{
_bg = new BarGraph(this, MDF_BG.row, MDF_BG.col, MDF_BG.len);
_bn = new BigNum(this);
setMainDisplay();
setModeReady();
setCV(0);
setSP(0);
setGaugePct(0);
setTempCel();
setLabel("");
}
~LCD_Display()
{
delete _bn;
delete _bg;
}
inline void clear(void) { LiquidCrystal_I2C::clear(); }
inline void setCV(uint16_t data) { _CV = data; } // Set the current value for the measured variable
inline void setSP(uint16_t data) { _SP = data; } // Set the Set Point for the measured variable
inline void setGaugePct(uint8_t data) { if (data < 100) _pctGauge = data; } // Set the gauge as a %age
// Set various display types
inline void setMainDisplay(void) { _dispMode = DM_MAIN; }
inline void setCfgDisplay(void) { _dispMode = DM_CONFIG; }
inline void setErrDisplay(void) { _dispMode = DM_ERROR; }
// Set various operating modes
inline void setModeHeating(void) { _opMode = OM_HEAT; }
inline void setModeCooling(void) { _opMode = OM_COOL; }
inline void setModeReady(void) { _opMode = OM_READY; }
// Set temperature display
inline void setTempCel(void) { _tempMode = TM_DEGC; }
inline void setTempFar(void) { _tempMode = TM_DEGF; }
inline void setTempRaw(void) { _tempMode = TM_RAW; }
// Set the control mode display
inline void setCtlPower(void) { _ctlMode = CM_PWR; }
inline void setCtlTemp(void) { _ctlMode = CM_TEMP; }
inline void setCtlNone(void) { _ctlMode = CM_NONE; }
void begin(void)
{
// initialise LCD LCD_Display
LiquidCrystal_I2C::begin(LCD_COLS, LCD_ROWS);
clear();
noAutoscroll();
noCursor();
// initialise dependent objects
_bn->begin();
_bg->begin();
_bg->setChar(7); // 0-6 are used by bigNum
}
void setLabel(const char *szMesg)
{
strncpy(_szLabel, szMesg, sizeof(_szLabel) - 2);
_szLabel[sizeof(_szLabel) - 1] = '\0';
}
void setError(const char *sz1, const char *sz2)
{
strncpy(_szLabel, sz1, sizeof(_szLabel) - 2);
_szLabel[sizeof(_szLabel) - 1] = '\0';
strncpy(_szError, sz2, sizeof(_szError) - 2);
_szLabel[sizeof(_szError) - 1] = '\0';
}
void update(void)
{
char szLine[LCD_COLS + 1];
this->noDisplay(); // temporarily disable the LCD display
this->clear();
switch (_dispMode)
{
case DM_MAIN:
{
// write the CV as double height digits
_bn->writeNumber(MDF_CV.row, MDF_CV.col, _CV, MDF_CV.len);
// units of measure
this->setCursor(MDF_DEG.col, MDF_DEG.row);
if (_tempMode == TM_DEGC || _tempMode == TM_DEGF)
this->write(0xdf);
else
this->write(' ');
this->setCursor(MDF_UOM.col, MDF_UOM.row);
this->print(strMode(_tempMode));
// Control mode
this->setCursor(MDF_PT.col, MDF_PT.row);
this->print(strMode(_ctlMode));
// Set point
this->setCursor(MDF_SP.col, MDF_SP.row);
sprintf(szLine, "%%0%dd", MDF_SP.len); // format string (* parameter not working?)
sprintf(szLine, szLine, _SP);
this->print(szLine);
// Status message
this->setCursor(MDF_MODE.col, MDF_MODE.row);
this->print(strMode(_opMode));
_bg->show(_pctGauge);
}
break;
case DM_CONFIG:
{
// write the CV as double height digits
_bn->writeNumber(CDF_CV.row, CDF_CV.col, _CV, CDF_CV.len);
// Units of measure
this->setCursor(CDF_DEG.col, CDF_DEG.row);
if (_tempMode == TM_DEGC || _tempMode == TM_DEGF)
this->write(0xdf);
else
this->write(' ');
this->setCursor(CDF_UOM.col, CDF_UOM.row);
this->print(strMode(_tempMode));
// Label and Set point value
this->setCursor(CDF_LBL.col, CDF_LBL.row);
this->print(_szLabel);
this->setCursor(CDF_DTA.col, CDF_DTA.row);
this->print(_SP);
}
break;
case DM_ERROR:
{
// Two line message
this->setCursor(EDF_1.col, EDF_1.row);
this->print(_szLabel);
this->setCursor(EDF_2.col, EDF_2.row);
this->print(_szError);
}
break;
default:
this->setCursor(0, 0);
this->print("?Display mode");
break;
}
this->display(); // re-enable the LCD display
}
private:
const char *strMode(enum opMode op)
{
switch (op)
{
case OM_READY: return("Ready");
case OM_HEAT: return(" Heat");
case OM_COOL: return(" Cool");
}
return("?????");
}
const char *strMode(enum tempMode op)
{
switch (op)
{
case TM_DEGC: return("C");
case TM_DEGF: return("F");
case TM_RAW: return(" ");
}
return("?");
}
const char *strMode(enum ctlMode op)
{
switch (op)
{
case CM_TEMP: return("T");
case CM_PWR: return("P");
case CM_NONE: return(" ");
}
return("?");
}
};