// 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] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111}; // filled gauge - 5 columns
byte gauge_left[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111}; // left part of gauge - empty
byte gauge_right[8] = {B11111, B00001, B00001, B00001, B00001, B00001, B00001, B11111}; // right part of gauge - empty
byte gauge_mask_left[8] = {B01111, B11111, B11111, B11111, B11111, B11111, B11111, B01111}; // mask for rounded corners for leftmost character
byte gauge_mask_right[8] = {B11110, B11111, B11111, B11111, B11111, B11111, B11111, B11110}; // mask for rounded corners for rightmost character
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
int cpu_gauge = 0; // value for the CPU gauge
char buffer[10]; // helper buffer to store C-style strings (generated with sprintf function)
int move_offset = 0; // used to shift bits for the custom characters
const int gauge_size_chars = 16; // width of the gauge in number of characters
char gauge_string[gauge_size_chars+1]; // string that will include all the gauge character to be printed
void setup()
{
lcd.init(); // initialize the 16x2 lcd module
lcd.createChar(7, gauge_empty); // middle empty gauge
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(0, warning_icon); // warning icon - just because we have one more custom character that we could use
lcd.backlight(); // enable backlight for the LCD module
}
void loop()
{
float units_per_pixel = (gauge_size_chars*5.0)/100.0; // every character is 5px wide, we want to count from 0-100
int value_in_pixels = round(cpu_gauge * units_per_pixel); // cpu_gauge value converted to pixel width
int tip_position = 0; // 0= not set, 1=tip in first char, 2=tip in middle, 3=tip in last char
if (value_in_pixels < 5) {tip_position = 1;} // tip is inside the first character
else if (value_in_pixels > gauge_size_chars*5.0-5) {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
for (int i=0; i<8; i++) { // dynamically create left part of the gauge
if (tip_position == 1) {gauge_left_dynamic[i] = (gauge_fill_5[i] << move_offset) | gauge_left[i];} // tip on the first character
else {gauge_left_dynamic[i] = gauge_fill_5[i];} // tip not on the first character
gauge_left_dynamic[i] = gauge_left_dynamic[i] & gauge_mask_left[i]; // apply mask for rounded corners
}
for (int i=0; i<8; i++) { // dynamically create right part of the gauge
if (tip_position == 3) {gauge_right_dynamic[i] = (gauge_fill_5[i] << move_offset) | gauge_right[i];} // tip on the last character
else {gauge_right_dynamic[i] = gauge_right[i];} // tip not on the last character
gauge_right_dynamic[i] = gauge_right_dynamic[i] & gauge_mask_right[i]; // apply mask for rounded corners
}
lcd.createChar(5, gauge_left_dynamic); // create custom character for the left part of the gauge
lcd.createChar(6, gauge_right_dynamic); // create custom character for the right part of the gauge
for (int i=0; i<gauge_size_chars; i++) { // set all the characters for the gauge
if (i==0) {gauge_string[i] = byte(5);} // first character = custom left piece
else if (i==gauge_size_chars-1) {gauge_string[i] = byte(6);} // last character = custom right piece
else { // character in the middle, could be empty, tip or fill
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
lcd.setCursor(0,0); // move cursor to top left
sprintf(buffer, "CPU:%3d%% ", cpu_gauge); // set a string as CPU: XX%, with the number always taking at least 3 character
lcd.print(buffer); // print the string on the display
lcd.write(byte(0)); // print warning character
lcd.setCursor(0,1); // move the cursor to the next line
lcd.print(gauge_string); // display the gauge
// increase the CPU value, set between 0-100
cpu_gauge = cpu_gauge +1;
if (cpu_gauge > 100) {cpu_gauge = 0;}
delay(100); // wait for a while - 100ms = update the screen 10x in a second
}
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Set the I2C address and LCD dimensions
int number = 0; // 3-bit binary number, initially 000
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Turing Machine");
delay(1000);
lcd.clear();
}
void addOneBit() {
number++; // Add 1 to the binary number
lcd.setCursor(0, 0);
lcd.print("Current: ");
lcd.print(number, BIN);
delay(1000);
}
void loop() {
while (number < 8) {
addOneBit();
}
lcd.setCursor(0, 1);
lcd.print("Reached 8!");
while (1) {
// End of program, do nothing
}
}
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
int number = 0; // 3-bit binary number, initially 000
bool incrementing = true;
//int number = 0; // 3-bit binäre Zahl, anfänglich 000
int underscorePos = 14; // Startposition des Unterstrichs
bool movingRight = true; // Bewegungsrichtung
void setup() {
//serial.begin(9600);
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Turing Machine");
delay(1000);
lcd.clear();
lcd.setCursor(underscorePos, 1);
lcd.print("_");
}
/*
void moveUnderscore() {
if (movingRight) {
underscorePos++;
if (underscorePos > 7) {
underscorePos = 6; // Stop an der rechten Seite
movingRight = false;
// delay(500);
}
} else {
underscorePos--;
if (underscorePos < 0) {
underscorePos = 1; // Stop an der linken Seite
movingRight = true;
//delay(500);
}
}
}
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
int underscorePos = 0; // Startposition des Unterstrichs
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Turing Machine");
delay(1000);
lcd.clear();
lcd.setCursor(underscorePos, 1);
lcd.print("_");
}
*/
void moveUnderscoreRight() {
lcd.setCursor(underscorePos, 1);
lcd.print(" "); // Löschen Sie den Unterstrich an der aktuellen Position
underscorePos++; // Bewegen Sie den Unterstrich nach rechts
if (underscorePos > 14) {
underscorePos = 14; // Verhindern, dass der Unterstrich über das Ende hinausgeht
}
lcd.setCursor(underscorePos, 1);
lcd.print("_"); // Stellen Sie den Unterstrich an der neuen Position wieder her
}
void moveUnderscoreLeft() {
lcd.setCursor(underscorePos, 1);
lcd.print(" "); // Löschen Sie den Unterstrich an der aktuellen Position
underscorePos--; // Bewegen Sie den Unterstrich nach links
if (underscorePos < 9) {
underscorePos = 9; // Verhindern, dass der Unterstrich unter das Anfangsbit geht
}
lcd.setCursor(underscorePos, 1);
lcd.print("_"); // Stellen Sie den Unterstrich an der neuen Position wieder her
}
/*
void loop() {
// Überprüfen Sie die Taster oder andere Eingabemethoden, um den Unterstrich zu bewegen.
// Zum Beispiel, wenn Sie zwei Tasten für die Bewegung nach links und rechts verwenden:
if (digitalRead(7) == LOW) { // Wenn der linke Taster (Pin 7) gedrückt wird
moveUnderscoreLeft();
delay(200); // Verhindern Sie das flimmernde Erkennen des Tastendrucks
}
if (digitalRead(8) == LOW) { // Wenn der rechte Taster (Pin 8) gedrückt wird
moveUnderscoreRight();
delay(200); // Verhindern Sie das flimmernde Erkennen des Tastendrucks
}
}
*/
void increment() {
number++;
lcd.setCursor(underscorePos, 1);
lcd.print("_");
lcd.setCursor(0, 1);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
//lcd.print(number, BIN);
//delay(500);
// Überprüfen, ob eine Bit-Zahl geändert werden muss (nach unten drücken)
/* if (digitalRead(7) == LOW) { // Wenn Sie den Taster an Pin 7 verwenden
bitWrite(number, underscorePos, !bitRead(number, underscorePos));
delay(500); // Kurze Pause, um den Tastendruck zu erfassen
}*/
/* if (number > 7) {
number = 0;/*
number--;
number = 0;
incrementing = false;
number --;
number =0;
incrementing = false;} */
}
/*
void decrement() {
number--;
if (number < 0) {
number = 7;
incrementing = true;
}
}*/
String toBinaryString(int num) {
String result = "";
for (int i = 3; i >= 0; i--) {
result += (char)('0' + ((num >> i) & 1));
}
return result;
}
void loop() {
lcd.setCursor(0, 0);
lcd.print("Current: ");
lcd.print(toBinaryString(number));
/*char binaryNumber[5]; // Buffer for formatted binary string
sprintf(binaryNumber, "%04b", number); // Format as a 4-digit binary string
lcd.print(binaryNumber);
lcd.print(number, BIN);*/
delay(1000);
if (incrementing) {
increment();
} /*else {
decrement();
}*/
/*
switch(number){
// Define the Turing machine transition function
case 0:
case 3:
case 5:
case 7:
case 9:
case 11:
case 13:
case 15:
moveUnderscoreLeft();
delay(200);
increment();
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
break;
case 2:
case 6:
case 10:
case 14:
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
increment();
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
break;
case 4:
case 12:
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
increment();
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
break;
case 8:
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
increment();
moveUnderscoreLeft();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
break;
}
*/
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreLeft();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
moveUnderscoreRight();
delay(200);
/*
moveUnderscoreRight();
delay(200);
//if (digitalRead(8) == LOW) { // Wenn der rechte Taster (Pin 8) gedrückt wird
moveUnderscoreRight();
delay(200); // Verhindern Sie das flimmernde Erkennen des Tastendrucks
//}*/
//moveUnderscore();
/*lcd.setCursor(0,1);
lcd.print("New: ");
lcd.print(number, BIN);
delay(1000);*/
}