Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Mirko Pavleski
Published © GPL3+

ESP32 Analog style VU meter with GC9A01 Round Dispalys

Simple to build ESP32 VU meter on Round Dispalys with Peak Meters.

BeginnerFull instructions provided3 hours321
ESP32 Analog style VU meter with GC9A01 Round Dispalys

Things used in this project

Hardware components

Espressif ESP32 Development Board - Developer Edition
Espressif ESP32 Development Board - Developer Edition
×2
GC9A01 Round Display
×1
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
×2
Resistor 10k ohm
Resistor 10k ohm
×2
Resistor 220 ohm
Resistor 220 ohm
×2
LED (generic)
LED (generic)
×2

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

Schematic

...

Code

code Left channel

C/C++
...
// GCA901_Nano_voltage_meter
// 
// grid voltage variation monitor (230V - 250V AC)
// rolling averaged voltage (of 10 readings) is sent to display
// NOTE: here voltage generated with random function
//
// microcontroller: Arduino Nano
// display 240*240 circular SPI 3.3V TFT with GC9A01 controller
// 
// note: random function drives fluctuations of the parameter named 'volt'
// CG9A01    Arduino Nano
//  RST -------- NC
//  CST -------- 10
//  DC ---------  9
//  SDA -------- 11 - green wire
//  SCL -------- 13 - yellow wire
//  
// Floris Wouterlood
// September 1, 2023
// public domain

// made for a 240*240 pixel circular display
// all x-y-coordinates relative to center = x = 120 and y = 120
     
   #include "SPI.h"
   #include "Adafruit_GFX.h"
   #include "Adafruit_GC9A01A.h"

   #define TFT_DC 2
   #define TFT_CS 15
 
   Adafruit_GC9A01A tft (TFT_CS, TFT_DC);
 
   #define BLACK      0x0000                                                               // some extra colors
   #define BLUE       0x001F
   #define RED        0xF800
   #define GREEN      0x07E0
   #define CYAN       0x07FF
   #define MAGENTA    0xF81F
   #define YELLOW     0xFFE0
   #define WHITE      0xFFFF
   #define ORANGE     0xFBE0
   #define GREY       0x84B5
   #define BORDEAUX   0xA000
   #define AFRICA     0xAB21                // 0xce5f  //0xAB21                                                               // current dial color
    const int vlez = 34;
   #define DEG2RAD 0.0174532925 
   //int vlez;
   int sig = 0;
   int multiplier;
   int    frametime = 100; 
   int    x_pos;
   int    y_pos; 
   int    center_x = 120;                                                                  // center x of dial on 240*240 TFT display
   int    center_y = 120;                                                                  // center y of dial on 240*240 TFT display
   float  pivot_x, pivot_y,pivot_x_old, pivot_y_old;
   float  p1_x,p1_y,p2_x,p2_y,p3_x, p3_y, p4_x, p4_y, p5_x, p5_y; 
   float  p1_x_old,p1_y_old, p2_x_old, p2_y_old, p3_x_old, p3_y_old;
   float  p4_x_old, p4_y_old, p5_x_old, p5_y_old;
   float  angleOffset = 3.14;
   float  arc_x;
   float  arc_y;
   int    radius = 120;                                                                    // center y of circular scale                                                   
   float  angle_circle = 0;
   float  needleAngle = 0;
   int    iteration = 0;
   int    j;                                                            
   float  volt = 220;
   int    needle_multiplier = 1;
   float  needle_setter;             
 float currentNeedleValue = 230;  // Start with a base voltage or level
float needleSpeed = 10;         // Speed at which the needle returns to the left
   
                                                     // to start with
   
