// simple project using Arduino UNO and 128x64 OLED Display, 
// created by upir, 2022
// youtube channel: https://www.youtube.com/upir_upir

// FULL TUTORIAL part1: https://youtu.be/NPfaLKKsf_Q 
// FULL TOTORIAL part2: https://youtu.be/sFGsYZ0Hszk

// Useful links:
// Big Black knob: https://s.click.aliexpress.com/e/_Aq1wGF
// Huge Aluminium Knob: https://s.click.aliexpress.com/e/_A4GlET
// Arduino UNO: https://s.click.aliexpress.com/e/_AXDw1h
// Normal OLED Display: https://s.click.aliexpress.com/e/_AWSVnt
// Transparent OLED Display: https://s.click.aliexpress.com/e/_ABpnu7
// Big OLED Display: https://s.click.aliexpress.com/e/_ADL0T9
// Arduino breadboard prototyping shield: https://s.click.aliexpress.com/e/_ApbCwx
// 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/projects/327642884399432274
// Photopea (online Photoshop-like tool): https://www.photopea.com/
// image2cpp (convert images into C code): https://javl.github.io/image2cpp/
// SSD1306 documentation: https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf



#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 communication: SCL = 13, SDA = 11, RES = 10, DC = 9, CS = 8

int potentiometer_value = 0; // value from the potentiometer
char buffer[20];       // helper buffer for converting values into C-style string (array of chars)
int string_width;      // helper value for string widths

float pixel_x = 0;     // x pos for pixel
float pixel_y = 0;     // y pos for pixel
float line_x = 0;      // x pos for line end
float line_y = 0;      // y pos for line end
float text_x = 0;      // x pos for text
float text_y = 0;      // y pos for text

int center_x = 64;     // x center of the knob 
int center_y = 108;    // y center of the knob (outside of the screen)
int radius_pixel = 92; // radius for pixel tickmarks
int radius_line = 87;  // radius for line end
int radius_text = 75;  // radius for text

int angle;             // angle for the individual tickmarks
int tick_value;        // numeric value for the individual tickmarks

byte precalculated_x_radius_pixel[180]; // lookup table to prevent expensive sin/cos calculations
byte precalculated_y_radius_pixel[180]; // lookup table to prevent expensive sin/cos calculations

unsigned long millis_time;       // fps
unsigned long millis_time_last;  // fps
int fps;                         // actual FPS value


const uint8_t upir_logo[] U8G_PROGMEM = {
B00010101, B11010111,     //  ░░░█░█░███░█░███
B00010101, B01000101,     //  ░░░█░█░█░█░░░█░█
B00010101, B10010110,     //  ░░░█░█░██░░█░██░
B00011001, B00010101      //  ░░░██░░█░░░█░█░█
};

int tick_pixel_array[50][2];    // pixels to be drawn, x,y
int tick_line_array[10][4];     // lines to be drawn, x,y,x,y
int tick_text_array[10][3];     // labels to be drawn, x,y,value

int tick_pixel_count;           // number of pixels to be drawn
int tick_line_count;            // number of lines to be drawn
int tick_text_count;            // number of labels to be drawn

int current_u8g_page;  // current page of the u8g drawing loop - there are 8 pages for 128x64 display


void setup() {
  
  u8g.setColorIndex(1);          // set color to white

  for (int i = 0; i < 180; i++) {    // pre-calculate x and y positions into the look-up tables
     precalculated_x_radius_pixel[i] =  sin(radians(i-90)) * radius_pixel + center_x; 
     precalculated_y_radius_pixel[i] = -cos(radians(i-90)) * radius_pixel + center_y;      
  }
}

