// simple project using Arduino UNO, potentiometer and 128x64 SSD1306 OLED display to show a simple gauge
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// FULL TUTORIAL: https://youtu.be/xI6dXTA02UQ
// Links from the video:
// Arduino UNO - https://s.click.aliexpress.com/e/_AXDw1h
// Arduino breadboard prototyping shield - https://s.click.aliexpress.com/e/_ApbCwx
// 128x64 SSD1306 OLED Display: https://s.click.aliexpress.com/e/_DCKdvnh
// Font for tickmarks - https://www.dafont.com/pixeled.font
// Font for speed - https://www.dafont.com/aldo-the-apache.font
// Huge Aluminium Knob: https://s.click.aliexpress.com/e/_A4GlET
// Big Black knob: https://s.click.aliexpress.com/e/_Aq1wGF
// u8g fonts (fonts available for u8g library): https://nodemcu-build.com/u8g-fonts.php
// u8g documentation: https://github.com/olikraus/u8glib/wiki/userreference
// Wokwi starting project: https://wokwi.com/arduino/projects/300867986768527882
// Transparent OLED display: https://s.click.aliexpress.com/e/_Dns6eLz
// Photopea (online Photoshop-like tool): https://www.photopea.com/
// image2cpp (convert images into C code): https://javl.github.io/image2cpp/
// Polar coordinates - https://en.wikipedia.org/wiki/Polar_coordinate_system
// PCBWay - https://www.pcbway.com/setinvite.aspx?inviteid=572577
// Use the link for PCBway to get 10 PCBs created for free (0$). Only pay for shipping.
// Related videos:
// Turbo pressure gauge with Arduino and OLED display - https://youtu.be/JXmw1xOlBdk
// Arduino Car Cluster with OLED Display - https://youtu.be/El5SJelwV_0
// Knob over OLED Display - https://youtu.be/SmbcNx7tbX8
// Arduino + OLED = 3D ? - https://youtu.be/kBAcaA7NAlA
#include "U8glib.h"
// if you are using SPI version of the display (with 7 pins), comment the first line and uncomment the second line
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST); // Fast I2C / TWI
//U8GLIB_SSD1306_128X64 u8g(13, 11, 8, 9, 10); // SPI connection - SCL = 13, SDA = 11, RES = 10, DC = 9, CS = 8
// images generated using image2cpp
// 'center_fill', 8x8px
const unsigned char bitmap_center_fill [] PROGMEM = {
0x00, 0x3c, 0x7e, 0x7e, 0x7e, 0x7e, 0x3c, 0x00
};
// 'center_outline', 8x8px
const unsigned char bitmap_center_outline [] PROGMEM = {
0x00, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x3c, 0x00
};
// 'gauge_bg', 72x64px
const unsigned char bitmap_gauge_bg [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0xff, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x20,
0x3c, 0x00, 0x3c, 0x04, 0x00, 0x00, 0x00, 0x00, 0x21, 0xc0, 0x00, 0x03, 0x84, 0x00, 0x00, 0x00,
0x00, 0x06, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 0x00, 0x02, 0x18, 0x00,
0x00, 0x00, 0x04, 0x20, 0x40, 0x00, 0x02, 0x04, 0x20, 0x00, 0x00, 0x04, 0xc0, 0x20, 0x00, 0x04,
0x03, 0x20, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x04, 0x00, 0x80, 0x00, 0x00, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x84, 0x00, 0xef, 0x00, 0xcf, 0x00, 0x21, 0x00, 0x00, 0x48,
0x01, 0x09, 0x01, 0x29, 0x00, 0x12, 0x00, 0x00, 0x10, 0x01, 0xc9, 0x00, 0xc9, 0x00, 0x08, 0x00,
0x00, 0x20, 0x01, 0x29, 0x01, 0x29, 0x00, 0x04, 0x00, 0x00, 0x40, 0x01, 0x29, 0x01, 0x29, 0x00,
0x02, 0x00, 0x04, 0x40, 0x00, 0xcf, 0x00, 0xcf, 0x00, 0x02, 0x20, 0x02, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, 0x40, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x04, 0x80, 0x02, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x40, 0x22,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x44, 0x14, 0x01, 0x2f, 0x00, 0x00, 0x02, 0xf7, 0x80,
0x28, 0x04, 0x01, 0x29, 0x00, 0x00, 0x02, 0x94, 0x80, 0x20, 0x04, 0x01, 0xe9, 0x00, 0x00, 0x02,
0x94, 0x80, 0x20, 0x08, 0x00, 0x29, 0x00, 0x00, 0x02, 0x94, 0x80, 0x10, 0x08, 0x00, 0x29, 0x00,
0x00, 0x02, 0x94, 0x80, 0x10, 0xc8, 0x00, 0x2f, 0x00, 0x00, 0x02, 0xf7, 0x80, 0x13, 0x08, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0xd0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0b, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x39, 0xe0,
0x00, 0x00, 0x00, 0x17, 0x3c, 0x08, 0x10, 0x05, 0x20, 0x00, 0x00, 0x00, 0x10, 0xa4, 0x08, 0x10,
0x09, 0x20, 0x00, 0x00, 0x00, 0x11, 0x24, 0x08, 0x17, 0x91, 0x20, 0x00, 0x00, 0x00, 0x12, 0x25,
0xe8, 0xd0, 0x21, 0x20, 0x00, 0x00, 0x00, 0x14, 0x24, 0x0b, 0x10, 0x3d, 0xe0, 0x00, 0x00, 0x00,
0x17, 0xbc, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x20, 0x02, 0x00, 0x1e, 0x00, 0x00, 0x29, 0x78, 0x00, 0x40, 0x02, 0x00, 0x12, 0x00, 0x00,
0x29, 0x48, 0x00, 0x40, 0x09, 0x00, 0x12, 0x00, 0x00, 0x2f, 0x48, 0x00, 0x90, 0x11, 0x00, 0x12,
0x00, 0x00, 0x21, 0x48, 0x00, 0x88, 0x00, 0x80, 0x12, 0x00, 0x00, 0x21, 0x48, 0x01, 0x00, 0x00,
0x40, 0x1e, 0x00, 0x00, 0x21, 0x78, 0x02, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x02, 0x02,
0x00, 0x00, 0x20, 0x80, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00,
0x00, 0x88, 0x80, 0x02, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x40, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00
};
// 'mph_label', 16x6px
const unsigned char bitmap_mph_label [] PROGMEM = {
0x8b, 0xd2, 0xda, 0x52, 0xab, 0xde, 0x8a, 0x12, 0x8a, 0x12, 0x8a, 0x12
};
// 'digit_0', 16x28px
const unsigned char bitmap_digit_0 [] PROGMEM = {
0x0f, 0xff, 0x1f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f,
0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f,
0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8
};
// 'digit_1', 16x28px
const unsigned char bitmap_digit_1 [] PROGMEM = {
0x01, 0xf8, 0x03, 0xf8, 0x07, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8,
0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8,
0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8,
0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8
};
// 'digit_2', 16x28px
const unsigned char bitmap_digit_2 [] PROGMEM = {
0x0f, 0xfe, 0x1f, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f,
0xfc, 0x3f, 0x00, 0x3f, 0x00, 0x7f, 0x00, 0xff, 0x01, 0xfe, 0x03, 0xfc, 0x0f, 0xf8, 0x1f, 0xf0,
0x3f, 0xe0, 0x7f, 0xc0, 0xff, 0x00, 0xfe, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
// 'digit_3', 16x28px
const unsigned char bitmap_digit_3 [] PROGMEM = {
0x0f, 0xfe, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3f, 0xf8, 0x3f,
0xf8, 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0xfe, 0x00, 0xfc, 0x00, 0xf8, 0x00, 0xff, 0x00, 0xff,
0x00, 0xff, 0x00, 0x3f, 0x00, 0x3f, 0xf8, 0x3f, 0xf8, 0x3f, 0xf8, 0x3f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8
};
// 'digit_4', 16x28px
const unsigned char bitmap_digit_4 [] PROGMEM = {
0x00, 0x70, 0x00, 0x78, 0x00, 0xfc, 0x00, 0xfe, 0x01, 0xff, 0x03, 0xff, 0x03, 0xff, 0x07, 0xff,
0x07, 0xff, 0x0f, 0xff, 0x0f, 0xbf, 0x1f, 0xbf, 0x1f, 0x3f, 0x3f, 0x3f, 0x3e, 0x3f, 0x7e, 0x3f,
0x7c, 0x3f, 0xfc, 0x3f, 0xff, 0xff, 0x7f, 0xff, 0x3f, 0xff, 0x3f, 0xff, 0x1f, 0xff, 0x00, 0x3f,
0x00, 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x3f
};
// 'digit_5', 16x28px
const unsigned char bitmap_digit_5 [] PROGMEM = {
0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xfc, 0x00, 0xfc, 0x00,
0xfc, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0xff, 0xf0, 0xff, 0xf8, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xff,
0x00, 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xff, 0xff,
0xff, 0xff, 0x7f, 0xff, 0x3f, 0xff, 0x1f, 0xff
};
// 'digit_6', 16x28px
const unsigned char bitmap_digit_6 [] PROGMEM = {
0x0f, 0xfe, 0x1f, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f,
0xfc, 0x3f, 0xfc, 0x00, 0xfc, 0x00, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x7f, 0xff, 0xff,
0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8, 0xff, 0xf0
};
// 'digit_7', 16x28px
const unsigned char bitmap_digit_7 [] PROGMEM = {
0xff, 0xf0, 0xff, 0xf8, 0xff, 0xfc, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x3e, 0xf8, 0x7e,
0xf8, 0x7e, 0x00, 0xfc, 0x00, 0xfc, 0x00, 0xfc, 0x01, 0xf8, 0x01, 0xf8, 0x01, 0xf8, 0x03, 0xf0,
0x03, 0xf0, 0x07, 0xf0, 0x07, 0xe0, 0x07, 0xe0, 0x0f, 0xc0, 0x0f, 0xc0, 0x0f, 0xc0, 0x1f, 0x80,
0x1f, 0x80, 0x3f, 0x80, 0x3f, 0x00, 0x3f, 0x00
};
// 'digit_8', 16x28px
const unsigned char bitmap_digit_8 [] PROGMEM = {
0x0f, 0xfe, 0x1f, 0xfe, 0x3f, 0xfe, 0x7f, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xfc, 0x3e, 0xfc, 0x3e,
0xfc, 0x3e, 0xfc, 0x3e, 0xfc, 0x3e, 0xfc, 0x3e, 0xff, 0xfc, 0xff, 0xf8, 0x1f, 0xff, 0x7f, 0xff,
0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8
};
// 'digit_9', 16x28px
const unsigned char bitmap_digit_9 [] PROGMEM = {
0x07, 0xff, 0x1f, 0xff, 0x3f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x3f, 0xfc, 0x3f,
0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0x3f, 0x00, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xfc, 0x3f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0xff, 0xfc, 0xff, 0xf8
};
// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 800)
const int bitmap_allArray_LEN = 10;
const unsigned char* bitmap_allArray[10] = {
bitmap_digit_0,
bitmap_digit_1,
bitmap_digit_2,
bitmap_digit_3,
bitmap_digit_4,
bitmap_digit_5,
bitmap_digit_6,
bitmap_digit_7,
bitmap_digit_8,
bitmap_digit_9
};
// 'upir_logo', 16x4px -- this is a second way how to define images, by manually typing the bits with "B"
const unsigned char bitmap_upir_logo [] PROGMEM = {
B00010101, B11010111, // ░░░█░█░███░█░███
B00010101, B01000101, // ░░░█░█░█░█░░░█░█
B00010101, B10010110, // ░░░█░█░██░░█░██░
B00011001, B00010101 // ░░░██░░█░░░█░█░█
};
int speed = 0; // current speed
char speed_string[10]; // speed number value converted to c-style string (array of characters)
int speed_string_length; // length of the speed_string
int speed_string_start_pos; // start x position for the big numbers - calculated based on the number of digits
int needle_angle_deg = 45; // angle of the needle in degrees, based on potentiometer value
int needle_start_x; // needle start point, x position
int needle_start_y; // needle start point, y position
int needle_end_x; // needle end point, x position
int needle_end_y; // needle end point, y position
int needle_center_x = 36; // needle center position, x position
int needle_center_y = 36; // needle center position, y position
int needle_radius_big = 30; // lenght of needle
int needle_radius_small = 10; // lenght of "tail" of the needle
int needle_offset_x; // second line offset x
int needle_offset_y; // second line offset y
void setup() {
u8g.setFont(u8g_font_tpssb); // set u8g font, although this is not used anywhere
u8g.setColorIndex(1); // set drawing color to white
pinMode(A0, INPUT); // set pin A0 as input to read potentiometer value later on
}
void loop() {
speed = map(analogRead(A0), 0, 1023, 0, 140); // read potentiometer value and map it between 0-140 (mph)
itoa (speed, speed_string, 10); // convert speed integer to c-style string speed_string, decimal format
speed_string_length = strlen(speed_string); // get speed_string length
speed_string_start_pos = 99 - speed_string_length * 8; // start x position of the big numbers
needle_angle_deg = map(speed, 0, 140, 45, 270+45); // calculate the angle in degrees based on the speed value, between 45-315
needle_start_x = needle_radius_big * -sin(radians(needle_angle_deg)) + needle_center_x; // calculate needle start x position
needle_start_y = needle_radius_big * cos(radians(needle_angle_deg)) + needle_center_y; // calculate needle start y position
needle_end_x = needle_radius_small * -sin(radians(needle_angle_deg + 180)) + needle_center_x; // calculate needle end x position
needle_end_y = needle_radius_small * cos(radians(needle_angle_deg + 180)) + needle_center_y; // calculate needle end y position
// calculate offset for the second line for the needle, based on the needle slope
if ((needle_angle_deg > 45 && needle_angle_deg < 135) || (needle_angle_deg > 225 && needle_angle_deg < 315)) {
// needle is more horizontal, offset the second line by y
needle_offset_x = 0;
needle_offset_y = 1;
} else {
// needle is more vertical, offset the second line by x
needle_offset_x = 1;
needle_offset_y = 0;
}
u8g.firstPage(); // u8g drawing
do {
//u8g.drawStr(90, 20, speed_string); // draw speed_string, not needed anymore
for (int i = 0; i < speed_string_length; i++) { // loop for every speed_string character
// draw the big digit
// subtract value 45 from the character value, since the ASCII value of digit "0" is 48
u8g.drawBitmapP(speed_string_start_pos + 18 * i, 16, 16/8, 28, bitmap_allArray[speed_string[i] - 48]);
}
u8g.drawBitmapP( 0, 0, 72/8, 64, bitmap_gauge_bg); // draw gauge background image
u8g.drawLine(needle_start_x, needle_start_y, needle_end_x, needle_end_y); // draw first line for the needle
u8g.drawLine(needle_start_x+needle_offset_x, needle_start_y+needle_offset_y, needle_end_x+needle_offset_x, needle_end_y+needle_offset_y); // draw second line for the needle
u8g.setColorIndex(0); // black color
u8g.drawBitmapP( 32, 33, 8/8, 8, bitmap_center_fill); // draw needle center cover
u8g.setColorIndex(1); // white color
u8g.drawBitmapP( 32, 33, 8/8, 8, bitmap_center_outline); // draw needle center piece
u8g.drawBitmapP( 93, 46, 16/8, 6, bitmap_mph_label); // draw MPH label
u8g.drawBitmapP( 26, 60, 16/8, 4, bitmap_upir_logo); // draw upir logo, feel free to comment out and put your own logo here :)
} while ( u8g.nextPage() ); // draw next page, explained here - https://youtu.be/sFGsYZ0Hszk
}