#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. ledcSetClockSource(LEDC_USE_XTAL_CLK); pinMode(GPIO_PWM, OUTPUT); stop(); } bool PwmGenerator::start(uint32_t hz, uint8_t dutyPct, ActualPwm &a) { 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; 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() { if (running_) ledcDetach(GPIO_PWM); pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_SAFE_LEVEL); running_ = false; }