Hi ! Newbie here, please be gentle - If I were to say that I am a complete novice, it would be an overstatement!
I am working on a project which was originally written for a 2004 LCD, unfortunately, this display is too big for my purposes and as I have a couple of non branded 128 * 64 IC2 SSD1306 displays at hand, I am trying to convert the sketch to use an OLED.
Hardware: Nano V3 clone, 4 x 4 matrix keyboard , rotary encoder and non branded 128*64 SSD1306 IC2 display. All hardware & wiring is correct and functional.
I found out through trial and error that my display is not 100% compatible with the Adafruit library, but that u8g2 works well.
I have the display configured using:
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
I am trying to work through the sketch 1 line at a time, having commented out all references to lcd so that the sketch will compile and run and so that I can test the results.
I have used "// To be modified for u8g2 library //" to denote what still needs to be done
So far I can only get the display to print simple text and a variables, but I am stumped now and don't seem able to proceed.
Would anyone be able to help please? Don't forget I am a complete novice
uint8_t zerodot[8] = {0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0};
uint8_t onedot[8] = {0x0, 0xe, 0x1f, 0x1b, 0x1f, 0xe, 0x0};
lcd.createChar(0, zerodot);
lcd.createChar(1, onedot);
I can see that these lines generates a graphic image on the lcd, the first a simple dot, the second similar to a donut. They are used to denote if a function option has been selected or not.
and:
lcd.setCursor(4, ActiveAddress);
lcd.blink();
This positions a cursor and blinks it. I have tried (to the best of my understanding) to follow a couple of examples found online, but I am stumped now.
Full code below - Thanks for looking
/*
Modified Oled throttle building on the work of Mark Fox & Dave Bodnar
Customised from Dave Bodnar's original code from 16th June 2016, his version 2.6a
and Mark Fox's edit & streamlining version 1.03 dated 6th August 2020.
This version attempts to utilise a non-branded 128 * 64 IC2 Oled display which does not work with Adafruit library
Attempting to use U8g2 library using page buffer due to memory constraints
Using an Arduino Nano V3 clone with a 4x4 keypad, non-branded 128 * 64 Ic2 Oled display,
Digital debouncing on the KY-040 rotary encoder without an interrupt, Copyright John Main - best-microcontroller-projects.com
This version (2.01) is an attempt to replace previous 2004 LCD display which utilised <LiquidCrystal_I2C.h>
Date March 2022.
*/
#include "Arduino.h"
#include <EEPROM.h>
#include <Wire.h>
#include <Keypad.h>
#include <U8g2lib.h>
// Display Type - Unbranded OLED 128x64 (does not display correctly using Adafruit library)
// Trying to use page buffer to reduce RAM useage
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// Encoder Pin definitions
#define RE_CLK 2 // Check rotation, pins 2 & 3 may need to be swapped if cw turn is not +ve
#define RE_DATA 3 // See above
#define RE_Button 4 // Use external 10K pullup resistor
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}; // Graphic for when function IS NOT selected
uint8_t onedot[8] = {0x0, 0xe, 0x1f, 0x1b, 0x1f, 0xe, 0x0}; // Graphic when function IS selected
// Keypad variables - 4 x 4 Matrix (8 pins, pin 1 to 4 = C1 to C4, pin 5 to 8 = R1 to R4
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'}
};
byte rowPins[ROWS] = {5, 6, 7, 8 };
byte colPins[COLS] = {9, 10, 11, 12};
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 ;
// Array set for 4 Locos maximum. Needs code tidyup to make truly flexible
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};
// Neater way of storing Loco Function Bytes.
// Rows are Locos, Columns are the five sets of function groups
// 128 = prefix for functions 0 to 4 (last five bits are functions 04321.)
// 176 = prefix for functions 5 to 8
// 160 = prefix for functions 9 to 12
// 0 & 0 are full bitpatterns for functions 13 to 20 and 21 to 28.
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"; // Set version
void setup() {
// Setup Encoder
pinMode(RE_CLK, INPUT);
pinMode(RE_CLK, INPUT_PULLUP);
pinMode(RE_DATA, INPUT);
pinMode(RE_DATA, INPUT_PULLUP);
// Setup Serial Monitor
Serial.begin(115200);
Serial.println("Entering Setup"); // Trouble shooting txt remove when operational
// Start OLED display
u8g2.begin();
u8g2.setFont(u8g2_font_7x13_tf);
u8g2.firstPage();
do {
u8g2.setCursor(0, 20);
u8g2.print(("Welcome"));
} while ( u8g2.nextPage() );
delay(2000);
Serial.println("Oled print Welcome "); // Trouble shooting txt remove when operational
// To be modified for u8g2 library // lcd.createChar(0, zerodot);
// To be modified for u8g2 library // lcd.createChar(1, onedot);
getAddresses(); // read loco IDs from eeprom
u8g2.firstPage();
do {
u8g2.setCursor(0, 20);
u8g2.print(("DCC++ THROTTLE"));
u8g2.setCursor(0, 40);
u8g2.print(("March 2022 v "));
for (int i = 0; i < 4; i++) {
u8g2.print(VersionNum[i]);
}
} while ( u8g2.nextPage() );
delay(1000);
Serial.println(" ");
Serial.print("March 2022 Version ");
for (int i = 0; i < 4; i++) {
Serial.print(VersionNum[i]);
}
Serial.println("");
if (debug == 1) Serial.println("");
Serial.print("<0>");// power off to DCC++ unit
delay(1500);
Serial.println(" Power off command"); // Trouble shooting txt remove when operational
delay(1500); // Trouble shooting txt remove when operational
u8g2.clearDisplay();
InitialiseSpeedsOLED();
InitialiseFunctionOLED();
// To be modified for u8g2 library // lcd.setCursor(4, ActiveAddress);
// To be modified for u8g2 library // lcd.blink();
Serial.println("End of setup"); // Trouble shooting txt remove when operational
} // 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();
InitialiseSpeedsOLED();
getLocoAddress();
updateSpeedsOLED();
key = 0;
break;
case '#':
ActiveAddress++;
if (ActiveAddress >= maxLocos) ActiveAddress = 0;
updateSpeedsOLED();
InitialiseFunctionOLED();
delay(200);
key = 0;
re_absolute = LocoSpeed[ActiveAddress];
doDCCspeed();
break;
case 'D':
doExtendedFunction();
updateSpeedsOLED();
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();
updateSpeedsOLED();
}
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();
updateSpeedsOLED();
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() {
// To be modified for u8g2 library // 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 ) {
// To be modified for u8g2 library // lcd.setCursor(9 , number + 1);
total = number * 10;
}
else if (counter == 2 && number < 10) {
// Second time around... and 0-9
// To be modified for u8g2 library // 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);
}
InitialiseFunctionOLED();
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);
InitialiseFunctionOLED();
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 {
// To be modified for u8g2 library // lcd.setCursor( counter , ActiveAddress);
key = keypad.getKey();
if (key == '#' || key == '*' || key == 'A' || key == 'B' || key == 'C' || key == 'D' ) { //abort when either is hit
//LocoAddress[ActiveAddress] = saveAddress;
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) {
// To be modified for u8g2 library // lcd.print(" ");
} else {
// To be modified for u8g2 library // 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 all zeroes entered, return to original address
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
updateSpeedsOLED();
}
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() {
/* Loads of bugs here.
A) tempx <= maxLocos meant five commands were sent, the fifth to a random(?) loco.
B) LocoSpeed and Direction were set to those of loco 1. Not good practice, although not required to be correct as <0> is sent after.
As of 4thAugust2020, modified to do what it says it does.
*/
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 InitialiseSpeedsOLED() {
for (int tempx = 0; tempx < maxLocos; tempx++) {
// Prints LocoID(right justified), direction arrow, and speed(right justified)
// To be modified for u8g2 library // lcd.setCursor(0, tempx);
String temp = " " + String(LocoAddress[tempx] , DEC);
int tlen = temp.length() - 4;
// To be modified for u8g2 library // lcd.print(temp.substring(tlen));
// ... direction...
if (LocoDirection[tempx] == 1 ) {
// To be modified for u8g2 library // lcd.print(">");
}
else {
// To be modified for u8g2 library // lcd.print("<");
}
// ... speed ...
temp = " " + String(LocoSpeed[tempx] , DEC);
tlen = temp.length() - 3;
// To be modified for u8g2 library // lcd.print(temp.substring(tlen));
}
// Return cursor to direction arrow for loco under control
// To be modified for u8g2 library // lcd.setCursor(4, ActiveAddress);
}
void InitialiseFunctionOLED() {
int funcount = 0;
// To be modified for u8g2 library // lcd.setCursor(9, 0);
// To be modified for u8g2 library // lcd.print("F0123456789");
for (int tempy = 0; tempy < 3; tempy++) {
// To be modified for u8g2 library // lcd.setCursor(9, tempy + 1);
// To be modified for u8g2 library // lcd.print(tempy);
for (int tempx = 0; tempx < 10; tempx++) {
// To be modified for u8g2 library // lcd.setCursor(tempx + 10, tempy + 1);
funcount = tempy * 10 + tempx;
// Funkiness to put function 0 on the fourth bit...
if (funcount == 0) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][0], 4)));
}
// ... and F1 through F4 on the zeroth to third bits.
else if (funcount >= 1 && funcount <= 4) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][0], funcount - 1)));
}
else if (funcount >= 5 && funcount <= 8) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][1], funcount - 5)));
}
else if (funcount >= 9 && funcount <= 12) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][2], funcount - 9)));
}
else if (funcount >= 13 && funcount <= 20) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][3], funcount - 13)));
}
else if (funcount >= 21 && funcount <= 28) {
// To be modified for u8g2 library // lcd.write(byte(bitRead(LocoFNs[ActiveAddress][4], funcount - 21)));
}
else {
// 29th location, hint that 29 will turn off all loco functions.
// To be modified for u8g2 library // lcd.print("X");
}
}
// To be modified for u8g2 library // lcd.setCursor(4, ActiveAddress);
}
}
void updateSpeedsOLED() {
int tempx = ActiveAddress;
// To be modified for u8g2 library // lcd.setCursor(0, tempx);
String temp = " " + String(LocoAddress[tempx] , DEC);
int tlen = temp.length() - 4;
// To be modified for u8g2 library // lcd.print(temp.substring(tlen));
if (LocoDirection[tempx] == 1 ) {
// To be modified for u8g2 library // lcd.print(">");
}
else {
// To be modified for u8g2 library // lcd.print("<");
}
temp = " " + String(LocoSpeed[tempx] , DEC);
tlen = temp.length() - 3;
// To be modified for u8g2 library // lcd.print(temp.substring(tlen));
// To be modified for u8g2 library // lcd.setCursor(4, ActiveAddress);
}
// Robust Rotary encoder reading - external pullup resistors not used
// 0.1uF cap across D2 & Gnd and D3 & Gnd
// 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;
}



