diff --git a/Firmware/factoryTest/FactoryTest_wMenu/FactoryTest_wMenu.ino b/Firmware/factoryTest/FactoryTest_wMenu/FactoryTest_wMenu.ino index 4075cb7..b65f95d 100644 --- a/Firmware/factoryTest/FactoryTest_wMenu/FactoryTest_wMenu.ino +++ b/Firmware/factoryTest/FactoryTest_wMenu/FactoryTest_wMenu.ino @@ -1,4 +1,4 @@ -#define FIRMWARE_VERSION "v0.4.2.7" +#define FIRMWARE_VERSION "v0.4.3.4" /* ------------------------------------------------------------------------------ File: FactoryTest_wMenu.ino @@ -29,7 +29,16 @@ Revision History: |v0.4.2.5 | 2026-1-7 | L. Erickson | Use myDFPlayer.getVersion() | |v0.4.2.6 | 2026-1-8 | Yukti | Fix DFPlayer test to fail cleanly when hardware | | | | | is missing; improve error handling | -|v0.4.2.7 | 2026-2-7 | L. Erickson | bugfix/356-firmware-factory-test-bring-up-add-flow-control-test-for-com-port| | +|v0.4.2.7 | 2026-2-7 | L. Erickson | bugfix/356-firmware-factory-test-bring-up-add- | +| flow-control-test-for-com-port | +|v0.4.3.0 | 2026-2-27 | Yukti | add global 'exit' command to abort any test | +| | | | and return to menu. | +|v0.4.3.1 | 2026-2-28 | Yukti | updated code and firmware ver. based on review | +|v0.4.3.2 | 2026-2-28 | Yukti | changed 'exit' to 'break' | +|v0.4.3.3 | 2026-2-29 | Yukti | added global break support to all tests | +|v0.4.3.4 | 2026-03-01| Yukti | Refactor GLOBAL BREAK handling; remove dynamic | +| | | String usage from interactive paths; add | +| | | cooperative break checks in long-running loops | ----------------------------------------------------------------------------------------| Overview: - Repeatable factory test sequence for ESP32-WROOM-32D Krake/GPAD v2 boards. @@ -56,6 +65,7 @@ Build notes: #include #include #include +#include // ============================================================================ // Configuration @@ -99,8 +109,7 @@ const int SPI_CS_PIN = 5; // ===== TEST PARAMETERS ===== static const uint32_t SPI_SPEED = 1000000; // 1 MHz (safe for factory) static const uint8_t TEST_PATTERN[] = { - 0x55, 0xAA, 0x00, 0xFF, 0x12, 0x34, 0xA5 -}; + 0x55, 0xAA, 0x00, 0xFF, 0x12, 0x34, 0xA5}; // RS-232 (UART1) – IMPORTANT: set these to YOUR PCB pins (via MAX3232) const int UART1_TXD1 = 2; // placeholder safe GPIO @@ -141,18 +150,18 @@ enum TestIndex { }; const char* TEST_NAMES[T_COUNT] = { - "0 Power / ID", - "1 Inputs (Encoder / Button)", - "2 LCD (I2C)", - "3 LEDs / Lamps", - "4 DFPlayer ", - "5 SD (DFPlayer card)", - "6 Speaker", - "7 Wi-Fi AP", - "8 Wi-Fi STA (manual SSID/PASS)", - "A LittleFS R/W", - "B UART0 (USB Serial)", - "C SPI loopback", + "0 Power / ID", + "1 Inputs (Encoder / Button)", + "2 LCD (I2C)", + "3 LEDs / Lamps", + "4 DFPlayer ", + "5 SD (DFPlayer card)", + "6 Speaker", + "7 Wi-Fi AP", + "8 Wi-Fi STA (manual SSID/PASS)", + "A LittleFS R/W", + "B UART0 (USB Serial)", + "C SPI loopback", "D RS-232 loopback" }; @@ -164,7 +173,20 @@ bool testResults[T_COUNT] = { false }; static char g_pendingCmd = 0; -static char up(char c) { +// ----------------------------------------------------------------------------- +// GLOBAL BREAK SUPPORT +// If user types "break" (any case), abort current operation and return to menu. +// ----------------------------------------------------------------------------- +static volatile bool g_globalBreakRequested = false; + +// Case-insensitive check for exact string "break" +static bool isBreakCommand(const char* s) +{ + return (s != nullptr) && (strcasecmp(s, "break") == 0); +} + +static char up(char c) +{ return (char)toupper((unsigned char)c); } @@ -188,6 +210,14 @@ static bool readLineOrMenuAbort(String& out, uint32_t timeoutMs = 15000) { if (c == '\n' || c == '\r') { out.trim(); + // --- GLOBAL BREAK CHECK --- + // If user typed "break", trigger global abort + if (isBreakCommand(out)) + { + g_globalBreakRequested = true; + out = ""; + return false; // abort current prompt + } if (out.length() == 1 && isMenuKey(out[0])) { g_pendingCmd = up(out[0]); @@ -210,26 +240,45 @@ static bool readLineOrMenuAbort(String& out, uint32_t timeoutMs = 15000) { static bool promptYesNo(const __FlashStringHelper* question, uint32_t timeoutMs = PROMPT_TIMEOUT_MS, - bool defaultNo = true) { + bool defaultNo = true) +{ Serial.println(question); Serial.println(F("Press Y to PASS or N to FAIL (Enter optional).")); Serial.println(F("(Menu keys abort this step and run next.)")); Serial.print(F("> ")); uint32_t start = millis(); - String buf; + + char buf[16]; // fixed buffer + uint8_t idx = 0; while (millis() - start < timeoutMs) { + while (Serial.available()) { + char c = Serial.read(); + // --- ENTER pressed --- if (c == '\n' || c == '\r') { - buf.trim(); - if (buf.length() == 0) continue; + + buf[idx] = '\0'; // null terminate + + // GLOBAL BREAK CHECK + if (isBreakCommand(buf)) { + g_globalBreakRequested = true; + Serial.println(F("\nGlobal BREAK requested.")); + return false; + } + + if (idx == 0) { + continue; // ignore empty line + } char k = up(buf[0]); + if (k == 'Y') return true; if (k == 'N') return false; + if (isMenuKey(k)) { g_pendingCmd = k; Serial.println(F("\nAborted -> FAIL.")); @@ -240,12 +289,14 @@ static bool promptYesNo(const __FlashStringHelper* question, return false; } - if (buf.length() == 0 && isMenuKey(c)) { + // Immediate single-key abort (first char only) + if (idx == 0 && isMenuKey(c)) { g_pendingCmd = up(c); Serial.println(F("\nAborted -> FAIL.")); return false; } + // Immediate Y/N without Enter char u = up(c); if (u == 'Y') { Serial.println(F("Y")); @@ -256,12 +307,18 @@ static bool promptYesNo(const __FlashStringHelper* question, return false; } - buf += c; + // Append safely + if (idx < sizeof(buf) - 1) { + buf[idx++] = c; + } } + delay(5); } - Serial.println(defaultNo ? F("\nNo response -> FAIL") : F("\nNo response -> PASS")); + Serial.println(defaultNo ? F("\nNo response -> FAIL") + : F("\nNo response -> PASS")); + return !defaultNo; } @@ -293,16 +350,16 @@ static void printSummary() { // Left column Serial.printf( - "%-33s : %-4s", - TEST_NAMES[i], - testResults[i] ? "PASS" : "FAIL"); + "%-33s : %-4s", + TEST_NAMES[i], + testResults[i] ? "PASS" : "FAIL"); // Right column (if present) if (i + 1 < T_COUNT) { Serial.printf( - " %-33s : %-4s", - TEST_NAMES[i + 1], - testResults[i + 1] ? "PASS" : "FAIL"); + " %-33s : %-4s", + TEST_NAMES[i + 1], + testResults[i + 1] ? "PASS" : "FAIL"); } Serial.println(); @@ -355,6 +412,7 @@ static bool runTest_Inputs() { unsigned long start = millis(); while (millis() - start < 10000UL) { + if (g_globalBreakRequested) return false; int clk = digitalRead(ENC_CLK); if (clk != lastCLK) { if (digitalRead(ENC_DT) != clk) { @@ -504,9 +562,9 @@ static bool runTest_LCD() { // STEP 3: Deterministic pattern test const char* patterns[4] = { - "####################", - "ABCDEFGHIJKLMNOPQRST", - "abcdefghijklmnopqrst", + "####################", + "ABCDEFGHIJKLMNOPQRST", + "abcdefghijklmnopqrst", "12345678901234567890" }; @@ -517,8 +575,8 @@ static bool runTest_LCD() { // STEP 4: Operator optical confirmation bool visible = promptYesNo( - F("Do you see 4 FULL lines, aligned, no garbage characters?"), - PROMPT_TIMEOUT_MS, + F("Do you see 4 FULL lines, aligned, no garbage characters?"), + PROMPT_TIMEOUT_MS, false ); @@ -541,11 +599,11 @@ static bool runTest_LEDs() { const int pins[] = { LAMP1, LAMP2, LAMP3, LAMP4, LAMP5, LED_Status }; const char* names[] = { - "LAMP1", - "LAMP2 (LAMP2)", - "LAMP3 (LAMP3)", - "LAMP4 (LAMP4)", - "LAMP5 (LAMP5)", + "LAMP1", + "LAMP2 (LAMP2)", + "LAMP3 (LAMP3)", + "LAMP4 (LAMP4)", + "LAMP5 (LAMP5)", "LED_Status (LED_Status)" }; @@ -640,7 +698,7 @@ static bool initDFPlayer() { Serial.println(F("DFPlayer detected and responding.")); return true; } - + // Put this OUTSIDE of runTest_DFPlayer() (global scope). // Call it when dfPlayer.available() is true. void printDetail(uint8_t type, int value) { @@ -776,6 +834,10 @@ static bool runTest_Speaker() { uint32_t start = millis(); while (millis() - start < 3000) { + if (g_globalBreakRequested) { + dfPlayer.stop(); + return false; + } delay(10); } @@ -849,6 +911,10 @@ static bool runTest_WifiSTA() { uint32_t start = millis(); while (WiFi.status() != WL_CONNECTED && millis() - start < WIFI_CONNECT_MS) { + if (g_globalBreakRequested) { + WiFi.disconnect(true, true); + return false; + } delay(500); Serial.print('.'); } @@ -961,12 +1027,12 @@ static bool runTest_RS232() { Serial.printf("Using UART1 TXD=%d, RXD=%d, BAUD=%ld\n", UART1_TXD1, UART1_RXD1, UART1_BAUD); //Set up UART1 - + pinMode(UART1_RTS1, OUTPUT); pinMode(UART1_CTS1, INPUT); // NO pull up required because MAX3232 drives. //digitalWrite(UART1_RTS1, HIGH); // will make CTS at DB9 negative voltage through loop back digitalWrite(UART1_RTS1, LOW); // will make CTS at DB9 positive voltage through loop back - + HardwareSerial rs232(1); rs232.begin(UART1_BAUD, SERIAL_8N1, UART1_RXD1, UART1_TXD1); delay(150); @@ -989,6 +1055,10 @@ static bool runTest_RS232() { String rx; uint32_t t0 = millis(); while (millis() - t0 < TIMEOUT_MS && rx.length() < n) { + if (g_globalBreakRequested) { + rs232.end(); + return false; + } while (rs232.available()) { char c = (char)rs232.read(); rx += c; @@ -999,7 +1069,7 @@ static bool runTest_RS232() { // Validate if (HIGH == digitalRead(UART1_CTS1)){ - Serial.print("[rs232] FAIL: FLOW CONTROL. Expected DB9 pin 7 between 3-18V. "); + Serial.print("[rs232] FAIL: FLOW CONTROL. Expected DB9 pin 7 between 3-18V. "); Serial.println("Tip: ensure rs232 RTS<->CTS loopback plug is installed."); return false; } @@ -1027,10 +1097,7 @@ static bool runTest_RS232() { Serial.print(rx); Serial.println("[rs232] PASS: UART1 loopback OK"); return true; - - bool ok = (rx == payload); - Serial.printf("RS-232 loopback test: %s\n", ok ? "PASS" : "FAIL (wiring/MAX3232/pins)"); - return ok; +//removed dead code }//end runTest_RS232() // ============================================================================ @@ -1060,6 +1127,13 @@ static void runAllTests() { Serial.println(F("\n[P] Running ALL tests (1 -> D) in order...")); for (int i = 0; i < T_COUNT; ++i) { + + if (g_globalBreakRequested) { + g_pendingCmd = 0; + Serial.println(F("[Run-All aborted by GLOBAL BREAK]")); + return; + } + if (g_pendingCmd) break; testResults[i] = runSingleTestFromIndex(static_cast(i)); @@ -1126,7 +1200,20 @@ void setup() { } void loop() { - // If a menu key was pressed during a prompt, execute it now. + + // --------------------------------------------------------------------------- + // GLOBAL BREAK HANDLER + // --------------------------------------------------------------------------- + if (g_globalBreakRequested) { + g_globalBreakRequested = false; + Serial.println(F("\n[GLOBAL BREAK] Aborting current operation.")); + printMenu(); + return; + } + + // --------------------------------------------------------------------------- + // Pending menu command from aborted prompt + // --------------------------------------------------------------------------- if (g_pendingCmd) { char c = g_pendingCmd; g_pendingCmd = 0; @@ -1137,11 +1224,40 @@ void loop() { return; } + // --------------------------------------------------------------------------- + // Main menu serial input (fixed buffer, no String) + // --------------------------------------------------------------------------- if (Serial.available()) { + + static char lineBuf[32]; // fixed-size buffer + static uint8_t idx = 0; // current write index + char c = Serial.read(); - if (c == '\r' || c == '\n') return; - Serial.println(up(c)); // echo - handleCommand(c); + // If newline -> process full line + if (c == '\r' || c == '\n') { + + lineBuf[idx] = '\0'; // null-terminate + + // GLOBAL BREAK CHECK + if (isBreakCommand(lineBuf)) { + Serial.println(F("\n[GLOBAL BREAK]")); + printMenu(); + } + // Single-character command + else if (idx == 1) { + char cmd = up(lineBuf[0]); + Serial.println(cmd); + handleCommand(cmd); + } + + idx = 0; // reset buffer + return; + } + + // Append character safely + if (idx < sizeof(lineBuf) - 1) { + lineBuf[idx++] = c; + } } -} +} \ No newline at end of file