The advantages of this device are that it is easy to build, and there is a fairly accurate representation of sound level.

A volume unit (VU) meter is a device displaying a representation of the signal level in audio equipment.

This time I will show you how to make a stereo VU meter with 39 bars. The device is made according to the "KTAudio VU Meter" by Tamas Kamocsai where you can also download the original code. In this case I modified the code so that now the VU meter is made with a large LCD display. I also made small changes to the visual appearance of the Bars. In the video you can see the appearance of several versions. You can also create your own look by changing the row:

byte Bar [8] = {

At the input I add a stereo Potentiometer to control the input signal.

The device is very simple to build and contains only a few components:

- Arduino nano microcontroller

- 40 on 2 Big LCD display with HD44780 chip

- Stereo potentiometer

- and several resistors and capacitors

The advantages of this device are that it is easy to build, and there is fairly accurate representation of sound level. It can also be easily connected to a DIY amplifier or preamplifier. Disadvantages are: Linear representation of the volume level (From around -14dB to 0dB) and not accurate enough for true representation for studio or usage where accuracy is essential.

Finally, if we use it as a stand-alone device, it should be installed in a suitable box, which in this case is made of PVC material and coated with self-adhesive wallpaper.

        /*
  Arduino based VU meter by mircemk.
  Developed by ThomAce (Tamas Kamocsai) based on siemenwauters, theredstonelabz and michiel H's VU meter.

  GNU GPL License v3

  Developer: ThomAce (Tamas Kamocsai)
  Mail: [email protected]
  Version: 1.0
  Last modification date: 2019.09.24

  Original version:
  https://www.instructables.com/id/ARDUINO-VU-METER/

  Original description:
  VU meter by siemenwauters, theredstonelabz and michiel H don't forget to like and subscribe to support my work. tnx

  Modified by "mircemk" date: 2021.07.13 
*/

#include <LiquidCrystal.h>

byte Bar[8] = {
        B11111,
        B00000,
        B00000,
        B11111,
        B11111,
        B00000,
        B00000,
        B11111
};

byte L[8] = {
        B00111,
        B01000,
        B10100,
        B10100,
        B10100,
        B10111,
        B01000,
        B00111
};

byte R[8] = {
        B00111,
        B01000,
        B10110,
        B10101,
        B10110,
        B10101,
        B01000,
        B00111
};

byte EndMark[8] = {
        B10000,
        B01000,
        B00100,
        B00100,
        B00100,
        B00100,
        B01000,
        B10000
};

byte EmptyBar[8] = {
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111
};

byte peakHoldChar[8] = {
        B11111,
        B00000,
        B00011,
        B00011,
        B00011,
        B00011,
        B00000,
        B11111
};

String main_version = "1.1";
int left, right;                        //Variables to store and calculate the channel levels             
const int numReadings = 1;              //Refresh rate. Lower value = higher rate. 5 is the defaul
int indexL = 0;                         //Actual channel index
int totalL = 0;                         //Total channel data
int maxL = 0;                           //Maximal level
int indexR = 0;                         
int totalR = 0;                         
int maxR = 0;
int inputPinL = A1;                     //Input pin Analog 1 for LEFT channel
int inputPinR = A0;                     //Input pin Analog 0 for RIGHT channel
int volL = 0;
int volR = 0;
int rightAvg = 0;
int leftAvg = 0;
long peakHoldTime = 1500;               //peak hold time in miliseconds
long peakHold = 0;
int rightPeak = 0;
int leftPeak = 0;
long decayTime = 0;
long actualMillis = 0;

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);  //lcd configuration

