работает с перемычкой на 1МГц 2%
This commit is contained in:
@@ -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<Role>(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<Role>(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<Role>(settings_.role)), startText, endText, stepText,
|
||||
params_.accuracyPct, params_.testTimeMs, params_.repeats, params_.dutyPct, stageCount_);
|
||||
}
|
||||
printConfiguration();
|
||||
const Role role = static_cast<Role>(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<Role>(settings_.role) == Role::SOLO) { if (prepareStage()) state_ = AppState::SOLO_MEASURE; }
|
||||
else if (static_cast<Role>(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<uint8_t>(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<float>(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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<uint8_t>(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<uint64_t>(frequencyHz) * levels;
|
||||
if (denominator > sourceClockHz || sourceClockHz % denominator) continue;
|
||||
const uint32_t divider = static_cast<uint32_t>(sourceClockHz / denominator);
|
||||
if (!divider || divider > 1024U) continue;
|
||||
if ((static_cast<uint32_t>(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<uint64_t>(requestedHz) * levels;
|
||||
uint32_t lowerDivider = static_cast<uint32_t>(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<uint64_t>(requestedHz) * denominator;
|
||||
const uint64_t error = targetClock > sourceClockHz
|
||||
? targetClock - sourceClockHz : sourceClockHz - targetClock;
|
||||
const uint32_t dutyCount = (static_cast<uint64_t>(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<uint64_t>(dutyError) * bestLevels <
|
||||
static_cast<uint64_t>(bestDutyError) * levels;
|
||||
const bool dutyEqual = frequencyEqual &&
|
||||
static_cast<uint64_t>(dutyError) * bestLevels ==
|
||||
static_cast<uint64_t>(bestDutyError) * levels;
|
||||
if (!frequencyBetter && !dutyBetter && !(dutyEqual && bits > config.bits)) continue;
|
||||
|
||||
config.actualHz = static_cast<uint32_t>(
|
||||
(static_cast<uint64_t>(sourceClockHz) + denominator / 2U) / denominator);
|
||||
config.divider = static_cast<uint16_t>(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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<uint32_t>(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<uint32_t>((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<Measurement *>(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); }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
#include "Pwm.h"
|
||||
#include "Config.h"
|
||||
#include "Core.h"
|
||||
#include <hal/ledc_ll.h>
|
||||
|
||||
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<uint32_t>(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<uint32_t>(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<uint64_t>(levels) * dutyPct + 50U) / 100U;
|
||||
if (!ledcWriteChannel(LEDC_CHANNEL, duty)) { ledcDetach(GPIO_PWM); return false; }
|
||||
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<uint32_t>((2000000ULL + hz - 1U) / hz);
|
||||
if (settleUs > 2000U) settleUs = 2000U;
|
||||
delayMicroseconds(settleUs);
|
||||
const uint32_t actualHz = ledcReadFreq(GPIO_PWM);
|
||||
if (!actualHz) { ledcDetach(GPIO_PWM); return false; }
|
||||
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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user