void loop() {

    tick_pixel_count=0;   // reset number of pixels
    tick_line_count=0;    // reset number of lines
    tick_text_count=0;    // reset number of labels

    // calculate tickmarks
    for (int i=-48; i<=48; i=i+3) {                                // only try to calculate tickmarks that would end up be displayed
      angle = i + ((potentiometer_value*3)/10) % 3;                // final angle for the tickmark
      tick_value = round((potentiometer_value/10.0) + angle/3.0);  // get number value for each tickmark

      //pixel_x =  sin(radians(angle)) * radius_pixel + center_x;    // calculate the tickmark pixel x value
      //pixel_y = -cos(radians(angle)) * radius_pixel + center_y;    // calculate the tickmark pixel y value
      pixel_x = precalculated_x_radius_pixel[angle+90];              // get x value from lookup table
      pixel_y = precalculated_y_radius_pixel[angle+90];              // get y value from lookup table

      if (pixel_x > 0 && pixel_x < 128 && pixel_y > 0 && pixel_y < 64) {  // only draw inside of the screen

        if(tick_value >= 0 && tick_value <= 100) {  // only draw tickmarks between values 0-100%, could be removed when using rotary controller

          if (tick_value % 10 == 0) {                                // draw big tickmark == lines + text
            line_x =  sin(radians(angle)) * radius_line + center_x;  // calculate x pos for the line end
            line_y = -cos(radians(angle)) * radius_line + center_y;  // calculate y pos for the line end
            //u8g.drawLine(pixel_x, pixel_y, line_x, line_y);          // NOT draw the line
            tick_line_array[tick_line_count][0] = line_x;              // but instead...
            tick_line_array[tick_line_count][1] = line_y;              // store those values...
            tick_line_array[tick_line_count][2] = pixel_x;             // inside this...
            tick_line_array[tick_line_count][3] = pixel_y;             // array
            tick_line_count++;                                         // and increment the counter


            text_x =  sin(radians(angle)) * radius_text + center_x;  // calculate x pos for the text
            text_y = -cos(radians(angle)) * radius_text + center_y;  // calculate y pos for the text 
            //itoa(tick_value, buffer, 10);                            // convert integer to string
            //string_width = u8g.getStrWidth(buffer);                  // get string width
            //u8g.drawStr(text_x - string_width/2, text_y, buffer);    // NOT draw text - tickmark value
            tick_text_array[tick_text_count][0] = text_x;              // but instead, store the values...
            tick_text_array[tick_text_count][1] = text_y;              // inside this...
            tick_text_array[tick_text_count][2] = tick_value;          // array
            tick_text_count++;                                         // and increment the counter            
            
          } 
          else {                                                     // draw small tickmark == pixel tickmark
            //u8g.drawPixel(pixel_x, pixel_y);                       // NOT draw a single pixel
            tick_pixel_array[tick_pixel_count][0] = pixel_x;         // but instead, store the values...
            tick_pixel_array[tick_pixel_count][1] = pixel_y;         // inside this array
            tick_pixel_count++;                                      // and increment the counter        
          }      
  
        }
      }
    }



  u8g.firstPage();                // required for u8g library
  current_u8g_page=0;             // reset the u8g page number
  do {                            // 

    

    u8g.setColorIndex(1);          // set color to white
    u8g.setFont(u8g_font_6x10r);   // set smaller font for tickmarks   
 
    if (current_u8g_page>=2 && current_u8g_page<=4) {
      // draw pixels
      for (int i=0; i<tick_pixel_count; i++) { 
          u8g.drawPixel(tick_pixel_array[i][0], tick_pixel_array[i][1]);      // draw the pixel
      } 
    }

    if (current_u8g_page>=2 && current_u8g_page<=5) {
      // draw lines
      for (int i=0; i<tick_line_count; i++) { 
          u8g.drawLine(tick_line_array[i][0], tick_line_array[i][1],tick_line_array[i][2],tick_line_array[i][3]);          // draw the line
      } 
    }   

    if (current_u8g_page>=2 && current_u8g_page<=6) {
      // draw labels
      for (int i=0; i<tick_text_count; i++) { 
        itoa(tick_text_array[i][2] , buffer, 10);                // convert integer to string
        string_width = u8g.getStrWidth(buffer);                  // get string width
        u8g.drawStr(tick_text_array[i][0] - string_width/2, tick_text_array[i][1], buffer);    // draw text - tickmark value
      } 
    } 



 
    if (current_u8g_page<2) {
      // draw the big value on top
      u8g.setFont(u8g_font_8x13r);                      // set bigger font
      dtostrf(potentiometer_value/10.0, 1, 1, buffer);  // float to string, -- value, min. width, digits after decimal, buffer to store
      sprintf(buffer, "%s%s", buffer, "%");             // add some random ending character

      string_width = u8g.getStrWidth(buffer);           // calculate string width

      u8g.setColorIndex(1);                             // set color to white
      u8g.drawRBox(64-(string_width+4)/2, 0, string_width+4, 11, 2);  // draw background rounded rectangle
      u8g.drawTriangle( 64-3, 11,   64+4, 11,   64, 15);              // draw small arrow below the rectangle
      u8g.setColorIndex(0);                                           // set color to black 
      u8g.drawStr(64-string_width/2, 10, buffer);                     // draw the value on top of the display
    }

    if (current_u8g_page==0) {
      // draw upir logo
      u8g.setColorIndex(1);
      u8g.drawBitmapP(112, 0, 2, 4, upir_logo);  

      // display FPS, could be commented out for the final product
      u8g.setColorIndex(1);                                           // set color to white   
      u8g.setFont(u8g_font_5x7r);                                     // set very small font
      itoa(fps, buffer, 10);                                          // convert FPS number to string
      u8g.drawStr(0,8,buffer);                                       // draw the FPS number

    }

    current_u8g_page++;    // increment the current number counter

  } while ( u8g.nextPage() );    // required for u8g library


  potentiometer_value = map(analogRead(A0), 0, 1023, 1000, 0);     // read the potentiometer value, remap it to 0-1000

  millis_time_last = millis_time;                                  // store last millisecond value
  millis_time = millis();                                          // get millisecond value from the start of the program
  fps = round(1000.0/ (millis_time*1.0-millis_time_last));         // calculate FPS (frames per second) value


}