diff --git a/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h b/components/esp_driver_mcpwm/include/driver/mcpwm_cap.h index a56d903bc37..a0a82e40fc5 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-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -140,7 +140,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 ec799d66370..8d206cd47b6 100644 --- a/components/esp_driver_mcpwm/src/mcpwm_cap.c +++ b/components/esp_driver_mcpwm/src/mcpwm_cap.c @@ -276,7 +276,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_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_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); @@ -298,7 +301,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 8c01654f4f8..79febd3d4c9 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-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,10 +7,9 @@ #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "freertos/event_groups.h" #include "unity.h" #include "soc/soc_caps.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" @@ -262,3 +261,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/hal/esp32/include/hal/mcpwm_ll.h b/components/hal/esp32/include/hal/mcpwm_ll.h index 3aef65d8771..b41e71222ff 100644 --- a/components/hal/esp32/include/hal/mcpwm_ll.h +++ b/components/hal/esp32/include/hal/mcpwm_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1604,16 +1604,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); } //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// diff --git a/components/hal/esp32c5/include/hal/mcpwm_ll.h b/components/hal/esp32c5/include/hal/mcpwm_ll.h index b1ba7b13625..645e1548062 100644 --- a/components/hal/esp32c5/include/hal/mcpwm_ll.h +++ b/components/hal/esp32c5/include/hal/mcpwm_ll.h @@ -1635,16 +1635,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/hal/esp32c6/include/hal/mcpwm_ll.h b/components/hal/esp32c6/include/hal/mcpwm_ll.h index c535e641164..267da280b5d 100644 --- a/components/hal/esp32c6/include/hal/mcpwm_ll.h +++ b/components/hal/esp32c6/include/hal/mcpwm_ll.h @@ -1613,16 +1613,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/hal/esp32h2/include/hal/mcpwm_ll.h b/components/hal/esp32h2/include/hal/mcpwm_ll.h index b78b503a5eb..026013325a3 100644 --- a/components/hal/esp32h2/include/hal/mcpwm_ll.h +++ b/components/hal/esp32h2/include/hal/mcpwm_ll.h @@ -1611,16 +1611,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/hal/esp32p4/include/hal/mcpwm_ll.h b/components/hal/esp32p4/include/hal/mcpwm_ll.h index 4031c060707..6b4259f5470 100644 --- a/components/hal/esp32p4/include/hal/mcpwm_ll.h +++ b/components/hal/esp32p4/include/hal/mcpwm_ll.h @@ -1685,16 +1685,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/hal/esp32s3/include/hal/mcpwm_ll.h b/components/hal/esp32s3/include/hal/mcpwm_ll.h index bebf87589fa..cba490b1b8b 100644 --- a/components/hal/esp32s3/include/hal/mcpwm_ll.h +++ b/components/hal/esp32s3/include/hal/mcpwm_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -1612,16 +1612,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); } //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// 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.rst b/docs/en/api-reference/peripherals/mcpwm.rst index 5ee2b89e049..cadee2e45ca 100644 --- a/docs/en/api-reference/peripherals/mcpwm.rst +++ b/docs/en/api-reference/peripherals/mcpwm.rst @@ -936,6 +936,49 @@ The parameter ``user_data`` of :cpp:func:`mcpwm_capture_channel_register_event_c This function will lazy install interrupt service for the MCPWM capture channel, whereas the service can only be removed in :cpp:type:`mcpwm_del_capture_channel`. +Input Prescale +~~~~~~~~~~~~~~ + +The capture channel processes the input in a **fixed, serial** order: + +1. **First** divide the GPIO waveform by ``prescale`` (``0`` or ``1`` means bypass); +2. **Then** use ``pos_edge``/``neg_edge`` to decide which post-prescale events are reported to software. + +So hardware does **not** first filter physical GPIO edges by polarity and only then divide them. With ``prescale > 1``: + +- The :cpp:member:`cap_edge ` seen 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** from them. + +Always compute period or frequency from timestamps of the same reported edge type. + +.. note:: + + Keep ``prescale = 1`` and capture both edges when measuring pulse width or duty cycle. Raise ``prescale`` only when you need to reduce the capture rate for fast inputs and you only care about period or frequency. + +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`` every rising edge captures as ``R``. With ``prescale > 1``, the first capture is at cycle ``prescale / 2 - 1``, then every ``prescale``-th rising edge. + +.. 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`` every falling edge captures as ``F``. With ``prescale > 1``, GPIO falling edges do not directly produce captures; events land on rising steps while ``cap_edge`` still reports ``F``. + +.. figure:: /../_static/mcpwm/capture_prescale_both.svg + :align: center + :alt: Both-edge capture with prescale bypass and prescale 4. + +Both-edge capture. With ``prescale = 1``, ``R``/``F`` match the physical pin edges. With ``prescale > 1``, captures fire on rising pin steps only, while reported ``R``/``F`` alternate after prescaling. + +.. warning:: + + Do not infer the physical GPIO transition from :cpp:member:`mcpwm_capture_event_data_t::cap_edge` when ``prescale > 1``, and do not treat adjacent opposite-edge gaps as pulse width. + Enable and Disable Capture Channel ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm.rst b/docs/zh_CN/api-reference/peripherals/mcpwm.rst index 52ca6f16c97..437885d9941 100644 --- a/docs/zh_CN/api-reference/peripherals/mcpwm.rst +++ b/docs/zh_CN/api-reference/peripherals/mcpwm.rst @@ -936,6 +936,49 @@ MCPWM 捕获通道支持在信号上检测到有效边沿时发送通知。须 此函数会延迟安装 MCPWM 捕获的中断服务。中断服务只能通过 :cpp:type:`mcpwm_del_capture_channel` 移除。 +输入预分频 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +捕获通道内部按**固定顺序**串行处理输入: + +1. 先用 ``prescale`` 对 GPIO 波形做分频(``0`` 或 ``1`` 表示 bypass); +2. 再根据 ``pos_edge``/``neg_edge`` 决定哪些分频后的事件上报给软件。 + +因此,硬件并不是先按物理 GPIO 边沿极性过滤,再对这些边沿做分频。对于 ``prescale > 1``: + +- 回调里看到的 :cpp:member:`cap_edge ` 可能和 GPIO 的真实物理边沿不一致。 +- 相邻异沿之间的时间差**不代表脉宽**,并且**无法据此还原真实占空比**。 + +计算周期或频率时,请始终使用相同上报边沿类型(同沿)的两次时间戳。 + +.. note:: + + 测量脉宽或占空比时,请保持 ``prescale = 1`` 并同时捕获双边沿。只有在输入过快、你只关心周期或频率时,才建议提高 ``prescale`` 来降低捕获速率。 + +下图说明 ``prescale`` 对仅上升沿、仅下降沿和双边沿捕获时序的影响。 + +.. figure:: /../_static/mcpwm/capture_prescale_rising.svg + :align: center + :alt: 仅上升沿捕获:prescale bypass 与 prescale 4。 + +仅上升沿。``prescale = 1`` 时,每个上升沿都会捕获为 ``R``。``prescale > 1`` 时,首次捕获出现在 cycle ``prescale / 2 - 1``,之后每隔 ``prescale`` 个上升沿捕获一次。 + +.. figure:: /../_static/mcpwm/capture_prescale_falling.svg + :align: center + :alt: 仅下降沿捕获:prescale bypass 与 prescale 4。 + +仅下降沿。``prescale = 1`` 时,每个下降沿都会捕获为 ``F``。``prescale > 1`` 时,GPIO 的下降沿不会直接触发捕获;事件会落在上升沿步骤上,但 ``cap_edge`` 仍然上报 ``F``。 + +.. figure:: /../_static/mcpwm/capture_prescale_both.svg + :align: center + :alt: 双边沿捕获:prescale bypass 与 prescale 4。 + +双边沿。``prescale = 1`` 时,``R``/``F`` 与引脚真实边沿一致。``prescale > 1`` 时,捕获只会落在上升沿步骤上,而分频后的上报事件在 ``R``/``F`` 之间交替。 + +.. warning:: + + 当 ``prescale > 1`` 时,不要根据 :cpp:member:`mcpwm_capture_event_data_t::cap_edge` 去推断 GPIO 的真实跳变方向,也不要把相邻异沿间隔当作脉宽。 + 启用或禁用捕获通道 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~