void setup() {

   //randomSeed (analogRead(0)); 
 pinMode(12, OUTPUT);
 
    
   pinMode(vlez, INPUT);
   tft.begin();    
   Serial.begin (9600); 
   Serial.println (""); 
   Serial.println (""); 
   tft.setRotation (0);  
   
   tft.fillScreen (BLACK);
   tft.drawCircle (center_x, center_y,120, BLACK);             
   pivot_x = center_x;
   pivot_y = center_y+50;

   p1_x_old = center_x; p1_y_old = center_y+50;
   p2_x_old = center_x; p2_y_old = center_y+50;
   p3_x_old = center_x; p3_y_old = center_y+50;
   p4_x_old = center_x; p4_y_old = center_y+50;
   p5_x_old = center_x; p5_y_old = center_y+30;
                                                                          
   create_dial ();
   needle_setter = volt;
   needleAngle = (((needle_setter)*DEG2RAD*1.8)-3.14);
   needle();  
   draw_pivot ();
}


void loop (){

  // Map the analog input (voltage) to the needle range
  float targetNeedleValue = map(analogRead(vlez), 0, 400, 230, 270);
  Serial.println(targetNeedleValue);
  sig = analogRead(vlez);
  if (sig > 280)  {digitalWrite(12, HIGH);} else {digitalWrite(12, LOW);}

  
  // If the target value is greater than the current needle position, move quickly
  if (targetNeedleValue > currentNeedleValue) {
    currentNeedleValue = targetNeedleValue;
  } 
  // If the target value is lower, move more slowly to simulate damping
  else if (targetNeedleValue < currentNeedleValue) {
    currentNeedleValue -= needleSpeed; // Decrease the value gradually
    if (currentNeedleValue < targetNeedleValue) {
      currentNeedleValue = targetNeedleValue;  // Ensure we don't overshoot
    }
  }

  // Update the needle position
  needle_setter = currentNeedleValue;
  needle();
  draw_pivot(); 

  delay(frametime);  // Control the update rate
} 


void needle (){                                                                            // dynamic needle management

   tft.drawLine (pivot_x, pivot_y, p1_x_old, p1_y_old, AFRICA);                            // remove old needle  
   tft.fillTriangle (p1_x_old, p1_y_old, p2_x_old, p2_y_old, p3_x_old, p3_y_old, AFRICA);  // remove old arrow head
   tft.fillTriangle (pivot_x, pivot_y, p4_x_old, p4_y_old, p5_x_old, p5_y_old, AFRICA);    // remove old arrow head
    
   needleAngle = (((needle_setter)*0.01745331*1.8)-3.14);
   p1_x = (pivot_x + ((radius)*cos(needleAngle)));                                         // needle tip
   p1_y = (pivot_y + ((radius)*sin(needleAngle))); 

   p2_x = (pivot_x + ((radius-15)*cos(needleAngle-0.05)));                                 // needle triange left
   p2_y = (pivot_y + ((radius-15)*sin(needleAngle-0.05))); 

   p3_x = (pivot_x + ((radius-15)*cos(needleAngle+0.05)));                                 // needle triange right
   p3_y = (pivot_y + ((radius-15)*sin(needleAngle+0.05))); 

   p4_x = (pivot_x + ((radius-90)*cos(angleOffset+(needleAngle-0.2))));                    // needle triange left
   p4_y = (pivot_y + ((radius-90)*sin(angleOffset+(needleAngle-0.2)))); 

   p5_x = (pivot_x + ((radius-90)*cos(angleOffset+(needleAngle+0.2))));                    // needle triange right
   p5_y = (pivot_y + ((radius-90)*sin(angleOffset+(needleAngle+0.2)))); 
  
   p1_x_old = p1_x; p1_y_old = p1_y;                                                       // remember previous needle position
   p2_x_old = p2_x; p2_y_old = p2_y;                                                                         
   p3_x_old = p3_x; p3_y_old = p3_y;                                                                      

   p4_x_old = p4_x; p4_y_old = p4_y;                                                       // remember previous needle counterweight position
   p5_x_old = p5_x; p5_y_old = p5_y;                                                                      

   tft.drawLine (pivot_x, pivot_y, p1_x, p1_y, BLACK);                                     // create needle 
   tft.fillTriangle (p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, BLACK);                           // create needle tip pointer
  // tft.drawLine (center_x-80, center_y+70, center_x+80,center_y+70, BLACK);                // repair floor 
   tft.fillTriangle (pivot_x, pivot_y, p4_x, p4_y, p5_x, p5_y, BLACK);                     // create needle counterweight
}


