Hi David, thanks for your message.
I have opted for the OLED 0.96 display principally, because I have a few of them lying around and cost is a major consideration. I also need to use an IC2 display as I have used all digital pins for the keyboard and rotary encoder.
The Adafruit library does not appear to work correctly with this display, whereas the u8g2 does.
As requested, please find herewith the original 2004 LCD sketch
/*
Mark Fox's DCC++ 'Bodnar Throttle'.
Rewritten and customised from Dave Bodnar's code from June 16th, 2016, his version 2.6a
Version 1.00 uses an Arduino MEGA2560 (devboard) with a 4x4 keypad, 20x4 I2C LCD Display, and uses digital debouncing on the KY-040 rotary encoder without an interrupt.
This version is 1.03, a heavy edit streamlining all the function comms.
Date 6th August 2020.
*/
#include "Arduino.h"
#include <EEPROM.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#define RE_CLK 2 // Encoder on digital pins 2 & 3
#define RE_DATA 3
#define RE_Button 4 // Pushbutton on digital pin 4. Use 10k external pullup resistor to Vcc
#define I2C_ADDR 0x27
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR, En_pin, Rw_pin, Rs_pin, D4_pin, D5_pin, D6_pin, D7_pin);
int debug = 0; // set to 1 to show debug info on serial port - assume that it will cause issues with DCC++ depending on what is sent
uint8_t zerodot[8] = {0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0}; // Character for function not selected
uint8_t onedot[8] = {0x0, 0xe, 0x1f, 0x1b, 0x1f, 0xe, 0x0}; // Character for function selected
// Setup Keypad variables
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// #=35; *=42; 0-9=48to57; A-D=65to68
byte rowPins[ROWS] = {5, 6, 7, 8 }; //{8,7,6,5 }; //connect to the row pinouts of the keypad
byte colPins[COLS] = {9, 10, 11, 12}; // {12,11,10,9}; //connect to the column pinouts of the keypad
// Now setup the hardware
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
static uint8_t prevNextCode = 0; // statics for rotary encoder
static uint16_t store = 0;
int buttonState = 0;
int re_absolute = 0;
char key ;
int maxLocos = 4;// number of loco addresses
int LocoAddress[4] = {1111, 2222, 3333, 4444};
int LocoDirection[4] = {1, 1, 1, 1};
int LocoSpeed[4] = {0, 0, 0, 0};
byte LocoFNs[4][5] = {
{128, 176, 160, 0, 0},
{128, 176, 160, 0, 0},
{128, 176, 160, 0, 0},
{128, 176, 160, 0, 0},
};
int ActiveAddress = 0; // make address1 active
int i = 0;
char VersionNum[] = "2.01";
void setup() {
//Setup Encoder Here
pinMode(RE_CLK, INPUT);
pinMode(RE_CLK, INPUT_PULLUP);
pinMode(RE_DATA, INPUT);
pinMode(RE_DATA, INPUT_PULLUP);
lcd.begin (20, 4);
lcd.setBacklightPin(BACKLIGHT_PIN, POSITIVE);
lcd.setBacklight(HIGH);
lcd.createChar(0, zerodot); // Using glyph generated above
lcd.createChar(1, onedot);
lcd.home (); // go home
Serial.begin (115200); // Output to be transmitted to serial monitor and TX pin on Nano
getAddresses(); // read loco IDs from eeprom
lcd.print("DCC ++ Throttle");
lcd.setCursor(0, 1);
lcd.print("March 2022 v");
for (int i = 0; i < 4; i++) {
lcd.print(VersionNum[i]);
}
Serial.print("March 2022 Version ");
for (int i = 0; i < 4; i++) {
Serial.print(VersionNum[i]);
}
if (debug == 1) Serial.println("");
Serial.print("<0>");// power off command to control unit vial serial monitor
delay(1500);
lcd.clear();
InitialiseSpeedsLCD();
InitialiseFunctionLCD();
lcd.setCursor(4, ActiveAddress);
lcd.blink();
} // END SETUP
void loop() {
static int8_t re_val;
// First check the keypad
key = keypad.getKey();
if (key) {
if (debug == 1) {
Serial.println(" ");
Serial.println(key);
}
switch (key) {
case '*':
all2ZeroSpeed();
InitialiseSpeedsLCD();
getLocoAddress();
updateSpeedsLCD();
key = 0;
break;
case '#':
ActiveAddress++;
if (ActiveAddress >= maxLocos) ActiveAddress = 0;
updateSpeedsLCD();
InitialiseFunctionLCD();
delay(200);
key = 0;
re_absolute = LocoSpeed[ActiveAddress];
doDCCspeed();
break;
case 'D':
//Do nowt for now - menu goes in here?
doExtendedFunction();
updateSpeedsLCD();
break;
default:
// It's 0 - 9 or A - C so perform a loco function
key = key - 48;
if (key > 10) key = key - 7;
doFunction(key);
break;
}
}
// Read encoder
if ( re_val = read_rotary() ) {
re_absolute += re_val;
re_absolute = constrain(re_absolute, 0, 126);
LocoSpeed[ActiveAddress] = re_absolute;
doDCCspeed();
updateSpeedsLCD();
}
buttonState = digitalRead(RE_Button);
if (buttonState == LOW) {
delay(50);
buttonState = digitalRead(RE_Button); // check a 2nd time to be sure
if (buttonState == LOW) {// check a 2nd time to be sure
// Reverse direction...
LocoDirection[ActiveAddress] = !LocoDirection[ActiveAddress];
// ... and set speed to zero (saves loco running away on slow decel/accel set in decoder.)
LocoSpeed[ActiveAddress] = 0;
re_absolute = 0;
doDCCspeed();
updateSpeedsLCD();
do { // routine to stay here till button released & not toggle direction
buttonState = digitalRead(RE_Button);
} while (buttonState == LOW);
}
}
} //END LOOP
//START DO FUNCTION BUTTONS
void doExtendedFunction() {
lcd.setCursor(9 , 0);
int counter = 0;
int total = 0;
do {
key = keypad.getKey();
if (key) {
counter++;
// Abort if # or *
if (key < 48) return;
// otherwise...
int number = key - 48;
// if it 3-9 or A-D, and this is the first key...
if (number > 2 && counter == 1) {
if (debug == 1) Serial.print("First Time, 3 to D");
return;
}
// else we can assume it's 0,1,2...
else if ( counter == 1 ) {
lcd.setCursor(9 , number + 1);
total = number * 10;
}
else if (counter == 2 && number < 10) {
// Second time around... and 0-9
lcd.setCursor(number + 10 , total / 10 + 1);
total = total + number;
}
else if (counter == 2 && number > 9) {
if (debug == 1) Serial.print("Second Time, A-D");
return;
}
}
} while (counter <= 1); // collect exactly 2 digits
if (debug == 1) Serial.print(total);
doFunction(total);
}
void doFunction(int FunctoFlip) {
// Will be passed a number from 0 to 28.
int FuncSet;
int FuncLoc;
if (debug == 1) {
Serial.print("doFunction - passed:");
Serial.println(FunctoFlip);
}
switch (FunctoFlip)
{
case (0) ... (4):
if (debug == 1) Serial.print("0-4");
FuncSet = 0;
FuncLoc = FunctoFlip - 1;
if (FuncLoc == -1) FuncLoc = 4;
break;
case (5) ... (8):
if (debug == 1) Serial.print("5-8");
FuncSet = 1;
FuncLoc = FunctoFlip - 5;
break;
case (9) ... (12):
if (debug == 1) Serial.print("9-12");
FuncSet = 2;
FuncLoc = FunctoFlip - 9;
break;
case (13) ... (20):
if (debug == 1) Serial.print("13-20");
FuncSet = 3;
FuncLoc = FunctoFlip - 13;
break;
case (21) ... (28):
if (debug == 1) Serial.print("21-28");
FuncSet = 4;
FuncLoc = FunctoFlip - 21;
break;
case (29):
if (debug == 1) Serial.print("29");
// Maybe set all the LocoFNs to zero?
// Like this! :)
LocoFNs[ActiveAddress][0] = 128;
LocoFNs[ActiveAddress][1] = 176;
LocoFNs[ActiveAddress][2] = 160;
LocoFNs[ActiveAddress][3] = 0;
LocoFNs[ActiveAddress][4] = 0;
for (int ddf = 0; ddf < 4; ddf++) {
doDCCfunction(ddf);
}
InitialiseFunctionLCD();
return;
break;
}
// What we now have is the number of which bit we'd like to change in that specific function's bitpattern
// The next command is effectively 2^number, thus giving us a bitpattern of the number...
FuncLoc = 1 << FuncLoc ;
// ... which we can then simply XOR onto the existing bitpattern to flip the bit.
LocoFNs[ActiveAddress][FuncSet] ^= FuncLoc;
doDCCfunction(FuncSet);
InitialiseFunctionLCD();
if (debug == 1) {
Serial.println("**");
Serial.print(LocoFNs[ActiveAddress][FuncSet], BIN);
Serial.print(" - ");
Serial.println(LocoFNs[ActiveAddress][FuncSet], DEC);
Serial.println(ActiveAddress);
Serial.println(FuncSet);
Serial.println("**");
}
}
void getLocoAddress() {
int saveAddress = LocoAddress[ActiveAddress];
Serial.print("<0>");// power off to tracks
int total = 0;
int counter = 0;
do {
lcd.setCursor( counter , ActiveAddress);
key = keypad.getKey();
if (key == '#' || key == '*' || key == 'A' || key == 'B' || key == 'C' || key == 'D' ) { //abort when either is hit
total = saveAddress;
break; // exits the do...while loop if above buttons pressed - ABORT new address
}
if (key) {
counter++;
int number = key - 48;
total = total * 10 + number;
if (key == 48 && total == 0) {
lcd.print(" ");
} else {
lcd.print(key);
}
if (debug == 1) Serial.print("Counter = ");
if (debug == 1) Serial.print(counter);
if (debug == 1) Serial.print(" key = ");
if (debug == 1) Serial.print(key);
if (debug == 1) Serial.print(" val = ");
if (debug == 1) Serial.println(number);
}
} while (counter <= 3); // collect exactly 4 digits
if (total == 0) total = saveAddress;
LocoAddress[ActiveAddress] = total;
if (debug == 1) Serial.print("Actually saving: ");
if (debug == 1) Serial.println(total);
saveAddresses();
Serial.println("<1>");// power back on to tracks
updateSpeedsLCD();
}
void doDCCspeed() {
if (debug == 1) Serial.println(LocoDirection[ActiveAddress] );
Serial.print("<1>");
Serial.print("<t1 ");
Serial.print(LocoAddress[ActiveAddress] );//locoID);
Serial.print(" ");
Serial.print(LocoSpeed[ActiveAddress] );
Serial.print(" ");
Serial.print(LocoDirection[ActiveAddress] );
Serial.println(">");
}
void doDCCfunction(int FuncSetX) {
Serial.write("<f ");
Serial.print(LocoAddress[ActiveAddress] );
Serial.print(" ");
switch (FuncSetX) {
case (0) ... (2):
// First three function sets are plain single byte commands...
break;
case (3):
// Last two 8-bit sets are prefixed with 222/223.
Serial.print("222 ");
break;
case (4):
Serial.print("223 ");
break;
}
int fx = LocoFNs[ActiveAddress][FuncSetX];
Serial.print(fx);
Serial.print(" >");
}
void all2ZeroSpeed() {
for (int tempx = 0; tempx < maxLocos; tempx++) {
// Set the recorded speeds to zero...
LocoSpeed[tempx] = 0;
// ... then transmit the commands too.
Serial.print("<t1 ");
Serial.print(LocoAddress[tempx] );//locoID);
Serial.print(" 0 ");
Serial.print(LocoDirection[tempx] );
Serial.write(">");
}
}
void getAddresses() {
int xxx = 0;
for (int xyz = 0; xyz <= maxLocos - 1; xyz++) {
LocoAddress[xyz] = EEPROM.read(xyz * 2) * 256;
LocoAddress[xyz] = LocoAddress[xyz] + EEPROM.read(xyz * 2 + 1);
if (LocoAddress[xyz] >= 10000) LocoAddress[xyz] = 3;
if (debug == 1) {
Serial.println(" ");
Serial.print("loco = ");
Serial.print(LocoAddress[xyz]);
Serial.print(" address# = ");
Serial.print(xyz + 1);
}
}
if (debug == 1) Serial.println(" ");
maxLocos = EEPROM.read(20);
if (debug == 1) Serial.print("EEPROM maxLocos = ");
if (debug == 1) Serial.println(maxLocos);
if (maxLocos >= 4) maxLocos = 4;
}
void saveAddresses() {
int xxx = 0;
for (int xyz = 0; xyz <= maxLocos - 1; xyz++) {
xxx = LocoAddress[xyz] / 256;
if (debug == 1) {
Serial.println(" ");
Serial.print("loco = ");
Serial.print(LocoAddress[xyz]);
Serial.print(" address# = ");
Serial.print(xyz);
Serial.print(" msb ");
Serial.print(xxx);
Serial.print(" writing to ");
Serial.print(xyz * 2);
Serial.print(" and ");
Serial.print(xyz * 2 + 1);
} // Endif
EEPROM.write(xyz * 2, xxx);
xxx = LocoAddress[xyz] - (xxx * 256);
if (debug == 1) {
Serial.print(" lsb ");
Serial.print(xxx);
}
EEPROM.write(xyz * 2 + 1, xxx);
}
EEPROM.write(20, maxLocos);
}
void InitialiseSpeedsLCD() {
for (int tempx = 0; tempx < maxLocos; tempx++) {
// Prints LocoID(right justified), direction arrow, and speed(right justified)
lcd.setCursor(0, tempx);
String temp = " " + String(LocoAddress[tempx] , DEC);
int tlen = temp.length() - 4;
lcd.print(temp.substring(tlen));
// ... direction...
if (LocoDirection[tempx] == 1 ) {
lcd.print(">");
}
else {
lcd.print("<");
}
// ... speed ...
temp = " " + String(LocoSpeed[tempx] , DEC);
tlen = temp.length() - 3;
lcd.print(temp.substring(tlen));
}
// Return cursor to direction arrow for loco under control
lcd.setCursor(4, ActiveAddress);
}
void InitialiseFunctionLCD() {
int funcount = 0;
lcd.setCursor(9, 0);
lcd.print("F0123456789");
for (int tempy = 0; tempy < 3; tempy++) {
lcd.setCursor(9, tempy + 1);
lcd.print(tempy);
for (int tempx = 0; tempx < 10; tempx++) {
lcd.setCursor(tempx + 10, tempy + 1);
funcount = tempy * 10 + tempx;
// Funkiness to put function 0 on the fourth bit...
if (funcount == 0) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][0], 4)));
}
// ... and F1 through F4 on the zeroth to third bits.
else if (funcount >= 1 && funcount <= 4) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][0], funcount - 1)));
}
else if (funcount >= 5 && funcount <= 8) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][1], funcount - 5)));
}
else if (funcount >= 9 && funcount <= 12) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][2], funcount - 9)));
}
else if (funcount >= 13 && funcount <= 20) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][3], funcount - 13)));
}
else if (funcount >= 21 && funcount <= 28) {
lcd.write(byte(bitRead(LocoFNs[ActiveAddress][4], funcount - 21)));
}
else {
// 29th location, hint that 29 will turn off all loco functions.
lcd.print("X");
}
}
lcd.setCursor(4, ActiveAddress);
}
}
void updateSpeedsLCD() {
int tempx = ActiveAddress;
lcd.setCursor(0, tempx);
String temp = " " + String(LocoAddress[tempx] , DEC);
int tlen = temp.length() - 4;
lcd.print(temp.substring(tlen));
if (LocoDirection[tempx] == 1 ) {
lcd.print(">");
}
else {
lcd.print("<");
}
temp = " " + String(LocoSpeed[tempx] , DEC);
tlen = temp.length() - 3;
lcd.print(temp.substring(tlen));
lcd.setCursor(4, ActiveAddress);
}
// Robust Rotary encoder reading
// Copyright John Main - best-microcontroller-projects.com
// A vald CW or CCW move returns 1, invalid returns 0.
int8_t read_rotary() {
static int8_t rot_enc_table[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};
prevNextCode <<= 2;
if (digitalRead(RE_DATA)) prevNextCode |= 0x02;
if (digitalRead(RE_CLK)) prevNextCode |= 0x01;
prevNextCode &= 0x0f;
// If valid then store as 16 bit data.
if (rot_enc_table[prevNextCode] ) {
store <<= 4;
store |= prevNextCode;
if ((store & 0xff) == 0x2b) return -1;
if ((store & 0xff) == 0x17) return 1;
}
return 0;
}