diff --git a/OpticalChannelTester/App.cpp b/OpticalChannelTester/App.cpp index 29f5c12..393ab23 100644 --- a/OpticalChannelTester/App.cpp +++ b/OpticalChannelTester/App.cpp @@ -123,13 +123,16 @@ void formatMenuLine(const char *label, const char *value, char *out, size_t size snprintf(out, size, "%s%*s%s", label, padding, "", value); } -uint32_t overallProgress(uint32_t stageIndex, uint8_t step) { - if (step > MEASUREMENT_PROGRESS_STEPS) step = MEASUREMENT_PROGRESS_STEPS; - return stageIndex * MEASUREMENT_PROGRESS_STEPS + step; +uint32_t overallProgress(uint32_t stageIndex, uint8_t step, + uint8_t stepsPerStage = MEASUREMENT_PROGRESS_STEPS) { + if (step > stepsPerStage) step = stepsPerStage; + return stageIndex * stepsPerStage + step; } -uint32_t overallProgressTotal(uint32_t stageCount) { - return stageCount * MEASUREMENT_PROGRESS_STEPS; +uint32_t overallProgressTotal( + uint32_t stageCount, + uint8_t stepsPerStage = MEASUREMENT_PROGRESS_STEPS) { + return stageCount * stepsPerStage; } uint32_t stageWallTimeMs(uint32_t testTimeMs, uint32_t frequencyHz) { @@ -1536,10 +1539,13 @@ void App::showDriverResult(const DriverStats &s, bool testPassed) { snprintf(two, sizeof(two), UiText::DRIVER_MEASUREMENT_FORMAT, delay, response); } + const bool finished = testPassed || s.reason != FailReason::NONE; + const uint8_t progressSteps = driverTest_.progressSteps(); display_.show(one, two, - overallProgress(stageIndex_, driverTest_.progressStep()), - overallProgressTotal(stageCount_), s.reason == FailReason::NONE ? nullptr : - roleCorner(Role::SOLO)); + finished ? 0U : overallProgress( + stageIndex_, driverTest_.progressStep(), progressSteps), + finished ? 0U : overallProgressTotal(stageCount_, progressSteps), + s.reason == FailReason::NONE ? nullptr : roleCorner(Role::SOLO)); } void App::showRemoteResult(const ProtocolPacket &packet) { diff --git a/OpticalChannelTester/Config.h b/OpticalChannelTester/Config.h index a4d0037..c2efc6b 100644 --- a/OpticalChannelTester/Config.h +++ b/OpticalChannelTester/Config.h @@ -109,6 +109,8 @@ static_assert(MEASUREMENT_AVERAGING_PERIODS > 0, "Averaging window must contain at least one period"); constexpr uint8_t MEASUREMENT_PROGRESS_STEPS = 10; constexpr uint32_t OLED_PROGRESS_UPDATE_MS = 15; +constexpr uint8_t DRIVER_SHORT_SAMPLE_PROGRESS_STEPS = 10; +constexpr uint32_t DRIVER_PROGRESS_INTERVAL_MS = 100; constexpr uint32_t IDLE_POWER_SAVE_TIMEOUT_MS = 60000; // usb_serial_jtag_is_connected() needs no open COM port or CDC traffic, but a @@ -162,10 +164,10 @@ constexpr uint32_t DRIVER_RESPONSE_TIMEOUT_NS = 10000; // the selected maximum down to the selected minimum. Widths are stored in // nanoseconds so sub-microsecond pulses remain representable without floats. constexpr uint32_t PWM_FREQUENCY_OPTIONS_HZ[] = { - 500, 1000, 2000, 5000, 10000, 25000, + 500, 1000, 2000, 5000, 10000, }; constexpr uint32_t MAX_PULSE_OPTIONS_NS[] = { - 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000 + 2000, 5000, 10000, 50000, 100000, 500000 }; constexpr uint32_t MIN_PULSE_OPTIONS_NS[] = { 250, 500, 1000, 2000, 5000, 10000, 50000 @@ -175,6 +177,6 @@ constexpr uint32_t TEST_PULSE_WIDTHS_NS[] = { 100000, 200000, 500000, 1000000 }; constexpr float ACCURACY_OPTIONS_PCT[] = {1.0f, 2.0f, 5.0f, 10.0f}; -constexpr uint32_t TEST_TIME_OPTIONS_MS[] = {100, 250, 500, 1000, 2000, 5000}; +constexpr uint32_t TEST_TIME_OPTIONS_MS[] = {100, 250, 500, 1000, 2000, 5000, 60000}; template constexpr size_t countOf(const T (&)[N]) { return N; } diff --git a/OpticalChannelTester/DriverTest.cpp b/OpticalChannelTester/DriverTest.cpp index c02a109..0d46b83 100644 --- a/OpticalChannelTester/DriverTest.cpp +++ b/OpticalChannelTester/DriverTest.cpp @@ -5,11 +5,22 @@ #include #include +#include #include #include #include #include +static inline uint32_t IRAM_ATTR maskAllInterrupts() { + uint32_t state; + asm volatile("rsil %0, 15" : "=a"(state) :: "memory"); + return state; +} + +static inline void IRAM_ATTR restoreInterrupts(uint32_t state) { + asm volatile("wsr %0, ps\nrsync" :: "a"(state) : "memory"); +} + void DriverEdgeStats::reset() { memset(this, 0, sizeof(*this)); minDelayTicks = minResponseTicks = UINT32_MAX; @@ -78,7 +89,13 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs, faultLongTicks_ = nsToTicks(DRIVER_FAULT_MIN_NS); stuckTicks_ = nsToTicks(DRIVER_RX_STUCK_MIN_NS); testTicks_ = static_cast(captureHz_) * testTimeMs / 1000ULL; - subsampleTicks_ = testTicks_ / SUBSAMPLE_COUNT; + const uint32_t requestedSubsamples = testTimeMs < 1000U ? + DRIVER_SHORT_SAMPLE_PROGRESS_STEPS : + (testTimeMs + DRIVER_PROGRESS_INTERVAL_MS - 1U) / + DRIVER_PROGRESS_INTERVAL_MS; + subsampleCount_ = static_cast( + requestedSubsamples > UINT8_MAX ? UINT8_MAX : requestedSubsamples); + subsampleTicks_ = testTicks_ / subsampleCount_; if (!pollPeriodCycles_ || !pollWindowAfterCycles_ || !ackStartMaxTicks_ || !faultLongTicks_ || !stuckTicks_ || !testTicks_ || !subsampleTicks_) @@ -117,14 +134,14 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs, } bool DriverTest::armCapture() { - Serial.flush(); + // Never wait for USB/Serial here: a disconnected or slow host must not + // delay a subsample or consume the test's global timeout. if (!__atomic_load_n(&core0WdtDisabled_, __ATOMIC_ACQUIRE)) { - const bool disabled = disableCore0WDT(); + TaskHandle_t idle0 = xTaskGetIdleTaskHandleForCore(0); + const bool watched = idle0 && esp_task_wdt_status(idle0) == ESP_OK; + const bool disabled = watched && disableCore0WDT(); __atomic_store_n(&core0WdtDisabled_, disabled, __ATOMIC_RELEASE); - if (!disabled) { - state_ = DriverState::IDLE; - return false; - } + // If IDLE0 is not watched there is nothing to remove or restore. } __atomic_store_n(&captureReady_, false, __ATOMIC_RELEASE); __atomic_store_n(&captureActive_, true, __ATOMIC_RELEASE); @@ -184,6 +201,8 @@ void IRAM_ATTR DriverTest::pollTaskLoop() { uint8_t hotCount = 0; bool sawTxStart = false; bool critical = false; + bool allInterruptsMasked = false; + uint32_t interruptState = 0; auto sampleOnce = [&]() { const uint32_t current = GPIO.in & PIN_MASK; @@ -218,12 +237,18 @@ void IRAM_ATTR DriverTest::pollTaskLoop() { for (uint8_t i = 0; i < 16U; ++i) sampleOnce(); if (sawTxStart) windowEnd = lastTxStart + pollWindowAfterCycles_; const bool synchronized = sawTxStart; + if (synchronized) { + interruptState = maskAllInterrupts(); + allInterruptsMasked = true; + } while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) && synchronized) { while (__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE) && static_cast(esp_cpu_get_cycle_count() - windowEnd) < 0) for (uint8_t i = 0; i < 16U; ++i) sampleOnce(); + restoreInterrupts(interruptState); + allInterruptsMasked = false; portEXIT_CRITICAL(&pollMux_); critical = false; flushHot(); @@ -249,8 +274,11 @@ void IRAM_ATTR DriverTest::pollTaskLoop() { portENTER_CRITICAL(&pollMux_); critical = true; + interruptState = maskAllInterrupts(); + allInterruptsMasked = true; windowEnd = nextStart + pollWindowAfterCycles_; } + if (allInterruptsMasked) restoreInterrupts(interruptState); if (critical) portEXIT_CRITICAL(&pollMux_); flushHot(); if (__atomic_exchange_n(&core0WdtDisabled_, false, @@ -325,7 +353,7 @@ void DriverTest::processSettling(const TimedEvent &event) { const uint64_t measuredBefore = static_cast(completedSubsamples_) * subsampleTicks_; const uint64_t thisSubsampleTicks = - completedSubsamples_ + 1U == SUBSAMPLE_COUNT ? + completedSubsamples_ + 1U == subsampleCount_ ? testTicks_ - measuredBefore : subsampleTicks_; deadlineTick_ = event.tick + thisSubsampleTicks; processTx(event, lightOn); @@ -509,7 +537,7 @@ void DriverTest::completeIfPossible(uint64_t now) { fail(FailReason::DATA_LOSS, lastEventTick_); return; } - if (completedSubsamples_ >= SUBSAMPLE_COUNT) { + if (completedSubsamples_ >= subsampleCount_) { __atomic_store_n(&progressUpdatePending_, false, __ATOMIC_RELEASE); state_ = DriverState::PASS; } else { diff --git a/OpticalChannelTester/DriverTest.h b/OpticalChannelTester/DriverTest.h index fca035e..c2533a2 100644 --- a/OpticalChannelTester/DriverTest.h +++ b/OpticalChannelTester/DriverTest.h @@ -57,6 +57,7 @@ class DriverTest { void printSummary() const; void printTrace() const; uint8_t progressStep() const { return currentStep_; } + uint8_t progressSteps() const { return subsampleCount_; } uint32_t tickHz() const { return captureHz_; } const DriverStats &stats() const { return publishedStats_; } @@ -113,7 +114,6 @@ class DriverTest { uint64_t ticksToNs(uint64_t ticks) const; static constexpr uint8_t MAX_PENDING = 8; - static constexpr uint8_t SUBSAMPLE_COUNT = 10; static constexpr uint16_t RING_CAPACITY = 2048; static constexpr uint8_t TRACE_CAPACITY = 32; static_assert((RING_CAPACITY & (RING_CAPACITY - 1U)) == 0, @@ -161,6 +161,7 @@ class DriverTest { uint64_t lastActiveTxTick_ = 0; uint8_t settleCycles_ = 0; uint8_t settledCycles_ = 0; + uint8_t subsampleCount_ = DRIVER_SHORT_SAMPLE_PROGRESS_STEPS; uint8_t completedSubsamples_ = 0; bool rxActiveRawHigh_ = true; bool txPulseLightOn_ = true;