// simple project using Arduino UNO and 16x2 character display to display smooth gauge,
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// FULL TUTORIAL: https://youtu.be/ZzIGHiHObYw
// GAUGE IN 11 MINUTES TUTORIAL: https://youtu.be/upE17NHrdPc
// Links related to this project:
// Arduino UNO - https://s.click.aliexpress.com/e/_AXDw1h
// Arduino breadboard prototyping shield - https://s.click.aliexpress.com/e/_ApbCwx
// 16x2 displays with IIC - https://s.click.aliexpress.com/e/_9Hl3JV
// 16x2 display with RGB backlight - https://s.click.aliexpress.com/e/_9wgpeb
// original sketch from YWROBOT - https://wokwi.com/arduino/libraries/LiquidCrystal_I2C/HelloWorld
// character creator - https://tusindfryd.github.io/screenduino/
// another character creator - https://maxpromer.github.io/LCD-Character-Creator/
// sprintf explanation - https://www.programmingelectronics.com/sprintf-arduino/
// custom characters simplest project - https://wokwi.com/projects/294395602645549578
// Arduino I2C scanner - https://playground.arduino.cc/Main/I2cScanner/
// 16x2 available characters - https://docs.wokwi.com/parts/wokwi-lcd1602#font
// Bitwise Operators in GIFs - https://blog.wokwi.com/bitwise-operators-in-gifs/
// Bitwise operators Arduino documentation - https://www.arduino.cc/reference/en/language/structure/bitwise-operators/bitshiftleft/
#include <LiquidCrystal_I2C.h> // if you don´t have I2C version of the display, use LiquidCrystal.h library instead
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
//LiquidCrystal_I2C lcd(0x3f,16,2); // set the LCD address to 0x3f for a 16 chars and 2 line display
// if you don´t know the I2C address of the display, use I2C scanner first (https://playground.arduino.cc/Main/I2cScanner/)
// define custom characters/arrays - every character is 5x8 "pixels"
byte gauge_empty[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111}; // empty middle piece
byte gauge_fill_1[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111}; // filled gauge - 1 column
byte gauge_fill_2[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111}; // filled gauge - 2 columns
byte gauge_fill_3[8] = {B11111, B11100, B11100, B11100, B11100, B11100, B11100, B11111}; // filled gauge - 3 columns
byte gauge_fill_4[8] = {B11111, B11110, B11110, B11110, B11110, B11110, B11110, B11111}; // filled gauge - 4 columns
byte gauge_fill_5[8] = {B01111, B11111, B11111, B11111, B11111, B11111, B11111, B01111}; // filled gauge - 5 columns
byte gauge_left[8] = {B01111, B10000, B10000, B10000, B10000, B10000, B10000, B01111}; // left part of gauge - empty
byte gauge_right[8] = {B11110, B00001, B00001, B00001, B00001, B00001, B00001, B11110}; // right part of gauge - empty
byte warning_icon[8] = {B00100, B00100, B01110, B01010, B11011, B11111, B11011, B11111}; // warning icon - just because we still have one custom character left
byte gauge_left_dynamic[8]; // left part of gauge dynamic - will be set in the loop function
byte gauge_right_dynamic[8]; // right part of gauge dynamic - will be set in the loop function
uint8_t cpu_gauge = 0; // value for the CPU gauge
int move_offset = 0; // used to shift bits for the custom characters
const uint8_t gauge_size_chars = 16; // width of the gauge in number of characters
const uint8_t pixels_per_char = 5; // width of the chars in number of pixels
char gauge_string[gauge_size_chars + 1]; // string that will include all the gauge character to be printed
const byte gauge_max = 100;
float units_per_pixel = (gauge_size_chars * 5.0) / (gauge_max * 1.0); // every character is 5px wide, we want to count from 0-100
byte value_in_pixels_arr[gauge_max];
void setup()
{
lcd.init(); // initialize the 16x2 lcd module
lcd.createChar(0, warning_icon); // warning icon - just because we have one more custom character that we could use
lcd.createChar(1, gauge_fill_1); // filled gauge - 1 column
lcd.createChar(2, gauge_fill_2); // filled gauge - 2 columns
lcd.createChar(3, gauge_fill_3); // filled gauge - 3 columns
lcd.createChar(4, gauge_fill_4); // filled gauge - 4 columns
lcd.createChar(5, gauge_left); // left gauge
lcd.createChar(6, gauge_right); // right gauge
lcd.createChar(7, gauge_empty); // middle empty gauge
lcd.backlight(); // enable backlight for the LCD module
// Print static parts of screen in order to not write them over and over. ~3ms per char.
lcd.setCursor(0, 0); // move cursor to top left
lcd.print("Time:");
lcd.setCursor(12, 0); // move cursor unit display
lcd.print("us");
lcd.setCursor(0, 1); // move cursor to bottom left
lcd.write(byte(5));
lcd.setCursor(gauge_size_chars - 1, 1); // move cursor to bottom right
lcd.write(byte(6));
// Pre calculate pixel values
for (uint8_t i = 0; i < gauge_max; i++)
value_in_pixels_arr[i] = round(i * units_per_pixel);
// write top and bottom for guage sides as they will never change.
gauge_left_dynamic[0] = gauge_left[0];
gauge_left_dynamic[7] = gauge_left[7];
gauge_right_dynamic[0] = gauge_right[0];
gauge_right_dynamic[7] = gauge_right[7];
}
long unsigned int timeStamp = 0;
uint8_t tip_position = 0; // 0= not set, 1=tip in first char, 2=tip in middle, 3=tip in last char
uint8_t last_tip_position = 0; // 0= not set, 1=tip in first char, 2=tip in middle, 3=tip in last char
void loop()
{
timeStamp = micros();
byte value_in_pixels = value_in_pixels_arr[cpu_gauge]; // cpu_gauge value converted to pixel width
if (value_in_pixels < 6) {
tip_position = 1; // tip is inside the first character
}
else if (value_in_pixels > (gauge_size_chars - 1) * pixels_per_char) {
tip_position = 3; // tip is inside the last character
}
else {
tip_position = 2; // tip is somewhere in the middle
}
move_offset = 4 - ((value_in_pixels - 1) % 5); // value for offseting the pixels for the smooth filling
uint8_t oldByte; // Keep track if any pixels changed in dynamic chars
bool updateChar; // Update only if appearence change since createChar costs ~15ms
//Only update char if tip position is 1 or was 1 last time around.
if (tip_position == 1 || last_tip_position == 1) {
updateChar = false;
for (int i = 1; i < 7; i++) { // dynamically create left part of the gauge
oldByte = gauge_left_dynamic[i];
if ( tip_position > 1 ) gauge_left_dynamic[i] = gauge_fill_5[i]; // Fill gauge if tip pos > 1
else gauge_left_dynamic[i] = (gauge_fill_5[i] << move_offset | gauge_left[i]);
if (oldByte != gauge_left_dynamic[i]) updateChar = true;
}
if (updateChar) lcd.createChar(5, gauge_left_dynamic); // create custom character for the left part of the gauge
}
//Only update char if tip position is 3 or was 3 last time around.
if (tip_position == 3 || last_tip_position == 3) {
updateChar = false;
for (int i = 1; i < 7; i++) { // dynamically create right part of the gauge
oldByte = gauge_right_dynamic[i];
if ( tip_position < 3 ) gauge_right_dynamic[i] = gauge_right[i]; // Empty gauge if tip pos < 3
else gauge_right_dynamic[i] = (gauge_fill_5[i] << move_offset) | gauge_right[i];
if (oldByte != gauge_right_dynamic[i]) updateChar = true;
} // tip on the last character
if (updateChar) lcd.createChar(6, gauge_right_dynamic); // create custom character for the right part of the gauge
}
char old_gauge_string[gauge_size_chars + 1];
for (int i = 1; i < gauge_size_chars - 1; i++) { // set all the characters for the gauge
old_gauge_string[i] = gauge_string[i];
if (value_in_pixels <= i * 5) {
gauge_string[i] = byte(7); // empty character
}
else if (value_in_pixels > i * 5 && value_in_pixels < (i + 1) * 5) {
gauge_string[i] = byte(5 - move_offset); // tip
}
else {
gauge_string[i] = byte(255); // filled character
}
}
// gauge drawing
for (int i = 1; i < gauge_size_chars - 1; i++) { // set all the characters for the gauge
if (gauge_string[i] != old_gauge_string[i]) {
lcd.setCursor(i, 1); // Only update the one that is changed
lcd.write(gauge_string[i]);
}
}
last_tip_position = tip_position;
// increase the CPU value, set between 0-100
cpu_gauge = cpu_gauge + 1;
if (cpu_gauge > 100) {
cpu_gauge = 0;
}
//cpu_gauge = rand() % 101;
lcd.setCursor(6, 0); // move cursor to top left
lcd.print(micros() - timeStamp); // print the string on the display
lcd.print(" ");
//lcd.write(byte(0)); // print warning character
delay(200 - ((micros() - timeStamp) / 1000)); // wait for a while - 100ms = update the screen 10x in a second
}