// 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);



}