OLED Design System

SIDKIT OLED Pages • 128x64 Mono + 256x128 4-bit Greyscale

128x64 Mono:
256x128 Grey:

SYNTH PAGE

Full OSC page layout with all controls

Verbatim testFinalSynthPage() - uses setFont() and baseline Y positioning

SynthPage.ino
/**
 * SIDKIT Synth Page - Standalone OLED Test
 * 128x64 SSD1309 via HW SPI on Teensy 4.1
 */

#include <U8g2lib.h>
#include <SPI.h>

#define OLED_CS   36
#define OLED_DC   37
#define OLED_RST  5

U8G2_SSD1309_128X64_NONAME0_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RST);

// Current selection (linked to buttons)
int currentSID = 2;   // 1-4 (SID1-SID4 buttons)
int currentOSC = 1;   // 1-3 (OSC1-OSC3 buttons)

float wavePhase = 0.0f;

void setup() {
    pinMode(OLED_RST, OUTPUT);
    digitalWrite(OLED_RST, LOW);
    delay(50);
    digitalWrite(OLED_RST, HIGH);
    delay(50);
    u8g2.begin();
    u8g2.setContrast(255);
}

void loop() {
    wavePhase += 0.05f;
    if (wavePhase > 2.0f * PI) wavePhase -= 2.0f * PI;

    u8g2.clearBuffer();
    testFinalSynthPage();
    u8g2.sendBuffer();
    delay(16);
}

// ============================================================================
// DRAWING FUNCTIONS
// ============================================================================

void drawDottedRect(int x, int y, int w, int h, int spacing) {
    for (int i = 0; i <= w; i += spacing) {
        u8g2.drawPixel(x + i, y + h);
    }
    for (int j = 0; j <= h; j += spacing) {
        u8g2.drawPixel(x, y + j);
        u8g2.drawPixel(x + w, y + j);
    }
}

void drawWaveCell(int col, int waveIndex) {
    int cx = col * 32 + 16;
    int yOffset = 17;

    u8g2.setFont(u8g2_font_04b_03b_tr);
    const char* label = "WAVE";
    int lblW = u8g2.getStrWidth(label);
    u8g2.drawStr(cx - lblW / 2, yOffset + 3, label);

    int waveY = yOffset + 5;
    int waveW = 26;
    int waveH = 11;
    int waveX = cx - waveW / 2;

    float phase = wavePhase;  // Global animated phase
    int prevY = waveY + waveH / 2;
    float prevPhase = 0;

    for (int x = 0; x <= waveW; x++) {
        float p = fmod((float)x / waveW * 2.0f * PI + phase, 2.0f * PI);
        int y = waveY + waveH / 2;

        switch (waveIndex) {
            case 0: y = waveY + waveH / 2; break;
            case 1:
                if (p < PI) y = waveY + waveH - (int)((p / PI) * waveH);
                else y = waveY + (int)(((p - PI) / PI) * waveH);
                break;
            case 2:
                y = waveY + (int)((p / (2.0f * PI)) * waveH);
                if (x > 0 && p < prevPhase) u8g2.drawVLine(waveX + x, waveY, waveH + 1);
                break;
            case 3:
                {
                    bool high = (p < PI);
                    y = high ? waveY : waveY + waveH;
                    if (x > 0 && (p < PI) != (prevPhase < PI))
                        u8g2.drawVLine(waveX + x, waveY, waveH + 1);
                }
                break;
            case 4:
                y = waveY + random(waveH + 1);
                break;
        }
        u8g2.drawPixel(waveX + x, y);
        if (x > 0 && (waveIndex == 1 || waveIndex == 2) && !(waveIndex == 2 && p < prevPhase)) {
            if (abs(y - prevY) > 1) u8g2.drawLine(waveX + x - 1, prevY, waveX + x, y);
        }
        prevY = y;
        prevPhase = p;
    }
}

void drawSwitchCell(int col, bool state, const char* label) {
    int cx = col * 32 + 16;
    int yOffset = 17;

    u8g2.setFont(u8g2_font_04b_03b_tr);
    int lblW = u8g2.getStrWidth(label);
    u8g2.drawStr(cx - lblW / 2, yOffset + 3, label);

    int swW = 22;
    int swH = 10;
    int swX = cx - swW / 2;
    int swY = yOffset + 6;

    if (state) {
        u8g2.drawBox(swX, swY, swW, swH);
        u8g2.setDrawColor(0);
        u8g2.setFont(u8g2_font_micro_tr);
        u8g2.drawStr(swX + 7, swY + 8, "ON");
        u8g2.setDrawColor(1);
    } else {
        u8g2.drawFrame(swX, swY, swW, swH);
        u8g2.setFont(u8g2_font_micro_tr);
        u8g2.drawStr(swX + 5, swY + 8, "OFF");
    }
}

