Code:
/*
* Made by KinCony IoT: https://www.kincony.com
* CO16 VOLTAGE SHOW v2.0 - live 0..10V visuals for YouTube Shorts.
* Screen driver copied unchanged from the user's working Cyber Show.
* Libraries: Adafruit GFX + Adafruit BusIO. No ADS1115 library required.
* Actual voltage drives all meters. Animation easing is visual only.
* Serial: 1=digital, 2=ring, a=auto scenes, f=next focus, m=auto focus.
*/
#include <Arduino.h>
#include <Wire.h>
#include <math.h>
#include "CO16_Display.h"
constexpr float VOLTAGE_MULTIPLIER = 5.16696f;
constexpr float ADC_ZERO_THRESHOLD = 0.0025f;
constexpr uint8_t ADC_ADDR[4] = {0x48,0x49,0x4B,0x4A};
constexpr uint32_t FRAME_MS = 70, SCENE_MS = 12000;
constexpr uint16_t BG=0x0001, GRID=0x10C4, DIM=0x5B8E, WHITE=0xEFFF;
constexpr uint16_t CYAN=0x07FF, PINK=0xF81F, RED=0xFA08;
constexpr float PI_F=3.14159265f;
Co16St7789 display;
GFXcanvas16 canvas(320,240);
float voltage[16]={}, eased[16]={}, peak[16]={}, flash[16]={};
bool valid[16]={}, attempted[16]={}, pending[4]={};
uint32_t peakAt[16]={};
uint8_t mux=0, focus=0, scene=0;
int8_t forcedScene=-1;
bool autoFocus=true, settling=false;
uint32_t adcStarted=0, lastFrame=0, lastTrace=0, sweeps=0;
float history[120]={};
bool historyOK[120]={};
uint8_t historyHead=0;
bool writeADCRegister(uint8_t addr, uint8_t reg, uint16_t value) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(uint8_t(value >> 8));
Wire.write(uint8_t(value));
return Wire.endTransmission(true) == 0;
}
bool readADCRegister(uint8_t addr, uint8_t reg, uint16_t &value) {
Wire.beginTransmission(addr);
Wire.write(reg);
// STOP between pointer write and data read, matching the working library.
if (Wire.endTransmission(true) != 0) return false;
if (Wire.requestFrom(addr, uint8_t(2)) != 2) return false;
int highByte = Wire.read(), lowByte = Wire.read();
if (highByte < 0 || lowByte < 0) return false;
value = (uint16_t(highByte) << 8) | uint16_t(lowByte);
return true;
}
uint16_t desiredConfig(uint8_t index) {
// MUX=AIN0..3 to GND; PGA=+/-4.096V; MODE=continuous;
// 128 samples/s; comparator disabled. CH1 lower 15 bits = 0x4283.
return 0x8000 | (uint16_t(4 + index % 4) << 12) | 0x0283;
}
// Same conversion mode, gain, settle time and checked I2C reads as v1.3.
// Four independent chips convert together; each chip still samples one pin.
void serviceADC() {
if (!settling) {
for (uint8_t b=0;b<4;++b) {
pending[b]=writeADCRegister(ADC_ADDR[b],1,desiredConfig(mux));
if (!pending[b]) { attempted[b*4+mux]=true; valid[b*4+mux]=false; }
}
adcStarted=millis(); settling=true; return;
}
if (millis()-adcStarted<30) return;
for (uint8_t b=0;b<4;++b) {
uint8_t i=b*4+mux;
if (!pending[b]) continue;
uint16_t cfg=0,raw=0;
bool ok=readADCRegister(ADC_ADDR[b],1,cfg)
&& ((cfg&0x7FFF)==(desiredConfig(mux)&0x7FFF))
&& readADCRegister(ADC_ADDR[b],0,raw);
attempted[i]=true;
if (ok) {
float pinV=int16_t(raw)*0.000125f;
float v=pinV<=ADC_ZERO_THRESHOLD?0.0f:pinV*VOLTAGE_MULTIPLIER;
if (valid[i]) flash[i]=fminf(1.0f,flash[i]+fabsf(v-voltage[i])/1.5f);
voltage[i]=v;
}
valid[i]=ok;
}
mux=(mux+1)&3;
if (!mux) ++sweeps;
settling=false;
}
float ratio(float v) { return fminf(1.0f,fmaxf(0.0f,v/10.0f)); }
uint16_t rgb(int r,int g,int b) { return ((r&248)<<8)|((g&252)<<3)|(b>>3); }
uint16_t dimColor(uint16_t c,float f) {
return rgb(int(((c>>11)&31)*255/31*f),int(((c>>5)&63)*255/63*f),int((c&31)*255/31*f));
}
uint16_t voltageColor(float v) {
float t=ratio(v);
if (t<0.4f) return rgb(0,190+int(t*150),255-int(t*400));
if (t<0.75f) return rgb(int((t-.4f)/.35f*255),250,70);
return rgb(255,220-int((t-.75f)*680),80+int((t-.75f)*600));
}
void textAt(int x,int y,uint8_t size,uint16_t color,const char *text) {
canvas.setCursor(x,y); canvas.setTextSize(size); canvas.setTextColor(color); canvas.print(text);
}
void focusChanged() {
for(int i=0;i<120;++i) historyOK[i]=false;
historyHead=0; lastTrace=millis();
}
void controls() {
while(Serial.available()) {
char c=Serial.read();
if(c=='1'||c=='2') forcedScene=c-'1';
if(c=='a') forcedScene=-1;
if(c=='f') { autoFocus=false; focus=(focus+1)%16; focusChanged(); }
if(c=='m') autoFocus=true;
}
}
void updateAnimation(uint32_t now,float dt) {
if(autoFocus) {
uint8_t candidate=focus;
for(uint8_t i=0;i<16;++i)
if(valid[i] && (!valid[candidate] || voltage[i]>voltage[candidate]+0.25f)) candidate=i;
if(candidate!=focus) { focus=candidate; focusChanged(); }
}
float alpha=1.0f-expf(-dt/0.18f);
for(uint8_t i=0;i<16;++i) {
eased[i]+=((valid[i]?voltage[i]:0)-eased[i])*alpha;
flash[i]=fmaxf(0,flash[i]-dt*1.8f);
if(valid[i] && voltage[i]>=peak[i]) { peak[i]=voltage[i]; peakAt[i]=now; }
else if(now-peakAt[i]>900) peak[i]=fmaxf(eased[i],peak[i]-dt*2.0f);
}
// Fixed cadence, sample-and-hold recent real readings. Not an oscilloscope.
if(now-lastTrace>=100) {
lastTrace=now;
history[historyHead]=voltage[focus]; historyOK[historyHead]=valid[focus];
historyHead=(historyHead+1)%120;
}
scene=forcedScene<0 ? (now/SCENE_MS)%2 : forcedScene;
}
void drawHeader(uint32_t now) {
textAt(7,5,2,WHITE,"KinCony"); textAt(202,7,1,CYAN,"CO16 / LIVE");
canvas.fillCircle(307,10,3,(now/450)%2?CYAN:GRID);
canvas.drawFastHLine(7,25,306,GRID);
int x=7+(now/8)%278;
canvas.drawFastHLine(x,25,28,PINK);
}
void drawTrend() {
canvas.drawRoundRect(165,36,148,92,4,GRID);
textAt(174,43,1,DIM,"LIVE TREND"); textAt(288,43,1,DIM,"10V");
for(int y=61;y<=117;y+=14) canvas.drawFastHLine(173,y,132,GRID);
for(int x=173;x<=305;x+=22) canvas.drawFastVLine(x,61,57,GRID);
uint16_t color=voltageColor(voltage[focus]);
for(int i=1;i<120;++i) {
int a=(historyHead+i-1)%120,b=(historyHead+i)%120;
if(!historyOK[a]||!historyOK[b]) continue;
int x0=174+(i-1)*130/119,x1=174+i*130/119;
int y0=117-int(ratio(history[a])*56),y1=117-int(ratio(history[b])*56);
canvas.drawLine(x0,y0+1,x1,y1+1,dimColor(color,.3f));
canvas.drawLine(x0,y0,x1,y1,color);
}
if(valid[focus]) canvas.fillCircle(304,117-int(ratio(voltage[focus])*56),2,WHITE);
}
void drawHero(uint32_t now) {
char label[32],number[16];
snprintf(label,sizeof(label),"CH%02u %s",unsigned(focus+1),autoFocus?"AUTO":"LOCK");
snprintf(number,sizeof(number),"%.2f",double(voltage[focus]));
uint16_t color=valid[focus]?voltageColor(voltage[focus]):DIM;
if(scene==0) {
textAt(8,37,1,color,label);
textAt(8,58,4,valid[focus]?WHITE:DIM,valid[focus]?number:"--.--");
textAt(12,97,1,color,"VOLTS / 0-10V INPUT");
canvas.drawRoundRect(8,114,146,12,3,GRID);
int w=int(ratio(eased[focus])*140);
if(valid[focus]&&w) canvas.fillRoundRect(11,117,w,6,2,color);
} else {
textAt(8,33,1,color,label);
const int cx=79,cy=91,r=43;
for(int n=0;n<=54;++n) {
float a=(135+n*5)*PI_F/180;
uint16_t c=valid[focus] && n<=ratio(eased[focus])*54?color:GRID;
canvas.drawLine(cx+int(cosf(a)*r),cy+int(sinf(a)*r),
cx+int(cosf(a)*(r-7)),cy+int(sinf(a)*(r-7)),c);
}
int width=int(strlen(valid[focus]?number:"--.--"))*18;
textAt(cx-width/2,78,3,WHITE,valid[focus]?number:"--.--");
textAt(64,105,1,color,"VOLTS");
if(valid[focus]) {
float a=(135+ratio(eased[focus])*270)*PI_F/180;
canvas.fillCircle(cx+int(cosf(a)*43),cy+int(sinf(a)*43),3,WHITE);
}
}
if(!valid[focus]) textAt(174,121,1,RED,attempted[focus]?"ADC ERROR":"WAITING");
else if(voltage[focus]>10) textAt(174,121,1,RED,"OVER RANGE");
// Small decorative orbit; speed responds to the measured input.
float angle=now*.001f*(1.0f+ratio(eased[focus])*3);
if(valid[focus]) canvas.fillCircle(147+int(5*cosf(angle)),43+int(5*sinf(angle)),1,color);
}
void drawMeters(uint32_t now) {
textAt(8,140,1,DIM,"16 CHANNELS"); textAt(213,140,1,DIM,"LEVEL / 0-10V");
for(int line=0;line<4;++line) canvas.drawFastHLine(8,157+line*17,304,GRID);
for(uint8_t i=0;i<16;++i) {
int x=8+i*19;
float t=ratio(eased[i]);
uint16_t color=voltageColor(eased[i]);
if(i==focus) canvas.drawRoundRect(x-1,152,18,73,2,dimColor(color,.55f));
if(!valid[i]) {
textAt(x+2,180,1,attempted[i]?RED:DIM,attempted[i]?"!":"-");
} else {
int segments=int(t*12+.5f);
for(int j=0;j<12;++j) {
int y=208-j*4;
uint16_t c=j<segments?voltageColor((j+1)*10.0f/12):GRID;
canvas.fillRect(x+2,y,12,3,c);
if(j<segments) canvas.drawPixel(x+14,y,dimColor(c,.4f));
}
int py=210-int(ratio(peak[i])*48);
canvas.drawFastHLine(x+1,py,14,WHITE);
if(flash[i]>.05f) {
// Sparks occur only after a measured change; no fabricated readings.
int offset=int((1-flash[i])*15);
canvas.drawPixel(x+3,py-offset,dimColor(color,flash[i]));
canvas.drawPixel(x+12,py-offset-3,dimColor(WHITE,flash[i]));
}
if(voltage[i]>10) textAt(x+5,155,1,RED,"!");
}
char id[4]; snprintf(id,sizeof(id),"%02u",unsigned(i+1));
textAt(x+2,215,1,i==focus?WHITE:DIM,id);
}
canvas.drawFastHLine(8,229,304,GRID);
uint8_t ok=0; for(uint8_t i=0;i<16;++i) if(valid[i])++ok;
char status[32]; snprintf(status,sizeof(status),"LIVE %02u/16",unsigned(ok));
textAt(8,232,1,ok==16?CYAN:RED,status);
textAt(104,232,1,DIM,scene==0?"VOLTAGE MATRIX":"ENERGY DIAL");
textAt(244,232,1,PINK,"kincony.com");
}
void setup() {
Serial.begin(115200);
const uint8_t inactiveCS[]={9,13,14,42};
for(uint8_t pin:inactiveCS) { digitalWrite(pin,HIGH); pinMode(pin,OUTPUT); }
delay(400);
if(!display.begin() || !canvas.getBuffer()) {
while(true) { Serial.println("Display initialization or canvas allocation failed"); delay(1000); }
}
canvas.setTextWrap(false);
canvas.fillScreen(BG);
textAt(45,72,3,CYAN,"KinCony");
textAt(48,116,2,WHITE,"VOLTAGE SHOW");
textAt(72,148,1,PINK,"REAL INPUT. REAL MOTION.");
display.present(canvas.getBuffer());
Wire.begin(8,18); Wire.setClock(100000); Wire.setTimeOut(20);
// Warm up actual ADC readings while the title is visible.
uint32_t start=millis();
while(millis()-start<900) { serviceADC(); delay(1); }
Serial.println("CO16 VOLTAGE SHOW v2.0 | 1=digital 2=ring a=auto f=next-channel m=auto-focus");
lastFrame=millis();
}
void loop() {
controls(); serviceADC();
uint32_t now=millis();
if(now-lastFrame>=FRAME_MS) {
float dt=fminf(.2f,(now-lastFrame)/1000.0f); lastFrame=now;
updateAnimation(now,dt);
canvas.fillScreen(BG);
drawHeader(now); drawTrend(); drawHero(now); serviceADC();
drawMeters(now);
if(!display.present(canvas.getBuffer())) Serial.println("Display transfer failed");
serviceADC();
}
delay(1);
}
14-CO16_Voltage_Show.zip (Size: 4.33 MB / Downloads: 3)
BIN file (you can use esp32 download tool download to ESP32-S3 with address 0x0 then directly to use) download:
14-CO16_Voltage_Show.ino.merged.zip (Size: 219.48 KB / Downloads: 3)
YouTube: https://www.youtube.com/c/KinCony
Online Store: https://shop.kincony.com
Alibaba Store: https://kincony.en.alibaba.com/
Online Store: https://shop.kincony.com
Alibaba Store: https://kincony.en.alibaba.com/

