#include #include #define PIN_SW D1 #define PIN_DT D2 #define PIN_CLK D3 #define I2C_SDA 5 #define I2C_SCL 6 #define LCD_COLS 16 #define LCD_ROWS 2 static const uint8_t LCD_ADDR_CANDIDATES[] = {0x27, 0x3F}; LiquidCrystal_I2C *lcd = nullptr; uint8_t lcdAddr = 0; enum Page : uint8_t { PAGE_SYSTEM = 0, PAGE_INPUT, PAGE_OUTPUT, PAGE_COMMS }; const char *PAGE_NAMES[] = { "W8 SYSTEM", "W9 INPUT", "W10 OUTPUT", "W11 COMMUNICATIONS" }; const uint8_t PAGE_COUNT = 4; Page page = PAGE_SYSTEM; int val = 0; int encoderSteps = 0; uint32_t serialRxCount = 0; uint32_t serialTxCount = 0; uint8_t lastClk = HIGH; bool lastButtonReading = HIGH; bool stableButtonState = HIGH; unsigned long lastButtonChangeMs = 0; unsigned long buttonPressStartMs = 0; bool pageSwitchHandled = false; const unsigned long BUTTON_DEBOUNCE_MS = 35; const unsigned long PAGE_SWITCH_MS = 2000; const unsigned long SERIAL_LOG_MS = 500; const unsigned long DISPLAY_REFRESH_MS = 120; unsigned long lastSerialLogMs = 0; unsigned long lastDisplayMs = 0; bool displayDirty = true; Page lastDrawnPage = PAGE_SYSTEM; int lastDrawnVal = -9999; int lastDrawnEncoderSteps = -9999; int lastDrawnClk = -1; int lastDrawnDt = -1; int lastDrawnSw = -1; uint32_t lastDrawnRxCount = 0xffffffff; uint32_t lastDrawnTxCount = 0xffffffff; bool i2cDevicePresent(uint8_t addr); void initLCD(); void printStartupInfo(); void processEncoder(); void applyEncoderStep(int8_t step); void processButton(); void switchPage(); void processSerial(); void handleCommand(String command); void sendResponse(const String &response); void printStatusFrame(); void updateDisplay(bool force); void printSerialStatus(); const char *pageName(Page currentPage); void setup() { Serial.begin(115200); delay(500); pinMode(PIN_SW, INPUT_PULLUP); pinMode(PIN_DT, INPUT_PULLUP); pinMode(PIN_CLK, INPUT_PULLUP); lastClk = digitalRead(PIN_CLK); Wire.begin(I2C_SDA, I2C_SCL); Wire.setClock(100000); delay(100); initLCD(); printStartupInfo(); updateDisplay(true); printSerialStatus(); } void loop() { for (int i = 0; i < 64; i++) { processEncoder(); } processButton(); processSerial(); unsigned long now = millis(); if (displayDirty || now - lastDisplayMs >= DISPLAY_REFRESH_MS) { lastDisplayMs = now; updateDisplay(false); } if (now - lastSerialLogMs >= SERIAL_LOG_MS) { lastSerialLogMs = now; printSerialStatus(); } } bool i2cDevicePresent(uint8_t addr) { Wire.beginTransmission(addr); return Wire.endTransmission() == 0; } void initLCD() { for (uint8_t i = 0; i < 2; i++) { uint8_t addr = LCD_ADDR_CANDIDATES[i]; if (!i2cDevicePresent(addr)) { continue; } lcd = new LiquidCrystal_I2C(addr, LCD_COLS, LCD_ROWS); lcd->begin(LCD_COLS, LCD_ROWS); lcd->backlight(); lcd->display(); lcdAddr = addr; return; } } void printStartupInfo() { Serial.println(); Serial.println(F("=== pcb_testing Fab W8-W11 ===")); Serial.println(F("Board : Seeed XIAO ESP32-S3")); Serial.println(F("Input : KY-040 SW=D1 DT=D2 CLK=D3")); Serial.println(F("Output: I2C LCD SDA=D4 SCL=D5")); Serial.println(F("Use : hold SW 2s to change page")); Serial.println(F("Serial protocol: newline-terminated ASCII commands")); Serial.println(F("Commands: VAL n, READ")); Serial.print(F("LCD : ")); if (lcd != nullptr) { Serial.print(F("0x")); Serial.println(lcdAddr, HEX); } else { Serial.println(F("not found")); } Serial.println(F("--------------------------------")); } void processEncoder() { uint8_t clk = digitalRead(PIN_CLK); if (clk == lastClk) { return; } uint8_t dt = digitalRead(PIN_DT); int8_t step = (dt != clk) ? 1 : -1; lastClk = clk; applyEncoderStep(step); } void applyEncoderStep(int8_t step) { encoderSteps += step; if (page == PAGE_OUTPUT) { val += step; if (val < 0) val = 0; if (val > 999) val = 999; } displayDirty = true; } void processButton() { bool reading = digitalRead(PIN_SW); if (reading != lastButtonReading) { lastButtonChangeMs = millis(); lastButtonReading = reading; } if (millis() - lastButtonChangeMs <= BUTTON_DEBOUNCE_MS) { return; } if (reading != stableButtonState) { stableButtonState = reading; if (stableButtonState == LOW) { buttonPressStartMs = millis(); pageSwitchHandled = false; } } if (stableButtonState == LOW && !pageSwitchHandled) { if (millis() - buttonPressStartMs >= PAGE_SWITCH_MS) { pageSwitchHandled = true; switchPage(); } } } void switchPage() { page = static_cast((static_cast(page) + 1) % PAGE_COUNT); Serial.print(F("EVENT,page=")); Serial.println(pageName(page)); displayDirty = true; } void processSerial() { static String line; while (Serial.available() > 0) { char c = static_cast(Serial.read()); if (c == '\r' || c == '\n') { if (line.length() > 0) { handleCommand(line); line = ""; } } else if (line.length() < 48) { line += c; } } } void handleCommand(String command) { command.trim(); command.toUpperCase(); serialRxCount++; if (command == "READ") { printStatusFrame(); } else if (command.startsWith("VAL ")) { int value = command.substring(4).toInt(); if (value < 0) value = 0; if (value > 999) value = 999; val = value; sendResponse(String(F("OK,VAL=")) + val); } else if (command.length() == 0) { sendResponse(F("ERR,EMPTY")); } else { sendResponse(F("ERR,USE VAL n OR READ")); } displayDirty = true; } void sendResponse(const String &response) { serialTxCount++; Serial.println(response); } void printStatusFrame() { serialTxCount++; Serial.print(F("READ,page=")); Serial.print(pageName(page)); Serial.print(F(",val=")); Serial.print(val); Serial.print(F(",steps=")); Serial.print(encoderSteps); Serial.print(F(",clk=")); Serial.print(digitalRead(PIN_CLK)); Serial.print(F(",dt=")); Serial.print(digitalRead(PIN_DT)); Serial.print(F(",sw=")); Serial.print(digitalRead(PIN_SW) == LOW ? 0 : 1); Serial.print(F(",rx=")); Serial.print(serialRxCount); Serial.print(F(",tx=")); Serial.print(serialTxCount); Serial.print(F(",lcd=0x")); Serial.println(lcdAddr, HEX); } void updateDisplay(bool force) { if (lcd == nullptr) { displayDirty = false; return; } int clk = digitalRead(PIN_CLK); int dt = digitalRead(PIN_DT); int sw = digitalRead(PIN_SW) == LOW ? 0 : 1; bool changed = page != lastDrawnPage || val != lastDrawnVal || encoderSteps != lastDrawnEncoderSteps || clk != lastDrawnClk || dt != lastDrawnDt || sw != lastDrawnSw || serialRxCount != lastDrawnRxCount || serialTxCount != lastDrawnTxCount; if (!force && !displayDirty && !changed) { return; } lastDrawnPage = page; lastDrawnVal = val; lastDrawnEncoderSteps = encoderSteps; lastDrawnClk = clk; lastDrawnDt = dt; lastDrawnSw = sw; lastDrawnRxCount = serialRxCount; lastDrawnTxCount = serialTxCount; displayDirty = false; lcd->clear(); lcd->setCursor(0, 0); lcd->print(pageName(page)); lcd->setCursor(0, 1); switch (page) { case PAGE_SYSTEM: lcd->print(F("t")); lcd->print(millis() / 1000); lcd->print(F("s I2C:")); lcd->print(lcdAddr, HEX); break; case PAGE_INPUT: lcd->print(F("C")); lcd->print(clk); lcd->print(F(" D")); lcd->print(dt); lcd->print(F(" S")); lcd->print(sw == 0 ? F("DN ") : F("UP ")); lcd->print(F("d")); lcd->print(encoderSteps); break; case PAGE_OUTPUT: lcd->print(F("Val:")); lcd->print(val); break; case PAGE_COMMS: lcd->print(F("Val:")); lcd->print(val); lcd->print(F(" R")); lcd->print(serialRxCount); lcd->print(F(" T")); lcd->print(serialTxCount); break; } } void printSerialStatus() { Serial.print(F("DATA,page=")); Serial.print(pageName(page)); Serial.print(F(",val=")); Serial.print(val); Serial.print(F(",steps=")); Serial.print(encoderSteps); Serial.print(F(",clk=")); Serial.print(digitalRead(PIN_CLK)); Serial.print(F(",dt=")); Serial.print(digitalRead(PIN_DT)); Serial.print(F(",sw=")); Serial.print(digitalRead(PIN_SW) == LOW ? 0 : 1); Serial.print(F(",rx=")); Serial.print(serialRxCount); Serial.print(F(",tx=")); Serial.print(serialTxCount); Serial.print(F(",lcd=0x")); Serial.println(lcdAddr, HEX); } const char *pageName(Page currentPage) { return PAGE_NAMES[static_cast(currentPage)]; }