void drawPWCell(int col, float pwValue, bool isPulseWave) {
    int cx = col * 32 + 16;
    int yOffset = 17;

    if (!isPulseWave) {
        u8g2.setFont(u8g2_font_04b_03b_tr);
        int w = u8g2.getStrWidth("---");
        u8g2.drawStr(cx - w / 2, yOffset + 10, "---");
        return;
    }

    u8g2.setFont(u8g2_font_04b_03b_tr);
    u8g2.drawStr(cx - u8g2.getStrWidth("PW") / 2, yOffset + 3, "PW");

    u8g2.setFont(u8g2_font_micro_tr);
    int val = (int)(pwValue * 100);
    char buf[8];
    snprintf(buf, sizeof(buf), "%d", val);
    u8g2.drawStr(cx - u8g2.getStrWidth(buf) / 2, yOffset + 10, buf);

    int barX = cx - 12;
    int barY = yOffset + 12;
    u8g2.drawFrame(barX, barY, 25, 4);
    int fill = (int)(pwValue * 23);
    if (fill > 0) u8g2.drawBox(barX + 1, barY + 1, fill, 2);
}

void drawDetuneKnob(int col, int detValue) {
    int cx = col * 32 + 16;
    int yOffset = 44;

    u8g2.setFont(u8g2_font_04b_03b_tr);
    u8g2.drawStr(cx - u8g2.getStrWidth("DETN") / 2, yOffset - 1, "DETN");

    int knobY = yOffset + 9 - 2;
    int radius = 6;
    u8g2.drawCircle(cx, knobY, radius);

    float angle = (detValue / 50.0f) * (PI / 2);
    angle = -PI / 2 + angle;
    int px = cx + (int)(cos(angle) * (radius - 1));
    int py = knobY + (int)(sin(angle) * (radius - 1));
    u8g2.drawLine(cx, knobY, px, py);

    u8g2.setFont(u8g2_font_micro_tr);
    char buf[8];
    if (detValue >= 0) snprintf(buf, sizeof(buf), "+%d", detValue);
    else snprintf(buf, sizeof(buf), "%d", detValue);
    int tw = u8g2.getStrWidth(buf);
    u8g2.drawStr(cx - tw / 2 - 2, yOffset + 20, buf);
}

void drawFMCell(int col, int sourceOsc, int amountPercent) {
    int cx = col * 32 + 16;
    int yOffset = 44;

    u8g2.setFont(u8g2_font_04b_03b_tr);
    const char* label = "FM OSC";
    int lblW = u8g2.getStrWidth(label);
    u8g2.drawStr(cx - lblW / 2, yOffset - 1, label);

    if (sourceOsc == 0) {
        int swW = 22, swH = 10;
        int swX = cx - swW / 2;
        int swY = yOffset + 3;

        u8g2.drawFrame(swX, swY, swW, swH);
        u8g2.setFont(u8g2_font_micro_tr);
        u8g2.drawStr(swX + 5, swY + 8, "OFF");
    } else {
        int btnW = 9, btnH = 10, gap = 1;
        int totalW = 3 * btnW + 2 * gap;
        int startX = cx - totalW / 2;
        int btnY = yOffset + 3;

        for (int i = 0; i < 3; i++) {
            int bx = startX + i * (btnW + gap);
            int oscNum = i + 1;

            if (sourceOsc == oscNum) {
                u8g2.drawBox(bx, btnY, btnW, btnH);
                u8g2.setDrawColor(0);
            } else {
                u8g2.drawFrame(bx, btnY, btnW, btnH);
            }

            char numBuf[2];
            snprintf(numBuf, sizeof(numBuf), "%d", oscNum);
            u8g2.setFont(u8g2_font_4x6_tf);
            u8g2.drawStr(bx + 3, btnY + 8, numBuf);
            u8g2.setDrawColor(1);
        }

        u8g2.setFont(u8g2_font_micro_tr);
        char amtBuf[6];
        snprintf(amtBuf, sizeof(amtBuf), "%d%%", amountPercent);
        int amtW = u8g2.getStrWidth(amtBuf);

        int pctBoxW = amtW + 4;
        int pctBoxH = 7;
        int pctBoxX = cx - pctBoxW / 2;
        int pctBoxY = yOffset + 14;

        u8g2.drawBox(pctBoxX, pctBoxY, pctBoxW, pctBoxH);
        u8g2.setDrawColor(0);
        u8g2.drawStr(cx - amtW / 2, pctBoxY + 6, amtBuf);
        u8g2.setDrawColor(1);
    }
}

void drawVolumeCell(int col, bool enabled, int volumePct) {
    int cx = col * 32 + 16;
    int yOffset = 44;

    u8g2.setFont(u8g2_font_04b_03b_tr);
    u8g2.drawStr(cx - u8g2.getStrWidth("VOL") / 2, yOffset - 1, "VOL");

    int boxW = 22, boxH = 10;
    int boxX = cx - boxW / 2;
    int boxY = yOffset + 3;

    if (!enabled) {
        u8g2.drawFrame(boxX, boxY, boxW, boxH);
        u8g2.setFont(u8g2_font_micro_tr);
        u8g2.drawStr(boxX + 5, boxY + 8, "OFF");
    } else {
        u8g2.drawBox(boxX, boxY, boxW, boxH);
        u8g2.setDrawColor(0);
        u8g2.setFont(u8g2_font_micro_tr);

        char buf[8];
        snprintf(buf, sizeof(buf), "%d%%", volumePct);
        int tw = u8g2.getStrWidth(buf);
        u8g2.drawStr(cx - tw / 2, boxY + 8, buf);
        u8g2.setDrawColor(1);

        if (volumePct > 100) {
            u8g2.drawPixel(boxX + boxW - 3, boxY + 2);
            u8g2.drawPixel(boxX + boxW - 2, boxY + 2);
            u8g2.drawPixel(boxX + boxW - 4, boxY + 2);
            u8g2.drawPixel(boxX + boxW - 3, boxY + 1);
            u8g2.drawPixel(boxX + boxW - 3, boxY + 3);
        }
    }
}

