sâmbătă, 4 aprilie 2026

VU metru cu Arduino si ST7735

 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 ; audio stereo de la orice preamp (prin 1N4148, 10k, 1-10uF spre masa, A0/A1) sau de la iesirea unui preamplificator de microfon electret (caz in care se unesc A0si A1 iar semnalul afisat va fi mono).

Aici am postat un mic clip cu functionarea.

Enjoy ! 


 


/*
 * VU-METER 04.2026
 * BASED ON GRAPHIC IDEEA OF ericBcreator 
 * https://youtu.be/E2tdxQf2Cbg?si=SEzUlVO9j4TcPfIn

Display    Arduino
SCL       D13
SDA       D11
RST       D9
DC        D8
CS        D10

ENCODER
OUT1      D2
OUT2      D3
SW        D4

INPUT AUDIO : A0 SI A1

*/

#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; long oldPosition = -999; int leftPeak = 0, rightPeak = 0; int lastLeftPeakY = 139, lastRightPeakY = 139; unsigned long lastPeakMillis = 0; byte lastStateL[15] = {0}, lastStateR[15] = {0}; 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); deseneazaInterfataStatica(); actualizeazaSelector(0); } void loop() { long newPos = myEnc.read() / 4; if (newPos != oldPosition) { menuIndex = abs(newPos % 4); actualizeazaSelector(menuIndex); oldPosition = newPos; } if (digitalRead(ENC_SW) == LOW) { delay(200); optStates[menuIndex] = !optStates[menuIndex]; actualizeazaSelector(menuIndex); while(digitalRead(ENC_SW) == LOW); } int gain = optStates[2] ? 450 : 800; int rawL = analogRead(A0); int rawR = analogRead(A1); int segL = constrain(map(rawL, 10, gain, 0, 15), 0, 15); int segR = constrain(map(rawR, 10, gain, 0, 15), 0, 15); 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; int fallSpeed = optStates[3] ? 100 : 50; if (millis() - lastPeakMillis > fallSpeed) { if (leftPeak > 0) leftPeak -= 1; if (rightPeak > 0) rightPeak -= 1; lastPeakMillis = millis(); } deseneazaDale(POS_L, segL, lastStateL); deseneazaDale(POS_R, segR, lastStateR); if (optStates[0]) { actualizeazaPeak(POS_L, leftPeak, lastLeftPeakY); actualizeazaPeak(POS_R, rightPeak, lastRightPeakY); } // --- LOGICA LEVEL GAUGE (X=30, Y_start=91, H=49) --- int lvlH = constrain(map(rawL, 10, gain, 0, 49), 0, 49); if (lvlH != lastLvlH) { int xLvl = 30; int yBottom = 140; tft.fillRect(xLvl, 91, 10, 49 - lvlH, 0x000F); // Fundal inactiv if (lvlH > 0) { tft.fillRect(xLvl, yBottom - lvlH, 10, lvlH, 0x5DFF); // Bara activa } lastLvlH = lvlH; } delay(5); } void deseneazaDale(int x, int count, byte last[]) { for (int i = 0; i < 15; i++) { byte s = (i < count) ? 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; } } } void actualizeazaPeak(int x, int p, int &lY) { int cY = 139 - p; if (cY != lY) { tft.drawFastHLine(x, lY, BAR_W, ST7735_BLACK); if (p > 2) tft.drawFastHLine(x, cY, BAR_W, ST7735_WHITE); lY = cY; } } 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"); // --- LEVEL CALIBRATE --- 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"); // Cele 10 gradații 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) { for(int i = 0; i < 4; i++) { tft.fillRect(2, 40 + (i * 12), 7, 8, ST7735_BLACK); tft.fillRect(58, 40 + (i * 12), 6, 8, ST7735_BLACK); if (i == index) { tft.setTextColor(ST7735_RED); tft.setCursor(2, 40 + (i * 12)); tft.print(">"); } if (optStates[i]) { tft.fillRect(59, 43 + (i * 12), 3, 3, ST7735_GREEN); } } }

Niciun comentariu:

Trimiteți un comentariu