void create_dial (){

   tft.fillCircle (center_x, center_y,120, AFRICA);                                        // general dial field
   tft.drawCircle (center_x, center_y,118,GREY);  
   tft.drawCircle (center_x, center_y,117,BLACK);
   tft.drawCircle (center_x, center_y,116,BLACK);  
   tft.drawCircle (center_x, center_y,115,GREY);

   for (j= 30; j<60    ; j+=5)
       {
        needleAngle = ((j*DEG2RAD*1.8)-3.14);
        arc_x = (pivot_x + ((radius+15)*cos(needleAngle)));                                // needle tip
        arc_y = (pivot_y + ((radius+15)*sin(needleAngle))); 
        tft.drawPixel  (arc_x,arc_y, BLACK);
        tft.fillCircle (arc_x,arc_y,2, BLACK);
        }

   for (j= 60; j<75    ; j+=5)
       {
        needleAngle = ((j*DEG2RAD*1.8)-3.14);
        arc_x = (pivot_x + ((radius+15)*cos(needleAngle)));                                // needle tip
        arc_y = (pivot_y + ((radius+15)*sin(needleAngle))); 
        tft.drawPixel  (arc_x,arc_y, RED);
        tft.fillCircle (arc_x,arc_y,2, RED);
        }        

   tft.setTextColor (BLACK,AFRICA);    
   tft.setTextSize (4);
   tft.setCursor (center_x+55, center_y+40);
   tft.print ("L");  
      tft.setTextSize (4);
   tft.setCursor (center_x-70, center_y+40);
  tft.print ("VU");   
                                                                                                                                                                           
  // tft.drawLine (center_x-80, center_y+70, center_x+80,center_y+70, WHITE);                // create floor   
}


void draw_pivot (){
 
   tft.fillCircle (pivot_x, pivot_y,8,RED);               
   tft.drawCircle (pivot_x, pivot_y,8,BLACK);            
   tft.drawCircle (pivot_x, pivot_y,3,BLACK);      
}

code Right channel

C/C++
..
// GCA901_Nano_voltage_meter
// 
// grid voltage variation monitor (230V - 250V AC)
// rolling averaged voltage (of 10 readings) is sent to display
// NOTE: here voltage generated with random function
//
// microcontroller: Arduino Nano
// display 240*240 circular SPI 3.3V TFT with GC9A01 controller
// 
// note: random function drives fluctuations of the parameter named 'volt'
// CG9A01    Arduino Nano
//  RST -------- NC
//  CST -------- 10
//  DC ---------  9
//  SDA -------- 11 - green wire
//  SCL -------- 13 - yellow wire
//  
// Floris Wouterlood
// September 1, 2023
// public domain

