Va salut !
La cererea prietenului Dragos (care mi-a trimis un clip cu un vu-metru cu display color), bazandu-ma pe IA am conceput acest vu-metru, cu un Arduino Nano si un display LCD tip ST7735.
Are un encoder care permite selectia diferitelor tipuri de afisare ; rotirea encoderului permite navigarea printre moduri, apasarea permite selectarea/deselectarea modurilor (se pot selecta toate). Prin combinatiile intre moduri se poate ajunge la o functionare conforma preferintelor.
Semnalul audio se aplica pe pinii A0 si A1.
Aici am postat un mic clip cu functionarea.
Enjoy !
UPDATE 12.07.2026 : Soft modificat ; functioneaza bine cu preamp de microfon condenser cu LM386
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <SPI.h>
#include <Encoder.h>
#define TFT_CS 10
#define TFT_RST 9
#define TFT_DC 8
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Encoder myEnc(2, 3);
#define ENC_SW 4
const char* menuOptions[] = {"PEAK HLD", "FULL SCR", "WIDE AGC", "SMOOTH"};
const uint16_t menuColors[] = {ST7735_WHITE, ST7735_YELLOW, 0x07E0, 0x07FF};
bool optStates[] = {true, false, false, false};
int menuIndex = 0;
int lastMenuIndex = -1;
long oldPosition = -999;
int leftPeak = 0, rightPeak = 0;
int lastLeftPeakY = 139, lastRightPeakY = 139;
unsigned long lastPeakMillis = 0;
unsigned long lastButtonMillis = 0;
byte lastStateL[15];
byte lastStateR[15];
int lastLvlH = -1;
const int BORDER_X = 65;
const int BORDER_W = 58;
const int BAR_W = 18;
const int PADDING = 6;
const int POS_L = BORDER_X + PADDING;
const int POS_R = BORDER_X + BORDER_W - PADDING - BAR_W;
void setup() {
pinMode(ENC_SW, INPUT_PULLUP);
tft.initR(INITR_BLACKTAB);
tft.setRotation(0);
tft.fillScreen(ST7735_BLACK);
for(int i = 0; i < 15; i++) {
lastStateL[i] = 255;
lastStateR[i] = 255;
}
deseneazaInterfataStatica();
actualizeazaSelector(0);
}
void loop() {
// 1. ROTATIE ENCODER
long newPos = myEnc.read() / 4;
if (newPos != oldPosition) {
menuIndex = abs(newPos % 4);
actualizeazaSelector(menuIndex);
oldPosition = newPos;
}
// 2. APASARE BUTON
if (digitalRead(ENC_SW) == LOW) {
if (millis() - lastButtonMillis > 300) {
optStates[menuIndex] = !optStates[menuIndex];
tft.fillRect(59, 40 + (menuIndex * 12), 4, 8, ST7735_BLACK);
if (optStates[menuIndex]) {
tft.fillRect(59, 43 + (menuIndex * 12), 3, 3, ST7735_GREEN);
}
lastButtonMillis = millis();
}
}
// --- 3. ESANTIONARE AUDIO STEREO REALA ---
int signalMaxL = 0, signalMinL = 1023;
int signalMaxR = 0, signalMinR = 1023;
unsigned long startMillis = millis();
while (millis() - startMillis < 30) {
int sampleL = analogRead(A0);
int sampleR = analogRead(A1);
if (sampleL < 1024) {
if (sampleL > signalMaxL) signalMaxL = sampleL;
if (sampleL < signalMinL) signalMinL = sampleL;
}
if (sampleR < 1024) {
if (sampleR > signalMaxR) signalMaxR = sampleR;
if (sampleR < signalMinR) signalMinR = sampleR;
}
}
// Calculam amplitudinea Peak-to-Peak (Stereo Independent)
int peakToPeakL = signalMaxL - signalMinL;
int peakToPeakR = signalMaxR - signalMinR;
// Sensibilitatea dinamica din meniu (WIDE AGC)
int maxVal = optStates[2] ? 40 : 180;
int segL = constrain(map(peakToPeakL, 4, maxVal, 0, 15), 0, 15);
int segR = constrain(map(peakToPeakR, 4, maxVal, 0, 15), 0, 15);
// 4. LOGICA PEAK HOLD
int curPeakL = map(segL, 0, 15, 0, 92);
int curPeakR = map(segR, 0, 15, 0, 92);
if (curPeakL > leftPeak) leftPeak = curPeakL;
if (curPeakR > rightPeak) rightPeak = curPeakR;
// --- TIMING SI VITEZA ACCELERATA DE REVENIRE LA ZERO ---
int fallSpeed = optStates[3] ? 90 : 35; // Mai rapid decat inainte (35ms vs 50ms)
if (millis() - lastPeakMillis > fallSpeed) {
if (leftPeak > 0) leftPeak -= 3; // REPARAT: Scade cu 3 pixeli deodata pentru o coborare mult mai alerta
if (rightPeak > 0) rightPeak -= 3; // REPARAT: Scade cu 3 pixeli deodata pentru o coborare mult mai alerta
if (leftPeak < 0) leftPeak = 0;
if (rightPeak < 0) rightPeak = 0;
lastPeakMillis = millis();
}
if (!optStates[0]) { leftPeak = 0; rightPeak = 0; }
// 5. DESENARE STEREO INDEPENDENTA
actualizeazaBaraBruta(POS_L, segL, leftPeak, lastLeftPeakY, optStates[0], lastStateL);
actualizeazaBaraBruta(POS_R, segR, rightPeak, lastRightPeakY, optStates[0], lastStateR);
// 6. INDICATOR LEVEL
int lvlH = constrain(map(peakToPeakL, 4, maxVal, 0, 49), 0, 49);
if (lvlH != lastLvlH) {
int yBaseLevel = 140;
tft.fillRect(30, yBaseLevel - lvlH, 10, lvlH, 0x5DFF);
if (lvlH < 49) {
tft.fillRect(30, 91, 10, 49 - lvlH, 0x000F);
}
lastLvlH = lvlH;
}
}
void actualizeazaBaraBruta(int x, int val, int peak, int &lY, bool peakActive, byte last[]) {
for (int i = 0; i < 15; i++) {
byte s = (i < val) ? 1 : 0;
if (s != last[i]) {
uint16_t c = (s == 1) ? (i < 9 ? ST7735_GREEN : (i < 12 ? ST7735_YELLOW : ST7735_RED)) : 0x1082;
tft.fillRect(x, 135 - (i * 6), BAR_W, 4, c);
last[i] = s;
}
}
int cY = 139 - peak;
if (cY != lY) {
tft.drawFastHLine(x, lY, BAR_W, ST7735_BLACK);
lY = cY;
}
if (peakActive && peak > 2) {
tft.drawFastHLine(x, cY, BAR_W, ST7735_WHITE);
}
}
void deseneazaInterfataStatica() {
tft.fillRect(5, 5, 118, 20, ST7735_BLUE);
tft.setTextColor(ST7735_WHITE);
tft.setCursor(38, 12); tft.print("VU METER");
for (int i = 0; i < 4; i++) {
tft.setTextColor(menuColors[i]);
tft.setCursor(10, 40 + (i * 12)); tft.print(menuOptions[i]);
}
tft.drawRect(BORDER_X, 40, BORDER_W, 101, ST7735_WHITE);
tft.fillRect(POS_L, 145, BAR_W, 12, ST7735_RED);
tft.fillRect(POS_R, 145, BAR_W, 12, ST7735_RED);
tft.setTextColor(ST7735_WHITE);
tft.setCursor(POS_L + 6, 147); tft.print("L");
tft.setCursor(POS_R + 6, 147); tft.print("R");
int xLvl = 30; int yTop = 90; int yBot = 141;
tft.drawRect(xLvl - 1, yTop, 12, (yBot - yTop) + 1, ST7735_WHITE);
tft.setCursor(12, yTop + 2); tft.print("10");
tft.setCursor(18, yBot - 8); tft.print("0");
tft.setCursor(20, 147); tft.print("LEVEL");
for (int i = 0; i <= 10; i++) {
int yT = yBot - (i * 5);
tft.drawFastHLine(xLvl - 4, yT, 3, ST7735_WHITE);
tft.drawFastHLine(xLvl + 11, yT, 3, ST7735_WHITE);
}
}
void actualizeazaSelector(int index) {
if (index != lastMenuIndex) {
if (lastMenuIndex >= 0) tft.fillRect(1, 40 + (lastMenuIndex * 12), 6, 8, ST7735_BLACK);
tft.setTextColor(ST7735_RED); tft.setCursor(1, 40 + (index * 12)); tft.print(">");
lastMenuIndex = index;
}
for(int i = 0; i < 4; i++) {
tft.fillRect(59, 40 + (i * 12), 4, 8, ST7735_BLACK);
if (optStates[i]) tft.fillRect(59, 43 + (i * 12), 3, 3, ST7735_GREEN);
}
}
