#include <U8g2lib.h>
#include <Encoder.h>
#include <EEPROM.h>
/* ================= HARDWARE ================= */
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2);
Encoder encoder(2, 3);
#define ENC_SW 4
#define BAUDRATE 38400
/* ================= ENCODER FILTER ================= */
#define ENC_DEBOUNCE_US 1200
long encLastRaw = 0;
unsigned long encLastTime = 0;
/* ================= PAGE ANIMATION ================= */
#define SWIPE_STEPS 10
#define FADE_STEPS 6
#define OLED_W 128
bool swipeActive = false;
uint8_t swipeStep = 0;
uint8_t fadeStep = 0;
uint8_t prevPage = 0;
int8_t swipeDir = 1;
/* ================= EEPROM ================= */
#define EEPROM_MAGIC 0x55
#define EEPROM_ADDR 0
struct Settings {
uint8_t magic;
uint8_t vol, sub, pre;
uint8_t bal, fad;
uint8_t tre, mid, bas;
uint8_t inVol;
} cfg;
/* ================= UI STATE ================= */
uint8_t page = 0, item = 0;
bool btnHeld = false;
unsigned long btnTime = 0;
/* ================= MUTE ANIMATION ================= */
bool muteState = false;
bool muteAnimating = false;
bool muteTarget = false;
uint8_t muteStep = 0;
/* ================= ANIMATION BUFFERS ================= */
uint8_t aVol,aSub,aTre,aMid,aBas,aBal,aFad,aIn;
/* ================= EEPROM ================= */
void loadEEPROM() {
EEPROM.get(EEPROM_ADDR, cfg);
if (cfg.magic != EEPROM_MAGIC) {
cfg = {EEPROM_MAGIC,0x08,0x08,0,0x40,0x40,0x08,0x08,0x08,0x00};
EEPROM.put(EEPROM_ADDR, cfg);
}
}
void saveEEPROM() { EEPROM.put(EEPROM_ADDR, cfg); }
/* ================= UART ================= */
void sendFrame(uint8_t *f){ Serial.write(f,8); }
void sendP1(){ uint8_t f[8]={0x08,0x43,cfg.vol,cfg.sub,0xFF,0xFF,cfg.pre,0x00}; sendFrame(f); }
void sendMute(){ uint8_t f[8]={0x08,0x43,0xF0,0xFF,0xFF,0xFF,0xFF,0x00}; sendFrame(f); }
void sendP2(){ uint8_t f[8]={0x08,0x43,0xFF,0xFF,cfg.bal,cfg.fad,0xFF,0x00}; sendFrame(f); }
void sendP3(uint8_t m){
uint8_t f[8]={0x08,0x44,0xFF,0xFF,0xFF,0x00,0x00,0x00};
if(m==0)f[2]=cfg.tre;
if(m==1)f[3]=cfg.mid;
if(m==2)f[4]=cfg.bas;
sendFrame(f);
}
void sendP4(){ uint8_t f[8]={0x08,0x43,cfg.inVol,0x0F,0x40,0x40,0x00,0x01}; sendFrame(f); }
/* ================= HELPERS ================= */
void animate(uint8_t &a,uint8_t t){ if(a<t)a++; else if(a>t)a--; }
void bar(int x,int y,int w,uint8_t v,bool sel){
u8g2.drawFrame(x,y,w,8);
u8g2.drawBox(x+1,y+1,map(v,0,15,0,w-2),6);
if(sel) u8g2.drawFrame(x-2,y-2,w+4,12);
}
void slider(int x,int y,uint8_t v,uint8_t mn,uint8_t mx,bool sel){
u8g2.drawHLine(x,y,108);
int p=map(v,mn,mx,x,x+108);
u8g2.drawDisc(p,y,sel?4:3);
}
/* ================= VECTOR ICONS ================= */
void drawVectorSpeaker(int x,int y){
u8g2.drawTriangle(x,y+4,x+6,y,x+6,y+8);
u8g2.drawBox(x+6,y+2,2,4);
}
void drawVectorMuteX(int x,int y){
u8g2.drawLine(x,y,x+8,y+8);
u8g2.drawLine(x+8,y,x,y+8);
}
void drawSpeakerWaves(uint8_t f){
uint8_t r=f%3;
u8g2.drawCircle(68,14,4+r);
if(r>1) u8g2.drawCircle(68,14,7);
}
/* ================= MUTE CROSSFADE ================= */
void drawMuteCrossfade(bool toMute){
drawVectorSpeaker(56,10);
if(toMute){
if(muteStep>1) drawVectorMuteX(56,10);
} else {
if(muteStep<FADE_STEPS-2) drawVectorMuteX(56,10);
}
}
/* ================= PAGE DRAW ================= */
void drawPage(uint8_t p,int16_t xo){
if(p==0){
u8g2.drawStr(xo,10,"VOL"); bar(xo+30,3,90,15-aVol,item==0);
u8g2.drawStr(xo,30,"SUB"); bar(xo+30,23,90,15-aSub,item==1);
u8g2.drawStr(xo,50,"PRE");
u8g2.drawCircle(xo+60,46,10);
u8g2.drawStr(xo+55,50,cfg.pre==0?"P1":cfg.pre==1?"P2":cfg.pre==2?"P3":"P4");
}
if(p==1){
u8g2.drawStr(xo,15,"BAL"); slider(xo+10,20,aBal,0x30,0x50,item==0);
u8g2.drawStr(xo,45,"FAD"); slider(xo+10,50,aFad,0x30,0x50,item==1);
}
if(p==2){
u8g2.drawStr(xo,15,"TRE"); slider(xo+10,20,aTre,1,15,item==0);
u8g2.drawStr(xo,35,"MID"); slider(xo+10,40,aMid,1,15,item==1);
u8g2.drawStr(xo,55,"BAS"); slider(xo+10,60,aBas,1,15,item==2);
}
if(p==3){
u8g2.drawStr(xo,30,"IN VOL");
bar(xo+30,25,90,240-aIn,true);
}
}
/* ================= UI ================= */
void drawUI(){
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x12_tf);
animate(aVol,cfg.vol); animate(aSub,cfg.sub);
animate(aTre,cfg.tre); animate(aMid,cfg.mid); animate(aBas,cfg.bas);
animate(aBal,cfg.bal); animate(aFad,cfg.fad); animate(aIn,cfg.inVol);
if(muteAnimating){
drawMuteCrossfade(muteTarget);
muteStep++;
if(muteStep>=FADE_STEPS){ muteAnimating=false; muteStep=0; }
u8g2.sendBuffer();
return;
}
if(muteState){
drawVectorSpeaker(56,10);
drawVectorMuteX(56,10);
u8g2.setFont(u8g2_font_logisoso24_tf);
u8g2.drawStr(18,48,"MUTE");
u8g2.sendBuffer();
return;
}
if(swipeActive){
int off=map(swipeStep,0,SWIPE_STEPS,0,OLED_W);
drawPage(prevPage,-off*swipeDir);
drawPage(page,(OLED_W-off)*swipeDir);
swipeStep++;
if(swipeStep>=SWIPE_STEPS) swipeActive=false;
} else {
drawPage(page,0);
}
drawVectorSpeaker(56,10);
drawSpeakerWaves(millis()/120);
u8g2.sendBuffer();
}
/* ================= SETUP ================= */
void setup(){
pinMode(ENC_SW,INPUT_PULLUP);
Serial.begin(BAUDRATE);
u8g2.begin();
loadEEPROM();
}
/* ================= LOOP ================= */
void loop(){
long raw=encoder.read();
unsigned long now=micros();
long delta=raw-encLastRaw;
if(abs(delta)>=4 && now-encLastTime>ENC_DEBOUNCE_US){
int d=(delta>0)?1:-1;
encLastRaw=raw; encLastTime=now;
if(page==0){
if(item==0)cfg.vol=constrain(cfg.vol-d,0,15),sendP1();
if(item==1)cfg.sub=constrain(cfg.sub-d,0,15),sendP1();
if(item==2)cfg.pre=constrain(cfg.pre+d,0,3),sendP1();
}
if(page==1){
if(item==0)cfg.bal=constrain(cfg.bal+d,0x30,0x50),sendP2();
if(item==1)cfg.fad=constrain(cfg.fad+d,0x30,0x50),sendP2();
}
if(page==2){
if(item==0)cfg.tre=constrain(cfg.tre+d,1,15),sendP3(0);
if(item==1)cfg.mid=constrain(cfg.mid+d,1,15),sendP3(1);
if(item==2)cfg.bas=constrain(cfg.bas+d,1,15),sendP3(2);
}
if(page==3){
cfg.inVol=constrain(cfg.inVol+d,0,240);
sendP4();
}
saveEEPROM();
}
if(!digitalRead(ENC_SW)){
if(!btnHeld){ btnHeld=true; btnTime=millis(); }
} else if(btnHeld){
btnHeld=false;
if(millis()-btnTime>1200){
muteTarget=!muteState;
muteAnimating=true;
muteState=muteTarget;
sendMute();
} else {
item++;
if((page==0&&item>2)||(page==1&&item>1)||(page==2&&item>2)||(page==3)){
item=0;
prevPage=page;
page=(page+1)%4;
swipeActive=true;
swipeStep=0;
}
}
}
drawUI();
}
Loading
grove-oled-sh1107
grove-oled-sh1107