diff --git a/software/firmware/board_check/src/board_check.c b/software/firmware/board_check/src/board_check.c index ea2a943..1d0a34e 100644 --- a/software/firmware/board_check/src/board_check.c +++ b/software/firmware/board_check/src/board_check.c @@ -31,7 +31,8 @@ const uint8_t statusLEDs[] = { PIN_INIT, PIN_RDY, PIN_RUN, PIN_IDLE }; const uint8_t cols[] = { PIN_COL_A, PIN_COL_B, PIN_COL_C, PIN_COL_D, PIN_COL_E }; - +const uint8_t inputPins[] = { PIN_INPUT_A, PIN_INPUT_B, PIN_INPUT_C, PIN_INPUT_D }; +const uint8_t inputCols[] = { PIN_COL_A, PIN_COL_B, PIN_COL_C, PIN_COL_D }; // TC1–TC4 typedef enum { MODE_DEMO, MODE_INPUT @@ -62,10 +63,13 @@ void setupPins() { funPinMode(PIN_ROW_G, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); funDigitalWrite(PIN_ROW_R, FUN_HIGH); // Start rows OFF funDigitalWrite(PIN_ROW_G, FUN_HIGH); + + for (int i = 0; i < 4; i++) { + funPinMode(inputPins[i], GPIO_CNF_IN_PUPD); + funDigitalWrite(inputPins[i], FUN_HIGH); // enable pull‑up } -const uint8_t inputPins[] = { PIN_INPUT_A, PIN_INPUT_B, PIN_INPUT_C, PIN_INPUT_D }; -const uint8_t inputCols[] = { PIN_COL_A, PIN_COL_B, PIN_COL_C, PIN_COL_D }; // TC1–TC4 +} bool isAnyButtonPressed() { for (int i = 0; i < 4; i++) { diff --git a/software/firmware/tester_runtime/src/buttons.c b/software/firmware/tester_runtime/src/buttons.c new file mode 100644 index 0000000..c764a15 --- /dev/null +++ b/software/firmware/tester_runtime/src/buttons.c @@ -0,0 +1,47 @@ + + +#include "pins.h" +#include "buttons.h" +#include +#include +#include "ch32fun.h" +#include "hardware.h" +#include "globals.h" +#include "pins.h" + +volatile ButtonState buttons[4]; +static uint8_t lastButtonSample = 0xFF; +static uint32_t initLedOffAt = 0; + +void pollButtons(void) { + uint8_t sample = 0; + for (int i = 0; i < 4; i++) { + if (!gpio_get(BTN[i].port, BTN[i].mask)) { + sample |= (1 << i); + } + } + + uint8_t changed = sample ^ lastButtonSample; + if (changed) { + for (int i = 0; i < 4; i++) { + if ((changed & (1 << i)) && (sample & (1 << i))) { + buttons[i].pressed = true; + buttons[i].justPressed = true; + buttons[i].lastPressTime = msTicks; + buttons[i].pressCount++; + gpio_clear(GPIOC, BIT(6)); + initLedOffAt = msTicks + 50; + } else if (changed & (1 << i)) { + buttons[i].pressed = false; + buttons[i].lastReleaseTime = msTicks; + } + } + } + + if (initLedOffAt && msTicks >= initLedOffAt) { + gpio_set(GPIOC, BIT(6)); + initLedOffAt = 0; + } + + lastButtonSample = sample; +} diff --git a/software/firmware/tester_runtime/src/buttons.h b/software/firmware/tester_runtime/src/buttons.h new file mode 100644 index 0000000..4fa3ca8 --- /dev/null +++ b/software/firmware/tester_runtime/src/buttons.h @@ -0,0 +1,18 @@ +#ifndef BUTTONS_H +#define BUTTONS_H + +#include +#include + +typedef struct { + uint32_t lastPressTime; + uint32_t lastReleaseTime; + uint16_t pressCount; + bool pressed; + bool justPressed; +} ButtonState; + +extern volatile ButtonState buttons[4]; +void pollButtons(void); + +#endif // BUTTONS_H diff --git a/software/firmware/tester_runtime/src/funconfig.h b/software/firmware/tester_runtime/src/funconfig.h index 7ca9cfd..8135df1 100644 --- a/software/firmware/tester_runtime/src/funconfig.h +++ b/software/firmware/tester_runtime/src/funconfig.h @@ -3,6 +3,7 @@ // you can put various configurations in here, you can see a full list in ch32fun.h // part selection is made in the Makefile +#define FUNCONF_ENABLE_HPE 0 #endif diff --git a/software/firmware/tester_runtime/src/globals.h b/software/firmware/tester_runtime/src/globals.h new file mode 100644 index 0000000..14ba8db --- /dev/null +++ b/software/firmware/tester_runtime/src/globals.h @@ -0,0 +1,12 @@ +#ifndef GLOBALS_H +#define GLOBALS_H +#include +#include + +extern volatile uint32_t msTicks; +extern volatile bool flashState; +extern volatile bool rapidFlashState; +extern volatile bool runMode; +extern volatile uint8_t currentCol; + +#endif // GLOBALS_H diff --git a/software/firmware/tester_runtime/src/hardware.c b/software/firmware/tester_runtime/src/hardware.c new file mode 100644 index 0000000..a9318d9 --- /dev/null +++ b/software/firmware/tester_runtime/src/hardware.c @@ -0,0 +1,149 @@ + +#include +#include +#include "ch32fun.h" +#include "pins.h" +#include "hardware.h" +#include "buttons.h" +#include "leds.h" +#include "globals.h" +#include "pins.h" + +// Pin port/mask arrays/structs (define here) +const PinDef COL[5] = { + {GPIOC, 0x01}, {GPIOC, 0x02}, {GPIOC, 0x04}, {GPIOC, 0x08}, {GPIOC, 0x10} +}; +const PinDef ROW_RED = { GPIOA, 0x02 }; +const PinDef ROW_GREEN = { GPIOA, 0x04 }; +const PinDef BTN[4] = { + {GPIOD, 0x04}, {GPIOD, 0x08}, {GPIOD, 0x10}, {GPIOD, 0x20} +}; + +// Global variables (define here) +volatile uint32_t msTicks = 0; +volatile bool flashState = false; +volatile bool rapidFlashState = false; +volatile bool runMode = false; +volatile uint8_t currentCol = 0; + +static const int statusLEDs[] = { PIN_PWR, PIN_INIT, PIN_RDY, PIN_RUN, PIN_IDLE }; +static const int testCols[] = { PIN_COL_A, PIN_COL_B, PIN_COL_C, PIN_COL_D, PIN_COL_E }; +static const int inputPins[] = { PIN_INPUT_A, PIN_INPUT_B, PIN_INPUT_C, PIN_INPUT_D }; + +// Timer ISR for 1ms tick, button polling, and LED scan +void TIM1_UP_IRQHandler(void) INTERRUPT_DECORATOR; +void TIM1_UP_IRQHandler(void) +{ + if (TIM1->INTFR & TIM_UIF) { + TIM1->INTFR &= ~TIM_UIF; + msTicks++; + pollButtons(); + static uint32_t nextFlashAt = 250; + if (msTicks >= nextFlashAt) { + nextFlashAt += 250; + flashState = !flashState; + } + static uint32_t nextRapidFlashAt = 125; + if (msTicks >= nextRapidFlashAt) { + nextRapidFlashAt += 125; + rapidFlashState = !rapidFlashState; + } + scanStep(); + } +} + +void setupPins(void) { + funGpioInitAll(); + RCC->APB2PCENR |= RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD; + for (int i = 0; i < 5; i++) { + funPinMode(statusLEDs[i], GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); + funDigitalWrite(statusLEDs[i], FUN_HIGH); + } + for (int i = 0; i < 5; i++) { + funPinMode(testCols[i], GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); + funDigitalWrite(testCols[i], FUN_LOW); + } + funPinMode(PIN_ROW_R, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); + funPinMode(PIN_ROW_G, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); + funDigitalWrite(PIN_ROW_R, FUN_HIGH); + funDigitalWrite(PIN_ROW_G, FUN_HIGH); + for (int i = 0; i < 4; i++) { + funPinMode(inputPins[i], GPIO_CNF_IN_PUPD); + funDigitalWrite(inputPins[i], FUN_HIGH); + } +} + +void setupStatusFlasher(void) { + RCC->APB2PCENR |= RCC_APB2Periph_TIM1; + RCC->APB2PRSTR |= RCC_APB2Periph_TIM1; + RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1; + TIM1->PSC = 48 - 1; + TIM1->ATRLR = 1000 - 1; + TIM1->SWEVGR |= TIM_UG; + TIM1->INTFR &= ~TIM_UIF; + TIM1->DMAINTENR |= TIM_UIE; + NVIC_EnableIRQ(TIM1_UP_IRQn); + TIM1->CTLR1 |= TIM_CEN; +} + +void runStatus(bool isRun) { + runMode = isRun; + funDigitalWrite(PIN_RUN, FUN_HIGH); + funDigitalWrite(PIN_IDLE, FUN_HIGH); +} + +void waitTicks(uint32_t ticks) { + uint32_t start = msTicks; + while ((uint32_t)(msTicks - start) < ticks) { + __WFI(); + } +} + +void scanStep(void) { + static int8_t prev = -1; + if (activeStates == NULL) { + gpio_set(ROW_RED.port, ROW_RED.mask); + gpio_set(ROW_GREEN.port, ROW_GREEN.mask); + return; + } + gpio_set(ROW_RED.port, ROW_RED.mask); + gpio_set(ROW_GREEN.port, ROW_GREEN.mask); + if (prev >= 0 && prev < 5) { + if (COL[prev].port != NULL) { + gpio_clear(COL[prev].port, COL[prev].mask); + } + } + if (currentCol >= 0 && currentCol < 5) { + TestCaseState state = activeStates[currentCol]; + switch (state) { + case TC_PASS: + gpio_clear(ROW_GREEN.port, ROW_GREEN.mask); + break; + case TC_FAIL: + gpio_clear(ROW_RED.port, ROW_RED.mask); + break; + case TC_IN_PROGRESS: + if (flashState) + gpio_clear(ROW_RED.port, ROW_RED.mask); + else + gpio_clear(ROW_GREEN.port, ROW_GREEN.mask); + break; + case TC_WARNING: + if (flashState) + gpio_clear(ROW_RED.port, ROW_RED.mask); + break; + case TC_RETRY: + if (rapidFlashState) + gpio_clear(ROW_RED.port, ROW_RED.mask); + break; + case TC_NO_RESULT: + default: + break; + } + if (COL[currentCol].port != NULL) { + gpio_set(COL[currentCol].port, COL[currentCol].mask); + } + } + prev = currentCol; + currentCol = (currentCol + 1) % 5; +} diff --git a/software/firmware/tester_runtime/src/hardware.h b/software/firmware/tester_runtime/src/hardware.h new file mode 100644 index 0000000..1d230b8 --- /dev/null +++ b/software/firmware/tester_runtime/src/hardware.h @@ -0,0 +1,15 @@ +#ifndef HARDWARE_H +#define HARDWARE_H + +#include +#include +#include "ch32fun.h" +#include "test_cases.h" + +void setupPins(void); +void setupStatusFlasher(void); +void runStatus(bool isRun); +void waitTicks(uint32_t ticks); +void scanStep(void); + +#endif // HARDWARE_H diff --git a/software/firmware/tester_runtime/src/leds.c b/software/firmware/tester_runtime/src/leds.c new file mode 100644 index 0000000..06c5ab1 --- /dev/null +++ b/software/firmware/tester_runtime/src/leds.c @@ -0,0 +1,66 @@ + + + +#include +#include +#include "ch32fun.h" +#include "hardware.h" +#include "test_cases.h" +#include "globals.h" +#include "pins.h" + +// Double-buffered states (publish by pointer swap) +static TestCaseState bufA[5], bufB[5]; +volatile const TestCaseState* activeStates = bufA; +static uint8_t activeIdx = 0; + + + +void initTestCaseStates(void) { + for (int i = 0; i < 5; i++) { bufA[i] = bufB[i] = TC_NO_RESULT; } + activeStates = bufA; + activeIdx = 0; +} + +void setTestCaseResult(const TestCaseState states[5]) { + TestCaseState* dst = (activeIdx == 0) ? bufB : bufA; + for (int i = 0; i < 5; i++) dst[i] = states[i]; + __disable_irq(); + __asm volatile ("" ::: "memory"); + activeStates = (volatile const TestCaseState*)dst; + activeIdx ^= 1; + __asm volatile ("" ::: "memory"); + __enable_irq(); +} + +void serviceStatusLeds(void) { + static bool lastFlash = 0, lastMode = 0; + if (flashState != lastFlash || runMode != lastMode) { + uint16_t activePin = runMode ? PIN_RUN : PIN_IDLE; + uint16_t inactivePin = runMode ? PIN_IDLE : PIN_RUN; + funDigitalWrite(activePin, flashState ? FUN_LOW : FUN_HIGH); + funDigitalWrite(inactivePin, FUN_HIGH); + lastFlash = flashState; + lastMode = runMode; + } +} + +void testCaseLEDStartupPattern(void) { + static TestCaseState demoStates[5]; + for (int k = 0; k < 4; k++) { + for (int i = 0; i < 5; i++) { + for (int j = 0; j < 5; j++) demoStates[j] = TC_NO_RESULT; + demoStates[i] = TC_PASS; + setTestCaseResult(demoStates); + waitTicks(30); + } + for (int i = 4; i >= 0; i--) { + for (int j = 0; j < 5; j++) demoStates[j] = TC_NO_RESULT; + demoStates[i] = TC_FAIL; + setTestCaseResult(demoStates); + waitTicks(30); + } + } + for (int j = 0; j < 5; j++) demoStates[j] = TC_NO_RESULT; + setTestCaseResult(demoStates); +} diff --git a/software/firmware/tester_runtime/src/leds.h b/software/firmware/tester_runtime/src/leds.h new file mode 100644 index 0000000..42ad518 --- /dev/null +++ b/software/firmware/tester_runtime/src/leds.h @@ -0,0 +1,19 @@ +#include +#include "test_cases.h" + +extern volatile const TestCaseState* activeStates; +#ifndef LEDS_H +#define LEDS_H + +#include +#include + + +#include "test_cases.h" + +void serviceStatusLeds(void); +void setTestCaseResult(const TestCaseState states[5]); +void initTestCaseStates(void); +void testCaseLEDStartupPattern(void); + +#endif // LEDS_H diff --git a/software/firmware/tester_runtime/src/main.c b/software/firmware/tester_runtime/src/main.c index c7348ac..f6c511f 100644 --- a/software/firmware/tester_runtime/src/main.c +++ b/software/firmware/tester_runtime/src/main.c @@ -1,287 +1,52 @@ #include "ch32fun.h" -#include #include +#include "globals.h" +#include "pins.h" +#include "hardware.h" +#include "leds.h" +#include "buttons.h" +#include "test_cases.h" -// Status Pins -#define PIN_PWR PC5 -#define PIN_INIT PC6 -#define PIN_RDY PC7 -#define PIN_RUN PD0 -#define PIN_IDLE PD6 +// Optional: trap registers for debug (RISC-V) +volatile uint32_t last_mcause = 0, last_mepc = 0; -// Test Case Columns (TC1–TC5) -#define PIN_COL_A PC0 -#define PIN_COL_B PC1 -#define PIN_COL_C PC2 -#define PIN_COL_D PC3 -#define PIN_COL_E PC4 - -// Test Case Rows -#define PIN_ROW_R PA1 // Red (fail) -#define PIN_ROW_G PA2 // Green (pass) - -// Input buttons -#define PIN_INPUT_A PD2 -#define PIN_INPUT_B PD3 -#define PIN_INPUT_C PD4 -#define PIN_INPUT_D PD5 - -// Reset button is connected to NRST pin PD7 which should reset the MCU when pressed. -// This is a a hardware feature and does not require configuration. - -const uint8_t statusLEDs[] = { PIN_PWR, PIN_INIT, PIN_RDY, PIN_RUN, PIN_IDLE }; -const uint8_t testCols[] = { PIN_COL_A, PIN_COL_B, PIN_COL_C, PIN_COL_D, PIN_COL_E }; -const uint8_t testRows[] = { PIN_ROW_R, PIN_ROW_G }; -const uint8_t rowRed = PIN_ROW_R; -const uint8_t rowGreen = PIN_ROW_G; - -const uint8_t inputPins[] = { PIN_INPUT_A, PIN_INPUT_B, PIN_INPUT_C, PIN_INPUT_D }; - -#define FLASH_INTERVAL_MS 10 // Flash every 10ms - -volatile bool runMode = true; // Current mode: true = RUN, false = IDLE -volatile bool ledState = false; // Current LED state (on/off) - -typedef enum { - TC_NO_RESULT, - TC_PASS, - TC_FAIL, - TC_IN_PROGRESS, - TC_RETRY -} TestCaseState; - -volatile TestCaseState testCaseStates[5]; // TC_PASS, TC_FAIL, etc. - -volatile uint16_t tickCount = 0; -volatile bool flashState = false; - -void scanTestCaseLEDs(void); - -void initTestCaseStates() { - for (int i = 0; i < 5; i++) { - testCaseStates[i] = TC_NO_RESULT; - } -} - -// Forward declare with correct attribute (WCH RISC-V ISR) -void TIM1_UP_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast"))); - -void TIM1_UP_IRQHandler(void) -{ - if (TIM1->INTFR & TIM_UIF) - { - TIM1->INTFR &= ~TIM_UIF; - tickCount++; - - // Fast task: scan test case LEDs every tick - scanTestCaseLEDs(); - - // Slow task: toggle status LED every 500ms (50 ticks) - if (tickCount >= 50) { - tickCount = 0; - flashState = !flashState; - - uint8_t activePin = runMode ? PIN_RUN : PIN_IDLE; - uint8_t inactivePin = runMode ? PIN_IDLE : PIN_RUN; - - funDigitalWrite(activePin, flashState ? FUN_LOW : FUN_HIGH); - funDigitalWrite(inactivePin, FUN_HIGH); - } - } -} - -// Non-blocking delay using tickCount -void waitTicks(uint16_t ticks) { - uint16_t start = tickCount; - while ((tickCount - start) < ticks) { - // Let ISR run - printf("."); - } -} - - -// Initialize status LED pins and TIM1 for periodic toggling -void setupStatusFlasher(void) +void HardFault_Handler(void) { - // Enable and reset TIM1 (on APB2) - RCC->APB2PCENR |= RCC_APB2Periph_TIM1; - RCC->APB2PRSTR |= RCC_APB2Periph_TIM1; - RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1; - - // 48MHz core -> 1kHz timer tick - TIM1->PSC = 48000 - 1; // prescaler to 1 kHz - TIM1->ATRLR = FLASH_INTERVAL_MS - 1; // ARR = 499 -> 500ms - TIM1->SWEVGR |= TIM_UG; // load PSC/ARR - TIM1->INTFR &= ~TIM_UIF; // clear any pending update flag - TIM1->DMAINTENR |= TIM_UIE; // enable update interrupt - - NVIC_EnableIRQ(TIM1_UP_IRQn); // enable TIM1 update IRQ in NVIC - TIM1->CTLR1 |= TIM_CEN; // start TIM1 -} - -// Switch which status LED is flashing -void runStatus(bool isRun) -{ - runMode = isRun; - ledState = false; - - // Ensure both OFF immediately; ISR will take over - funDigitalWrite(PIN_RUN, FUN_HIGH); - funDigitalWrite(PIN_IDLE, FUN_HIGH); -} - -void setupPins() { - funGpioInitAll(); - - // Status LEDs (LEDs are active LOW) - for (int i = 0; i < 5; i++) { - funPinMode(statusLEDs[i], GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); - funDigitalWrite(statusLEDs[i], FUN_HIGH); // Ensure all status LEDs start OFF - } - - // Test Case LEDs - - // Columns (active HIGH, but row must be LOW to light LED) - for (int i = 0; i < 5; i++) { - funPinMode(testCols[i], GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); - funDigitalWrite(testCols[i], FUN_LOW); // Start all columns LOW - } - - // Rows (active LOW) - for (int i = 0; i < 2; i++) { - funPinMode(testRows[i], GPIO_Speed_10MHz | GPIO_CNF_OUT_PP); - funDigitalWrite(testRows[i], FUN_HIGH); // Start all columns LOW - } + __disable_irq(); + asm volatile ("csrr %0, mcause" : "=r"(last_mcause)); + asm volatile ("csrr %0, mepc" : "=r"(last_mepc)); + for (;;) { /* spin */ } } - -int buttonPressed() { - // Returns the index of the button pressed (0-3) or -1 if none pressed - for (int i = 0; i < 4; i++) { - if (funDigitalRead(inputPins[i]) == FUN_LOW) { - return i; - } - } - return -1; -} - - -void startupSequence() { +static void startupSequence(void) { + // Power LED on, blink INIT, RDY on, start timer, set IDLE flashing, LED pattern funDigitalWrite(PIN_PWR, FUN_LOW); // PWR stays on - // blink INIT for 3 times for (int i = 0; i < 3; i++) { funDigitalWrite(PIN_INIT, FUN_LOW); Delay_Ms(200); funDigitalWrite(PIN_INIT, FUN_HIGH); Delay_Ms(200); } - // Turn on RDY funDigitalWrite(PIN_RDY, FUN_LOW); - // Turn on IDLE (flashing) - setupStatusFlasher(); // Start timer - runStatus(false); // Set IDLE flashing -} - -// Set the test case results via wrapper function -void setTestCaseResult(TestCaseState states[5]) { - for (int i = 0; i < 5; i++) { - testCaseStates[i] = states[i]; - } -} - -// Scan and update the test case LEDs; to be called periodically via timer or main loop -void scanTestCaseLEDs() { - for (int i = 0; i < 5; i++) { - uint8_t col = testCols[i]; - TestCaseState state = testCaseStates[i]; - - // Activate column - funDigitalWrite(col, FUN_HIGH); - - switch (state) { - case TC_NO_RESULT: - // Both rows OFF - funDigitalWrite(rowRed, FUN_HIGH); - funDigitalWrite(rowGreen, FUN_HIGH); - break; - case TC_PASS: - funDigitalWrite(rowRed, FUN_HIGH); - funDigitalWrite(rowGreen, FUN_LOW); - break; - - case TC_FAIL: - funDigitalWrite(rowRed, FUN_LOW); - funDigitalWrite(rowGreen, FUN_HIGH); - break; - - case TC_IN_PROGRESS: - // Alternate flashing: handled by timer toggling a flag - if (flashState) { - funDigitalWrite(rowRed, FUN_LOW); - funDigitalWrite(rowGreen, FUN_HIGH); - } else { - funDigitalWrite(rowRed, FUN_HIGH); - funDigitalWrite(rowGreen, FUN_LOW); - } - break; - - case TC_RETRY: - if (i == 4) { - funDigitalWrite(rowRed, flashState ? FUN_LOW : FUN_HIGH); - funDigitalWrite(rowGreen, FUN_HIGH); // Always OFF - } - break; - } - - Delay_Ms(2); // Short dwell time per column - - // Deactivate column - funDigitalWrite(col, FUN_LOW); - } - - // Reset rows to avoid ghosting - funDigitalWrite(rowRed, FUN_HIGH); - funDigitalWrite(rowGreen, FUN_HIGH); + setupStatusFlasher(); + __enable_irq(); + runStatus(false); + testCaseLEDStartupPattern(); } - -void runTestCaseDemo() { - TestCaseState demoStates[5]; - - demoStates[0] = TC_PASS; - demoStates[1] = TC_FAIL; - demoStates[2] = TC_IN_PROGRESS; - demoStates[3] = TC_PASS; - demoStates[4] = TC_RETRY; - setTestCaseResult(demoStates); - - // Delay_Ms(5000); // Initial 2 second delay - // waitTicks(500); // 5 seconds - - demoStates[0] = TC_FAIL; - demoStates[1] = TC_PASS; - demoStates[2] = TC_FAIL; - demoStates[3] = TC_RETRY; - demoStates[4] = TC_IN_PROGRESS; - setTestCaseResult(demoStates); - waitTicks(5); // 5 seconds - setTestCaseResult((TestCaseState[]){ TC_PASS, TC_PASS, TC_PASS, TC_PASS, TC_PASS } ); - setTestCaseResult((TestCaseState[]){ TC_FAIL, TC_FAIL, TC_FAIL, TC_FAIL, TC_FAIL } ); -} - - -int main() { + int main(void) { SystemInit(); - __enable_irq(); // Allow ISRs globally - - printf("Tester PCB Business Card Runtime\n"); setupPins(); + initTestCaseStates(); startupSequence(); - printf("Running test case demo\n"); - runTestCaseDemo(); - printf("Demo complete. Entering main loop.\n"); + waitTicks(100); while (1) { - // Do main loop tasks here - + serviceStatusLeds(); + if (!testActive && buttons[3].pressed) { + test_cases_start(currentTest); + } + test_cases_monitor_inputs(); + __WFI(); } } + diff --git a/software/firmware/tester_runtime/src/pins.h b/software/firmware/tester_runtime/src/pins.h new file mode 100644 index 0000000..b6e32dd --- /dev/null +++ b/software/firmware/tester_runtime/src/pins.h @@ -0,0 +1,46 @@ +#ifndef BIT +#define BIT(x) (1U << (x)) +#endif +#ifndef PINS_H +#define PINS_H +#include "ch32fun.h" + +// Status LEDs (active LOW) +#define PIN_PWR PC5 +#define PIN_INIT PC6 +#define PIN_RDY PC7 +#define PIN_RUN PD0 +#define PIN_IDLE PD6 + +// Test Case Columns (TC1–TC5), active HIGH +#define PIN_COL_A PC0 +#define PIN_COL_B PC1 +#define PIN_COL_C PC2 +#define PIN_COL_D PC3 +#define PIN_COL_E PC4 + +// Test Case Rows (active LOW) +#define PIN_ROW_R PA1 // Red (fail) +#define PIN_ROW_G PA2 // Green (pass) + +// Input buttons (assumed active LOW with pull-ups) +#define PIN_INPUT_A PD2 +#define PIN_INPUT_B PD3 +#define PIN_INPUT_C PD4 +#define PIN_INPUT_D PD5 + + +typedef struct { GPIO_TypeDef* port; int mask; } PinDef; + +extern const PinDef COL[5]; +extern const PinDef ROW_RED; +extern const PinDef ROW_GREEN; +extern const PinDef BTN[4]; + +static inline void gpio_set(GPIO_TypeDef* p, int m) { p->BSHR = m; } +static inline void gpio_clear(GPIO_TypeDef* p, int m) { p->BCR = m; } +static inline uint8_t gpio_get(GPIO_TypeDef* p, int m) { + return (p->INDR & m) ? 1U : 0U; +} + +#endif // PINS_H diff --git a/software/firmware/tester_runtime/src/test_cases.c b/software/firmware/tester_runtime/src/test_cases.c new file mode 100644 index 0000000..3e4cc6e --- /dev/null +++ b/software/firmware/tester_runtime/src/test_cases.c @@ -0,0 +1,333 @@ + +#include "test_cases.h" +#include "buttons.h" +#include "leds.h" +#include "hardware.h" +#include "globals.h" +#include "pins.h" +#include +#include +#include + +static bool testCaseShouldEndEarly = false; +TestCaseState tcResults[5] = { TC_NO_RESULT }; +uint8_t currentTest = 0; +bool testActive = false; +static uint32_t testStartTime = 0; + + +// ===== TC1: All A–C pressed ===== +static bool seenA, seenB, seenC; + +static void tc1_init(void) { + seenA = seenB = seenC = false; +} + +static void tc1_update(void) { + if (buttons[0].pressed) seenA = true; + if (buttons[1].pressed) seenB = true; + if (buttons[2].pressed) seenC = true; +} + +static TestCaseState tc1_eval(void) { + return (seenA && seenB && seenC) ? TC_PASS : TC_FAIL; +} + +// ===== TC2: Debounce timing ===== +// TC2: Fast double-press detection +static uint32_t pressTimes[4]; +static uint8_t pressIndex; +static const uint32_t FAST_THRESHOLD_MS = 200; +static const uint32_t MIN_GAP_MS = 30; // reject bounce + +static void tc2_init(void) { + pressIndex = 0; + for (uint8_t i = 0; i < 4; i++) { + pressTimes[i] = 0; + } +} + +static void tc2_update(void) { + if (buttons[1].justPressed) { + buttons[1].justPressed = false; // consume event + if (pressIndex < 4) { + pressTimes[pressIndex++] = msTicks; + } + } +} + +static TestCaseState tc2_eval(void) { + for (uint8_t i = 0; i < pressIndex - 1; i++) { + uint32_t gap = pressTimes[i + 1] - pressTimes[i]; + + if (gap >= MIN_GAP_MS && gap < FAST_THRESHOLD_MS) { + return TC_PASS; // found a valid fast double press + } + } + return TC_FAIL; // no valid pair found +} + +// ===== TC3: Conditional Logic – Decision Table Test ===== + +// Latch states for A, B, C during the 5s window +static bool seenA3, seenB3, seenC3; + +static void tc3_init(void) { + seenA3 = seenB3 = seenC3 = false; +} + +static void tc3_update(void) { + if (buttons[0].justPressed) { + buttons[0].justPressed = false; + seenA3 = !seenA3; // toggle + } + if (buttons[1].justPressed) { + buttons[1].justPressed = false; + seenB3 = !seenB3; // toggle + } + if (buttons[2].justPressed) { + buttons[2].justPressed = false; + seenC3 = !seenC3; // toggle + } +} + +// Decision table returning your existing TestCaseState +static TestCaseState tc3_eval(void) { + // Decision table mapping + if (!seenA3 && !seenB3 && !seenC3) return TC_FAIL; // 000 + if (!seenA3 && !seenB3 && seenC3) return TC_WARNING; // 001 + if (!seenA3 && seenB3 && !seenC3) return TC_WARNING; // 010 + if (!seenA3 && seenB3 && seenC3) return TC_PASS; // 011 ✅ + if ( seenA3 && !seenB3 && !seenC3) return TC_FAIL; // 100 + if ( seenA3 && !seenB3 && seenC3) return TC_WARNING; // 101 + if ( seenA3 && seenB3 && !seenC3) return TC_WARNING; // 110 + if ( seenA3 && seenB3 && seenC3) return TC_WARNING; // 111 + return TC_FAIL; +} + +// ===== TC4: Input Range Check ===== + +// Press counters for A, B, C +static uint8_t countA4, countB4, countC4; + +// Valid ranges +#define A_MIN 5 +#define A_MAX 7 +// 6 is a pass +#define B_MIN 7 +#define B_MAX 12 +// 8 - 11 are passes +#define C_MIN 2 +#define C_MAX 4 +// 3 is a pass + +static void tc4_init(void) { + countA4 = countB4 = countC4 = 0; +} + +static void tc4_update(void) { + if (buttons[0].justPressed) { buttons[0].justPressed = false; countA4++; } + if (buttons[1].justPressed) { buttons[1].justPressed = false; countB4++; } + if (buttons[2].justPressed) { buttons[2].justPressed = false; countC4++; } +} + +static TestCaseState tc4_eval(void) { + bool validA = (countA4 >= A_MIN && countA4 <= A_MAX); + bool validB = (countB4 >= B_MIN && countB4 <= B_MAX); + bool validC = (countC4 >= C_MIN && countC4 <= C_MAX); + + bool anyValid = validA || validB || validC; + + if (!anyValid) { + return TC_FAIL; + } + + // Optional: treat borderline values (exactly at min or max) as RETRY + bool borderlineA = validA && (countA4 == A_MIN || countA4 == A_MAX); + bool borderlineB = validB && (countB4 == B_MIN || countB4 == B_MAX); + bool borderlineC = validC && (countC4 == C_MIN || countC4 == C_MAX); + + if (borderlineA || borderlineB || borderlineC) { + return TC_WARNING; // blink pattern for borderline + } + + return TC_PASS; +} + +// ===== TC5: Unlock Pattern – State Transition / Timeout + Recovery ===== +static const uint8_t correctSeq[5] = { 0, 1, 2, 1, 0 }; +#define MAX_INPUTS 5 +#define MAX_ATTEMPTS 3 +#define RECOVERY_WINDOW_MS 5000 + +static uint8_t inputSeq[MAX_INPUTS]; +static uint8_t inputCount; +static uint8_t attempts; +static bool inRecovery; +static uint32_t recoveryStart; +static bool lastWasC; +static TestCaseState tc5Outcome; + +static void tc5_init(void) { + inputCount = 0; + attempts = 0; + inRecovery = false; + recoveryStart = 0; + lastWasC = false; + tc5Outcome = TC_IN_PROGRESS; + + tcResults[currentTest] = TC_IN_PROGRESS; + setTestCaseResult(tcResults); +} + +static void tc5_update(void) { + // Handle recovery timeout + if (inRecovery && (msTicks - recoveryStart >= RECOVERY_WINDOW_MS)) { + attempts++; + inRecovery = false; + lastWasC = false; + + // Set internal outcome only; defer LED signaling to tc5_eval + tc5Outcome = TC_FAIL; + return; + } + + // Handle button presses + for (uint8_t i = 0; i < 3; i++) { + if (buttons[i].justPressed) { + buttons[i].justPressed = false; + + // Recovery mode logic + if (inRecovery) { + if (i == 2) { // Button C + if (lastWasC) { + // Successful recovery: reset state and retry + inRecovery = false; + inputCount = 0; + lastWasC = false; + tc5Outcome = TC_IN_PROGRESS; + + tcResults[currentTest] = TC_IN_PROGRESS; + setTestCaseResult(tcResults); + return; + } else { + lastWasC = true; + } + } else { + lastWasC = false; + } + return; + } + + // Normal input sequence + if (inputCount < MAX_INPUTS) { + inputSeq[inputCount++] = i; + + if (inputCount == MAX_INPUTS) { + bool correct = true; + for (uint8_t j = 0; j < MAX_INPUTS; j++) { + if (inputSeq[j] != correctSeq[j]) { + correct = false; + break; + } + } + + if (correct) { + tc5Outcome = TC_PASS; + testCaseShouldEndEarly = true; + return; + } else { + attempts++; + if (attempts >= MAX_ATTEMPTS) { + tc5Outcome = TC_FAIL; + testCaseShouldEndEarly = true; + return; + } else { + inRecovery = true; + recoveryStart = msTicks; + lastWasC = false; + tc5Outcome = TC_RETRY; + + tcResults[currentTest] = TC_RETRY; + setTestCaseResult(tcResults); + } + } + } + } + } + } +} + +static TestCaseState tc5_eval(void) { + // Final outcome only resolved here + if (tc5Outcome == TC_PASS || tc5Outcome == TC_FAIL) { + tcResults[currentTest] = tc5Outcome; + setTestCaseResult(tcResults); + return tc5Outcome; + } + + // If still in progress or retry, treat as failure on timeout + tcResults[currentTest] = TC_FAIL; + setTestCaseResult(tcResults); + return TC_FAIL; +} + +// ===== Test Case Framework ===== +typedef struct { + uint32_t durationMs; + void (*initFn)(void); + void (*updateFn)(void); + TestCaseState (*evalFn)(void); +} TestCaseDef; + +// ===== Test Case List ===== + +static const TestCaseDef testCases[] = { + { 5000, tc1_init, tc1_update, tc1_eval }, + { 5000, tc2_init, tc2_update, tc2_eval }, + { 5000, tc3_init, tc3_update, tc3_eval }, + { 5000, tc4_init, tc4_update, tc4_eval }, + { 30000, tc5_init, tc5_update, tc5_eval } +}; +static const size_t NUM_TEST_CASES = sizeof(testCases) / sizeof(testCases[0]); + +// ===== Test Case Runner ===== + +void test_cases_start(uint8_t idx) { + if (idx == 0) { + for (size_t i = 0; i < NUM_TEST_CASES; i++) { + tcResults[i] = TC_NO_RESULT; + } + } + testCaseShouldEndEarly = false; + testStartTime = msTicks; + testActive = true; + runStatus(true); + tcResults[idx] = TC_IN_PROGRESS; + setTestCaseResult(tcResults); + testCases[idx].initFn(); +} + +void test_cases_end(void) { + testActive = false; + runStatus(false); + TestCaseState outcome = testCases[currentTest].evalFn(); + tcResults[currentTest] = outcome; + setTestCaseResult(tcResults); + currentTest = (currentTest + 1) % NUM_TEST_CASES; +} + +void test_cases_monitor_inputs(void) { + if (!testActive) return; + + testCases[currentTest].updateFn(); + + if (testCaseShouldEndEarly || + (uint32_t)(msTicks - testStartTime) >= testCases[currentTest].durationMs) { + test_cases_end(); + } +} + +void test_cases_init(void) {} +void test_cases_update(void) {} +TestCaseState test_cases_eval(void) { return TC_NO_RESULT; } diff --git a/software/firmware/tester_runtime/src/test_cases.h b/software/firmware/tester_runtime/src/test_cases.h new file mode 100644 index 0000000..cc2cace --- /dev/null +++ b/software/firmware/tester_runtime/src/test_cases.h @@ -0,0 +1,28 @@ +#ifndef TEST_CASES_H +#define TEST_CASES_H + +#include +#include + +typedef enum { + TC_NO_RESULT, + TC_PASS, + TC_FAIL, + TC_WARNING, + TC_IN_PROGRESS, + TC_RETRY +} TestCaseState; + + +void test_cases_init(void); +void test_cases_update(void); +TestCaseState test_cases_eval(void); +void test_cases_start(uint8_t idx); +void test_cases_end(void); +void test_cases_monitor_inputs(void); + +extern TestCaseState tcResults[5]; +extern uint8_t currentTest; +extern bool testActive; + +#endif // TEST_CASES_H