Доработки по тесту драйвера
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <esp_cpu.h>
|
||||
#include <esp_timer.h>
|
||||
#include <esp32-hal-cpu.h>
|
||||
#include <soc/gpio_struct.h>
|
||||
#include <string.h>
|
||||
@@ -36,7 +37,7 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs,
|
||||
uint8_t settleCycles, bool activeTxLightOn,
|
||||
bool activeRxLightOn) {
|
||||
(void)tolerancePct;
|
||||
(void)activeRxLightOn;
|
||||
(void)settleCycles;
|
||||
if (!receiver_.highRateBackend() || !frequencyHz || !pulseNs ||
|
||||
!testTimeMs || GPIO_PWM >= 32U || GPIO_RX >= 32U) return false;
|
||||
|
||||
@@ -49,8 +50,16 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs,
|
||||
"driver-analyze", 4096, this, 4, &analyzerTask_, 1) != pdPASS)
|
||||
return false;
|
||||
|
||||
// The ACK edges can be less than 1 us apart. MCPWM capture delivers all
|
||||
// channels through one group ISR and can overwrite an earlier channel
|
||||
// timestamp before that ISR reaches it. During DRIVER test dedicate core 0
|
||||
// to direct GPIO sampling; PWM itself remains fully hardware-generated.
|
||||
captureHz_ = getCpuFrequencyMhz() * 1000000UL;
|
||||
if (!captureHz_ || captureHz_ % frequencyHz) return false;
|
||||
captureFrequencyHz_ = frequencyHz;
|
||||
capturePulseNs_ = pulseNs;
|
||||
captureTxLightOn_ = activeTxLightOn;
|
||||
txPulseLightOn_ = activeTxLightOn;
|
||||
pollPeriodCycles_ = captureHz_ / frequencyHz;
|
||||
pollWindowBeforeCycles_ = captureHz_ / 200000U; // 5 us
|
||||
const uint64_t periodNs = 1000000000ULL / frequencyHz;
|
||||
@@ -62,16 +71,16 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs,
|
||||
const uint8_t activeTxRaw = activeTxLightOn ? TX_LIGHT_ON_GPIO_LEVEL :
|
||||
TX_LIGHT_OFF_GPIO_LEVEL;
|
||||
pollTxStartRawHigh_ = activeTxRaw == HIGH;
|
||||
rxActiveRawHigh_ = RX_LIGHT_ON_GPIO_LEVEL == LOW; // ACK/fault = light OFF
|
||||
rxActiveRawHigh_ =
|
||||
((RX_LIGHT_ON_GPIO_LEVEL == HIGH) == activeRxLightOn);
|
||||
|
||||
ackStartMaxTicks_ = nsToTicks(DRIVER_ACK_START_MAX_NS);
|
||||
faultLongTicks_ = nsToTicks(DRIVER_FAULT_MIN_NS);
|
||||
shortCircuitTicks_ = nsToTicks(DRIVER_SHORT_CIRCUIT_MIN_NS);
|
||||
stuckTicks_ = nsToTicks(DRIVER_RX_STUCK_MIN_NS);
|
||||
testTicks_ = static_cast<uint64_t>(captureHz_) * testTimeMs / 1000ULL;
|
||||
subsampleTicks_ = testTicks_ / SUBSAMPLE_COUNT;
|
||||
if (!pollPeriodCycles_ || !pollWindowAfterCycles_ || !ackStartMaxTicks_ ||
|
||||
!faultLongTicks_ || !shortCircuitTicks_ || !stuckTicks_ ||
|
||||
if (!pollPeriodCycles_ || !pollWindowAfterCycles_ ||
|
||||
!ackStartMaxTicks_ || !faultLongTicks_ || !stuckTicks_ ||
|
||||
!testTicks_ || !subsampleTicks_)
|
||||
return false;
|
||||
|
||||
@@ -81,12 +90,23 @@ bool DriverTest::start(uint32_t frequencyHz, uint32_t pulseNs,
|
||||
pendingCount_ = 0;
|
||||
response_ = {};
|
||||
measurementStartTick_ = deadlineTick_ = 0;
|
||||
pointOriginTick_ = lastEventTick_ = 0;
|
||||
settleCycles_ = settleCycles;
|
||||
pointOriginTick_ = lastEventTick_ = lastActiveTxTick_ = 0;
|
||||
// Skip two complete periods after the polling task synchronizes with TX.
|
||||
settleCycles_ = DRIVER_CAPTURE_SYNC_CYCLES;
|
||||
Log::printf("DRIVER", "capture=GPIO-%luMHz sync-periods=%u ACK-timeout=%luns",
|
||||
static_cast<unsigned long>(captureHz_ / 1000000UL),
|
||||
static_cast<unsigned>(settleCycles_),
|
||||
static_cast<unsigned long>(DRIVER_ACK_START_MAX_NS));
|
||||
const uint64_t periodUs =
|
||||
(1000000ULL + frequencyHz - 1ULL) / frequencyHz;
|
||||
settlingTimeoutUs_ = periodUs *
|
||||
(static_cast<uint64_t>(DRIVER_CAPTURE_SYNC_CYCLES) + 2ULL) + 1000ULL;
|
||||
settlingDeadlineUs_ = 0;
|
||||
settledCycles_ = 0;
|
||||
completedSubsamples_ = 0;
|
||||
measurementClosed_ = false;
|
||||
havePointOrigin_ = false;
|
||||
haveLastActiveTx_ = false;
|
||||
rxActive_ = (gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)) != 0) ==
|
||||
rxActiveRawHigh_;
|
||||
currentStep_ = 0;
|
||||
@@ -118,6 +138,8 @@ bool DriverTest::armCapture() {
|
||||
state_ = DriverState::IDLE;
|
||||
return false;
|
||||
}
|
||||
settlingDeadlineUs_ = static_cast<uint64_t>(esp_timer_get_time()) +
|
||||
settlingTimeoutUs_;
|
||||
xTaskNotifyGive(analyzerTask_);
|
||||
return true;
|
||||
}
|
||||
@@ -133,8 +155,11 @@ bool DriverTest::resumeSubsample() {
|
||||
pendingCount_ = 0;
|
||||
response_ = {};
|
||||
measurementStartTick_ = deadlineTick_ = 0;
|
||||
lastActiveTxTick_ = 0;
|
||||
settlingDeadlineUs_ = 0;
|
||||
settledCycles_ = 0;
|
||||
measurementClosed_ = false;
|
||||
haveLastActiveTx_ = false;
|
||||
rxActive_ = (gpio_get_level(static_cast<gpio_num_t>(GPIO_RX)) != 0) ==
|
||||
rxActiveRawHigh_;
|
||||
state_ = DriverState::SETTLING;
|
||||
@@ -147,7 +172,7 @@ void DriverTest::pollTaskEntry(void *context) {
|
||||
static_cast<DriverTest *>(context)->pollTaskLoop();
|
||||
}
|
||||
|
||||
void DriverTest::pollTaskLoop() {
|
||||
void IRAM_ATTR DriverTest::pollTaskLoop() {
|
||||
constexpr uint32_t PIN_MASK = (1UL << GPIO_PWM) | (1UL << GPIO_RX);
|
||||
for (;;) {
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||
@@ -202,6 +227,10 @@ void DriverTest::pollTaskLoop() {
|
||||
portEXIT_CRITICAL(&pollMux_);
|
||||
critical = false;
|
||||
flushHot();
|
||||
// This marker is written only after every TX/RX edge from the completed
|
||||
// sampling window. The analyzer may now safely decide that an ACK was
|
||||
// absent without racing the producer that writes those edges.
|
||||
recordRaw(esp_cpu_get_cycle_count(), false, Source::WINDOW_END);
|
||||
if (!__atomic_load_n(&captureActive_, __ATOMIC_ACQUIRE)) break;
|
||||
nextStart = lastTxStart + pollPeriodCycles_;
|
||||
sawTxStart = false;
|
||||
@@ -248,12 +277,24 @@ void DriverTest::analyzerTaskLoop() {
|
||||
stats_.droppedItems += dropped;
|
||||
fail(FailReason::DATA_LOSS, lastEventTick_);
|
||||
}
|
||||
if (!count && state_ == DriverState::SETTLING &&
|
||||
static_cast<uint64_t>(esp_timer_get_time()) >=
|
||||
settlingDeadlineUs_) {
|
||||
fail(FailReason::ACK_MISSING, lastEventTick_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DriverTest::processEvent(const TimedEvent &event) {
|
||||
lastEventTick_ = event.tick;
|
||||
if (event.source == Source::WINDOW_END) {
|
||||
if (state_ == DriverState::RUNNING) {
|
||||
expirePending(event.tick);
|
||||
if (state_ == DriverState::RUNNING) completeIfPossible(event.tick);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!havePointOrigin_) {
|
||||
pointOriginTick_ = event.tick;
|
||||
havePointOrigin_ = true;
|
||||
@@ -270,12 +311,15 @@ void DriverTest::processSettling(const TimedEvent &event) {
|
||||
}
|
||||
const uint8_t rawLevel = event.rising ? HIGH : LOW;
|
||||
const bool lightOn = rawLevel == TX_LIGHT_ON_GPIO_LEVEL;
|
||||
if (!lightOn) return;
|
||||
if (lightOn != txPulseLightOn_) return;
|
||||
if (settledCycles_ < settleCycles_) {
|
||||
++settledCycles_;
|
||||
return;
|
||||
}
|
||||
if (rxActive_) return;
|
||||
if (rxActive_) {
|
||||
fail(FailReason::DRIVER_FAULT, event.tick);
|
||||
return;
|
||||
}
|
||||
state_ = DriverState::RUNNING;
|
||||
measurementStartTick_ = event.tick;
|
||||
const uint64_t measuredBefore =
|
||||
@@ -284,26 +328,35 @@ void DriverTest::processSettling(const TimedEvent &event) {
|
||||
completedSubsamples_ + 1U == SUBSAMPLE_COUNT ?
|
||||
testTicks_ - measuredBefore : subsampleTicks_;
|
||||
deadlineTick_ = event.tick + thisSubsampleTicks;
|
||||
processTx(event, true);
|
||||
processTx(event, lightOn);
|
||||
}
|
||||
|
||||
void DriverTest::processRunning(const TimedEvent &event) {
|
||||
expirePending(event.tick);
|
||||
if (state_ != DriverState::RUNNING) return;
|
||||
|
||||
if (response_.active && event.tick - response_.startTick >= stuckTicks_) {
|
||||
const uint64_t delay = response_.associated ?
|
||||
response_.startTick - response_.tx.tick : 0;
|
||||
fail(FailReason::SHORT_CIRCUIT_FAULT, event.tick, delay,
|
||||
event.tick - response_.startTick);
|
||||
const uint64_t trigger = haveLastActiveTx_ &&
|
||||
response_.startTick >= lastActiveTxTick_ ?
|
||||
response_.startTick - lastActiveTxTick_ : delay;
|
||||
fail(FailReason::DRIVER_FAULT, event.tick, delay,
|
||||
event.tick - response_.startTick, trigger);
|
||||
return;
|
||||
}
|
||||
if (event.source == Source::TX) {
|
||||
expirePending(event.tick);
|
||||
if (state_ != DriverState::RUNNING) return;
|
||||
const uint8_t rawLevel = event.rising ? HIGH : LOW;
|
||||
const bool lightOn = rawLevel == TX_LIGHT_ON_GPIO_LEVEL;
|
||||
if (event.tick < deadlineTick_) processTx(event, lightOn);
|
||||
else measurementClosed_ = true;
|
||||
} else processRx(event, event.rising == rxActiveRawHigh_);
|
||||
} else {
|
||||
// A delayed fault indication can start after the normal ACK deadline.
|
||||
// Measure the RX pulse before expiring its possible causal TX edge.
|
||||
processRx(event, event.rising == rxActiveRawHigh_);
|
||||
if (state_ != DriverState::RUNNING) return;
|
||||
if (!response_.active) expirePending(event.tick);
|
||||
}
|
||||
if (state_ != DriverState::RUNNING) return;
|
||||
completeIfPossible(event.tick);
|
||||
}
|
||||
|
||||
@@ -318,6 +371,10 @@ bool DriverTest::addPending(uint64_t tick, bool lightOn) {
|
||||
|
||||
void DriverTest::processTx(const TimedEvent &event, bool lightOn) {
|
||||
if (!addPending(event.tick, lightOn)) return;
|
||||
if (lightOn == txPulseLightOn_) {
|
||||
lastActiveTxTick_ = event.tick;
|
||||
haveLastActiveTx_ = true;
|
||||
}
|
||||
++stats_.inputEdges;
|
||||
}
|
||||
|
||||
@@ -358,10 +415,11 @@ void DriverTest::processRx(const TimedEvent &event, bool activeNow) {
|
||||
if (!response_.active) return;
|
||||
const uint64_t width = event.tick - response_.startTick;
|
||||
if (!response_.associated) {
|
||||
const uint64_t trigger = haveLastActiveTx_ &&
|
||||
response_.startTick >= lastActiveTxTick_ ?
|
||||
response_.startTick - lastActiveTxTick_ : 0;
|
||||
response_ = {};
|
||||
fail(width >= shortCircuitTicks_ ? FailReason::SHORT_CIRCUIT_FAULT :
|
||||
FailReason::GATE_MONITOR_FAULT,
|
||||
event.tick, 0, width);
|
||||
fail(FailReason::DRIVER_FAULT, event.tick, trigger, width, trigger);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -370,17 +428,19 @@ void DriverTest::processRx(const TimedEvent &event, bool activeNow) {
|
||||
if (pending_[i].tick > response_.startTick &&
|
||||
event.tick - pending_[i].tick >= guard) {
|
||||
const uint64_t delay = response_.startTick - response_.tx.tick;
|
||||
const uint64_t trigger = event.tick - pending_[i].tick;
|
||||
response_ = {};
|
||||
fail(FailReason::ACK_MERGED, event.tick, delay, width);
|
||||
fail(FailReason::ACK_MERGED, event.tick, delay, width, trigger);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (width >= faultLongTicks_) {
|
||||
const uint64_t delay = response_.startTick - response_.tx.tick;
|
||||
const uint64_t trigger = haveLastActiveTx_ &&
|
||||
response_.startTick >= lastActiveTxTick_ ?
|
||||
response_.startTick - lastActiveTxTick_ : delay;
|
||||
response_ = {};
|
||||
fail(width >= shortCircuitTicks_ ? FailReason::SHORT_CIRCUIT_FAULT :
|
||||
FailReason::GATE_MONITOR_FAULT,
|
||||
event.tick, delay, width);
|
||||
fail(FailReason::DRIVER_FAULT, event.tick, delay, width, trigger);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -424,8 +484,11 @@ void DriverTest::acceptAcknowledgement(uint64_t delay, uint64_t width,
|
||||
|
||||
void DriverTest::expirePending(uint64_t now) {
|
||||
for (uint8_t i = 0; i < pendingCount_; ++i) {
|
||||
if (now <= pending_[i].tick + ackStartMaxTicks_) continue;
|
||||
fail(FailReason::ACK_MISSING, now, now - pending_[i].tick, 0);
|
||||
if (now < pending_[i].tick + ackStartMaxTicks_) continue;
|
||||
// The failure belongs to the ACK deadline itself. A later TX edge or the
|
||||
// end-of-window marker is only the safe moment when absence is confirmed.
|
||||
fail(FailReason::ACK_MISSING,
|
||||
pending_[i].tick + ackStartMaxTicks_, ackStartMaxTicks_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -456,12 +519,18 @@ void DriverTest::completeIfPossible(uint64_t now) {
|
||||
}
|
||||
|
||||
void DriverTest::fail(FailReason reason, uint64_t tick, uint64_t delay,
|
||||
uint64_t pulseWidth) {
|
||||
uint64_t pulseWidth, uint64_t triggerAfterTx) {
|
||||
if (state_ == DriverState::FAIL || state_ == DriverState::PASS) return;
|
||||
if (stats_.reason == FailReason::NONE) {
|
||||
stats_.reason = reason;
|
||||
if (tick && havePointOrigin_ && tick >= pointOriginTick_)
|
||||
stats_.errorElapsedTicks = tick - pointOriginTick_;
|
||||
const uint64_t trigger = triggerAfterTx ? triggerAfterTx : delay;
|
||||
if (trigger) {
|
||||
stats_.errorTriggerTicks = trigger > UINT32_MAX ? UINT32_MAX :
|
||||
static_cast<uint32_t>(trigger);
|
||||
stats_.errorTriggerValid = true;
|
||||
}
|
||||
if (delay) {
|
||||
stats_.errorDelayTicks = delay > UINT32_MAX ? UINT32_MAX :
|
||||
static_cast<uint32_t>(delay);
|
||||
@@ -603,16 +672,20 @@ void DriverTest::printSummary() const {
|
||||
static_cast<unsigned long>(publishedStats_.unexpectedResponses),
|
||||
failName(publishedStats_.reason));
|
||||
if (publishedStats_.reason != FailReason::NONE) {
|
||||
Log::printf("DRIVER",
|
||||
"error timing: T=%lluns D=%s%lluns P=%s%lluns",
|
||||
static_cast<unsigned long long>(
|
||||
ticksToNs(publishedStats_.errorElapsedTicks)),
|
||||
publishedStats_.errorDelayValid ? "" : "N/A/",
|
||||
static_cast<unsigned long long>(
|
||||
ticksToNs(publishedStats_.errorDelayTicks)),
|
||||
publishedStats_.errorPulseValid ? "" : "N/A/",
|
||||
static_cast<unsigned long long>(
|
||||
ticksToNs(publishedStats_.errorPulseTicks)));
|
||||
auto formatOptional = [&](bool valid, uint32_t ticks,
|
||||
char *out, size_t size) {
|
||||
if (!valid) snprintf(out, size, "---");
|
||||
else snprintf(out, size, "%lluns",
|
||||
static_cast<unsigned long long>(ticksToNs(ticks)));
|
||||
};
|
||||
char trigger[24], pulse[24];
|
||||
formatOptional(publishedStats_.errorTriggerValid,
|
||||
publishedStats_.errorTriggerTicks, trigger, sizeof(trigger));
|
||||
formatOptional(publishedStats_.errorPulseValid,
|
||||
publishedStats_.errorPulseTicks, pulse, sizeof(pulse));
|
||||
if (publishedStats_.errorPulseValid)
|
||||
Log::printf("DRIVER", "error timing: T=%s P=%s", trigger, pulse);
|
||||
else Log::printf("DRIVER", "error timing: T=%s", trigger);
|
||||
}
|
||||
printEdge("ON", publishedStats_.turnOn);
|
||||
printEdge("OFF", publishedStats_.turnOff);
|
||||
|
||||
Reference in New Issue
Block a user