// made for a 240*240 pixel circular display
// all x-y-coordinates relative to center = x = 120 and y = 120
     
   #include "SPI.h"
   #include "Adafruit_GFX.h"
   #include "Adafruit_GC9A01A.h"

   #define TFT_DC 2
   #define TFT_CS 15
 
   Adafruit_GC9A01A tft (TFT_CS, TFT_DC);
 
   #define BLACK      0x0000                                                               // some extra colors
   #define BLUE       0x001F
   #define RED        0xF800
   #define GREEN      0x07E0
   #define CYAN       0x07FF
   #define MAGENTA    0xF81F
   #define YELLOW     0xFFE0
   #define WHITE      0xFFFF
   #define ORANGE     0xFBE0
   #define GREY       0x84B5
   #define BORDEAUX   0xA000
   #define AFRICA     0xAB21                // 0xce5f  //0xAB21                                                               // current dial color
    const int vlez = 34;
   #define DEG2RAD 0.0174532925 
   //int vlez;
   int sig = 0;
   int multiplier;
   int    frametime = 100; 
   int    x_pos;
   int    y_pos; 
   int    center_x = 120;                                                                  // center x of dial on 240*240 TFT display
   int    center_y = 120;                                                                  // center y of dial on 240*240 TFT display
   float  pivot_x, pivot_y,pivot_x_old, pivot_y_old;
   float  p1_x,p1_y,p2_x,p2_y,p3_x, p3_y, p4_x, p4_y, p5_x, p5_y; 
   float  p1_x_old,p1_y_old, p2_x_old, p2_y_old, p3_x_old, p3_y_old;
   float  p4_x_old, p4_y_old, p5_x_old, p5_y_old;
   float  angleOffset = 3.14;
   float  arc_x;
   float  arc_y;
   int    radius = 120;                                                                    // center y of circular scale                                                   
   float  angle_circle = 0;
   float  needleAngle = 0;
   int    iteration = 0;
   int    j;                                                            
   float  volt = 220;
   int    needle_multiplier = 1;
   float  needle_setter;             
 float currentNeedleValue = 230;  // Start with a base voltage or level
float needleSpeed = 10;         // Speed at which the needle returns to the left
   
                                                     // to start with
   
void setup() {

   //randomSeed (analogRead(0)); 
 pinMode(12, OUTPUT);
 
    
   pinMode(vlez, INPUT);
   tft.begin();    
   Serial.begin (9600); 
   Serial.println (""); 
   Serial.println (""); 
   tft.setRotation (0);  
   
   tft.fillScreen (BLACK);
   tft.drawCircle (center_x, center_y,120, BLACK);             
   pivot_x = center_x;
   pivot_y = center_y+50;

   p1_x_old = center_x; p1_y_old = center_y+50;
   p2_x_old = center_x; p2_y_old = center_y+50;
   p3_x_old = center_x; p3_y_old = center_y+50;
   p4_x_old = center_x; p4_y_old = center_y+50;
   p5_x_old = center_x; p5_y_old = center_y+30;
                                                                          
   create_dial ();
   needle_setter = volt;
   needleAngle = (((needle_setter)*DEG2RAD*1.8)-3.14);
   needle();  
   draw_pivot ();
}


void loop (){

  // Map the analog input (voltage) to the needle range
  float targetNeedleValue = map(analogRead(vlez), 0, 400, 230, 270);
  Serial.println(targetNeedleValue);
  sig = analogRead(vlez);
  if (sig > 280)  {digitalWrite(12, HIGH);} else {digitalWrite(12, LOW);}

  
  // If the target value is greater than the current needle position, move quickly
  if (targetNeedleValue > currentNeedleValue) {
    currentNeedleValue = targetNeedleValue;
  } 
  // If the target value is lower, move more slowly to simulate damping
  else if (targetNeedleValue < currentNeedleValue) {
    currentNeedleValue -= needleSpeed; // Decrease the value gradually
    if (currentNeedleValue < targetNeedleValue) {
      currentNeedleValue = targetNeedleValue;  // Ensure we don't overshoot
    }
  }

  // Update the needle position
  needle_setter = currentNeedleValue;
  needle();
  draw_pivot(); 

  delay(frametime);  // Control the update rate
} 


