#include "Pwm.h" #include "Config.h" #include "Core.h" #if CONFIG_IDF_TARGET_ESP32C3 #include #elif CONFIG_IDF_TARGET_ESP32S3 #include #endif namespace { #if CONFIG_IDF_TARGET_ESP32C3 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); } #elif CONFIG_IDF_TARGET_ESP32S3 mcpwm_timer_handle_t mcpwmTimer = nullptr; mcpwm_oper_handle_t mcpwmOperator = nullptr; mcpwm_cmpr_handle_t mcpwmComparator = nullptr; mcpwm_gen_handle_t mcpwmGenerator = nullptr; uint32_t mcpwmFrequencyHz = 0; void releaseMcpwm() { if (mcpwmGenerator) { mcpwm_del_generator(mcpwmGenerator); mcpwmGenerator = nullptr; } if (mcpwmComparator) { mcpwm_del_comparator(mcpwmComparator); mcpwmComparator = nullptr; } if (mcpwmOperator) { mcpwm_del_operator(mcpwmOperator); mcpwmOperator = nullptr; } if (mcpwmTimer) { mcpwm_timer_disable(mcpwmTimer); mcpwm_del_timer(mcpwmTimer); mcpwmTimer = nullptr; } } uint8_t periodResolutionBits(uint32_t periodTicks) { uint8_t bits = 0; while (periodTicks > 1U) { periodTicks >>= 1U; ++bits; } return bits ? bits : 1U; } #endif } void PwmGenerator::begin() { #if CONFIG_IDF_TARGET_ESP32C3 // Match LEDC_SOURCE_CLOCK_HZ and make the timer calculation deterministic. ledcSetClockSource(LEDC_USE_XTAL_CLK); pinMode(GPIO_PWM, OUTPUT); stop(); #elif CONFIG_IDF_TARGET_ESP32S3 mcpwm_timer_config_t timerConfig = {}; timerConfig.group_id = 0; timerConfig.clk_src = MCPWM_TIMER_CLK_SRC_PLL160M; timerConfig.resolution_hz = MCPWM_RESOLUTION_HZ; timerConfig.count_mode = MCPWM_TIMER_COUNT_MODE_UP; timerConfig.period_ticks = MCPWM_RESOLUTION_HZ / 1000U; mcpwm_operator_config_t operatorConfig = {}; operatorConfig.group_id = 0; mcpwm_comparator_config_t comparatorConfig = {}; mcpwm_generator_config_t generatorConfig = {}; generatorConfig.gen_gpio_num = GPIO_PWM; bool ok = mcpwm_new_timer(&timerConfig, &mcpwmTimer) == ESP_OK; ok = ok && mcpwm_new_operator(&operatorConfig, &mcpwmOperator) == ESP_OK; ok = ok && mcpwm_operator_connect_timer(mcpwmOperator, mcpwmTimer) == ESP_OK; ok = ok && mcpwm_new_comparator(mcpwmOperator, &comparatorConfig, &mcpwmComparator) == ESP_OK; ok = ok && mcpwm_new_generator(mcpwmOperator, &generatorConfig, &mcpwmGenerator) == ESP_OK; ok = ok && mcpwm_comparator_set_compare_value(mcpwmComparator, timerConfig.period_ticks / 2U) == ESP_OK; ok = ok && mcpwm_generator_set_action_on_timer_event(mcpwmGenerator, MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)) == ESP_OK; ok = ok && mcpwm_generator_set_action_on_compare_event(mcpwmGenerator, MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, mcpwmComparator, MCPWM_GEN_ACTION_LOW)) == ESP_OK; ok = ok && mcpwm_timer_enable(mcpwmTimer) == ESP_OK; if (!ok) { releaseMcpwm(); pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_SAFE_LEVEL); return; } mcpwm_generator_set_force_level(mcpwmGenerator, PWM_SAFE_LEVEL, true); #endif } bool PwmGenerator::start(uint32_t hz, uint8_t dutyPct, ActualPwm &a) { #if CONFIG_IDF_TARGET_ESP32C3 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 == config.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; #elif CONFIG_IDF_TARGET_ESP32S3 if (!mcpwmTimer || !mcpwmComparator || !mcpwmGenerator || !hz || dutyPct > 100U || MCPWM_RESOLUTION_HZ % hz) return false; const uint32_t periodTicks = MCPWM_RESOLUTION_HZ / hz; if (periodTicks < 2U || periodTicks > MCPWM_MAX_PERIOD_TICKS) return false; uint32_t activeTicks = (static_cast(periodTicks) * dutyPct + 50U) / 100U; if (activeTicks == 0U) activeTicks = 1U; if (activeTicks >= periodTicks) activeTicks = periodTicks - 1U; stop(); bool ok = mcpwm_timer_set_period(mcpwmTimer, periodTicks) == ESP_OK; ok = ok && mcpwm_comparator_set_compare_value(mcpwmComparator, activeTicks) == ESP_OK; // stop() applies a continuous force level (hold_on=true). Remove that same // continuous-force action; hold_on=false addresses a different, one-shot // force mechanism and would leave the safe level permanently active. ok = ok && mcpwm_generator_set_force_level(mcpwmGenerator, -1, true) == ESP_OK; ok = ok && mcpwm_timer_start_stop(mcpwmTimer, MCPWM_TIMER_START_NO_STOP) == ESP_OK; if (!ok) { mcpwm_generator_set_force_level(mcpwmGenerator, PWM_SAFE_LEVEL, true); return false; } a = {hz, hz, 100.0f * activeTicks / periodTicks, periodResolutionBits(periodTicks)}; mcpwmFrequencyHz = hz; running_ = true; return true; #endif } void PwmGenerator::stop() { #if CONFIG_IDF_TARGET_ESP32C3 if (running_) ledcDetach(GPIO_PWM); pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_SAFE_LEVEL); #elif CONFIG_IDF_TARGET_ESP32S3 if (mcpwmGenerator) mcpwm_generator_set_force_level(mcpwmGenerator, PWM_SAFE_LEVEL, true); if (running_ && mcpwmTimer) { mcpwm_timer_start_stop(mcpwmTimer, MCPWM_TIMER_STOP_EMPTY); const uint32_t waitUs = mcpwmFrequencyHz ? (1000000U / mcpwmFrequencyHz + 2U) : 2U; delayMicroseconds(waitUs); } mcpwmFrequencyHz = 0; #endif running_ = false; } void PwmGenerator::active() { // First detach/stop the PWM peripheral, then select the independently // configured active level. The active and safe levels may be equal. stop(); #if CONFIG_IDF_TARGET_ESP32C3 digitalWrite(GPIO_PWM, PWM_ACTIVE_LEVEL); #elif CONFIG_IDF_TARGET_ESP32S3 if (mcpwmGenerator) mcpwm_generator_set_force_level(mcpwmGenerator, PWM_ACTIVE_LEVEL, true); else { pinMode(GPIO_PWM, OUTPUT); digitalWrite(GPIO_PWM, PWM_ACTIVE_LEVEL); } #endif }