From 05d6e7665ffa44153a6db996e007b99fd42a5b97 Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Wed, 2 Sep 2026 16:42:36 +0800 Subject: [PATCH] fix(mcpwm): correct the wrong capture prescale --- .../include/driver/mcpwm_cap.h | 8 +- components/esp_driver_mcpwm/src/mcpwm_cap.c | 7 +- .../test_apps/mcpwm/main/test_mcpwm_cap.c | 213 +++++++++++++++++- .../esp32/include/hal/mcpwm_ll.h | 10 +- .../esp32c5/include/hal/mcpwm_ll.h | 8 +- .../esp32c6/include/hal/mcpwm_ll.h | 8 +- .../esp32h2/include/hal/mcpwm_ll.h | 8 +- .../esp32h21/include/hal/mcpwm_ll.h | 8 +- .../esp32h4/include/hal/mcpwm_ll.h | 8 +- .../esp32p4/include/hal/mcpwm_ll.h | 8 +- .../esp32s3/include/hal/mcpwm_ll.h | 10 +- .../esp32s31/include/hal/mcpwm_ll.h | 8 +- docs/_static/mcpwm/capture_prescale_both.svg | 74 ++++++ .../mcpwm/capture_prescale_falling.svg | 61 +++++ .../_static/mcpwm/capture_prescale_rising.svg | 60 +++++ .../peripherals/mcpwm/mcpwm_cap.rst | 47 +++- .../peripherals/mcpwm/mcpwm_cap.rst | 47 +++- 17 files changed, 551 insertions(+), 42 deletions(-) create mode 100644 docs/_static/mcpwm/capture_prescale_both.svg create mode 100644 docs/_static/mcpwm/capture_prescale_falling.svg create mode 100644 docs/_static/mcpwm/capture_prescale_rising.svg diff --git a/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h b/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h index f740f70b44d..b4832367be5 100644 --- a/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h +++ b/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -141,7 +141,11 @@ typedef struct { int gpio_num; /*!< GPIO used capturing input signal */ int intr_priority; /*!< MCPWM capture interrupt priority, if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */ - uint32_t prescale; /*!< Prescale of input signal, effective frequency = cap_input_clk/prescale */ + uint32_t prescale; /*!< Input prescale ratio: same-edge capture spacing in input signal periods + (e.g. rising-to-rising). Effective capture rate = input_rate / prescale. + 0 or 1: no prescaling (bypass); otherwise must be even. + Prescaling is applied before edge selection; with prescale > 1, + reported cap_edge may not match the physical GPIO edge. */ /// Extra configuration flags for capture channel struct extra_capture_channel_flags { diff --git a/components/esp_driver_mcpwm/src/mcpwm_cap.c b/components/esp_driver_mcpwm/src/mcpwm_cap.c index 2fb3849c49c..0672a67784a 100644 --- a/components/esp_driver_mcpwm/src/mcpwm_cap.c +++ b/components/esp_driver_mcpwm/src/mcpwm_cap.c @@ -267,7 +267,10 @@ esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mc esp_err_t ret = ESP_OK; mcpwm_cap_channel_t *cap_chan = NULL; ESP_GOTO_ON_FALSE(cap_timer && config && ret_cap_channel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(config->prescale && config->prescale <= MCPWM_LL_GET(MAX_CAPTURE_PRESCALE), ESP_ERR_INVALID_ARG, err, TAG, "invalid prescale"); + // prescale: same-edge ratio; 0/1 (bypass) or even. + uint32_t prescale = config->prescale ? config->prescale : 1; + ESP_GOTO_ON_FALSE((prescale == 1 || (prescale % 2) == 0) && (prescale / 2) < MCPWM_LL_GET(MAX_CAPTURE_PRESCALE), + ESP_ERR_INVALID_ARG, err, TAG, "invalid prescale"); if (config->intr_priority) { ESP_GOTO_ON_FALSE(1 << (config->intr_priority) & MCPWM_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, err, TAG, "invalid interrupt priority:%d", config->intr_priority); @@ -285,7 +288,7 @@ esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mc mcpwm_ll_capture_enable_negedge(hal->dev, cap_chan_id, config->flags.neg_edge); mcpwm_ll_capture_enable_posedge(hal->dev, cap_chan_id, config->flags.pos_edge); mcpwm_ll_invert_input(hal->dev, cap_chan_id, config->flags.invert_cap_signal); - mcpwm_ll_capture_set_prescale(hal->dev, cap_chan_id, config->prescale); + mcpwm_ll_capture_set_prescale(hal->dev, cap_chan_id, prescale); if (config->gpio_num >= 0) { // GPIO configuration diff --git a/components/esp_driver_mcpwm/test_apps/mcpwm/main/test_mcpwm_cap.c b/components/esp_driver_mcpwm/test_apps/mcpwm/main/test_mcpwm_cap.c index f7c2092ef94..ac172c28122 100644 --- a/components/esp_driver_mcpwm/test_apps/mcpwm/main/test_mcpwm_cap.c +++ b/components/esp_driver_mcpwm/test_apps/mcpwm/main/test_mcpwm_cap.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,10 +7,10 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/event_groups.h" #include "unity.h" +#include "sdkconfig.h" #include "hal/mcpwm_ll.h" -#include "esp_private/esp_clk.h" +#include "esp_rom_sys.h" #include "driver/mcpwm_cap.h" #include "driver/mcpwm_sync.h" #include "driver/gpio.h" @@ -273,3 +273,210 @@ TEST_CASE("mcpwm_capture_timer_sync_phase_lock", "[mcpwm]") TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer)); TEST_ESP_OK(mcpwm_del_sync_src(soft_sync)); } + +/** + * Capture input prescale: step GPIO high/low and see which edges fire a capture. + * Frequency/pulse-width do not matter; only edge -> capture mapping does. + * + * For each cycle: drive rising edge, check; drive falling edge, check. + * Do enough cycles to observe prescale phase + spacing (prescale * GROUPS). + */ +#define TEST_PRESCALE_GROUPS 8 +#define TEST_PRESCALE_SETTLE_US 50 +#define TEST_PRESCALE_MAX_CAPS 64 + +typedef struct { + uint32_t count; + mcpwm_capture_edge_t edges[TEST_PRESCALE_MAX_CAPS]; + uint32_t cycles[TEST_PRESCALE_MAX_CAPS]; +} test_prescale_ctx_t; + +TEST_MCPWM_CALLBACK_ATTR +static bool test_prescale_cap_cb(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_data) +{ + test_prescale_ctx_t *ctx = user_data; + if (ctx->count < TEST_PRESCALE_MAX_CAPS) { + ctx->edges[ctx->count++] = edata->cap_edge; + } + return false; +} + +// Drive one edge, wait for ISR, return true if a new capture arrived. +static bool test_prescale_step(int gpio, int level, test_prescale_ctx_t *ctx, + uint32_t cycle, mcpwm_capture_edge_t *out_edge) +{ + uint32_t before = ctx->count; + gpio_set_level(gpio, level); + esp_rom_delay_us(TEST_PRESCALE_SETTLE_US); + if (ctx->count == before) { + return false; + } + ctx->cycles[before] = cycle; + if (out_edge) { + *out_edge = ctx->edges[before]; + } + return true; +} + +static void test_prescale_check(int gpio, uint32_t prescale, bool pos_edge, bool neg_edge) +{ + const bool both = pos_edge && neg_edge; + // Enough full high/low cycles to get GROUPS captures per enabled edge type + const uint32_t cycles = prescale * TEST_PRESCALE_GROUPS; + const uint32_t expect = both ? (2 * TEST_PRESCALE_GROUPS) : TEST_PRESCALE_GROUPS; + + // Recreate timer+channel every case so group register reset clears prior prescale phase. + mcpwm_cap_timer_handle_t cap_timer = NULL; + mcpwm_capture_timer_config_t cap_cfg = { + .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT, + .group_id = 0, + }; + TEST_ESP_OK(mcpwm_new_capture_timer(&cap_cfg, &cap_timer)); + TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer)); + TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer)); + + mcpwm_cap_channel_handle_t chan = NULL; + mcpwm_capture_channel_config_t cfg = { + .gpio_num = gpio, + .prescale = prescale, + .flags.pos_edge = pos_edge, + .flags.neg_edge = neg_edge, + }; + test_prescale_ctx_t ctx = {}; + mcpwm_capture_event_callbacks_t cbs = {.on_cap = test_prescale_cap_cb}; + + // Always start low so every mode drives the same rise-then-fall sequence. + gpio_set_level(gpio, 0); + + TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cfg, &chan)); + TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(chan, &cbs, &ctx)); + TEST_ESP_OK(mcpwm_capture_channel_enable(chan)); + + printf("prescale=%" PRIu32 " mode=%s cycles=%" PRIu32 " expect_caps=%" PRIu32 "\n", + prescale, both ? "both" : (pos_edge ? "pos" : "neg"), cycles, expect); + + uint32_t rise_caps = 0; + uint32_t fall_caps = 0; + int first_cap_cycle = -1; + const char *first_cap_via = NULL; + + for (uint32_t c = 0; c < cycles; c++) { + mcpwm_capture_edge_t edge; + if (test_prescale_step(gpio, 1, &ctx, c, &edge)) { + const char *name = (edge == MCPWM_CAP_EDGE_POS) ? "R" : "F"; + printf(" cycle%-2" PRIu32 " rise -> %s\n", c, name); + if (edge == MCPWM_CAP_EDGE_POS) { + rise_caps++; + } else { + fall_caps++; + } + if (first_cap_cycle < 0) { + first_cap_cycle = (int)c; + first_cap_via = "rise"; + } + } + if (test_prescale_step(gpio, 0, &ctx, c, &edge)) { + const char *name = (edge == MCPWM_CAP_EDGE_POS) ? "R" : "F"; + printf(" cycle%-2" PRIu32 " fall -> %s\n", c, name); + if (edge == MCPWM_CAP_EDGE_POS) { + rise_caps++; + } else { + fall_caps++; + } + if (first_cap_cycle < 0) { + first_cap_cycle = (int)c; + first_cap_via = "fall"; + } + } + } + + printf(" caps=%" PRIu32 " (R-reported=%" PRIu32 " F-reported=%" PRIu32 ") first@cycle%d via %s\n", + ctx.count, rise_caps, fall_caps, first_cap_cycle, first_cap_via ? first_cap_via : "?"); + + TEST_ASSERT_EQUAL_UINT32(expect, ctx.count); + // After a clean timer+channel recreate, cadence and first-cycle phase are both fixed. + const uint32_t same_gap = (prescale <= 1) ? 1 : (both ? (prescale / 2) : prescale); + if (both && (prescale <= 1)) { + for (uint32_t i = 2; i < ctx.count; i++) { + TEST_ASSERT_EQUAL_UINT32(1, ctx.cycles[i] - ctx.cycles[i - 2]); + } + } else { + for (uint32_t i = 1; i < ctx.count; i++) { + TEST_ASSERT_EQUAL_UINT32(same_gap, ctx.cycles[i] - ctx.cycles[i - 1]); + } + } + + if (pos_edge && !neg_edge) { + // Rising-only: always reports R, fires on rising GPIO steps. + // After clean reset: first at cycle (prescale/2 - 1) when prescale > 1. + TEST_ASSERT_EQUAL_UINT32(expect, rise_caps); + TEST_ASSERT_EQUAL_UINT32(0, fall_caps); + TEST_ASSERT_NOT_NULL(first_cap_via); + TEST_ASSERT_EQUAL_STRING("rise", first_cap_via); + TEST_ASSERT_EQUAL_INT((prescale <= 1) ? 0 : (int)(prescale / 2 - 1), first_cap_cycle); + } else if (neg_edge && !pos_edge) { + // Falling-only path always reports F (cap_edge neg). + // Bypass: fires on GPIO falling steps at cycle 0. + // Prescale > 1: fires on GPIO rising steps, first at cycle (prescale - 1). + TEST_ASSERT_EQUAL_UINT32(expect, fall_caps); + TEST_ASSERT_EQUAL_UINT32(0, rise_caps); + TEST_ASSERT_NOT_NULL(first_cap_via); + if (prescale <= 1) { + TEST_ASSERT_EQUAL_STRING("fall", first_cap_via); + TEST_ASSERT_EQUAL_INT(0, first_cap_cycle); + } else { + TEST_ASSERT_EQUAL_STRING("rise", first_cap_via); + TEST_ASSERT_EQUAL_INT((int)(prescale - 1), first_cap_cycle); + } + } else { + // Both edges: equal R/F counts. Bypass maps to real rise/fall. + // Prescale > 1: rising steps only, alternates R/F from R; first like rising-only. + TEST_ASSERT_EQUAL_UINT32(TEST_PRESCALE_GROUPS, rise_caps); + TEST_ASSERT_EQUAL_UINT32(TEST_PRESCALE_GROUPS, fall_caps); + TEST_ASSERT_NOT_NULL(first_cap_via); + TEST_ASSERT_EQUAL_STRING("rise", first_cap_via); + TEST_ASSERT_EQUAL(MCPWM_CAP_EDGE_POS, ctx.edges[0]); + TEST_ASSERT_EQUAL_INT((prescale <= 1) ? 0 : (int)(prescale / 2 - 1), first_cap_cycle); + } + + TEST_ESP_OK(mcpwm_capture_channel_disable(chan)); + TEST_ESP_OK(mcpwm_del_capture_channel(chan)); + TEST_ESP_OK(mcpwm_capture_timer_stop(cap_timer)); + TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer)); + TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer)); +} + +TEST_CASE("mcpwm_capture_prescale_ratio", "[mcpwm]") +{ + const int gpio = TEST_CAP_GPIO; + gpio_config_t io_conf = { + .mode = GPIO_MODE_INPUT_OUTPUT, + .pin_bit_mask = BIT(gpio), + }; + TEST_ESP_OK(gpio_config(&io_conf)); + gpio_set_level(gpio, 0); + + // Odd API prescale must be rejected (uses a throwaway timer). + mcpwm_cap_timer_handle_t cap_timer = NULL; + mcpwm_capture_timer_config_t cap_cfg = { + .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT, + .group_id = 0, + }; + TEST_ESP_OK(mcpwm_new_capture_timer(&cap_cfg, &cap_timer)); + mcpwm_cap_channel_handle_t bad = NULL; + mcpwm_capture_channel_config_t bad_cfg = { + .gpio_num = gpio, .prescale = 3, .flags.pos_edge = true, + }; + TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mcpwm_new_capture_channel(cap_timer, &bad_cfg, &bad)); + TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer)); + + const uint32_t expected_prescales[] = {1, 2, 4, 16}; + for (size_t i = 0; i < sizeof(expected_prescales) / sizeof(expected_prescales[0]); i++) { + printf("------------------------------------------------\r\n"); + test_prescale_check(gpio, expected_prescales[i], true, false); + test_prescale_check(gpio, expected_prescales[i], false, true); + test_prescale_check(gpio, expected_prescales[i], true, true); + } + + TEST_ESP_OK(gpio_reset_pin(gpio)); +} diff --git a/components/esp_hal_mcpwm/esp32/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32/include/hal/mcpwm_ll.h index cea462f3332..2d4685c3d1b 100644 --- a/components/esp_hal_mcpwm/esp32/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32/include/hal/mcpwm_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1619,16 +1619,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } #ifdef __cplusplus diff --git a/components/esp_hal_mcpwm/esp32c5/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32c5/include/hal/mcpwm_ll.h index c91c431da83..8a623a00c68 100644 --- a/components/esp_hal_mcpwm/esp32c5/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32c5/include/hal/mcpwm_ll.h @@ -1659,16 +1659,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32c6/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32c6/include/hal/mcpwm_ll.h index 1272163769e..abb72831473 100644 --- a/components/esp_hal_mcpwm/esp32c6/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32c6/include/hal/mcpwm_ll.h @@ -1633,16 +1633,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32h2/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32h2/include/hal/mcpwm_ll.h index 05c873d8e02..7a6503369d1 100644 --- a/components/esp_hal_mcpwm/esp32h2/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32h2/include/hal/mcpwm_ll.h @@ -1631,16 +1631,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32h21/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32h21/include/hal/mcpwm_ll.h index fdff8ad65b3..01b85766e20 100644 --- a/components/esp_hal_mcpwm/esp32h21/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32h21/include/hal/mcpwm_ll.h @@ -1631,16 +1631,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32h4/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32h4/include/hal/mcpwm_ll.h index 276ae0d192a..393a0192daf 100644 --- a/components/esp_hal_mcpwm/esp32h4/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32h4/include/hal/mcpwm_ll.h @@ -1689,16 +1689,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32p4/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32p4/include/hal/mcpwm_ll.h index 1365ed8feba..d941e434f04 100644 --- a/components/esp_hal_mcpwm/esp32p4/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32p4/include/hal/mcpwm_ll.h @@ -1724,16 +1724,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/components/esp_hal_mcpwm/esp32s3/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32s3/include/hal/mcpwm_ll.h index 1eb8f926fbf..20cef04f125 100644 --- a/components/esp_hal_mcpwm/esp32s3/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32s3/include/hal/mcpwm_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1627,16 +1627,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } #ifdef __cplusplus diff --git a/components/esp_hal_mcpwm/esp32s31/include/hal/mcpwm_ll.h b/components/esp_hal_mcpwm/esp32s31/include/hal/mcpwm_ll.h index 22c829c5543..30d9be01d1a 100644 --- a/components/esp_hal_mcpwm/esp32s31/include/hal/mcpwm_ll.h +++ b/components/esp_hal_mcpwm/esp32s31/include/hal/mcpwm_ll.h @@ -1706,16 +1706,18 @@ static inline mcpwm_capture_edge_t mcpwm_ll_capture_get_edge(mcpwm_dev_t *mcpwm, } /** - * @brief Set the prescale of the input capture signal + * @brief Set capture input prescale (same-edge ratio) + * + * @note Hardware field N = 0 means bypass (ratio 1); N >= 1 means same-edge ratio = 2 * N. * * @param mcpwm Peripheral instance address * @param channel Channel ID, index from 0 to 2 - * @param prescale Prescale value + * @param prescale Desired same-edge ratio: 1 (bypass) or even */ static inline void mcpwm_ll_capture_set_prescale(mcpwm_dev_t *mcpwm, int channel, uint32_t prescale) { HAL_ASSERT(prescale > 0); - HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale - 1); + HAL_FORCE_MODIFY_U32_REG_FIELD(mcpwm->cap_chn_cfg[channel], capn_prescale, prescale / 2); } //////////////////////////////////////////MCPWM ETM Specific//////////////////////////////////////////////////////////// diff --git a/docs/_static/mcpwm/capture_prescale_both.svg b/docs/_static/mcpwm/capture_prescale_both.svg new file mode 100644 index 00000000000..321b0eee69c --- /dev/null +++ b/docs/_static/mcpwm/capture_prescale_both.svg @@ -0,0 +1,74 @@ + + + + + + + + + Both edges + API prescale = same-edge ratio + + A. prescale = 1 (bypass) + + input + + + + capture + + + + + + + + + + R + F + R + F + R = GPIO rising + F = GPIO falling + + + B. prescale = 4 + + input + + + + + + cycle 1 + cycle 3 + cycle 5 + cycle 7 + + capture + + + + + + + + + + R + F + R + F + + + + adj = 2 x T = (prescale/2) x T + + + + R-to-R = 4 x T = prescale x T + + diff --git a/docs/_static/mcpwm/capture_prescale_falling.svg b/docs/_static/mcpwm/capture_prescale_falling.svg new file mode 100644 index 00000000000..40251b93e28 --- /dev/null +++ b/docs/_static/mcpwm/capture_prescale_falling.svg @@ -0,0 +1,61 @@ + + + + + + + + + Falling-edge only + API prescale = same-edge ratio + + A. prescale = 1 (bypass) + + input + + + + capture + + + + + + + + F + F + F + Every falling edge -> F + Rising edges ignored + + + B. prescale = 4 + + input + + + + + + cycle 3 + cycle 7 + + + T + capture + + + + + + F + F + first = prescale - 1, then every 4 + + + same-edge = 4 x T = prescale x T + + diff --git a/docs/_static/mcpwm/capture_prescale_rising.svg b/docs/_static/mcpwm/capture_prescale_rising.svg new file mode 100644 index 00000000000..2b57c3b127b --- /dev/null +++ b/docs/_static/mcpwm/capture_prescale_rising.svg @@ -0,0 +1,60 @@ + + + + + + + + + Rising-edge only + API prescale = same-edge ratio + + A. prescale = 1 (bypass) + + input + + + + capture + + + + + + + + R + R + R + Every rising edge -> R + Falling edges ignored + + + B. prescale = 4 + + input + + + + + cycle 1 + cycle 5 + + + T + capture + + + + + + R + R + first = prescale/2 - 1, then every 4 + + + same-edge = 4 x T = prescale x T + + diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst index 4632d155fd8..616f172dcc5 100644 --- a/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst @@ -68,15 +68,56 @@ Capture channel configuration .. list:: - - :cpp:member:`gpio_num ` — the GPIO carrying the input signal. - - :cpp:member:`prescale ` — divides the input signal before capture; the effective input frequency is the capture clock divided by ``prescale``. Raise it to extend the measurable period range, at the cost of time resolution. + - :cpp:member:`gpio_num ` — the GPIO carrying the input signal. The driver configures it as an input but does not enable any pull-up or pull-down; if the signal is not actively driven to both levels, call :cpp:func:`gpio_set_pull_mode()` so the pin idles at the level you expect. + - :cpp:member:`prescale ` — input prescale ratio. Same-edge captures (same reported edge type) are spaced about ``prescale`` input periods apart (capture rate ≈ ``input_rate / prescale``). ``0`` or ``1`` means no prescaling (bypass); any other value must be even. Leaving the field at ``0`` (the C default) is therefore bypass. Raise it to extend the measurable period range, at the cost of time resolution. See :ref:`mcpwm-cap-input-prescale` for pipeline order and edge-mode behavior. - :cpp:member:`pos_edge ` and :cpp:member:`neg_edge ` — which edges are captured. The example captures both, which is what a pulse-width measurement needs. - :cpp:member:`invert_cap_signal ` — inverts the input signal before capture, so a logical ``1`` on the pin is seen as ``0`` by the capture peripheral and vice versa. - :cpp:member:`intr_priority ` — the interrupt priority used by the capture callbacks. Not setting it (``0``) lets the driver choose a low priority. +.. _mcpwm-cap-input-prescale: + +Input prescale +============== + +The capture channel processes the input in a **fixed, serial** order; the two stages cannot be swapped: + +1. **First** divide the GPIO waveform by ``prescale`` (``0``/``1`` means bypass); +2. **Then** use ``pos_edge``/``neg_edge`` to decide which post-prescale events are reported to software. + +In other words, hardware does **not** “pick GPIO edges by the configured polarity first, then divide those edges.” With ``prescale > 1``: + +- The :cpp:member:`cap_edge ` in the callback may not match the physical GPIO edge; +- Adjacent opposite-edge gaps are **not pulse width**, and **the true duty cycle cannot be recovered**. + +Always compute period or frequency from timestamps of the same reported edge type. See the figures below for per-mode timing. + .. note:: - The capture driver configures the GPIO as an input but does not set any pull-up or pull-down resistor. If the input signal is not actively driven to both levels, call :cpp:func:`gpio_set_pull_mode()` to select the pull direction that keeps the pin at the level you expect when the line is idle. + Keep ``prescale = 1`` (bypass) and capture both edges when measuring pulse width or duty cycle. Prefer raising ``prescale`` only for very fast inputs when you only need frequency or period, and capture a single edge type only. + +The figures below show how ``prescale`` affects capture timing for rising-only, falling-only, and both-edge modes. + +.. figure:: /../_static/mcpwm/capture_prescale_rising.svg + :align: center + :alt: Rising-edge only capture with prescale bypass and prescale 4. + +Rising-edge only. With ``prescale = 1`` (bypass), every rising edge captures as ``R``. With ``prescale > 1``, the first capture is at cycle ``prescale / 2 - 1``, then every ``prescale``-th rising edge (the figure shows ``prescale = 4``: cycle 1, 5, …). Same-edge spacing is ``prescale`` input periods. + +.. figure:: /../_static/mcpwm/capture_prescale_falling.svg + :align: center + :alt: Falling-edge only capture with prescale bypass and prescale 4. + +Falling-edge only. With ``prescale = 1`` (bypass), every falling edge captures as ``F``. With ``prescale > 1``, GPIO falling edges do not produce captures; **events land on rising steps**. The first capture is at cycle ``prescale - 1``, then every ``prescale`` rising steps, while ``cap_edge`` still reports ``F`` (the figure shows ``prescale = 4``: cycle 3, 7, …). + +.. figure:: /../_static/mcpwm/capture_prescale_both.svg + :align: center + :alt: Both-edge capture with prescale bypass and prescale 4. + +Both edges. With ``prescale = 1`` (bypass), ``R``/``F`` match the physical pin edges. With ``prescale > 1``, captures fire on rising pin steps only (every ``prescale / 2`` periods). The first capture matches rising-only (cycle ``prescale / 2 - 1``) and reports ``R``, then ``R``/``F`` alternate; adjacent ``R``/``F`` spacing is not pulse width. Same-edge gaps remain about ``prescale`` periods. Always compute period or frequency from timestamps of the same reported edge type. + +.. warning:: + + With ``prescale > 1``, edge polarity can disappear: reported ``R``/``F`` describe the post-prescale event type, not the physical GPIO edge. Falling-edge-only capture in particular can fire on rising pin steps while still reporting ``F``. Do not infer the pin transition from ``cap_edge``, and do not treat adjacent opposite-edge gaps as pulse width. Capture event callbacks ======================= diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst index e514f62edad..951c5363904 100644 --- a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst @@ -68,15 +68,56 @@ MCPWM 捕获:测量输入脉冲 .. list:: - - :cpp:member:`gpio_num ` — 承载输入信号的 GPIO。 - - :cpp:member:`prescale ` — 捕获前对输入信号分频,有效输入频率为捕获时钟除以 ``prescale``。提高它可扩展可测周期范围,但会降低时间分辨率。 + - :cpp:member:`gpio_num ` — 承载输入信号的 GPIO。驱动会把它配成输入,但不会设置上拉或下拉;若信号并非主动驱动到两个电平,请调用 :cpp:func:`gpio_set_pull_mode()`,让引脚空闲时保持在期望电平。 + - :cpp:member:`prescale ` — 输入预分频比。同沿(相同上报边沿类型)两次捕获的间隔约为 ``prescale`` 个输入周期(捕获速率 ≈ ``输入速率 / prescale``)。``0`` 或 ``1`` 表示不分频(bypass);其它值必须为偶数。结构体里不写该字段时默认为 ``0``,即 bypass。提高它可扩展可测周期范围,但会降低时间分辨率。流水线顺序与各边沿模式下的行为见 :ref:`mcpwm-cap-input-prescale`。 - :cpp:member:`pos_edge ` 和 :cpp:member:`neg_edge ` — 捕获哪些边沿。示例同时捕获两个边沿,这正是脉宽测量所需的。 - :cpp:member:`invert_cap_signal ` — 捕获前反相输入信号,让引脚上的逻辑 ``1`` 在捕获外设看来是 ``0``,反之亦然。 - :cpp:member:`intr_priority ` — 捕获回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。 +.. _mcpwm-cap-input-prescale: + +输入预分频 +========== + +捕获通道内部按**固定顺序**串行处理输入,两级不能对调: + +1. 先用 ``prescale`` 对 GPIO 波形做预分频(``0``/``1`` 为 bypass); +2. 再根据 ``pos_edge``/``neg_edge`` 决定哪些分频后的事件上报给软件。 + +也就是说,硬件不是“先按设定的边沿挑选 GPIO,再对选中的边沿分频”。因此 ``prescale > 1`` 时: + +- 回调里的 :cpp:member:`cap_edge ` 不一定等于 GPIO 的物理边沿; +- 相邻异沿间隔**不是真实脉宽**,**无法还原真实占空比**。 + +请用相同上报类型(同沿)的时间戳计算周期或频率。各边沿模式下的具体时序见下方示意图。 + .. note:: - 捕获驱动会把 GPIO 配置为输入,但不会设置任何上拉或下拉电阻。如果输入信号并非主动驱动到两个电平,请调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉方向,让引脚空闲时保持在你期望的电平。 + 测脉宽或占空比时保持 ``prescale = 1`` (bypass)并捕获双边沿。建议仅在输入过快、只需测量频率/周期时再提高 ``prescale``,且只捕获单边沿。 + +下图说明 ``prescale`` 在仅上升沿、仅下降沿和双边沿模式下如何影响捕获时机。 + +.. figure:: /../_static/mcpwm/capture_prescale_rising.svg + :align: center + :alt: 仅上升沿捕获:prescale bypass 与 prescale 4。 + +仅上升沿。``prescale = 1`` (bypass)时每个上升沿捕获为 ``R``。``prescale > 1`` 时,首次捕获在 cycle ``prescale / 2 - 1``,之后每隔 ``prescale`` 个上升沿一次(图中 ``prescale = 4``:cycle 1、5、…)。同沿间隔为 ``prescale`` 个输入周期。 + +.. figure:: /../_static/mcpwm/capture_prescale_falling.svg + :align: center + :alt: 仅下降沿捕获:prescale bypass 与 prescale 4。 + +仅下降沿。``prescale = 1`` (bypass)时每个下降沿捕获为 ``F``。``prescale > 1`` 时 GPIO 下降沿不产生捕获;**事件落在上升沿步骤上**。首次捕获在 cycle ``prescale - 1``,之后每隔 ``prescale`` 个上升沿一次,``cap_edge`` 仍报 ``F`` (图中 ``prescale = 4``:cycle 3、7、…)。 + +.. figure:: /../_static/mcpwm/capture_prescale_both.svg + :align: center + :alt: 双边沿捕获:prescale bypass 与 prescale 4。 + +双边沿。``prescale = 1`` (bypass)时 ``R``/``F`` 对应真实引脚边沿。``prescale > 1`` 时只在上升沿步骤触发(每隔 ``prescale / 2`` 个周期)。首次与仅上升沿相同(cycle ``prescale / 2 - 1``)且报 ``R``,随后 ``R``/``F`` 交替;相邻 ``R``/``F`` 间隔不是脉宽。同沿间隔仍约为 ``prescale`` 个周期。请用相同上报类型(同沿)的时间戳计算周期或频率。 + +.. warning:: + + ``prescale > 1`` 时边沿极性可能“消失”:上报的 ``R``/``F`` 描述的是分频之后的事件类型,不是 GPIO 物理边沿。尤其是仅下降沿路径会出现“上升沿步骤却报 ``F``”——不要用它推断引脚真实跳变,也不要用相邻异沿去算脉宽。 捕获事件回调 ============