diff --git a/OpticalChannelTester/Config.h b/OpticalChannelTester/Config.h index 22144b1..c10efe2 100644 --- a/OpticalChannelTester/Config.h +++ b/OpticalChannelTester/Config.h @@ -3,23 +3,43 @@ #include // ------------------------- Hardware configuration ------------------------- +// Uncomment for the hand-wired prototype. The production PCB assignments +// below follow the physical header positions shown in the schematic. +// #define MAKETKA + #if CONFIG_IDF_TARGET_ESP32C3 constexpr bool TARGET_IS_C3 = true; constexpr uint8_t GPIO_PWM = 3; constexpr uint8_t GPIO_RX = 4; +#ifdef MAKETKA constexpr uint8_t GPIO_BUTTON_MODE = 0; constexpr uint8_t GPIO_BUTTON_START = 1; +#else +constexpr uint8_t GPIO_BUTTON_MODE = 1; +constexpr uint8_t GPIO_BUTTON_START = 0; +#endif constexpr uint8_t GPIO_SDA = 6; constexpr uint8_t GPIO_SCL = 7; #elif CONFIG_IDF_TARGET_ESP32S3 constexpr bool TARGET_IS_C3 = false; +#ifdef MAKETKA constexpr uint8_t GPIO_PWM = 4; constexpr uint8_t GPIO_RX = 5; -constexpr uint8_t GPIO_BUTTON_MODE = 6; -constexpr uint8_t GPIO_BUTTON_START = 7; +constexpr uint8_t GPIO_BUTTON_MODE = 0; +constexpr uint8_t GPIO_BUTTON_START = 1; constexpr uint8_t GPIO_SDA = 8; constexpr uint8_t GPIO_SCL = 9; #else +// The S3 SuperMini is fitted so its 5V and GND pins occupy the same PCB +// contacts as on the C3 SuperMini. Signals therefore follow header position. +constexpr uint8_t GPIO_PWM = 12; +constexpr uint8_t GPIO_RX = 13; +constexpr uint8_t GPIO_BUTTON_MODE = 10; +constexpr uint8_t GPIO_BUTTON_START = 9; +constexpr uint8_t GPIO_SDA = 44; +constexpr uint8_t GPIO_SCL = 1; +#endif +#else #error "Only ESP32-C3 and ESP32-S3 are supported" #endif @@ -75,18 +95,23 @@ constexpr uint32_t S3_STRICT_MAX_HZ = 1000000; // still fits both levels of the current PWM signal: 20, 40 or 80 MHz. constexpr uint32_t CAPTURE_RESOLUTION_OPTIONS_HZ[] = {20000000, 40000000, 80000000}; constexpr uint32_t RMT_MAX_LEVEL_TICKS = 32766; -// Arduino-ESP32 uses the 40 MHz crystal as the default LEDC clock on C3/S3. +// C3 uses the 40 MHz crystal as the LEDC clock. // Keep this explicit so the resolution calculation never asks LEDC for an // impossible frequency/resolution combination. constexpr uint32_t LEDC_SOURCE_CLOCK_HZ = 40000000; constexpr uint8_t LEDC_CHANNEL = 0; constexpr uint8_t LEDC_MAX_BITS = 14; +// S3 uses the dedicated MCPWM peripheral. A 40 MHz timer clock keeps the +// longest 1 kHz period within the S3's 16-bit MCPWM counter and makes every +// frequency in TEST_FREQUENCIES_HZ exact. +constexpr uint32_t MCPWM_RESOLUTION_HZ = 40000000; +constexpr uint32_t MCPWM_MAX_PERIOD_TICKS = 65535; // -------------------------- Menu value arrays ----------------------------- // START and END deliberately have separate, independently cycling menu lists. -// Every value is exactly achievable from the 40 MHz XTAL with an integer LEDC -// divider. The test itself walks TEST_FREQUENCIES_HZ between the selected -// endpoints, so there is no separately configurable step. +// Every value is exactly achievable from a 40 MHz timer clock. The test walks +// TEST_FREQUENCIES_HZ between the selected endpoints, so there is no +// separately configurable step. constexpr uint32_t START_FREQ_OPTIONS_HZ[] = {1000, 10000, 100000}; constexpr uint32_t END_FREQ_OPTIONS_HZ[] = {100000, 500000, 1000000}; diff --git a/OpticalChannelTester/Pwm.cpp b/OpticalChannelTester/Pwm.cpp index 3c0b989..9406d42 100644 --- a/OpticalChannelTester/Pwm.cpp +++ b/OpticalChannelTester/Pwm.cpp @@ -1,9 +1,14 @@ #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; @@ -22,16 +27,90 @@ bool integerDividerIsSet(uint16_t expected) { 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; @@ -64,10 +143,44 @@ bool PwmGenerator::start(uint32_t hz, uint8_t dutyPct, ActualPwm &a) { } 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; + 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; }