void setup()
{  
  lcd.begin(40, 2); //Setting up LCD. 40 chars and 2 rows
    
  lcd.createChar(1, Bar);
  lcd.createChar(2, L);
  lcd.createChar(3, R);
  lcd.createChar(4, EmptyBar);
  lcd.createChar(5, EndMark);
  lcd.createChar(6, peakHoldChar);

  //Showing loading  message and loading bar
  
  String mircemk = "* mircemk *";  
  
  for (int i = 0; i <= 40; i++)
  {    
    lcd.setCursor(15, 0); 
    lcd.print(mircemk.substring(0, i));
    delay(50);
  }  

  mircemk = "              VU meter " + main_version;
  
  for (int i = 0; i <= mircemk.length(); i++)
  {    
    lcd.setCursor(0, 1);
    lcd.print(mircemk.substring(0, i));
    delay(50);
  }

  delay(500);
  
  lcd.clear();
  lcd.setCursor(15, 0); 
  lcd.print("Loading...");

  for (int i = 0; i < 40; i++)
  {
    lcd.setCursor(i, 1); 
    lcd.write(4);
  }
  
  for (int i = 0; i < 40; i++)
  {
    lcd.setCursor(i, 1); 
    lcd.write(1);

    delay(50);
  }  
    
  delay(5);
  lcd.clear();

  decayTime = millis();
}

void loop()
{    
  actualMillis = millis();
  
  lcd.setCursor(0, 0);        //L channel index
  lcd.write(2);               //L symbol 
  lcd.setCursor(0, 1);        //R channel index
  lcd.write(3);               //R symbol
  lcd.setCursor(39, 0);       //closing tag / end mark index 1
  lcd.write(5);               //closing tag / end mark
  lcd.setCursor(39, 1);       //closing tag / end mark index 2
  lcd.write(5);               //closing tag / end mark
   
  totalL = analogRead(inputPinL) / 4 - 128; //reducing the detected hum and noise
    
  if(totalL > maxL)
  {
    maxL = totalL;
  }
   
  indexL++;
   
  if (indexL >= numReadings)
  {             
    indexL = 0;                         
    left = maxL;
    maxL = 0;
  }   
     
  totalR = analogRead(inputPinR) / 4 - 128; //reducing the detected hum and noise
    
  if(totalR > maxR)
  {
    maxR = totalR;
  }
   
  indexR++;
   
  if (indexR >= numReadings)
  {             
    indexR = 0;                         
    right = maxR;
    maxR = 0;
  } 
    
  volR = right / 3;
    
  if(volR > 38)
  {
    volR = 38;
  }

  if (volR < (rightAvg - 2))
  {
    if (decayTime < actualMillis)
      rightAvg--;
      
    volR = rightAvg;
  }    
  else if (volR > (rightAvg + 2))
  {
    volR = (rightAvg + 2);
    rightAvg = volR;
  }
  else
  {
    rightAvg = volR;
  }

  if (volR > rightPeak)
  {
    rightPeak = volR;    
  }

  drawBar(volR, rightPeak, 1);

  volL = left / 3;
   
  if(volL > 38)
  {
    volL = 38;
  }

  if (volL < (leftAvg - 2))
  {
    if (decayTime < actualMillis)
      leftAvg--;   
         
    volL = leftAvg;
  }
  else if (volL > (leftAvg + 2))
  {
    volL = (leftAvg + 2);
    leftAvg = volL;
  }
  else
  {
    leftAvg = volL;
  }

  if (volL > leftPeak)
  {
    leftPeak = volL;
  }

  drawBar(volL, leftPeak, 0);

  if (decayTime < actualMillis)
    decayTime = (millis() + 50);

  if (peakHold < actualMillis)
  {
    peakHold = (millis() + peakHoldTime);
    rightPeak = -1;
    leftPeak = -1;
  }
}

void drawBar(int data, int peakData, int row)
{
  //If the previous peak data is 1 or 0, then not taking care of the value.
  if (peakData < 2)
  {
    peakData = -1;
  }

  //First char (idx 0) = R or L
  //Last (16th) char (idx 15) is the closing mark of the bar.
  //We have 14 chars to write.
  for (int col = 1; col < 39; col++)
  {
    lcd.setCursor(col, row);

    if (col < data)
    {
      lcd.write(1); //write bar element
    }
    else if (peakData == col)
    {
      lcd.write(6); //write the peak marker
    }
    else
    {
      lcd.write(4); //write "empty" 
    }
  }
}
    
Vu meter Schematic.jpg
Mirko Pavleski
Electronics , Arduino , Physics