#pragma once #include #include "Receiver.h" enum class DriverState : uint8_t { IDLE, SETTLING, RUNNING, SUBSAMPLE_DONE, PASS, FAIL }; struct DriverEdgeStats { uint32_t responses; uint32_t minDelayTicks; uint32_t maxDelayTicks; uint64_t delaySumTicks; uint32_t minResponseTicks; uint32_t maxResponseTicks; uint64_t responseSumTicks; void reset(); }; struct DriverStats { uint32_t inputEdges; uint32_t responses; uint32_t minDelayTicks; uint32_t maxDelayTicks; uint32_t minResponseTicks; uint32_t maxResponseTicks; uint32_t lastDelayTicks; uint32_t lastResponseTicks; uint32_t droppedItems; uint32_t unexpectedResponses; uint64_t errorElapsedTicks; uint32_t errorTriggerTicks; uint32_t errorDelayTicks; uint32_t errorPulseTicks; bool errorTriggerValid; bool errorDelayValid; bool errorPulseValid; DriverEdgeStats turnOn; DriverEdgeStats turnOff; FailReason reason; void reset(); }; class DriverTest { public: explicit DriverTest(PulseReceiver &receiver) : receiver_(receiver) {} bool start(uint32_t frequencyHz, uint32_t pulseNs, float tolerancePct, uint32_t testTimeMs, uint8_t settleCycles, bool activeTxLightOn, bool activeRxLightOn); DriverState update() const { return state_; } void abort(); void forceFail(FailReason reason); bool resumeSubsample(); bool takeProgressUpdate(); 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_; } private: enum class Source : uint8_t { TX, RX, WINDOW_END }; struct RawEvent { uint32_t tick; bool rising; Source source; }; struct TimedEvent { uint64_t tick; bool rising; Source source; }; struct PendingTx { uint64_t tick; bool lightOn; }; struct Response { bool active; bool associated; uint64_t startTick; PendingTx tx; }; struct TraceEvent { uint64_t tick; uint8_t source; uint8_t rising; uint8_t state; uint8_t pending; }; static void analyzerTaskEntry(void *context); static void pollTaskEntry(void *context); void analyzerTaskLoop(); void IRAM_ATTR pollTaskLoop(); void processEvent(const TimedEvent &event); void processSettling(const TimedEvent &event); void processRunning(const TimedEvent &event); void processTx(const TimedEvent &event, bool lightOn); void processRx(const TimedEvent &event, bool activeNow); void expirePending(uint64_t now); void completeIfPossible(uint64_t now); bool addPending(uint64_t tick, bool lightOn); int8_t matchingPending(uint64_t rxTick) const; void removePending(uint8_t index); uint64_t mergeGuardTicks() const; void acceptAcknowledgement(uint64_t delay, uint64_t width, bool lightOn); void fail(FailReason reason, uint64_t tick = 0, uint64_t delay = 0, uint64_t pulseWidth = 0, uint64_t triggerAfterTx = 0); void publishStats(); void rememberTrace(const TimedEvent &event); bool armCapture(); void requestCaptureStop(); bool waitCaptureStopped(uint32_t timeoutMs); void clearCapture(); void recordRaw(uint32_t tick, bool rising, Source source); size_t readRaw(TimedEvent *events, size_t capacity, TickType_t waitTicks); uint32_t takeDropped(); uint64_t nsToTicks(uint32_t ns) const; uint64_t ticksToNs(uint64_t ticks) const; static constexpr uint8_t MAX_PENDING = 8; static constexpr uint16_t RING_CAPACITY = 2048; static constexpr uint8_t TRACE_CAPACITY = 32; static_assert((RING_CAPACITY & (RING_CAPACITY - 1U)) == 0, "driver ring capacity must be a power of two"); PulseReceiver &receiver_; TaskHandle_t analyzerTask_ = nullptr; TaskHandle_t pollTask_ = nullptr; volatile DriverState state_ = DriverState::IDLE; DriverStats stats_ = {}; DriverStats publishedStats_ = {}; mutable portMUX_TYPE statsMux_ = portMUX_INITIALIZER_UNLOCKED; RawEvent ring_[RING_CAPACITY] = {}; volatile uint16_t ringWrite_ = 0; volatile uint16_t ringRead_ = 0; volatile uint32_t droppedItems_ = 0; volatile bool captureActive_ = false; volatile bool captureReady_ = false; volatile bool core0WdtDisabled_ = false; uint32_t captureHz_ = 0; uint32_t captureFrequencyHz_ = 0; uint32_t capturePulseNs_ = 0; uint32_t pollPeriodCycles_ = 0; uint32_t pollWindowBeforeCycles_ = 0; uint32_t pollWindowAfterCycles_ = 0; bool pollTxStartRawHigh_ = false; bool captureTxLightOn_ = true; portMUX_TYPE pollMux_ = portMUX_INITIALIZER_UNLOCKED; PendingTx pending_[MAX_PENDING] = {}; uint8_t pendingCount_ = 0; Response response_ = {}; uint64_t ackStartMaxTicks_ = 0; uint64_t faultLongTicks_ = 0; uint64_t stuckTicks_ = 0; uint64_t testTicks_ = 0; uint64_t subsampleTicks_ = 0; uint64_t settlingTimeoutUs_ = 0; uint64_t settlingDeadlineUs_ = 0; uint64_t measurementStartTick_ = 0; uint64_t deadlineTick_ = 0; uint64_t pointOriginTick_ = 0; uint64_t lastEventTick_ = 0; 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; bool rxActive_ = false; bool measurementClosed_ = false; bool havePointOrigin_ = false; bool haveLastActiveTx_ = false; bool haveRawTick_ = false; uint32_t lastRawTick_ = 0; uint64_t tickEpoch_ = 0; volatile uint8_t currentStep_ = 0; volatile bool progressUpdatePending_ = false; TraceEvent trace_[TRACE_CAPACITY] = {}; uint8_t traceWrite_ = 0; uint8_t traceCount_ = 0; };