Merge branch 'fix/mcpwm_cap_prescale_v5.4' into 'release/v5.4'

fix(mcpwm): correct the wrong capture prescale (v5.4)

See merge request espressif/esp-idf!52507
This commit is contained in:
morris
2026-09-14 15:16:32 +08:00
14 changed files with 533 additions and 27 deletions
@@ -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 {
+5 -2
View File
@@ -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
@@ -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 <inttypes.h>
#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));
}
+6 -4
View File
@@ -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//////////////////////////////////////////////////////////
@@ -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////////////////////////////////////////////////////////////
@@ -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////////////////////////////////////////////////////////////
@@ -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////////////////////////////////////////////////////////////
@@ -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////////////////////////////////////////////////////////////
@@ -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//////////////////////////////////////////////////////////