Stereo VU Meter

Hello and welcome to a new part of the "VU-Meter" series.

The great response to the first part of the series is overwhelming! We will of course take on the challenge and in the next step we will add another channel to our VU meter. The second channel is displayed negatively on the U64 panel. We already have a great stereo VU meter with an impressive counter-rotating effect!

Of course, there are also a few hardware extensions and optimizations on board. As I wrote in the first part, the U64 panel is quite power-hungry. Therefore, our first hardware change is an exchange of the breadboard supply module for a stable 5Volt / 2.6 A power supply. We can now increase the brightness of the panel's LEDs from 39% to 100% without any electricity problems. The display makes a little more. However, the code initially limits this brightness to 60% brightness. This limitation will then drop in the next part of the series without changing the power supply.

The breadboard supply module is of course not garbage, but can be used for all other superstructures tracking  equally exciting projects can continue to be used. The other additional parts, namely the two 680 ohms Resistances  and the two 5.1 volt Zener diodes perform circuit protection functions for the Arduino. This results in the following partial list for today's part of the series:

 

  • 1x Arduino Nano (with FTDI)
  • 1x U64 LED panel
  • 2x 10 KOhm Resistances 1%
  • 2x 680 ohms Resistances 5%
  • 1x 5 volt, min 2.6 amp power supply
  • 1x 10 uF 64 volt electrolytic capacitor
  • 1x stereo jack connector 3.5 mm socket
  • 2x 5.1 volt zener diode

 

We wire the components to the following circuit diagram:

Fritzing circuit diagram

 

After we have completely built or updated the circuit, we can now upload the updated and expanded code to our Arduino:

 

#include <Adafruit_NeoPixel.H>

// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN     13
// How many NeoPixels are attached to the Arduino?
#define LED_COUNT  64

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w / WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)

#define analogPinLeft A5 // Left Audio Channel, connected to analog pin A5
#define analogPinRight A4 // Right Audio Channel, connected to analog pin A4
#define Left_Channel_Deviation  5
#define Right_Channel_Deviation 5

int val_left_old = 0;  // variable to store the value read from Channel Left
int Base_Left = 0;   // 0 base
int val_right_old = 0;  // variable to store the value read from Channel Left
int Base_Right = 0;   // 0 base

int leftDropTime, rightDropTime;
int dropDelay = 4;                                        // hold time before dropping the leds
// NeoPixel brightness, 0 (min) to 255 (max)
byte BRIGHTNESS = 153;  // 60% brightness

float dropFactor = .98;

void set up()
{   strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)   strip.show();            // Turn OFF all pixels ASAP   strip.setBrightness(BRIGHTNESS); // Set BRIGHTNESS to about 1/5 (max = 255)   Base_Left = analogRead(analogPinLeft);   Base_Left += analogRead(analogPinLeft);   Base_Left += analogRead(analogPinLeft);   Base_Left += analogRead(analogPinLeft);   Base_Left = Base_Left / 4;   Base_Right = analogRead(analogPinRight);   Base_Right += analogRead(analogPinRight);   Base_Right += analogRead(analogPinRight);   Base_Right += analogRead(analogPinRight);   Base_Right = Base_Right / 4;   Serial.begin(9600);   colorWipe(strip.Color(255, 0, 0), 5); // Red   colorWipe(strip.Color(255, 255, 0), 5); // yellow   colorWipe(strip.Color(0, 255, 0), 5); // Green   rainbowFade2White(1, 1, 1);
}

void loop()
{   Left_VU_Meter(LED_COUNT / 2, 512);   Right_VU_Meter(LED_COUNT / 2, 511);   strip.show();   //  Update strip to match
}





void Left_VU_Meter(byte Level_Max_Pixels, int sensitivity)
{   int val_left = 0;   bool Overload = false;   uint32_t rgbcolor;   uint32_t hue;   int Signal_Strength = 0;   byte VU_Led_Level = 0;   val_left = analogRead(analogPinLeft);  // read the input pin   val_left += analogRead(analogPinLeft);  // read the input pin   val_left += analogRead(analogPinLeft);  // read the input pin   val_left += analogRead(analogPinLeft);  // read the input pin   val_left = val_left / 4;   if (!(abs(val_left - val_left_old) > Left_Channel_Deviation)) {     val_left = val_left_old;   }   if (val_left < val_left_old)   {     leftDropTime++;     if (leftDropTime > dropDelay)     {       val_left = val_left_old * dropFactor;       leftDropTime = 0;     }     else     {       val_left = val_left_old;     }   }   val_left_old = val_left;   Signal_Strength = val_left - Base_Left;   if (Signal_Strength < 0) {     Signal_Strength = - Signal_Strength;   }   VU_Led_Level =  map(Signal_Strength, 0, sensitivity , 0, Level_Max_Pixels);   if (VU_Led_Level > Level_Max_Pixels)   {     Overload = true;     VU_Led_Level = Level_Max_Pixels;   } else {     Overload = false;   }   for (int i = 0; i < Level_Max_Pixels; i++) {     strip.setPixelColor(i, 0, 0, 0);  // Clear pixel's color (in RAM)   }   for (int i = 0; i < VU_Led_Level; i++) { // For each pixel in strip...     hue = map(i, Level_Max_Pixels - 1, 0, 0, 21800);     if (Overload) {       rgbcolor = strip.Color(255, 0, 0);   // Hue to RGB Conversation     } else {       rgbcolor = strip.ColorHSV(hue, 255, BRIGHTNESS);     }     strip.setPixelColor(i, rgbcolor);         //  Set pixel's color (in RAM)   }   //   strip.show();   //  Update strip to match
}

