From f52f65686d64e4ece87b61d533eb0324ba2c914d Mon Sep 17 00:00:00 2001 From: Razvalyaev Date: Thu, 6 Aug 2026 15:53:32 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D1=81=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D1=8B=D1=87?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9=20=D0=BD=D0=B0=201=D0=9C=D0=93=D1=86=202%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpticalChannelTester/App.cpp | 45 ++++++++-------- OpticalChannelTester/App.h | 2 - OpticalChannelTester/Core.cpp | 76 +++++++++++++++++++++++++++- OpticalChannelTester/Core.h | 15 +++++- OpticalChannelTester/Display.cpp | 9 ++-- OpticalChannelTester/Measurement.cpp | 28 +++++++--- OpticalChannelTester/Measurement.h | 6 ++- OpticalChannelTester/Pwm.cpp | 62 +++++++++++++++++++---- OpticalChannelTester/Receiver.cpp | 13 ++--- OpticalChannelTester/Receiver.h | 4 +- README.md | 2 +- 11 files changed, 207 insertions(+), 55 deletions(-) diff --git a/OpticalChannelTester/App.cpp b/OpticalChannelTester/App.cpp index 62b0854..089de8f 100644 --- a/OpticalChannelTester/App.cpp +++ b/OpticalChannelTester/App.cpp @@ -92,12 +92,9 @@ void App::update() { } if (state_ == AppState::SOLO_MEASURE) { const MeasureState ms = measurement_.update(); - if (ms == MeasureState::FAIL) { printStageStats(measurement_.stats(), requestedHz_); finish(false, measurement_.reason()); } + if (ms == MeasureState::FAIL) { printStageStats(measurement_.stats(), actual_.actualHz); finish(false, measurement_.reason()); } else if (ms == MeasureState::PASS) { - printStageStats(measurement_.stats(), requestedHz_); - if (measurement_.reason() == FailReason::DATA_LOST) { - sweepHadDataLoss_ = true; if (!firstDataLossHz_) firstDataLossHz_ = requestedHz_; - } + printStageStats(measurement_.stats(), actual_.actualHz); stagePassed(); } } else if (state_ == AppState::MASTER_DISCOVER || state_ == AppState::MASTER_WAIT_READY || @@ -156,13 +153,18 @@ void App::showMenu() { void App::startTest() { params_ = store_.params(settings_); stageCount_ = frequencyPointCount(params_.startHz, params_.endHz, params_.stepHz); - stageIndex_ = 0; pendingReason_ = FailReason::NONE; sweepHadDataLoss_ = false; firstDataLossHz_ = 0; + stageIndex_ = 0; pendingReason_ = FailReason::NONE; if (!stageCount_) { finish(false, FailReason::UNSUPPORTED); return; } Log::printf("TEST", "starting role=%s stages=%lu", roleName(static_cast(settings_.role)), stageCount_); - if (SERIAL_MINIMAL_LOG) - Log::printf("CONFIG", "mode=%s range=%lu..%luHz step=%luHz accuracy=%.2f%% time=%lums repeats=%u duty=%u%% stages=%lu", - roleName(static_cast(settings_.role)), params_.startHz, params_.endHz, params_.stepHz, + if (SERIAL_MINIMAL_LOG) { + char startText[12], endText[12], stepText[12]; + Display::formatFrequency(params_.startHz, startText, sizeof(startText)); + Display::formatFrequency(params_.endHz, endText, sizeof(endText)); + Display::formatFrequency(params_.stepHz, stepText, sizeof(stepText)); + Log::printf("CONFIG", "mode=%s range=%s..%s step=%s accuracy=%.2f%% time=%lums repeats=%u duty=%u%% stages=%lu", + roleName(static_cast(settings_.role)), startText, endText, stepText, params_.accuracyPct, params_.testTimeMs, params_.repeats, params_.dutyPct, stageCount_); + } printConfiguration(); const Role role = static_cast(settings_.role); if (role == Role::SOLO) { @@ -214,11 +216,7 @@ bool App::startLocalMeasurement(float hz, float duty) { void App::stagePassed() { Log::printf("TEST", "stage %lu/%lu PASS; PWM stopping", stageIndex_ + 1, stageCount_); pwm_.stop(); - if (++stageIndex_ >= stageCount_) { - if (sweepHadDataLoss_) { requestedHz_ = firstDataLossHz_; finish(false, FailReason::DATA_LOST); } - else finish(true, FailReason::NONE); - return; - } + if (++stageIndex_ >= stageCount_) { finish(true, FailReason::NONE); return; } if (static_cast(settings_.role) == Role::SOLO) { if (prepareStage()) state_ = AppState::SOLO_MEASURE; } else if (static_cast(settings_.role) == Role::MASTER) { requestedHz_ = frequencyAt(params_.startHz, params_.endHz, params_.stepHz, stageIndex_); @@ -329,7 +327,7 @@ void App::updateSlave() { if (state_ == AppState::SLAVE_MEASURE) { const MeasureState ms = measurement_.update(); if (ms != MeasureState::PASS && ms != MeasureState::FAIL) return; - printStageStats(measurement_.stats(), requestedHz_); + printStageStats(measurement_.stats(), actual_.actualHz); pendingPacket_ = makePacket(MessageType::RESULT); pendingPacket_.passed = ms == MeasureState::PASS && measurement_.reason() == FailReason::NONE; pendingPacket_.reason = static_cast(measurement_.reason()); pendingPacket_.periods = measurement_.stats().periods; @@ -357,8 +355,11 @@ void App::finish(bool pass, FailReason reason) { if (state_ != AppState::IDLE && state_ != AppState::MENU) radio_.end(); state_ = AppState::FINISHED; pendingReason_ = reason; char one[24]; - if (pass) { snprintf(one, sizeof(one), "PASS %luHz-%lu", params_.startHz, params_.endHz); display_.show(one, "START=REPEAT"); } - else { snprintf(one, sizeof(one), "FAIL AT %lu", requestedHz_); display_.show(one, failName(reason)); } + if (pass) display_.show("PASS", "REPEAT"); + else { + char frequency[12]; Display::formatFrequency(requestedHz_, frequency, sizeof(frequency)); + snprintf(one, sizeof(one), "FAIL %s", frequency); display_.show(one, failName(reason)); + } } void App::printConfiguration() { @@ -388,9 +389,11 @@ void App::printStageStats(const StageStats &s, uint32_t hz) { if (!s.periods) return; const float measuredHz = static_cast(receiver_.tickHz()) * s.periods / s.periodSum; const float measuredDuty = 100.0f * s.activeSum / s.periodSum; - const char *status = s.reason == FailReason::NONE ? "PASS" : - (s.reason == FailReason::DATA_LOST ? "DATA_LOST" : "FAIL"); - Log::printf("RESULT", "%luHz %s periods=%lu measured=%.2fHz duty=%.2f%% lost=%lu%s%s", - hz, status, s.periods, measuredHz, measuredDuty, s.lostItems, + char requestedText[12], measuredText[12]; + Display::formatFrequency(hz, requestedText, sizeof(requestedText)); + Display::formatFrequency(measuredHz, measuredText, sizeof(measuredText)); + const char *status = s.reason == FailReason::NONE ? "PASS" : "FAIL"; + Log::printf("RESULT", "%s %s periods=%lu measured=%s duty=%.2f%% skipped=%lu%s%s", + requestedText, status, s.periods, measuredText, measuredDuty, s.droppedItems, s.reason == FailReason::NONE ? "" : " reason=", s.reason == FailReason::NONE ? "" : failName(s.reason)); } diff --git a/OpticalChannelTester/App.h b/OpticalChannelTester/App.h index 922365c..52c3035 100644 --- a/OpticalChannelTester/App.h +++ b/OpticalChannelTester/App.h @@ -64,7 +64,5 @@ class App { uint8_t retries_ = 0; ProtocolPacket pendingPacket_ = {}; bool initialized_ = false, bootResetCandidate_ = false; - bool sweepHadDataLoss_ = false; - uint32_t firstDataLossHz_ = 0; uint32_t bootCheckStartedMs_ = 0; }; diff --git a/OpticalChannelTester/Core.cpp b/OpticalChannelTester/Core.cpp index 5bb5d4e..f97732e 100644 --- a/OpticalChannelTester/Core.cpp +++ b/OpticalChannelTester/Core.cpp @@ -11,7 +11,7 @@ const char *roleName(Role r) { const char *failName(FailReason r) { static const char *names[] = {"NONE", "NO SIGNAL", "PERIOD OUT", "DUTY OUT", "EXTRA EDGE", "GLITCH", "LOST EDGE", "TOO FEW PERIODS", "LINK LOST", - "UNSUPPORTED", "RESOLUTION", "ABORTED", "DATA LOST"}; + "UNSUPPORTED", "RESOLUTION", "ABORTED"}; const uint8_t i = static_cast(r); return i < (sizeof(names) / sizeof(names[0])) ? names[i] : "UNKNOWN"; } @@ -72,6 +72,80 @@ uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, return bits; } +uint8_t chooseStablePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, + uint8_t maxBits, uint8_t dutyPct) { + const uint8_t fallback = choosePwmResolution(frequencyHz, sourceClockHz, maxBits); + if (!fallback || dutyPct > 100U) return fallback; + for (uint8_t bits = fallback; bits > 0; --bits) { + const uint32_t levels = 1UL << bits; + const uint64_t denominator = static_cast(frequencyHz) * levels; + if (denominator > sourceClockHz || sourceClockHz % denominator) continue; + const uint32_t divider = static_cast(sourceClockHz / denominator); + if (!divider || divider > 1024U) continue; + if ((static_cast(levels) * dutyPct) % 100U == 0U) return bits; + } + return fallback; +} + +bool chooseIntegerPwmConfig(uint32_t requestedHz, uint32_t sourceClockHz, + uint8_t maxBits, uint8_t dutyPct, + IntegerPwmConfig &config) { + if (!requestedHz || !sourceClockHz || !maxBits || dutyPct > 100U) return false; + + bool found = false; + uint64_t bestError = 0; + uint32_t bestDenominator = 1; + uint32_t bestDutyError = 0; + uint32_t bestLevels = 1; + + for (uint8_t bits = 1; bits <= maxBits && bits < 31; ++bits) { + const uint32_t levels = 1UL << bits; + const uint64_t requestedProduct = static_cast(requestedHz) * levels; + uint32_t lowerDivider = static_cast(sourceClockHz / requestedProduct); + if (lowerDivider < 1U) lowerDivider = 1U; + if (lowerDivider > 1023U) lowerDivider = 1023U; + + const uint32_t candidates[] = {lowerDivider, + lowerDivider < 1023U ? lowerDivider + 1U : lowerDivider}; + for (uint8_t candidate = 0; candidate < 2; ++candidate) { + const uint32_t divider = candidates[candidate]; + if (candidate && divider == candidates[0]) continue; + const uint32_t denominator = levels * divider; + const uint64_t targetClock = static_cast(requestedHz) * denominator; + const uint64_t error = targetClock > sourceClockHz + ? targetClock - sourceClockHz : sourceClockHz - targetClock; + const uint32_t dutyCount = (static_cast(levels) * dutyPct + 50U) / 100U; + const uint32_t representedDuty = dutyCount * 100U; + const uint32_t requestedDuty = levels * dutyPct; + const uint32_t dutyError = representedDuty > requestedDuty + ? representedDuty - requestedDuty : requestedDuty - representedDuty; + + const bool frequencyBetter = !found || + error * bestDenominator < bestError * denominator; + const bool frequencyEqual = found && + error * bestDenominator == bestError * denominator; + const bool dutyBetter = frequencyEqual && + static_cast(dutyError) * bestLevels < + static_cast(bestDutyError) * levels; + const bool dutyEqual = frequencyEqual && + static_cast(dutyError) * bestLevels == + static_cast(bestDutyError) * levels; + if (!frequencyBetter && !dutyBetter && !(dutyEqual && bits > config.bits)) continue; + + config.actualHz = static_cast( + (static_cast(sourceClockHz) + denominator / 2U) / denominator); + config.divider = static_cast(divider); + config.bits = bits; + bestError = error; + bestDenominator = denominator; + bestDutyError = dutyError; + bestLevels = levels; + found = true; + } + } + return found; +} + FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct, uint32_t captureHz, uint8_t pwmBits) { if (!frequencyHz || !captureHz || !pwmBits) return FailReason::RESOLUTION; diff --git a/OpticalChannelTester/Core.h b/OpticalChannelTester/Core.h index 4646638..202f628 100644 --- a/OpticalChannelTester/Core.h +++ b/OpticalChannelTester/Core.h @@ -6,7 +6,7 @@ enum class Role : uint8_t { SOLO, MASTER, SLAVE }; enum class FailReason : uint8_t { NONE, NO_SIGNAL, PERIOD_OUT, DUTY_OUT, EXTRA_EDGE, GLITCH, LOST_EDGE, - TOO_FEW_PERIODS, LINK_LOST, UNSUPPORTED, RESOLUTION, ABORTED, DATA_LOST + TOO_FEW_PERIODS, LINK_LOST, UNSUPPORTED, RESOLUTION, ABORTED }; const char *roleName(Role role); @@ -53,7 +53,7 @@ struct StageStats { uint8_t firstBadRepeat; float badFrequency; float badDuty; - uint32_t lostItems; + uint32_t droppedItems; FailReason reason; void reset(); }; @@ -65,6 +65,12 @@ struct PeriodLimits { uint32_t maxDutyX100; }; +struct IntegerPwmConfig { + uint32_t actualHz; + uint16_t divider; + uint8_t bits; +}; + uint32_t settingsChecksum(const Settings &s); uint32_t frequencyPointCount(uint32_t startHz, uint32_t endHz, uint32_t stepHz); uint32_t frequencyAt(uint32_t startHz, uint32_t endHz, uint32_t stepHz, uint32_t index); @@ -73,6 +79,11 @@ bool periodWithin(float measuredHz, float expectedHz, float tolerancePct); bool dutyWithin(float measuredPct, float expectedPct, float tolerancePct); uint8_t choosePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, uint8_t maxBits); +uint8_t chooseStablePwmResolution(uint32_t frequencyHz, uint32_t sourceClockHz, + uint8_t maxBits, uint8_t dutyPct); +bool chooseIntegerPwmConfig(uint32_t requestedHz, uint32_t sourceClockHz, + uint8_t maxBits, uint8_t dutyPct, + IntegerPwmConfig &config); FailReason validateResolution(uint32_t frequencyHz, float dutyPct, float accuracyPct, uint32_t captureResolutionHz, uint8_t pwmBits); FailReason evaluatePeriod(const PulsePeriod &period, uint32_t tickHz, float expectedHz, diff --git a/OpticalChannelTester/Display.cpp b/OpticalChannelTester/Display.cpp index 03b916f..6287371 100644 --- a/OpticalChannelTester/Display.cpp +++ b/OpticalChannelTester/Display.cpp @@ -35,9 +35,12 @@ void Display::show(const char *a, const char *b) { } void Display::formatFrequency(float hz, char *out, size_t n) { - if (hz >= 1000000.0f) snprintf(out, n, "%.2fM", hz / 1000000.0f); - else if (hz >= 1000.0f) snprintf(out, n, "%.2fk", hz / 1000.0f); - else snprintf(out, n, "%.0fHz", hz); + float value = hz; const char *suffix = "Hz"; + if (hz >= 1000000.0f) { value = hz / 1000000.0f; suffix = "M"; } + else if (hz >= 1000.0f) { value = hz / 1000.0f; suffix = "k"; } + if (fabsf(value - roundf(value)) < 0.005f) snprintf(out, n, "%.0f%s", value, suffix); + else if (fabsf(value * 10.0f - roundf(value * 10.0f)) < 0.005f) snprintf(out, n, "%.1f%s", value, suffix); + else snprintf(out, n, "%.2f%s", value, suffix); } void Display::formatDuration(uint64_t us, char *out, size_t n) { diff --git a/OpticalChannelTester/Measurement.cpp b/OpticalChannelTester/Measurement.cpp index 2682c35..605b14f 100644 --- a/OpticalChannelTester/Measurement.cpp +++ b/OpticalChannelTester/Measurement.cpp @@ -4,6 +4,7 @@ bool Measurement::start(float hz, float duty, float tolerance, uint32_t timeMs, uint8_t repeats, uint8_t settleCycles) { + if (!task_ && xTaskCreate(taskEntry, "optical-rx", 4096, this, 4, &task_) != pdPASS) return false; expectedHz_ = static_cast(hz + 0.5f); if (!expectedHz_ || !timeMs || !repeats || repeats > 10 || !makePeriodLimits(expectedHz_, duty, tolerance, receiver_.tickHz(), limits_) || @@ -15,7 +16,20 @@ bool Measurement::start(float hz, float duty, float tolerance, uint32_t timeMs, currentRepeat_ = 0; expectedPeriodMs_ = static_cast((1000ULL + expectedHz_ - 1U) / expectedHz_); if (!expectedPeriodMs_) expectedPeriodMs_ = 1; - state_ = MeasureState::SETTLING; return true; + state_ = MeasureState::SETTLING; + xTaskNotifyGive(task_); + return true; +} + +void Measurement::taskEntry(void *context) { + static_cast(context)->taskLoop(); +} + +void Measurement::taskLoop() { + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + while (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING) processOnce(); + } } void Measurement::fail(FailReason reason) { @@ -25,25 +39,25 @@ void Measurement::fail(FailReason reason) { void Measurement::completeWindow() { receiver_.stop(); - stats_.lostItems += receiver_.takeDroppedItems(); + stats_.droppedItems += receiver_.takeDroppedItems(); if (receiver_.overflowed()) { fail(FailReason::GLITCH); return; } for (uint8_t i = 0; i < repeats_; ++i) if (!repeatPeriods_[i]) { fail(FailReason::TOO_FEW_PERIODS); return; } - if (stats_.lostItems && stats_.reason == FailReason::NONE) stats_.reason = FailReason::DATA_LOST; state_ = MeasureState::PASS; } -MeasureState Measurement::update() { +MeasureState Measurement::processOnce() { if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_; if (receiver_.overflowed()) { fail(FailReason::GLITCH); return state_; } bool receivedPeriod = false; for (;;) { - const size_t periodCount = receiver_.readPeriods(periodBatch_, PERIOD_BATCH_SIZE); - stats_.lostItems += receiver_.takeDroppedItems(); + const size_t periodCount = receiver_.readPeriods(periodBatch_, PERIOD_BATCH_SIZE, pdMS_TO_TICKS(2)); + stats_.droppedItems += receiver_.takeDroppedItems(); if (!periodCount) break; receivedPeriod = true; for (size_t periodIndex = 0; periodIndex < periodCount; ++periodIndex) { + if (state_ != MeasureState::SETTLING && state_ != MeasureState::RUNNING) return state_; const PulsePeriod &period = periodBatch_[periodIndex]; if (state_ == MeasureState::SETTLING) { if (settleLeft_) --settleLeft_; @@ -86,4 +100,6 @@ MeasureState Measurement::update() { return state_; } +MeasureState Measurement::update() { return state_; } + void Measurement::abort() { if (state_ == MeasureState::SETTLING || state_ == MeasureState::RUNNING) fail(FailReason::ABORTED); } diff --git a/OpticalChannelTester/Measurement.h b/OpticalChannelTester/Measurement.h index 944d688..a9f4c98 100644 --- a/OpticalChannelTester/Measurement.h +++ b/OpticalChannelTester/Measurement.h @@ -14,10 +14,14 @@ class Measurement { FailReason reason() const { return stats_.reason; } const StageStats &stats() const { return stats_; } private: + static void taskEntry(void *context); + void taskLoop(); + MeasureState processOnce(); void fail(FailReason reason); void completeWindow(); PulseReceiver &receiver_; - MeasureState state_ = MeasureState::IDLE; + volatile MeasureState state_ = MeasureState::IDLE; + TaskHandle_t task_ = nullptr; StageStats stats_ = {}; PeriodLimits limits_ = {}; uint32_t expectedHz_ = 0; diff --git a/OpticalChannelTester/Pwm.cpp b/OpticalChannelTester/Pwm.cpp index b9fa72c..caecd77 100644 --- a/OpticalChannelTester/Pwm.cpp +++ b/OpticalChannelTester/Pwm.cpp @@ -1,6 +1,28 @@ #include "Pwm.h" #include "Config.h" #include "Core.h" +#include + +namespace { +constexpr ledc_mode_t PWM_SPEED_MODE = LEDC_LOW_SPEED_MODE; +constexpr ledc_timer_t PWM_TIMER = LEDC_TIMER_0; + +void setIntegerDivider(uint16_t divider) { + ledc_dev_t *hardware = LEDC_LL_GET_HW(); + ledc_ll_timer_pause(hardware, PWM_SPEED_MODE, PWM_TIMER); + ledc_ll_set_clock_divider(hardware, PWM_SPEED_MODE, PWM_TIMER, + static_cast(divider) << LEDC_LL_FRACTIONAL_BITS); + ledc_ll_timer_rst(hardware, PWM_SPEED_MODE, PWM_TIMER); + ledc_ll_ls_timer_update(hardware, PWM_SPEED_MODE, PWM_TIMER); + ledc_ll_timer_resume(hardware, PWM_SPEED_MODE, PWM_TIMER); +} + +bool integerDividerIsSet(uint16_t expected) { + uint32_t rawDivider = 0; + ledc_ll_get_clock_divider(LEDC_LL_GET_HW(), PWM_SPEED_MODE, PWM_TIMER, &rawDivider); + return rawDivider == (static_cast(expected) << LEDC_LL_FRACTIONAL_BITS); +} +} void PwmGenerator::begin() { // Match LEDC_SOURCE_CLOCK_HZ and make the timer calculation deterministic. @@ -10,18 +32,38 @@ void PwmGenerator::begin() { } bool PwmGenerator::start(uint32_t hz, uint8_t dutyPct, ActualPwm &a) { - stop(); - const uint8_t bits = choosePwmResolution(hz, LEDC_SOURCE_CLOCK_HZ, LEDC_MAX_BITS); - if (!bits) return false; - if (!ledcAttachChannel(GPIO_PWM, hz, bits, LEDC_CHANNEL)) return false; + IntegerPwmConfig config = {}; + if (!chooseIntegerPwmConfig(hz, LEDC_SOURCE_CLOCK_HZ, LEDC_MAX_BITS, dutyPct, config)) return false; + const uint8_t bits = config.bits; const uint32_t levels = 1UL << bits; const uint32_t duty = (static_cast(levels) * dutyPct + 50U) / 100U; - if (!ledcWriteChannel(LEDC_CHANNEL, duty)) { ledcDetach(GPIO_PWM); return false; } - const uint32_t actualHz = ledcReadFreq(GPIO_PWM); - if (!actualHz) { ledcDetach(GPIO_PWM); return false; } - a = {hz, actualHz, 100.0f * duty / levels, bits}; - running_ = true; - return true; + for (uint8_t attempt = 0; attempt < 2; ++attempt) { + stop(); + const bool attached = ledcAttachChannel(GPIO_PWM, config.actualHz, bits, LEDC_CHANNEL); + if (attached) { + // Arduino's LEDC API normally chooses an 8-bit fractional divider. + // Force the fractional byte to zero so every PWM period contains the + // same integer number of 40 MHz source-clock ticks. + setIntegerDivider(config.divider); + } + if (attached && integerDividerIsSet(config.divider) && ledcWriteChannel(LEDC_CHANNEL, duty)) { + // On the first configuration after power-up the duty update is latched + // on a timer edge. Reading immediately can therefore return zero. + uint32_t settleUs = static_cast((2000000ULL + hz - 1U) / hz); + if (settleUs > 2000U) settleUs = 2000U; + delayMicroseconds(settleUs); + const uint32_t actualHz = ledcReadFreq(GPIO_PWM); + if (actualHz) { + a = {hz, actualHz, 100.0f * duty / levels, bits}; + running_ = true; + return true; + } + } + if (attached) ledcDetach(GPIO_PWM); + delay(2); + } + pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_SAFE_LEVEL); + return false; } void PwmGenerator::stop() { diff --git a/OpticalChannelTester/Receiver.cpp b/OpticalChannelTester/Receiver.cpp index 282864e..53528a6 100644 --- a/OpticalChannelTester/Receiver.cpp +++ b/OpticalChannelTester/Receiver.cpp @@ -130,11 +130,12 @@ bool IRAM_ATTR PulseReceiver::onRmt(rmt_channel_handle_t, const rmt_rx_done_even return wake == pdTRUE; } -bool PulseReceiver::nextRmtEdge(Edge &edge) { +bool PulseReceiver::nextRmtEdge(Edge &edge, TickType_t waitTicks) { for (;;) { if (blockIndex_ >= block_.count) { - if (xQueueReceive(queue_, &block_, 0) != pdTRUE) return false; + if (xQueueReceive(queue_, &block_, waitTicks) != pdTRUE) return false; blockIndex_ = 0; phase_ = 0; + waitTicks = 0; } const rmt_symbol_word_t &s = block_.symbols[blockIndex_]; const bool nextLevel = phase_ == 0 ? s.level0 : s.level1; @@ -151,10 +152,10 @@ bool PulseReceiver::nextRmtEdge(Edge &edge) { } } -size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity) { +size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) { size_t count = 0; Edge e; - while (count < capacity && nextRmtEdge(e)) + while (count < capacity && nextRmtEdge(e, count ? 0 : waitTicks)) if (consumeEdge(e, periods[count])) ++count; return count; } @@ -170,10 +171,10 @@ void IRAM_ATTR PulseReceiver::onGpio(void *ctx) { if (wake) portYIELD_FROM_ISR(); } -size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity) { +size_t PulseReceiver::readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks) { size_t count = 0; Edge e; - while (count < capacity && xQueueReceive(queue_, &e, 0) == pdTRUE) + while (count < capacity && xQueueReceive(queue_, &e, count ? 0 : waitTicks) == pdTRUE) if (consumeEdge(e, periods[count])) ++count; return count; } diff --git a/OpticalChannelTester/Receiver.h b/OpticalChannelTester/Receiver.h index 4629cc4..2494bdd 100644 --- a/OpticalChannelTester/Receiver.h +++ b/OpticalChannelTester/Receiver.h @@ -18,7 +18,7 @@ class PulseReceiver { bool start(uint32_t expectedHz); void stop(); void resetStream(); - size_t readPeriods(PulsePeriod *periods, size_t capacity); + size_t readPeriods(PulsePeriod *periods, size_t capacity, TickType_t waitTicks = 0); bool overflowed(); uint32_t takeDroppedItems(); uint32_t tickHz() const; @@ -38,7 +38,7 @@ class PulseReceiver { static constexpr size_t BLOCK_SYMBOLS = RMT_MAX_RECEIVE_SYMBOLS; struct SymbolBlock { uint16_t count; rmt_symbol_word_t symbols[BLOCK_SYMBOLS]; }; static bool IRAM_ATTR onRmt(rmt_channel_handle_t, const rmt_rx_done_event_data_t *, void *); - bool nextRmtEdge(Edge &edge); + bool nextRmtEdge(Edge &edge, TickType_t waitTicks); rmt_channel_handle_t channel_ = nullptr; rmt_symbol_word_t receiveBuffer_[RMT_MAX_RECEIVE_SYMBOLS]; uint16_t receiveChunkSymbols_ = 0; diff --git a/README.md b/README.md index 2a72e64..095a8bc 100644 --- a/README.md +++ b/README.md @@ -202,7 +202,7 @@ PWM_SETTLE_CYCLES / actualFrequency + TEST_TIME * REPEATS | Target | Arduino-ESP32 | Flash | RAM | Результат | |---|---:|---:|---:|---| -| ESP32-C3 | 3.3.10 | 1,025,021 B (78%) | 45,380 B (13%) | PASS | +| ESP32-C3 | 3.3.10 | 1,026,823 B (78%) | 45,380 B (13%) | PASS | | ESP32-S3 | 3.3.10 | 950,608 B (72%) | 48,620 B (14%) | PASS | Локальные unit-тесты: `core tests: PASS`, `button tests: PASS`. Они покрывают неделимый диапазон, END без дубля, ALL, границы допусков, немедленный FAIL, resolution, checksum настроек, CRC протокола и отсутствие short после long.