// vi:ts=4
// ----------------------------------------------------------------------------
// HelloWorld - simple demonstration of lcd
// Created by Bill Perry 2016-07-02
// [email protected]
//
// This example code is unlicensed and is released into the public domain
// ----------------------------------------------------------------------------
//
// This sketch is for LCDs with PCF8574 or MCP23008 chip based backpacks
// 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.
//
// Sketch prints "Hello, World!" on the lcd
//
// If initialization of the LCD fails and the arduino supports a built in LED,
// the sketch will simply blink the built in LED.
//
// NOTE:
// If the sketch fails to produce the expected results, or blinks the LED,
// run the included I2CexpDiag sketch to test the i2c signals and the LCD.
//
// SEE ALSO:
// https://github.com/duinoWitchery/hd44780
// https://github.com/duinoWitchery/hd44780/blob/master/examples/ioClass/hd44780_I2Cexp/HelloWorld/HelloWorld.ino
// Wokwi simulation https://wokwi.com/projects/360021172047397889
// (sim adapted from https://wokwi.com/projects/345429854144954963
// https://github.com/duinoWitchery/hd44780/issues/35
//
// ----------------------------------------------------------------------------
// LiquidCrystal compability:
// Since hd44780 is LiquidCrystal API compatible, most existing LiquidCrystal
// sketches should work with hd44780 hd44780_I2Cexp i/o class once the
// includes are changed to use hd44780 and the lcd object constructor is
// changed to use the hd44780_I2Cexp i/o class.
// Data model -- A parameter table holding names and parameters for a PID
// Mypin-ish TA-4
// per https://blog.uvm.edu/cwcallah/files/2016/04/Mypin-TA4-manual1.pdf
typedef struct {
const int8_t id;
const int8_t next; // next SET (-means SET->abs(next) long set goes+1)
const char * prefix;
const char * name;
const char * units;
float val1;
float val2;
float *ref;
} TcStates;
TcStates tcStates[] = {
{0, 1, "Str", "Four button Menu", "", 0, 0, NULL},
{1, 2, "Unt", "Units ", "", 0, 0, NULL},
{2, 3, "InL", "Input Limits", "", 0, 0, NULL},
{3, -1, "M/S", "Measure+SetPoint", "", 0, 0, NULL},
{4, 5, "LSP", "Lower SP", "", 0, 0, NULL},
{5, 6, "USP", "Upper SP", "", 0, 0, NULL},
{6, 7, "Hs1", "Hys 1", "", 0, 0, NULL},
{7, 8, "Hy2", "Hys 2", "", 0, 0, NULL},
{8, 9, "Dec", "Decimal pts", "", 0, 0, NULL},
{9, 10, "Sec", "Security", "", 0, 0, NULL},
{10, 11, "AL1", "AL1 Set", "", 0, 0, NULL},
{11, 12, "Am1", "AL1 Mode", "", 0, 0, NULL},
{12, 13, "AL2", "AL2 Set", "", 0, 0, NULL},
{13, 14, "Am2", "AL2 Mode", "", 0, 0, NULL},
{14, 15, "PvF", "PvF Offset", "", 0, 0, NULL}, // -100-100
{15, 16, "InT", "Input Type", "", 0, 0, NULL},
{16, 17, "P", "Prop Range ", "", 0, 0, NULL}, // Off 0.1-1600
{17, 18, "I", "T_i", "", 0, 0, NULL},
{18, 19, "D", "T_d", "", 0, 0, NULL},
{19, 20, "Dir", "Out Dir", "", 0, 0, NULL}, // Heat/Cool
{20, 21, "Hys", "Hysteresis", "", 0, 0, NULL}, // Deadband
{21, 22, "Ctl", "Ctl", "", 0, 0, NULL}, // Seconds 1-150
{22, 23, "Pc", "Prop Cooling", "", 0, 0, NULL},
{23, 24, "Cdb", "Cooling Deadband", "", 0, 0, NULL},
{24, 25, "C-F", "C-F", "", 0, 0, NULL},
{25, 3, "Lck", "Lock", "", 0, 0, NULL},
};
const size_t ntcStates = sizeof(tcStates) / sizeof(tcStates[0]);
float tcVals [ntcStates]; // ram parameters
// required libraries
#include <Wire.h> // https://www.arduino.cc/reference/en/language/functions/communication/wire/
#include <hd44780.h> // https://github.com/duinoWitchery/hd44780
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#include <AceButton.h> // https://github.com/bxparks/AceButton
#include <Streaming.h> //
// inputs
ace_button::AceButton bUp, bDown, bShift, bSet;
hd44780_I2Cexp lcd; // declare lcd object: auto locate & auto config expander chip
constexpr byte bUp_pin = 9, bDown_pin = 10, bShift_pin = 11, bSet_pin = 12;
// Forward reference to prevent Arduino compiler becoming confused.
void handleEvent(ace_button::AceButton*, uint8_t, uint8_t);
// Outputs
// LCD geometry
const int LCD_COLS = 20;
const int LCD_ROWS = 4;
// Modes of operation
int8_t modifyMode = -1; // modifying the digits of a number
int8_t parameterMode = 0, parameter = 0, modeManualAutoTune;
float floatval = 0;
float increment = 0.01;
int intval = 0;
void setup()
{
int status;
status = lcd.begin(LCD_COLS, LCD_ROWS);
if (status) // non zero status means it was unsuccesful
{
// hd44780 has a fatalError() routine that blinks an led if possible
// begin() failed so blink error code using the onboard LED if possible
hd44780::fatalError(status); // does not return
}
// Print a message to the LCD
lcd.print("Hello, World!");
Serial.begin(115200);
pinMode(bUp_pin, INPUT_PULLUP);
bUp.init(bUp_pin);
pinMode(bDown_pin, INPUT_PULLUP);
bDown.init(bDown_pin);
pinMode(bShift_pin, INPUT_PULLUP);
bShift.init(bShift_pin);
pinMode(bSet_pin, INPUT_PULLUP);
bSet.init(bSet_pin);
bSet.setEventHandler(handleEvent);
printGraphviz(); //
}
char lines[4][21]; // LCD buffer
char dtostrbuff[10]; // for floats
static int state = 0; // which parameter
static char *l0 = "", *l1 = ""; // lones for updating buffer
void loop() {
checkButtons();
checkState();
updateScreen(l0, l1);
}
void checkButtons() {
static uint16_t prev = millis();
// The (uint16_t) cast is required on 32-bit processors, harmless on 8-bit.
uint16_t now = millis();
if ((uint16_t) (now - prev) >= 5) {
bDown.check();
bUp.check();
bShift.check();
bSet.check();
prev = now;
}
}
void checkState(void) {
static unsigned long last = -100;
if (millis() - last > 100) {
last = millis();
if (state >= ntcStates - 1) state = 0;
strncpy(lines[0], tcStates[state].prefix, 4);
// tcStates[state].val1 += (random(100) - 50) / 100.0;
dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
snprintf(lines[0], 20, "%3s %7s", tcStates[state].prefix, dtostrbuff);
l0 = lines[0];
l1 = tcStates[state].name;
//Serial.print('.');
// Serial.println(l0);
//Serial.println(l1);
//++state;
}
}
void update_l0(void) {
dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
snprintf(lines[0], 20, "%3s %7s ", tcStates[state].prefix, dtostrbuff);
lcd.setCursor(0, 0);
lcd.print(lines[0]);
}
void update_l1(void) {
snprintf(lines[1], 20, "%-20s", tcStates[state].name, dtostrbuff);
lcd.setCursor(0, 1);
lcd.print(lines[1]);
}
void update_l2(void) {
snprintf(lines[2], 20, "%-20s", tcStates[state].name, dtostrbuff);
lcd.setCursor(0, 2);
lcd.print(lines[2]);
}
void update_l3(void) {
char buff2[10];
dtostrf(tcStates[state].val1, 6, 2, dtostrbuff);
dtostrf(increment, 6, 2, buff2);
snprintf(lines[3], 20, "%s:%s +-%s", tcStates[state].prefix, dtostrbuff, buff2);
lcd.setCursor(0, 3);
lcd.print(lines[3]);
}
void updateScreen(const char * line0, const char* line1) {
static const char* l0 = "";
static const char* l1 = "";
if (line0 != l0 || line1 != l1) {
// lcd.clear();
// lcd.setCursor(0, 0);
// lcd.print(line0);
// lcd.setCursor(0, 1);
// lcd.print(line1);
l0 = line0;
l1 = line1;
}
}
void handleEventX(ace_button::AceButton* /* button */, uint8_t eventType,
uint8_t /* buttonState */) {
switch (eventType) {
case ace_button::AceButton::kEventPressed:
digitalWrite(LED_BUILTIN, HIGH);
break;
case ace_button::AceButton::kEventReleased:
digitalWrite(LED_BUILTIN, LOW);
if (++state >= ntcStates) state = 0;
break;
}
}
// The event handler for both buttons.
void handleEvent(ace_button::AceButton* button, uint8_t eventType, uint8_t buttonState) {
// Print out a message for all events, for both buttons.
Serial.print(F("handleEvent(): pin: "));
Serial.print(button->getPin());
Serial.print(F("; eventType: "));
Serial.print(eventType);
Serial.print(F("; buttonState: "));
Serial.println(buttonState);
// Control the LED only for the Pressed and Released events of Button 1.
// Notice that if the MCU is rebooted while the button is pressed down, no
// event is triggered and the LED remains off.
switch (eventType) {
case ace_button::AceButton::kEventPressed:
if (button->getPin() == bSet_pin) {
digitalWrite(LED_BUILTIN, HIGH);
}
break;
case ace_button::AceButton::kEventReleased:
if (button->getPin() == bSet_pin) {
digitalWrite(LED_BUILTIN, LOW);
if (++state >= ntcStates) state = 0;
update_l3();
update_l2();
} else if (button->getPin() == bUp_pin) {
tcStates[state].val1 += increment;
Serial.println(F("Up!"));
update_l3();
} else if (button->getPin() == bDown_pin) {
if(button)
tcStates[state].val1 -= increment;
Serial.println(F("Down clicked!"));
update_l3();
} else if (button->getPin() == bShift_pin) {
Serial.println(F("Shift clicked!"));
increment *= 10;
if (increment > 100) increment = 0.01;
Serial.println(increment);
update_l3();
}
break;
case ace_button::AceButton::kEventClicked:
if (button->getPin() == bShift_pin) {
Serial.println(F("Shift clicked!"));
} else if (button->getPin() == bDown_pin) {
Serial.println(F("Down clicked!"));
} else if (button->getPin() == bUp_pin) {
Serial.println(F("Up clicked!"));
}
break;
}
}
void printGraphviz() {
Serial.println("//// https://dreampuf.github.io/GraphvizOnline/");
Serial << "// for " << __FILE__ << "\n//" << __DATE__ " " __TIME__ <<'\n';
Serial.println("digraph G {\n PowerOn ->");
for (auto &x : tcStates) {
int next = x.next;
int n = &x - tcStates; // index
Serial << "##n:" << n << " next:" << next <<"\n";
Serial.print('"');
Serial.print(x.name);
Serial.print('"');
if (next < 0){ // long press to next menu head
Serial.print(" -> ");
Serial.print('"');
Serial.print(tcStates[n+1].name);
Serial.print('"');
Serial.println("[label = \"long press\"];");
next = -next;
Serial.print('"');
Serial.print(x.name);
Serial.print('"');
}
if (next >= 0) {
// Serial << "##n:" << n << " next:" << next <<"\n";
Serial.print(" -> ");
Serial.print('"');
Serial.print(tcStates[next].name);
Serial.print('"');
Serial.println(";");
}
}
Serial.println(" }");
}