#include <Arduino.h>
#include <U8g2lib.h>
#include <Bounce2.h>
// initialization for the 128x64px OLED display
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
static const unsigned char volume_high_icon[] = {0x48,0x8c,0xaf,0xaf,0x8c,0x48};
static const unsigned char volume_muted_icon[] = {0x01,0x0a,0x0c,0x0b,0x17,0x2c,0x48};
const uint8_t NUM_SLIDERS = 5;
const uint8_t analogInputs[NUM_SLIDERS] = {A0, A1, A2, A3, A4};
const uint8_t digitalInputs[NUM_SLIDERS] = {5, 6, 7, 18, 19};
const unsigned short SLIDER_MAX_VALUE = 4095;
const unsigned short UNMUTE_DISPLAY_DURATION = 500; // 0.5 second to show unmute icon
unsigned short analogSliderValues[NUM_SLIDERS];
unsigned short previousAnalogSliderValues[NUM_SLIDERS] = {0}; // Armazena valores anteriores
bool muteStates[NUM_SLIDERS] = {false};
unsigned long unmuteDisplayStart[NUM_SLIDERS] = {0};
bool firstRun = true; // Flag para forçar a primeira atualização
// bounce objects array
Bounce debouncers[NUM_SLIDERS];
// slider positions
struct SliderConfig {
uint8_t frame_x;
uint8_t knob_x;
uint8_t indicator_left_x1, indicator_left_x2;
uint8_t indicator_right_x1, indicator_right_x2;
uint8_t unmuted_icon_x;
uint8_t muted_icon_x;
uint8_t bottom_indicator_x;
};
const SliderConfig sliderConfigs[NUM_SLIDERS] = {
{ 11, 9, 6, 7, 19, 20, 9, 10, 10}, // Slider 0
{ 36, 34, 31, 32, 44, 45, 34, 35, 35}, // Slider 1
{ 61, 59, 56, 57, 69, 70, 59, 60, 58}, // Slider 2
{ 86, 84, 81, 82, 94, 95, 84, 85, 85}, // Slider 3
{111, 109, 106, 107, 119, 120, 109, 110, 107} // Slider 4
};
void setup() {
// Configure analog inputs
for (int i = 0; i < NUM_SLIDERS; i++)
pinMode(analogInputs[i], INPUT);
// Initialize buttons with debouncing
for (int i = 0; i < NUM_SLIDERS; i++) {
pinMode(digitalInputs[i], INPUT_PULLUP);
debouncers[i].attach(digitalInputs[i]);
debouncers[i].interval(25); // 25ms debounce interval
}
Serial.begin(9600);
u8g2.begin();
u8g2.clear();
u8g2.setFontMode(1);
u8g2.setBitmapMode(1);
u8g2.setFont(u8g2_font_4x6_tr);
}
void loop() {
updateButtons(); // update all debouncers
updateSliderValues();
// Verifica se houve alterações nos valores dos sliders
bool valuesChanged = false;
for (uint8_t i = 0; i < NUM_SLIDERS; i++) {
if (analogSliderValues[i] != previousAnalogSliderValues[i]) {
valuesChanged = true;
previousAnalogSliderValues[i] = analogSliderValues[i]; // Atualiza valores anteriores
}
}
// Verifica se algum ícone de unmute expirou
bool unmuteIconExpired = false;
unsigned long currentMillis = millis();
for (uint8_t i = 0; i < NUM_SLIDERS; i++) {
// Se o slider está desmutado e o tempo do ícone expirou
if (!muteStates[i] &&
unmuteDisplayStart[i] != 0 &&
(currentMillis - unmuteDisplayStart[i] >= UNMUTE_DISPLAY_DURATION)) {
unmuteIconExpired = true;
unmuteDisplayStart[i] = 0; // Reseta para evitar verificações repetidas
}
}
// Só envia dados se houver alterações, na primeira execução ou quando um ícone expirar
if (valuesChanged || firstRun || unmuteIconExpired) {
printSerialSliderValues();
u8g2.clearBuffer();
sendSliderValues();
u8g2.sendBuffer();
firstRun = false; // Desativa a flag após a primeira execução
}
}
void updateSliderValues() {
const uint8_t SAMPLES = 4; // number of samples for averaging
for (uint8_t i = 0; i < NUM_SLIDERS; i++) {
int32_t sum = 0;
// average multiple readings to reduce noise
for (uint8_t j = 0; j < SAMPLES; j++) {
sum += analogRead(analogInputs[i]);
delayMicroseconds(100); // brief delay between samples
}
analogSliderValues[i] = (sum / SAMPLES) * (muteStates[i] ? 0 : 1);
}
}
void printSerialSliderValues() {
String builtString = String("");
for (int i = 0; i < NUM_SLIDERS; i++) {
builtString += String((int)analogSliderValues[i]);
if (i < NUM_SLIDERS - 1) builtString += String("|");
}
Serial.println(builtString);
}
void sendSliderValues() {
for(uint8_t i = 0; i < NUM_SLIDERS; i++)
drawSliderWithConfig(i, analogSliderValues[i]);
}
void updateButtons() {
for (uint8_t i = 0; i < NUM_SLIDERS; i++) {
debouncers[i].update(); // update the debouncer
// check for falling edge (button pressed)
if (debouncers[i].fell()) {
muteStates[i] = !muteStates[i]; // toggle mute state
// If unmuted, record the time to show the unmute icon
if (!muteStates[i]) {
unmuteDisplayStart[i] = millis();
} else {
// Se mutou, reseta o tempo
unmuteDisplayStart[i] = 0;
}
}
}
}
void drawLevelsIndicators(const uint8_t x1, const uint8_t x2) {
static const uint8_t LEVEL_INDICATOR_MIN = 4;
static const uint8_t LEVEL_INDICATOR_MAX = 46;
for(uint8_t i = LEVEL_INDICATOR_MIN; i <= LEVEL_INDICATOR_MAX; i+=6)
u8g2.drawLine(x1, i, x2, i);
}
void drawSlider(const uint8_t knob_x, const uint8_t volume) {
static const uint8_t SLIDER_KNOB_BOTTOM = 42; // Y do knob em volume 0%
static const uint8_t SLIDER_KNOB_TOP = 5; // Y do knob em volume 100%
static const uint8_t SLIDER_BAR_BOTTOM = 45; // fundo fixo da barra
static const uint8_t SLIDER_BAR_TOP = 10; // topo da barra (volume 100%)
const uint8_t bar_x = knob_x + 4;
const uint8_t vol = (volume > 100) ? 100 : volume;
uint8_t knob_y = SLIDER_KNOB_BOTTOM - (vol * (SLIDER_KNOB_BOTTOM - SLIDER_KNOB_TOP)) / 100;
uint8_t bar_y = knob_y + 5;
if (bar_y > SLIDER_BAR_BOTTOM) bar_y = SLIDER_BAR_BOTTOM;
u8g2.drawRBox(knob_x, knob_y, 9, 4, 1);
u8g2.drawLine(bar_x, bar_y, bar_x, SLIDER_BAR_BOTTOM);
}
// generalized function to draw slider with config
void drawSliderWithConfig(uint8_t sliderIndex, int slider_value) {
if (sliderIndex >= NUM_SLIDERS) return;
const SliderConfig& config = sliderConfigs[sliderIndex];
const uint8_t volume = constrain(map(slider_value, 0, SLIDER_MAX_VALUE, 0, 100), 0, 100);
// draw slider frame
u8g2.drawRFrame(config.frame_x, 3, 5, 45, 2);
// draw slider
drawSlider(config.knob_x, volume);
// determine what to show in the bottom indicator
unsigned long currentMillis = millis();
bool showUnmuteIcon = (!muteStates[sliderIndex] &&
(currentMillis - unmuteDisplayStart[sliderIndex] < UNMUTE_DISPLAY_DURATION));
if (muteStates[sliderIndex]) {
// show muted icon
u8g2.drawXBM(config.muted_icon_x, /*muted_icon_y = */52, 7, 7, volume_muted_icon);
} else if (showUnmuteIcon) {
// show unmuted icon for a time after unmuting
u8g2.drawXBM(config.unmuted_icon_x, /*unmuted_icon_y = */ 53, 8, 6, volume_high_icon);
} else {
// show volume percentage
char buffer[10];
sprintf(buffer, "%d%%", volume);
// calculate text width to center it
uint8_t textWidth = u8g2.getStrWidth(buffer);
uint8_t textX = config.frame_x + 2 - (textWidth / 2); // center on slider frame
u8g2.drawStr(textX, /*bottom_indicator_y = */ 58, buffer);
}
// draw level indicators
drawLevelsIndicators(config.indicator_left_x1, config.indicator_left_x2);
drawLevelsIndicators(config.indicator_right_x1, config.indicator_right_x2);
}