void needle (){                                                                            // dynamic needle management

   tft.drawLine (pivot_x, pivot_y, p1_x_old, p1_y_old, AFRICA);                            // remove old needle  
   tft.fillTriangle (p1_x_old, p1_y_old, p2_x_old, p2_y_old, p3_x_old, p3_y_old, AFRICA);  // remove old arrow head
   tft.fillTriangle (pivot_x, pivot_y, p4_x_old, p4_y_old, p5_x_old, p5_y_old, AFRICA);    // remove old arrow head
    
   needleAngle = (((needle_setter)*0.01745331*1.8)-3.14);
   p1_x = (pivot_x + ((radius)*cos(needleAngle)));                                         // needle tip
   p1_y = (pivot_y + ((radius)*sin(needleAngle))); 

   p2_x = (pivot_x + ((radius-15)*cos(needleAngle-0.05)));                                 // needle triange left
   p2_y = (pivot_y + ((radius-15)*sin(needleAngle-0.05))); 

   p3_x = (pivot_x + ((radius-15)*cos(needleAngle+0.05)));                                 // needle triange right
   p3_y = (pivot_y + ((radius-15)*sin(needleAngle+0.05))); 

   p4_x = (pivot_x + ((radius-90)*cos(angleOffset+(needleAngle-0.2))));                    // needle triange left
   p4_y = (pivot_y + ((radius-90)*sin(angleOffset+(needleAngle-0.2)))); 

   p5_x = (pivot_x + ((radius-90)*cos(angleOffset+(needleAngle+0.2))));                    // needle triange right
   p5_y = (pivot_y + ((radius-90)*sin(angleOffset+(needleAngle+0.2)))); 
  
   p1_x_old = p1_x; p1_y_old = p1_y;                                                       // remember previous needle position
   p2_x_old = p2_x; p2_y_old = p2_y;                                                                         
   p3_x_old = p3_x; p3_y_old = p3_y;                                                                      

   p4_x_old = p4_x; p4_y_old = p4_y;                                                       // remember previous needle counterweight position
   p5_x_old = p5_x; p5_y_old = p5_y;                                                                      

   tft.drawLine (pivot_x, pivot_y, p1_x, p1_y, BLACK);                                     // create needle 
   tft.fillTriangle (p1_x, p1_y, p2_x, p2_y, p3_x, p3_y, BLACK);                           // create needle tip pointer
  // tft.drawLine (center_x-80, center_y+70, center_x+80,center_y+70, BLACK);                // repair floor 
   tft.fillTriangle (pivot_x, pivot_y, p4_x, p4_y, p5_x, p5_y, BLACK);                     // create needle counterweight
}


void create_dial (){

   tft.fillCircle (center_x, center_y,120, AFRICA);                                        // general dial field
   tft.drawCircle (center_x, center_y,118,GREY);  
   tft.drawCircle (center_x, center_y,117,BLACK);
   tft.drawCircle (center_x, center_y,116,BLACK);  
   tft.drawCircle (center_x, center_y,115,GREY);

   for (j= 30; j<60    ; j+=5)
       {
        needleAngle = ((j*DEG2RAD*1.8)-3.14);
        arc_x = (pivot_x + ((radius+15)*cos(needleAngle)));                                // needle tip
        arc_y = (pivot_y + ((radius+15)*sin(needleAngle))); 
        tft.drawPixel  (arc_x,arc_y, BLACK);
        tft.fillCircle (arc_x,arc_y,2, BLACK);
        }

   for (j= 60; j<75    ; j+=5)
       {
        needleAngle = ((j*DEG2RAD*1.8)-3.14);
        arc_x = (pivot_x + ((radius+15)*cos(needleAngle)));                                // needle tip
        arc_y = (pivot_y + ((radius+15)*sin(needleAngle))); 
        tft.drawPixel  (arc_x,arc_y, RED);
        tft.fillCircle (arc_x,arc_y,2, RED);
        }        

   tft.setTextColor (BLACK,AFRICA);    
   tft.setTextSize (4);
   tft.setCursor (center_x+40, center_y+40);
   tft.print ("VU");  
      tft.setTextSize (4);
   tft.setCursor (center_x-60, center_y+40);
  tft.print ("R");   
                                                                                                                                                                           
  // tft.drawLine (center_x-80, center_y+70, center_x+80,center_y+70, WHITE);                // create floor   
}


void draw_pivot (){
 
   tft.fillCircle (pivot_x, pivot_y,8,RED);               
   tft.drawCircle (pivot_x, pivot_y,8,BLACK);            
   tft.drawCircle (pivot_x, pivot_y,3,BLACK);      
}

Credits

Mirko Pavleski
168 projects • 1371 followers
Contact

Comments

Please log in or sign up to comment.