добавлена бета проверка драйверов
This commit is contained in:
@@ -40,6 +40,10 @@ bool PulseReceiver::begin() {
|
||||
|
||||
mcpwm_capture_channel_config_t channelConfig = {};
|
||||
channelConfig.gpio_num = GPIO_RX;
|
||||
// ACK pulses are sub-microsecond and consecutive acknowledgements can be
|
||||
// only 1 us apart. A low-priority capture interrupt can leave the channel
|
||||
// status pending long enough for the next timestamp to overwrite it.
|
||||
channelConfig.intr_priority = 3;
|
||||
channelConfig.prescale = 1;
|
||||
channelConfig.flags.pos_edge = true;
|
||||
channelConfig.flags.neg_edge = false;
|
||||
@@ -54,8 +58,13 @@ bool PulseReceiver::begin() {
|
||||
channelConfig.flags.neg_edge = true;
|
||||
if (mcpwm_new_capture_channel(captureTimer_, &channelConfig, &fallingChannel_) != ESP_OK)
|
||||
return false;
|
||||
return mcpwm_capture_channel_register_event_callbacks(
|
||||
fallingChannel_, &callbacks, this) == ESP_OK;
|
||||
if (mcpwm_capture_channel_register_event_callbacks(
|
||||
fallingChannel_, &callbacks, this) != ESP_OK) return false;
|
||||
|
||||
// The TX channel is created immediately before a driver test. Only the end
|
||||
// of the active PWM pulse is armed; handling its start here would occupy the
|
||||
// shared MCPWM ISR during the RX acknowledgement only ~300 ns later.
|
||||
return true;
|
||||
#else
|
||||
pinMode(GPIO_RX, INPUT);
|
||||
cpuTickHz_ = getCpuFrequencyMhz() * 1000000UL;
|
||||
@@ -64,7 +73,8 @@ bool PulseReceiver::begin() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct) {
|
||||
bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct,
|
||||
bool activeLightOn) {
|
||||
if (!plannedTickHz(expectedHz, expectedDutyPct)) return false;
|
||||
expectedHz_ = expectedHz;
|
||||
expectedDutyPct_ = expectedDutyPct;
|
||||
@@ -73,6 +83,64 @@ bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct) {
|
||||
if (!cpuTickHz_) return false;
|
||||
#endif
|
||||
resetStream();
|
||||
const bool rawHighMeansLightOn = RX_LIGHT_ON_GPIO_LEVEL == HIGH;
|
||||
activeStartRising_ = rawHighMeansLightOn == activeLightOn;
|
||||
Log::printf("CAPTURE", "RX active optical level=%s, raw active starts on %s",
|
||||
activeLightOn ? "H/light-on" : "L/light-off",
|
||||
activeStartRising_ ? "RISING" : "FALLING");
|
||||
return startCapture(false);
|
||||
}
|
||||
|
||||
#if OPTICAL_USE_MCPWM_CAPTURE
|
||||
bool PulseReceiver::configureDriverTxCapture(bool risingEdge) {
|
||||
if (running_) return false;
|
||||
if (txChannel_) {
|
||||
if (mcpwm_del_capture_channel(txChannel_) != ESP_OK) return false;
|
||||
txChannel_ = nullptr;
|
||||
}
|
||||
mcpwm_capture_channel_config_t config = {};
|
||||
config.gpio_num = GPIO_PWM;
|
||||
config.intr_priority = 3;
|
||||
config.prescale = 1;
|
||||
config.flags.pos_edge = risingEdge;
|
||||
config.flags.neg_edge = !risingEdge;
|
||||
config.flags.io_loop_back = true;
|
||||
if (mcpwm_new_capture_channel(captureTimer_, &config, &txChannel_) != ESP_OK)
|
||||
return false;
|
||||
mcpwm_capture_event_callbacks_t callbacks = {};
|
||||
callbacks.on_cap = onCapture;
|
||||
return mcpwm_capture_channel_register_event_callbacks(
|
||||
txChannel_, &callbacks, this) == ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool PulseReceiver::startDriver(uint32_t frequencyHz, uint32_t pulseNs,
|
||||
bool activeTxLightOn) {
|
||||
#if OPTICAL_USE_MCPWM_CAPTURE
|
||||
if (!frequencyHz || !pulseNs || !tickHz() || running_) return false;
|
||||
resetStream();
|
||||
const uint64_t pulseTicks =
|
||||
(static_cast<uint64_t>(pulseNs) * tickHz() + 500000000ULL) /
|
||||
1000000000ULL;
|
||||
const uint32_t periodTicks = tickHz() / frequencyHz;
|
||||
if (!pulseTicks || pulseTicks >= periodTicks || pulseTicks > UINT32_MAX)
|
||||
return false;
|
||||
driverPulseTicks_ = static_cast<uint32_t>(pulseTicks);
|
||||
driverReleaseSlackTicks_ = static_cast<uint32_t>(
|
||||
(static_cast<uint64_t>(DRIVER_RESPONSE_TIMEOUT_NS) * tickHz() +
|
||||
999999999ULL) / 1000000000ULL);
|
||||
const uint8_t activeRawLevel = activeTxLightOn ?
|
||||
TX_LIGHT_ON_GPIO_LEVEL : TX_LIGHT_OFF_GPIO_LEVEL;
|
||||
const bool pulseEndIsRising = activeRawLevel == LOW;
|
||||
if (!driverReleaseSlackTicks_ ||
|
||||
!configureDriverTxCapture(pulseEndIsRising)) return false;
|
||||
return startCapture(true);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PulseReceiver::startCapture(bool withTx) {
|
||||
#if OPTICAL_USE_MCPWM_CAPTURE
|
||||
// Progress updates keep one capture session alive. Pulse-width stages stop
|
||||
// capture only after PWM is quiet, so resetStream never races the ISR.
|
||||
@@ -87,15 +155,25 @@ bool PulseReceiver::start(uint32_t expectedHz, float expectedDutyPct) {
|
||||
mcpwm_capture_timer_disable(captureTimer_);
|
||||
return false;
|
||||
}
|
||||
if (withTx && mcpwm_capture_channel_enable(txChannel_) != ESP_OK) {
|
||||
mcpwm_capture_channel_disable(fallingChannel_);
|
||||
mcpwm_capture_channel_disable(risingChannel_);
|
||||
mcpwm_capture_timer_disable(captureTimer_);
|
||||
return false;
|
||||
}
|
||||
txCaptureEnabled_ = withTx;
|
||||
running_ = true;
|
||||
if (mcpwm_capture_timer_start(captureTimer_) != ESP_OK) {
|
||||
running_ = false;
|
||||
if (txCaptureEnabled_) mcpwm_capture_channel_disable(txChannel_);
|
||||
txCaptureEnabled_ = false;
|
||||
mcpwm_capture_channel_disable(fallingChannel_);
|
||||
mcpwm_capture_channel_disable(risingChannel_);
|
||||
mcpwm_capture_timer_disable(captureTimer_);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (withTx) return false;
|
||||
running_ = true;
|
||||
#endif
|
||||
return true;
|
||||
@@ -106,7 +184,9 @@ void PulseReceiver::stop() {
|
||||
running_ = false;
|
||||
#if OPTICAL_USE_MCPWM_CAPTURE
|
||||
if (wasRunning) {
|
||||
// Mask both edge interrupts before stopping the shared capture timer.
|
||||
// Mask capture interrupts before stopping the shared capture timer.
|
||||
if (txCaptureEnabled_) mcpwm_capture_channel_disable(txChannel_);
|
||||
txCaptureEnabled_ = false;
|
||||
mcpwm_capture_channel_disable(fallingChannel_);
|
||||
mcpwm_capture_channel_disable(risingChannel_);
|
||||
mcpwm_capture_timer_stop(captureTimer_);
|
||||
@@ -120,10 +200,15 @@ void PulseReceiver::stop() {
|
||||
void PulseReceiver::resetStream() {
|
||||
if (queue_) xQueueReset(queue_);
|
||||
haveReorderEdge_ = false;
|
||||
__atomic_store_n(&driverRingWrite_, 0U, __ATOMIC_RELEASE);
|
||||
__atomic_store_n(&driverRingRead_, 0U, __ATOMIC_RELEASE);
|
||||
haveLastDriverEdge_ = false;
|
||||
lastDriverEdge_ = {};
|
||||
driverPulseTicks_ = 0;
|
||||
driverReleaseSlackTicks_ = 0;
|
||||
droppedItems_ = 0;
|
||||
polarityKnown_ = false;
|
||||
activeStartRising_ = false;
|
||||
syncEdgeCount_ = 0;
|
||||
waitingForActiveEnd_ = true;
|
||||
activeStart_ = activeEnd_ = 0;
|
||||
haveRawTick_ = false;
|
||||
@@ -159,39 +244,13 @@ bool PulseReceiver::consumeEdge(const Edge &rawEdge, PulsePeriod &out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
syncEdges_[syncEdgeCount_++] = edge;
|
||||
if (syncEdgeCount_ < 3U) return false;
|
||||
|
||||
const uint64_t firstTicks = syncEdges_[1].tick - syncEdges_[0].tick;
|
||||
const uint64_t secondTicks = syncEdges_[2].tick - syncEdges_[1].tick;
|
||||
const double expectedTicks = static_cast<double>(tickHz()) * expectedDutyPct_ /
|
||||
(100.0 * expectedHz_);
|
||||
const double firstError = fabs(static_cast<double>(firstTicks) - expectedTicks);
|
||||
const double secondError = fabs(static_cast<double>(secondTicks) - expectedTicks);
|
||||
activeStartRising_ = firstError <= secondError ? syncEdges_[0].rising : syncEdges_[1].rising;
|
||||
// HH/HL/LH/LL defines the active optical state explicitly. Synchronize on
|
||||
// its physical starting edge instead of guessing polarity from pulse width.
|
||||
if (edge.rising != activeStartRising_) return false;
|
||||
polarityKnown_ = true;
|
||||
Log::printf("CAPTURE", "RX polarity auto: active starts on %s, first=%lluns second=%lluns",
|
||||
activeStartRising_ ? "RISING" : "FALLING",
|
||||
static_cast<unsigned long long>(firstTicks * 1000000000ULL / tickHz()),
|
||||
static_cast<unsigned long long>(secondTicks * 1000000000ULL / tickHz()));
|
||||
|
||||
bool produced = false;
|
||||
if (firstError <= secondError) {
|
||||
activeStart_ = syncEdges_[0].tick;
|
||||
activeEnd_ = syncEdges_[1].tick;
|
||||
const uint64_t periodTicks = syncEdges_[2].tick - activeStart_;
|
||||
out = {activeStart_, static_cast<uint32_t>(periodTicks),
|
||||
static_cast<uint32_t>(activeEnd_ - activeStart_), tickHz()};
|
||||
activeStart_ = syncEdges_[2].tick;
|
||||
waitingForActiveEnd_ = true;
|
||||
produced = true;
|
||||
} else {
|
||||
activeStart_ = syncEdges_[1].tick;
|
||||
activeEnd_ = syncEdges_[2].tick;
|
||||
waitingForActiveEnd_ = false;
|
||||
}
|
||||
syncEdgeCount_ = 0;
|
||||
return produced;
|
||||
activeStart_ = edge.tick;
|
||||
waitingForActiveEnd_ = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t PulseReceiver::takeDroppedItems() {
|
||||
@@ -199,13 +258,45 @@ uint32_t PulseReceiver::takeDroppedItems() {
|
||||
}
|
||||
|
||||
#if OPTICAL_USE_MCPWM_CAPTURE
|
||||
bool IRAM_ATTR PulseReceiver::onCapture(mcpwm_cap_channel_handle_t,
|
||||
bool IRAM_ATTR PulseReceiver::onCapture(mcpwm_cap_channel_handle_t channel,
|
||||
const mcpwm_capture_event_data_t *data,
|
||||
void *ctx) {
|
||||
PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
|
||||
if (!self->running_) return false;
|
||||
const bool rawRising = data->cap_edge == MCPWM_CAP_EDGE_POS;
|
||||
const Edge edge = {data->cap_value, static_cast<uint8_t>(rawRising)};
|
||||
const Edge edge = {data->cap_value, static_cast<uint8_t>(rawRising),
|
||||
static_cast<uint8_t>(channel == self->txChannel_ ? CaptureSource::TX :
|
||||
CaptureSource::RX)};
|
||||
if (self->txCaptureEnabled_) {
|
||||
// Three capture channels are independent ISR producers. Serialize their
|
||||
// reservation/publication of a ring slot; treating this as an SPSC ring
|
||||
// loses or duplicates RX events when TX and RX interrupts overlap.
|
||||
portENTER_CRITICAL_ISR(&self->driverRingMux_);
|
||||
if (self->haveLastDriverEdge_ &&
|
||||
self->lastDriverEdge_.tick == edge.tick &&
|
||||
self->lastDriverEdge_.rising == edge.rising &&
|
||||
self->lastDriverEdge_.source == edge.source) {
|
||||
// The same channel callback can be delivered twice while several MCPWM
|
||||
// capture status bits are pending. Two physical edges cannot have the
|
||||
// same source, direction and 12.5 ns hardware timestamp.
|
||||
portEXIT_CRITICAL_ISR(&self->driverRingMux_);
|
||||
return false;
|
||||
}
|
||||
const uint16_t write = __atomic_load_n(
|
||||
&self->driverRingWrite_, __ATOMIC_RELAXED);
|
||||
const uint16_t next = static_cast<uint16_t>(
|
||||
(write + 1U) % DRIVER_RING_CAPACITY);
|
||||
if (next == __atomic_load_n(&self->driverRingRead_, __ATOMIC_ACQUIRE)) {
|
||||
__atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
|
||||
} else {
|
||||
self->driverRing_[write] = edge;
|
||||
self->lastDriverEdge_ = edge;
|
||||
self->haveLastDriverEdge_ = true;
|
||||
__atomic_store_n(&self->driverRingWrite_, next, __ATOMIC_RELEASE);
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&self->driverRingMux_);
|
||||
return false;
|
||||
}
|
||||
BaseType_t wake = pdFALSE;
|
||||
if (xQueueSendFromISR(self->queue_, &edge, &wake) != pdTRUE)
|
||||
__atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
|
||||
@@ -215,9 +306,9 @@ bool IRAM_ATTR PulseReceiver::onCapture(mcpwm_cap_channel_handle_t,
|
||||
void IRAM_ATTR PulseReceiver::onGpio(void *ctx) {
|
||||
PulseReceiver *self = static_cast<PulseReceiver *>(ctx);
|
||||
if (!self->running_) return;
|
||||
bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX));
|
||||
if (RX_ACTIVE_LEVEL == LOW) level = !level;
|
||||
const Edge edge = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level)};
|
||||
const bool level = gpio_get_level(static_cast<gpio_num_t>(GPIO_RX));
|
||||
const Edge edge = {esp_cpu_get_cycle_count(), static_cast<uint8_t>(level),
|
||||
static_cast<uint8_t>(CaptureSource::RX)};
|
||||
BaseType_t wake = pdFALSE;
|
||||
if (xQueueSendFromISR(self->queue_, &edge, &wake) != pdTRUE)
|
||||
__atomic_fetch_add(&self->droppedItems_, 1U, __ATOMIC_RELAXED);
|
||||
@@ -256,3 +347,84 @@ size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity,
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
size_t PulseReceiver::readEvents(CaptureEvent *events, size_t capacity,
|
||||
TickType_t waitTicks) {
|
||||
if (!events || capacity < 2U || !txCaptureEnabled_ || !driverPulseTicks_ ||
|
||||
!driverReleaseSlackTicks_) return 0;
|
||||
|
||||
constexpr size_t MAX_BATCH = 64;
|
||||
const size_t limit = capacity < MAX_BATCH ? capacity : MAX_BATCH;
|
||||
Edge ordered[MAX_BATCH] = {};
|
||||
|
||||
uint16_t read = __atomic_load_n(&driverRingRead_, __ATOMIC_RELAXED);
|
||||
if (read == __atomic_load_n(&driverRingWrite_, __ATOMIC_ACQUIRE) && waitTicks) {
|
||||
vTaskDelay(waitTicks);
|
||||
read = __atomic_load_n(&driverRingRead_, __ATOMIC_RELAXED);
|
||||
}
|
||||
// Work on one immutable producer snapshot. RX belonging to a pulse start is
|
||||
// deliberately retained until that pulse's captured end arrives: only then
|
||||
// can the missing start interrupt be reconstructed and sorted before RX.
|
||||
const uint16_t write = __atomic_load_n(&driverRingWrite_, __ATOMIC_ACQUIRE);
|
||||
uint16_t scan = read;
|
||||
bool haveTxEnd = false;
|
||||
uint32_t lastTxEnd = 0;
|
||||
size_t projectedCount = 0;
|
||||
while (scan != write) {
|
||||
const Edge &edge = driverRing_[scan];
|
||||
const size_t needed = edge.source == static_cast<uint8_t>(CaptureSource::TX)
|
||||
? 2U : 1U;
|
||||
if (projectedCount + needed > limit) break;
|
||||
projectedCount += needed;
|
||||
if (edge.source == static_cast<uint8_t>(CaptureSource::TX)) {
|
||||
lastTxEnd = edge.tick;
|
||||
haveTxEnd = true;
|
||||
}
|
||||
scan = static_cast<uint16_t>((scan + 1U) % DRIVER_RING_CAPACITY);
|
||||
}
|
||||
if (!haveTxEnd) {
|
||||
if (waitTicks) vTaskDelay(waitTicks);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint32_t releaseThrough = lastTxEnd + driverReleaseSlackTicks_;
|
||||
size_t count = 0;
|
||||
while (read != write) {
|
||||
const Edge edge = driverRing_[read];
|
||||
if (edge.source == static_cast<uint8_t>(CaptureSource::RX) &&
|
||||
static_cast<int32_t>(edge.tick - releaseThrough) > 0)
|
||||
break;
|
||||
const size_t needed = edge.source == static_cast<uint8_t>(CaptureSource::TX)
|
||||
? 2U : 1U;
|
||||
if (count + needed > limit) break;
|
||||
read = static_cast<uint16_t>((read + 1U) % DRIVER_RING_CAPACITY);
|
||||
if (edge.source == static_cast<uint8_t>(CaptureSource::TX)) {
|
||||
Edge pulseStart = edge;
|
||||
pulseStart.tick -= driverPulseTicks_;
|
||||
pulseStart.rising = !edge.rising;
|
||||
ordered[count++] = pulseStart;
|
||||
}
|
||||
ordered[count++] = edge;
|
||||
}
|
||||
__atomic_store_n(&driverRingRead_, read, __ATOMIC_RELEASE);
|
||||
|
||||
// MCPWM channels share one timer but their callbacks can be dispatched in
|
||||
// channel order when several interrupts are pending. Restore the hardware
|
||||
// order inside the captured batch using the common timestamp.
|
||||
for (size_t i = 1; i < count; ++i) {
|
||||
const Edge key = ordered[i];
|
||||
size_t j = i;
|
||||
while (j && static_cast<int32_t>(ordered[j - 1].tick - key.tick) > 0) {
|
||||
ordered[j] = ordered[j - 1];
|
||||
--j;
|
||||
}
|
||||
ordered[j] = key;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
const TimedEdge timed = extendEdge(ordered[i]);
|
||||
events[i] = {timed.tick, timed.rising,
|
||||
static_cast<CaptureSource>(ordered[i].source)};
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user