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