Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[arduino code examples for CO16]-14 ST7789 TFT LCD voltage show
#1
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);
}
arduino ino file download: 

.zip   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:

.zip   14-CO16_Voltage_Show.ino.merged.zip (Size: 219.48 KB / Downloads: 3)
   
   
Reply


Forum Jump:


Users browsing this thread:
1 Guest(s)