lcd_chart.tc¶
lcd_chart.tc — 3-Channel Line Chart Demo for TinyC
// lcd_chart.tc — 3-Channel Line Chart Demo for TinyC
// Draws real-time scrolling line charts on any LCD/TFT display
// Simulates Temperature, Humidity, and Pressure waveforms
// One sample per second, chart fills in ~3 minutes
//
// Drawing strategy:
// Filling phase — only draw the new segment (fast, no flicker)
// Wrapped/full — full redraw (data scrolled, everything shifts)
#define NPTS 200 // data points per channel
#define NCH 3 // number of channels
// RGB565 color palette
#define BLACK 0x0000
#define WHITE 0xFFFF
#define RED 0xF800
#define GREEN 0x07E0
#define BLUE 0x001F
#define CYAN 0x07FF
#define YELLOW 0xFFE0
#define ORANGE 0xFD20
#define DKGRAY 0x4208
#define LTGRAY 0xC618
// --- Data storage (flat ring buffer) ---
// Layout: data[ch * NPTS + idx]
float data[NCH * NPTS];
int pos = 0; // ring buffer write position
int count = 0; // valid entries (saturates at NPTS)
int tick = 0; // simulation time counter
float cur[NCH]; // current values per channel
// --- Display geometry (set in main) ---
int scrW = 0;
int scrH = 0;
int cx = 0;
int cy = 0;
int cw = 0;
int cht = 0; // chart box: x, y, width, height
// --- Channel configuration ---
int ch_clr[NCH]; // RGB565 color per channel
float ch_lo[NCH]; // Y-axis minimum per channel
float ch_hi[NCH]; // Y-axis maximum per channel
// ============================================================
// Coordinate mapping — X always based on NPTS (fixed spacing)
// ============================================================
int mapY(float val, float vmin, float vmax) {
float f = (val - vmin) / (vmax - vmin);
if (f < 0.0) f = 0.0;
if (f > 1.0) f = 1.0;
return cy + cht - (int)(f * (float)cht);
}
int mapX(int i) {
return cx + (i * cw) / (NPTS - 1);
}
// ============================================================
// Drawing — full redraw (used on wrap / initial)
// ============================================================
void draw_grid() {
dspColor(DKGRAY, BLACK);
int i;
for (i = 1; i < 4; i++) {
dspPos(cx + 1, cy + (cht * i) / 4);
dspHLine(cw - 2);
}
int nv = cw / 60;
if (nv < 3) nv = 3;
for (i = 1; i < nv; i++) {
dspPos(cx + (cw * i) / nv, cy + 1);
dspVLine(cht - 2);
}
}
void draw_series(int c) {
if (count < 2) return;
dspColor(ch_clr[c], BLACK);
int base = c * NPTS;
int n = count;
int i;
int idx = pos - n;
if (idx < 0) idx = idx + NPTS;
int py = mapY(data[base + idx], ch_lo[c], ch_hi[c]);
if (py < cy) py = cy;
if (py > cy + cht) py = cy + cht;
dspPos(mapX(0), py);
for (i = 1; i < n; i++) {
idx = pos - n + i;
if (idx < 0) idx = idx + NPTS;
py = mapY(data[base + idx], ch_lo[c], ch_hi[c]);
if (py < cy) py = cy;
if (py > cy + cht) py = cy + cht;
dspLine(mapX(i), py);
}
}
void draw_legend() {
char buf[24];
dspFont(0);
dspSize(1);
int ly = cy + cht + 4;
int sp = cw / NCH;
// Clear legend area
dspColor(BLACK, BLACK);
dspPos(cx, ly);
dspFillRect(cw, 10);
// Ch0 — Temperature (red)
dspColor(RED, BLACK);
dspPos(cx + 2, ly);
dspFillRect(10, 8);
sprintf(buf, " %.1fC", cur[0]);
dspPos(cx + 14, ly);
dspDraw(buf);
// Ch1 — Humidity (cyan)
dspColor(CYAN, BLACK);
dspPos(cx + sp, ly);
dspFillRect(10, 8);
sprintf(buf, " %.0f%%", cur[1]);
dspPos(cx + sp + 12, ly);
dspDraw(buf);
// Ch2 — Pressure (yellow)
dspColor(YELLOW, BLACK);
dspPos(cx + 2 * sp, ly);
dspFillRect(10, 8);
sprintf(buf, " %.0fhPa", cur[2]);
dspPos(cx + 2 * sp + 12, ly);
dspDraw(buf);
}
void draw_header() {
char hdr[32];
dspFont(0);
dspSize(1);
// Clear header area
dspColor(BLACK, BLACK);
dspPos(cx, 0);
dspFillRect(cw, 12);
// Title
dspColor(WHITE, BLACK);
dspPos(cx, 2);
dspDraw("3-Channel Chart");
// Counter
sprintf(hdr, "[%d/%d]", count, NPTS);
dspPos(cx + cw - 48, 2);
dspDraw(hdr);
}
void draw_chart() {
// Full clear + redraw
dspColor(BLACK, BLACK);
dspPos(0, 0);
dspFillRect(scrW, scrH);
draw_header();
draw_grid();
dspColor(LTGRAY, BLACK);
dspPos(cx, cy);
dspRect(cw, cht);
draw_series(2);
draw_series(1);
draw_series(0);
draw_legend();
}
// ============================================================
// Incremental draw — only the new segment (filling phase)
// ============================================================
void draw_segment(int c) {
dspColor(ch_clr[c], BLACK);
int base = c * NPTS;
int n = count;
// Previous point (n-2 in display order)
int idx1 = pos - 2;
if (idx1 < 0) idx1 = idx1 + NPTS;
int py1 = mapY(data[base + idx1], ch_lo[c], ch_hi[c]);
if (py1 < cy) py1 = cy;
if (py1 > cy + cht) py1 = cy + cht;
// New point (n-1 in display order)
int idx2 = pos - 1;
if (idx2 < 0) idx2 = idx2 + NPTS;
int py2 = mapY(data[base + idx2], ch_lo[c], ch_hi[c]);
if (py2 < cy) py2 = cy;
if (py2 > cy + cht) py2 = cy + cht;
dspPos(mapX(n - 2), py1);
dspLine(mapX(n - 1), py2);
}
void draw_incremental() {
// Just add the latest segment for each channel + update text
draw_segment(2);
draw_segment(1);
draw_segment(0);
draw_header();
draw_legend();
}
// ============================================================
// Simulation — 3 waveforms with different characteristics
// ============================================================
void simulate() {
tick++;
float t = (float)tick * 0.05;
// Ch0: Temperature — sine, 15–30 °C
cur[0] = 22.5 + 7.5 * sin(t);
// Ch1: Humidity — cosine (different freq), 30–80 %
cur[1] = 55.0 + 25.0 * cos(t * 0.7);
// Ch2: Pressure — slow drift + random noise, 1000–1020 hPa
cur[2] = 1010.0 + 10.0 * sin(t * 0.3)
+ (float)random(-20, 21) * 0.1;
}
void store_sample() {
int c;
for (c = 0; c < NCH; c++) {
data[c * NPTS + pos] = cur[c];
}
pos++;
if (pos >= NPTS) pos = 0;
if (count < NPTS) count++;
}
// ============================================================
// Callbacks
// ============================================================
void EverySecond() {
simulate();
store_sample();
if (count >= NPTS) {
// Buffer full + wrapping — full redraw (data scrolls)
draw_chart();
} else if (count >= 2) {
// Filling — just add the new segment
draw_incremental();
} else {
// First point — draw initial chart frame
draw_chart();
}
}
int main() {
scrW = dspWidth();
scrH = dspHeight();
if (scrW < 100 || scrH < 64) {
printStr("ERROR: no display found\n");
return 1;
}
// Chart layout — adapts to any display size
cx = 5;
cy = 14;
cw = scrW - 10;
cht = scrH - cy - 16;
// Channel colors
ch_clr[0] = RED;
ch_clr[1] = CYAN;
ch_clr[2] = YELLOW;
// Y-axis ranges
ch_lo[0] = 10.0; ch_hi[0] = 35.0; // Temperature °C
ch_lo[1] = 20.0; ch_hi[1] = 90.0; // Humidity %
ch_lo[2] = 985.0; ch_hi[2] = 1035.0; // Pressure hPa
// Splash screen
dspClear();
dspColor(WHITE, BLACK);
dspFont(0);
dspSize(2);
dspPos(scrW / 2 - 60, scrH / 2 - 16);
dspDraw("Chart Demo");
dspSize(1);
dspPos(scrW / 2 - 55, scrH / 2 + 10);
dspDraw("Starting in 1 sec...");
char buf[64];
sprintf(buf, "Display: %dx%d\n", scrW, scrH);
printString(buf);
sprintf(buf, "Chart area: %dx%d\n", cw, cht);
printString(buf);
return 0;
}