void colorWipe(uint32_t color, int wait) {   for (int i = 0; i < strip.numPixels(); i++) { // For each pixel in strip...     strip.setPixelColor(i, color);         //  Set pixel's color (in RAM)     strip.show();                          //  Update strip to match     delay(wait);                           //  Pause for a moment   }
}

void Right_VU_Meter(byte Level_Max_Pixels, int sensitivity)
{   int val_right = 0;   bool Overload = false;   uint32_t rgbcolor;   uint32_t hue;   int Signal_Strength = 0;   byte VU_Led_Level = 0;   val_right  = analogRead(analogPinRight);  // read the input pin   val_right  += analogRead(analogPinRight);  // read the input pin   val_right  += analogRead(analogPinRight);  // read the input pin   val_right  += analogRead(analogPinRight);  // read the input pin   val_right  = val_right / 4;   if (!(abs(val_right - val_right_old) > Right_Channel_Deviation)) {     val_right = val_right_old;   }   if (val_right < val_right_old)   {     rightDropTime++;     if (rightDropTime > dropDelay)     {       val_right = val_right_old * dropFactor;       rightDropTime = 0;     }     else     {       val_right = val_right_old;     }   }   val_right_old = val_right;   Signal_Strength = val_right - Base_Right;   if (Signal_Strength < 0) {     Signal_Strength = - Signal_Strength;   }   VU_Led_Level =  map(Signal_Strength, 0, sensitivity , 0, Level_Max_Pixels);   if (VU_Led_Level > Level_Max_Pixels)   {     Overload = true;     VU_Led_Level = Level_Max_Pixels;   } else {     Overload = false;   }   int ColorVector = 0;   for (int i = LED_COUNT - Level_Max_Pixels; i < LED_COUNT; i++) {     strip.setPixelColor(i, 0, 0, 0);  // Clear pixel's color (in RAM)   }   int StartVector = LED_COUNT - VU_Led_Level;   for (int i = LED_COUNT - Level_Max_Pixels; i < LED_COUNT; i++) { // For each pixel in strip...     hue = map(ColorVector, Level_Max_Pixels - 1, 0, 21800, 0);     ColorVector++;     if ( i >= StartVector)     {       if (Overload) {         rgbcolor = strip.Color(255, 0, 0);   // Hue to RGB Conversation       } else {         rgbcolor = strip.ColorHSV(hue, 255, BRIGHTNESS);       }       strip.setPixelColor(i, rgbcolor);         //  Set pixel's color (in RAM)     }   }
}

void rainbowFade2White(int wait, int rainbowLoops, int whiteLoops) {   int fadeVal = 0, fadeMax = 100;   // Hue of first pixel runs 'rainbowLoops' complete loops through the color   // wheel. Color wheel has a range of 65536 but it's OK if we roll over, so   // just count from 0 to rainbowLoops*65536, using steps of 256 so we   // advance around the wheel at a decent clip.   for (uint32_t firstPixelHue = 0; firstPixelHue < rainbowLoops * 65536;        firstPixelHue += 256) {     for (int i = 0; i < strip.numPixels(); i++) { // For each pixel in strip...       // Offset pixel hue by an amount to make one full revolution of the       // color wheel (range of 65536) along the length of the strip       // (strip.numPixels() steps):       uint32_t pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());       // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or       // optionally add saturation and value (brightness) (each 0 to 255).       // Here we're using just the three-argument variant, though the       // second value (saturation) is a constant 255.       strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue, 255,                                            255 * fadeVal / fadeMax)));     }     strip.show();     delay(wait);     if (firstPixelHue < 65536) {                             // First loop,       if (fadeVal < fadeMax) fadeVal++;                      // fade in     } else if (firstPixelHue >= ((rainbowLoops - 1) * 65536)) { // Last loop,       if (fadeVal > 0) fadeVal--;                            // fade out     } else {       fadeVal = fadeMax; // Interim loop, make sure fade is at max     }   }   for (int k = 0; k < whiteLoops; k++) {     for (int j = 0; j < 256; j++) { // Ramp up 0 to 255       // Fill entire strip with white at gamma-corrected brightness level 'j':       strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));       strip.show();     }     for (int j = 255; j >= 0; j--) { // Ramp down 255 to 0       strip.fill(strip.Color(0, 0, 0, strip.gamma8(j)));       strip.show();     }   }
}

 

Es ist beim Betrieb zu beachten, das während des Einschaltens des VU Meters und dem Abspielen des Intros die virtuelle Nulllinie eingelesen wird. Damit diese Kalibrierung korrekt ablaufen kann, ist es wichtig, während der Einschaltphase noch kein Audio Signal anzulegen. Erst wenn die Startanimationen beendet sind, kann ein Analogsignal angelegt werden.

Ich wünsche viel Spaß beim Nachbauen bis zum nächsten Mal

 

 

Für arduinoProjekte für fortgeschrittene

2 comments

Tobias

Tobias

Hallo Niko,
Die Polung der Elkos bei Wechselspannung ist immer so eine Sache der Perspektive ;)
Dennoch ist die Polung hier Richtig, da der Bezug zur Masse des Audiosignals !über den 10 KOhm Wiederstand hergestellt wird.
Viele Grüße

Niko

Niko

Hallo Tobias,

ich will ja nicht lästig sein, aber bitte noch einmal die Polung der Elkos prüfen. Ich denke, die sollten andersrum gesteckt werden. Minus in Richtung Audiobuchse.

Schöne Grüße
und vielen Dank für die vielen Ideen und Vorschläge in deinem Blog!

Leave a comment

All comments are moderated before being published