// ============================================================================
// MAIN PAGE FUNCTION
// ============================================================================

void testFinalSynthPage() {
    // === HEADER ===
    u8g2.setFont(u8g2_font_NokiaSmallBold_tf);
    char header[20];
    snprintf(header, sizeof(header), "SYNTH SID%d OSC%d", currentSID, currentOSC);
    u8g2.drawStr(0, 9, header);
    u8g2.setFont(u8g2_font_micro_tr);
    u8g2.drawStr(99, 9, "CPU:25%");
    u8g2.drawHLine(0, 11, 128);

    // Animation values
    int waveIdx = ((int)(millis() / 2000)) % 5;
    bool isPulseWave = (waveIdx == 3);
    bool syncState = ((millis() / 1500) % 2) == 0;
    bool ringState = ((millis() / 1800) % 2) == 0;
    int fmSource = ((int)(millis() / 2500)) % 4;
    int fmAmountPct = (fmSource == 0) ? 0 : ((int)(millis() / 40) % 101);
    int detValue = (int)(sin(millis() / 1000.0f) * 50);
    int volCycle = ((int)(millis() / 1500)) % 5;
    bool volEnabled = (volCycle > 0);
    int volPct = 0;
    if (volCycle == 1) volPct = 0;
    else if (volCycle == 2) volPct = 50;
    else if (volCycle == 3) volPct = 100;
    else if (volCycle == 4) volPct = 110;

    // === TOP ROW: FREQ / WAVE / PW / SYNC ===
    {
        int col = 0, cx = col * 32 + 16, yOffset = 17;
        u8g2.setFont(u8g2_font_04b_03b_tr);
        u8g2.drawStr(cx - u8g2.getStrWidth("FREQ") / 2, yOffset + 3, "FREQ");
        u8g2.setFont(u8g2_font_micro_tr);
        float freqVal = 0.5f + 0.4f * sin(millis() / 800.0f);
        int val = (int)(freqVal * 100);
        char buf[8];
        snprintf(buf, sizeof(buf), "%d", val);
        u8g2.drawStr(cx - u8g2.getStrWidth(buf) / 2, yOffset + 10, buf);
        int barX = cx - 12, barY = yOffset + 12;
        u8g2.drawFrame(barX, barY, 25, 4);
        int fill = (int)(freqVal * 23);
        if (fill > 0) u8g2.drawBox(barX + 1, barY + 1, fill, 2);
    }
    drawWaveCell(1, waveIdx);
    drawPWCell(2, 0.5f + 0.3f * sin(millis() / 600.0f), isPulseWave);
    drawSwitchCell(3, syncState, "SYNC");

    // === BOTTOM ROW: DETN / RING / FM / VOL ===
    drawDetuneKnob(0, detValue);
    {
        int cx = 1 * 32 + 16, yOffset = 44;
        u8g2.setFont(u8g2_font_04b_03b_tr);
        u8g2.drawStr(cx - u8g2.getStrWidth("RING") / 2, yOffset - 1, "RING");
        int swW = 22, swH = 10, swX = cx - swW / 2, swY = yOffset + 3;
        if (ringState) {
            u8g2.drawBox(swX, swY, swW, swH);
            u8g2.setDrawColor(0);
            u8g2.setFont(u8g2_font_micro_tr);
            u8g2.drawStr(swX + 7, swY + 8, "ON");
            u8g2.setDrawColor(1);
        } else {
            u8g2.drawFrame(swX, swY, swW, swH);
            u8g2.setFont(u8g2_font_micro_tr);
            u8g2.drawStr(swX + 5, swY + 8, "OFF");
        }
    }
    drawFMCell(2, fmSource, fmAmountPct);
    drawVolumeCell(3, volEnabled, volPct);

    // === GRID ===
    for (int col = 0; col < 4; col++) {
        int cellX = col * 32;
        drawDottedRect(cellX, 12, 32, 23, 2);
        drawDottedRect(cellX, 36, 32, 28, 2);
    }
}

Display Modes

SSD1309 128x64
Resolution:128x64
Bit Depth:1-bit mono
Interface:I2C/SPI
Library:U8g2
SSD1363 256x128
Resolution:256x128
Bit Depth:4-bit (16 grey)
Interface:SPI
Library:Adafruit GFX
Fonts:u8g2_font_nokiafc22_tr • u8g2_font_micro_tr • u8g2_font_4x6_tr