// simple project using Arduino UNO, potentiometer and 128x64 SSD1306 OLED display
// to show a simple gauge driven by GPS sensor Reyax RYS8833
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// FULL TUTORIAL PART 2 (GPS sensor): https://youtu.be/dSD02o3M2sw
// FULL TUTORIAL PART 1 (graphics): 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
// REYAX Website : https://reyax.com/
// WOKWI starting project: https://wokwi.com/projects/343391709050176083
// NMEA generator: https://www.nmeagen.org/
// Logic analyzer: https://www.saleae.com/
// NMEA messages types: http://aprs.gids.nl/nmea/
// Realterm: https://sourceforge.net/projects/realterm/
// GNSS Monitor software: https://drive.google.com/file/d/1-RwTI4yV2rxFE_6nxDds-myMlEw33d6v/view
// GPS Sensor REYAX RYS8833 on TECHDesign: https://reurl.cc/KQ1lpn
// GPS Sensor REYAX RYS8833_Lite on TECHDesign: https://reurl.cc/NRKlZe
// GPS Sensor REYAX RYS8833_Lite on Amazon: https://reurl.cc/2mRjZv
// GPS Sensor: https://first-components.com/en/wireless-iot-gps-glonass-beidou
// How to make the display show any speed? Copy the NMEA message into the serial edit box on the right bottom of the page
// for example this message: (without //)
// Please watch the youtube video for more details
// $GPRMC,135227.00,A,5012.8809,N,01550.6383,E,32.7,137.7,050922,,,A,V*1C
// 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 website
// '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
int incomingByte = 0; // for incoming serial data
char msg_buffer[128]; // c-style string that holds the content of the received message
int msg_char_pos = 0; // which character in the string we are writing right now
int msg_received = 0; // message was fully received
int msg_new = 0; // message is new (starting with $)
int msg_tokenized = 0; // was the received message already tokenized (split into parts)?
int msg_token_counter = 0; // internal counter for tokens
char* msg_token_pointer; // pointer for hte strtok function
int msg_rmc_valid = 0; // is the received message a valid RMC message?
float msg_rmc_speed_knots = 0; // received speed in knots over ground (default units for NMEA messages)
int speed_mph = 0; // calculated speed in miles per hour
int speed_kmh = 0; // calculated speed in kilometers per hour
void setup() {
u8g.setFont(u8g_font_tpssb); // set u8g font, although this is not used anywhere
u8g.setColorIndex(1); // set drawing color to white
Serial.begin(115200); // initialize the serial connection. 115200 is the default speed for the GPS sensor
Serial.println("Serial Connection Initialized"); // we have to print something first in WOKWI to show the serial monitor
}
void loop() {
// we have received some serial data
while (Serial.available() > 0) {
// read the incoming byte:
incomingByte = Serial.read();
if (incomingByte == '$') { // we are getting a new message
msg_new = 1; // we are receiving a new message
msg_received = 0; // the message was not received fully
msg_char_pos = 0; // set the character pos to 0
memset(msg_buffer,0,sizeof(msg_buffer)); // fill the array with nulls
}
if (msg_received == 0 && msg_new == 1) { // adding received characters into our message
msg_buffer[msg_char_pos] = incomingByte; // save the incomingbyte into our message buffer
msg_char_pos++; // increase the position to where to save the new incomingbyte
}
if (incomingByte == 10 || incomingByte == 13 || msg_char_pos >= 63) { // message was received fully
msg_received = 1; // message was fully received
msg_new = 0; // there is no new message at this point
}
if (msg_received == 1 && msg_tokenized == 0) { // message was received, but not tokenized yet
Serial.println(msg_buffer); // once the message is received, print it back
// reset the internal token counter
msg_token_counter = 0;
// example message: $GPRMC,135237.00,A,5012.8200,N,01550.6811,E,19.8,188.0,050922,,,A,V*1B
// 0 $GPRMC RMC message
// 1 220516 Time Stamp
// 2 A validity - A-ok, V-invalid
// 3 5133.82 current Latitude
// 4 N North/South
// 5 00042.24 current Longitude
// 6 W East/West
// 7 173.8 Speed in knots
// 8 231.8 True course
// 9 130694 Date Stamp
// 10 004.2 Variation
// 11 W East/West
// 12 *70 checksum
// tokenize the first part of the message
msg_token_pointer = strtok(msg_buffer, ",");
// if the first part is $GPRMC, it´s the RMC message - which is what we need
if (strcmp(msg_token_pointer, "$GPRMC") == 0) {
Serial.println("This is a RMC message");
while (msg_token_pointer != NULL) { // tokenize all the other parts of the message
if (msg_token_counter == 2) { // check the validity of the message
if (msg_token_pointer[0] == 'A') {
msg_rmc_valid = 1; // message is valid (we get some real GPS data)
} else {
msg_rmc_valid = 0; // message is invalid (probably getting just zeros)
}
}
else if (msg_token_counter == 7) { // speed in knots
msg_rmc_speed_knots = atof(msg_token_pointer); // convert string to float value
speed_kmh = round(msg_rmc_speed_knots * 1.852); // convet knots to Km/h
speed_mph = round(msg_rmc_speed_knots * 1.15078); // convert knots to Mph
// print received data
Serial.print("Speed in knots: ");
Serial.print(msg_rmc_speed_knots);
Serial.print(", Speed in Km/h: ");
Serial.print(speed_kmh);
Serial.print(", Speed in Mph: ");
Serial.println(speed_mph);
}
msg_token_pointer = strtok(NULL, ","); // tokenize another part of the message
msg_token_counter++; // increase the internal counter
}
}
}
}
speed = speed_kmh; // set the displayed speed on the OLED to Km/h
//speed = speed_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
}