// simple project using Arduino UNO and 128x64 OLED Display,
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir
// image2cpp (convert images into C code): https://javl.github.io/image2cpp/
// u8g fonts (fonts available for u8g library): https://nodemcu-build.com/u8g-fonts.php
// u8g documentation: https://github.com/olikraus/u8glib/wiki/userreference#getstrwidth
// Photopea (online Photoshop-like tool): https://www.photopea.com/
// Wokwi starting project: https://wokwi.com/arduino/projects/300867986768527882
// Transparent display buy: https://a.aliexpress.com/_mKGmhKg
// Arduino uno: http://store.arduino.cc/products/arduino-uno-rev3
// Arduino breadboard prototyping shield: https://www.adafruit.com/product/2077
#include "U8glib.h"
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 for the display
// this is one way how to define images for u8g library
const uint8_t upir_logo[] U8G_PROGMEM = {
B00010101, B11010111, // ░░░█░█░███░█░███
B00010101, B01000101, // ░░░█░█░█░█░░░█░█
B00010101, B10010110, // ░░░█░█░██░░█░██░
B00011001, B00010101 // ░░░██░░█░░░█░█░█
};
const uint8_t img_bubble_tip[] U8G_PROGMEM = {
B01111100, //░█████░░
B00111000, //░░███░░░
B00010000 //░░░█░░░░
};
int potentiometer_value = 0;
int center_x = 64;
int center_y = 108;
int radius_pixel = 92;
int radius_line = 87;
int radius_text = 75;
float start_x = 0;
float start_y = 0;
float end_x = 0;
float end_y = 0;
float text_x = 0;
float text_y = 0;
float angle = 0;
int value_min = 0;
int value_max = 1000;
int tick_value = 0; // value for the current tickmark
byte precalculated_x_radius_pixel[150]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_y_radius_pixel[150]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_x_radius_line[150]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_y_radius_line[150]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_x_radius_text[150]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_y_radius_text[150]; // lookup table to prevent expensive sin/cos calculations
float fps; // FPS measurement for performance optimizations
unsigned long millis_time; // fps
unsigned long millis_time_last; // fps
char knob_value[20]; // big value on top of the screen
int string_width;
void setup() {
u8g.setColorIndex(1); // white color
//u8g.setRot180(); // screen rotation by 180°, was better for positioning the display next to knob, but slower in performance
// pre-calculate x and y positions for radius_pixel;
for (int i = 0; i < 150; i++) {
precalculated_x_radius_pixel[i] = round(-sin(radians(i-90))*radius_pixel + center_x);
precalculated_y_radius_pixel[i] = round(-cos(radians(i-90))*radius_pixel + center_y);
precalculated_x_radius_line[i] = round(-sin(radians(i-90))*radius_line + center_x);
precalculated_y_radius_line[i] = round(-cos(radians(i-90))*radius_line + center_y);
precalculated_x_radius_text[i] = round(-sin(radians(i-90))*radius_text + center_x);
precalculated_y_radius_text[i] = round(-cos(radians(i-90))*radius_text + center_y);
}
}
void loop() {
u8g.firstPage();
do {
// draw everything
for (int i = -42; i <= 42; i=i+3) {
angle = i + int((int(potentiometer_value+1)*3)%30/10); // potentiometer_value is multiplied by 10, 3° = 1 step
// get values from pre-calculated look-up table
start_x = precalculated_x_radius_pixel[round(angle+90)];
start_y = precalculated_y_radius_pixel[round(angle+90)];
if (start_x > 0 && start_y > 0 && start_x < 128 && start_y < 64) {
tick_value = round((potentiometer_value/10.0) - angle/3.0); // get real value for the currently drawn tickmark
if(tick_value >= (value_min/10) && tick_value <= (value_max/10)) { // only draw tickmarks between values 0-100%, could be removed when using rotary controller
if ((int)tick_value % 10 == 0) { // this is a big tickmark, as it´s divisible by 10
end_x = precalculated_x_radius_line[round(angle+90)];
end_y = precalculated_y_radius_line[round(angle+90)];
u8g.drawLine(round(start_x), round(start_y), round(end_x), round(end_y));
// draw also the label
u8g.setFont(u8g_font_6x10r);
text_x = precalculated_x_radius_text[round(angle+90)];
text_y = precalculated_y_radius_text[round(angle+90)];
if (text_x > 0 && text_x < 128 && text_y < 64) { // only draw text if it´s visible
snprintf(knob_value, sizeof(knob_value), "%d", (int)(tick_value)); // convert to c string
string_width = u8g.getStrWidth(knob_value); // get string width in pixels
u8g.setPrintPos(text_x-string_width/2, text_y); // set position for text, centered
u8g.print(knob_value); // draw the text
}
// end label drawing
}
else // this is a small tickmark, we don´t need to calculate end position, as only pixels are drawn in this case
{
u8g.drawPixel(start_x, start_y); // dots instead of lines
}
} // end draw only between 0-100
}
}
u8g.setFont(u8g_font_8x13r); // set the font for the big readout
dtostrf(potentiometer_value/10.0, 1, 1, knob_value); // float to string, -- value, min. width, digits after decimal, buffer to store
sprintf(knob_value, "%s%s", knob_value, "%"); // add some random ending character
string_width = u8g.getStrWidth(knob_value); // get string width
if (string_width % 2 == 1) {string_width++;} // it easier to position things when the width is even number
// draw bubble for the text value
u8g.setColorIndex(1); // white color
u8g.drawBox(64-(string_width+4)/2,0,string_width+4,11);
u8g.drawBitmapP(64-3,11, 1, 3, img_bubble_tip); // bubble tip
u8g.setColorIndex(0); // black color
u8g.drawPixel(64-(string_width+4)/2,0); // corners
u8g.drawPixel(64+(string_width+4)/2-1,0); // corners
u8g.drawPixel(64-(string_width+4)/2,10); // corners
u8g.drawPixel(64+(string_width+4)/2-1,10); // corners
// draw big text on the top of the screen
u8g.setPrintPos(64-string_width/2, 10);
u8g.print(knob_value);
u8g.setColorIndex(1); // white color
// draw upir logo in the bottom right corner
u8g.drawBitmapP(112,0, 2, 4, upir_logo);
// FPS counter (optional, disable for final version)
u8g.setColorIndex(1); // white color
u8g.setFont(u8g_font_4x6r);
u8g.setPrintPos(0,8);
u8g.print((int)fps);
} while ( u8g.nextPage() );
// get value from potentiometer
potentiometer_value = constrain(map(analogRead(A0), 0, 1023, value_max, value_min), value_min, value_max);
// FPS counter calculations
millis_time_last = millis_time;
millis_time = millis();
fps = millis_time - millis_time_last;
fps = round(1000.0 / fps*1.0);
}