Files
OptoTest/OpticalChannelTester/Pwm.cpp

187 lines
6.9 KiB
C++

#include "Pwm.h"
#include "Config.h"
#include "Core.h"
#if CONFIG_IDF_TARGET_ESP32C3
#include <hal/ledc_ll.h>
#elif CONFIG_IDF_TARGET_ESP32S3
#include <driver/mcpwm_prelude.h>
#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<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);
}
#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<uint64_t>(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<uint32_t>((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<uint64_t>(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;
ok = ok && mcpwm_generator_set_force_level(mcpwmGenerator, -1, false) == 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;
}