mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
feat(pcnt): add support for step notify
PCNT can add watch of value increment that we call step notify. This commit add a step notify driver and a test for the driver. Closes https://github.com/espressif/esp-idf/issues/9604 Closes https://github.com/espressif/esp-idf/issues/12136
This commit is contained in:
parent
c9a504cdcb
commit
d81546628a
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -52,7 +52,7 @@ typedef bool (*pcnt_watch_cb_t)(pcnt_unit_handle_t unit, const pcnt_watch_event_
|
||||
* @note When CONFIG_PCNT_ISR_IRAM_SAFE is enabled, the callback itself and functions callbed by it should be placed in IRAM.
|
||||
*/
|
||||
typedef struct {
|
||||
pcnt_watch_cb_t on_reach; /*!< Called when PCNT unit counter reaches any watch point */
|
||||
pcnt_watch_cb_t on_reach; /*!< Called when PCNT unit counter reaches any watch point or step notify*/
|
||||
} pcnt_event_callbacks_t;
|
||||
|
||||
/**
|
||||
@ -65,6 +65,10 @@ typedef struct {
|
||||
if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
|
||||
struct {
|
||||
uint32_t accum_count: 1; /*!< Whether to accumulate the count value when overflows at the high/low limit */
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
uint32_t en_step_notify_up: 1; /*!< Enable step notify in the positive direction*/
|
||||
uint32_t en_step_notify_down: 1; /*!< Enable step notify in the negative direction*/
|
||||
#endif
|
||||
} flags; /*!< Extra flags */
|
||||
} pcnt_unit_config_t;
|
||||
|
||||
@ -283,7 +287,6 @@ esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt
|
||||
/**
|
||||
* @brief Add a watch point for PCNT unit, PCNT will generate an event when the counter value reaches the watch point value
|
||||
*
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] watch_point Value to be watched
|
||||
* @return
|
||||
@ -308,6 +311,31 @@ esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point);
|
||||
*/
|
||||
esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point);
|
||||
|
||||
/**
|
||||
* @brief Add a step notify for PCNT unit, PCNT will generate an event when the incremental(can be positive or negative) of counter value reaches the step interval
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @param[in] step_interval PCNT step notify interval value
|
||||
* @return
|
||||
* - ESP_OK: Add step notify successfully
|
||||
* - ESP_ERR_INVALID_ARG: Add step notify failed because of invalid argument (e.g. the value incremental to be watched is out of the limitation set in `pcnt_unit_config_t`)
|
||||
* - ESP_ERR_INVALID_STATE: Add step notify failed because the step notify has already been added
|
||||
* - ESP_FAIL: Add step notify failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_add_watch_step(pcnt_unit_handle_t unit, int step_interval);
|
||||
|
||||
/**
|
||||
* @brief Remove a step notify for PCNT unit
|
||||
*
|
||||
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
|
||||
* @return
|
||||
* - ESP_OK: Remove step notify successfully
|
||||
* - ESP_ERR_INVALID_ARG: Remove step notify failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Remove step notify failed because the step notify was not added by `pcnt_unit_add_watch_step()` yet
|
||||
* - ESP_FAIL: Remove step notify failed because of other error
|
||||
*/
|
||||
esp_err_t pcnt_unit_remove_watch_step(pcnt_unit_handle_t unit);
|
||||
|
||||
/**
|
||||
* @brief Create PCNT channel for specific unit, each PCNT has several channels associated with it
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -93,8 +93,10 @@ struct pcnt_unit_t {
|
||||
int unit_id; // allocated unit numerical ID
|
||||
int low_limit; // low limit value
|
||||
int high_limit; // high limit value
|
||||
int clear_signal_gpio_num; // which gpio clear signal input
|
||||
int step_limit; // step limit value
|
||||
int clear_signal_gpio_num; // which gpio clear signal input
|
||||
int accum_value; // accumulated count value
|
||||
int step_interval; // PCNT step notify interval value
|
||||
pcnt_chan_t *channels[SOC_PCNT_CHANNELS_PER_UNIT]; // array of PCNT channels
|
||||
pcnt_watch_point_t watchers[PCNT_LL_WATCH_EVENT_MAX]; // array of PCNT watchers
|
||||
intr_handle_t intr; // interrupt handle
|
||||
@ -107,6 +109,10 @@ struct pcnt_unit_t {
|
||||
void *user_data; // user data registered by user, which would be passed to the right callback function
|
||||
struct {
|
||||
uint32_t accum_count: 1; /*!< Whether to accumulate the count value when overflows at the high/low limit */
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
uint32_t en_step_notify_up: 1; /*!< Enable step notify in the positive direction*/
|
||||
uint32_t en_step_notify_down: 1; /*!< Enable step notify in the negative direction*/
|
||||
#endif
|
||||
} flags;
|
||||
};
|
||||
|
||||
@ -194,7 +200,9 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
|
||||
ESP_GOTO_ON_FALSE(1 << (config->intr_priority) & PCNT_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, err,
|
||||
TAG, "invalid interrupt priority:%d", config->intr_priority);
|
||||
}
|
||||
|
||||
#if PCNT_LL_STEP_NOTIFY_DIR_LIMIT
|
||||
ESP_GOTO_ON_FALSE(!(config->flags.en_step_notify_up && config->flags.en_step_notify_down), ESP_ERR_NOT_SUPPORTED, err, TAG, "This target can only notify in one direction");
|
||||
#endif
|
||||
unit = heap_caps_calloc(1, sizeof(pcnt_unit_t), PCNT_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(unit, ESP_ERR_NO_MEM, err, TAG, "no mem for unit");
|
||||
// register unit to the group (because one group can have several units)
|
||||
@ -244,6 +252,19 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
|
||||
unit->clear_signal_gpio_num = -1;
|
||||
unit->flags.accum_count = config->flags.accum_count;
|
||||
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
unit->flags.en_step_notify_down = config->flags.en_step_notify_down;
|
||||
unit->flags.en_step_notify_up = config->flags.en_step_notify_up;
|
||||
#if PCNT_LL_STEP_NOTIFY_DIR_LIMIT
|
||||
if (config->flags.en_step_notify_up) {
|
||||
unit->step_limit = config->high_limit;
|
||||
} else if (config->flags.en_step_notify_down) {
|
||||
unit->step_limit = config->low_limit;
|
||||
}
|
||||
pcnt_ll_set_step_limit_value(group->hal.dev, unit_id, unit->step_limit);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// clear/pause register is shared by all units, so using group's spinlock
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
pcnt_ll_stop_count(group->hal.dev, unit_id);
|
||||
@ -622,6 +643,51 @@ esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
esp_err_t pcnt_unit_add_watch_step(pcnt_unit_handle_t unit, int step_interval)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE((step_interval > 0 && unit->flags.en_step_notify_up) || (step_interval < 0 && unit->flags.en_step_notify_down),
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid step interval");
|
||||
ESP_RETURN_ON_FALSE(unit->flags.en_step_notify_up || unit->flags.en_step_notify_down,
|
||||
ESP_ERR_INVALID_STATE, TAG, "step limit is not enabled yet");
|
||||
ESP_RETURN_ON_FALSE(unit->step_interval == 0,
|
||||
ESP_ERR_INVALID_STATE, TAG, "watch step has been set to %d already", unit->step_interval);
|
||||
ESP_RETURN_ON_FALSE(step_interval >= unit->low_limit && step_interval <= unit->high_limit,
|
||||
ESP_ERR_INVALID_ARG, TAG, "step interval out of range [%d,%d]", unit->low_limit, unit->high_limit);
|
||||
ESP_RETURN_ON_FALSE(unit->step_limit % step_interval == 0,
|
||||
ESP_ERR_INVALID_ARG, TAG, "step interval should be a divisor of step limit");
|
||||
|
||||
group = unit->group;
|
||||
unit->step_interval = step_interval;
|
||||
pcnt_ll_set_step_value(group->hal.dev, unit->unit_id, step_interval);
|
||||
// different units are mixing in the same register, so we use the group's spinlock here
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
pcnt_ll_enable_step_notify(group->hal.dev, unit->unit_id, true);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pcnt_unit_remove_watch_step(pcnt_unit_handle_t unit)
|
||||
{
|
||||
pcnt_group_t *group = NULL;
|
||||
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
group = unit->group;
|
||||
ESP_RETURN_ON_FALSE(unit->step_interval != 0, ESP_ERR_INVALID_STATE, TAG, "watch step not added yet");
|
||||
|
||||
unit->step_interval = 0;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
pcnt_ll_enable_step_notify(group->hal.dev, unit->unit_id, false);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif //SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *config, pcnt_channel_handle_t *ret_chan)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@ -841,13 +907,21 @@ IRAM_ATTR static void pcnt_default_isr(void *args)
|
||||
} else if (event_id == PCNT_LL_WATCH_EVENT_HIGH_LIMIT) {
|
||||
unit->accum_value += unit->high_limit;
|
||||
}
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
// zero cross event priority is higher than step limit event, ensure to accumulate the value when the zero cross is caused by step limit
|
||||
if ((event_id == PCNT_LL_WATCH_EVENT_ZERO_CROSS) && (event_status & 1 << PCNT_LL_STEP_EVENT_REACH_LIMIT)) {
|
||||
unit->accum_value += unit->step_limit;
|
||||
} else if (event_id == PCNT_LL_STEP_EVENT_REACH_LIMIT) {
|
||||
unit->accum_value += unit->step_limit;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&unit->spinlock);
|
||||
|
||||
// invoked user registered callback
|
||||
if (on_reach) {
|
||||
pcnt_watch_event_data_t edata = {
|
||||
.watch_point_value = unit->watchers[event_id].watch_point_value,
|
||||
.watch_point_value = event_id < PCNT_LL_WATCH_EVENT_MAX ? unit->watchers[event_id].watch_point_value : pcnt_ll_get_count(group->hal.dev, unit_id),
|
||||
.zero_cross_mode = pcnt_ll_get_zero_cross_mode(group->hal.dev, unit_id),
|
||||
};
|
||||
if (on_reach(unit, &edata, unit->user_data)) {
|
||||
@ -855,6 +929,10 @@ IRAM_ATTR static void pcnt_default_isr(void *args)
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
// The priority of step and step limit event is lowest. Clear the step and step limit event to ensure that in a particular point, event can only be triggered once
|
||||
event_status &= ~(1 << PCNT_LL_STEP_EVENT_REACH_INTERVAL | 1 << PCNT_LL_STEP_EVENT_REACH_LIMIT);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (need_yield) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -581,3 +581,111 @@ TEST_CASE("pcnt_zero_input_signal", "[pcnt]")
|
||||
TEST_ESP_OK(pcnt_del_unit(unit));
|
||||
}
|
||||
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
||||
|
||||
#if SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
TEST_CASE("pcnt_step_notify_event", "[pcnt]")
|
||||
{
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.low_limit = -100,
|
||||
.high_limit = 100,
|
||||
.flags.accum_count = true,
|
||||
.flags.en_step_notify_down = true,
|
||||
};
|
||||
|
||||
printf("install pcnt unit\r\n");
|
||||
pcnt_unit_handle_t unit = NULL;
|
||||
TEST_ESP_OK(pcnt_new_unit(&unit_config, &unit));
|
||||
pcnt_glitch_filter_config_t filter_config = {
|
||||
.max_glitch_ns = 1000,
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_set_glitch_filter(unit, &filter_config));
|
||||
|
||||
printf("install two pcnt channels with different edge/level action\r\n");
|
||||
pcnt_chan_config_t channel_config = {
|
||||
.edge_gpio_num = TEST_PCNT_GPIO_A,
|
||||
.level_gpio_num = TEST_PCNT_GPIO_B,
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
pcnt_channel_handle_t channelA = NULL;
|
||||
TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelA));
|
||||
TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
|
||||
TEST_ESP_OK(pcnt_channel_set_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_HOLD));
|
||||
|
||||
// ensure the simulation signal in a stable state
|
||||
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_A, 1));
|
||||
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_B, 1));
|
||||
|
||||
pcnt_event_callbacks_t cbs = {
|
||||
.on_reach = test_pcnt_quadrature_reach_watch_point,
|
||||
};
|
||||
test_pcnt_quadrature_context_t user_data = {
|
||||
.index = 0,
|
||||
.triggered_watch_values = {0},
|
||||
};
|
||||
TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &user_data));
|
||||
|
||||
printf("add step notify\r\n");
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, 0));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, 20));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, -120));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, -30));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_step(unit, -50));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_step(unit, -100));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -100));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -50));
|
||||
#if !SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE
|
||||
// the above added watch point won't take effect at once, unless we clear the internal counter manually
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(unit));
|
||||
#endif
|
||||
|
||||
TEST_ESP_OK(pcnt_unit_enable(unit));
|
||||
TEST_ESP_OK(pcnt_unit_start(unit));
|
||||
int count_value;
|
||||
|
||||
// trigger 150 rising edge on GPIO
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 150);
|
||||
|
||||
printf("checking count value\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
printf("count_value=%d\r\n", count_value);
|
||||
|
||||
for (int i = 0 ; i < user_data.index; i++) {
|
||||
printf("%d:%d\r\n", i, user_data.triggered_watch_values[i]);
|
||||
}
|
||||
TEST_ASSERT_EQUAL(-150, count_value);
|
||||
TEST_ASSERT_EQUAL(4, user_data.index);
|
||||
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[0]);
|
||||
TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[1]);
|
||||
TEST_ASSERT_EQUAL(-0, user_data.triggered_watch_values[2]);
|
||||
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[3]);
|
||||
|
||||
printf("add a new step interval\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_step(unit));
|
||||
TEST_ESP_OK(pcnt_unit_clear_count(unit));
|
||||
TEST_ESP_OK(pcnt_unit_add_watch_step(unit, -100));
|
||||
user_data.index = 0;
|
||||
|
||||
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 120);
|
||||
|
||||
printf("checking count value\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
|
||||
printf("count_value=%d\r\n", count_value);
|
||||
for (int i = 0 ; i < user_data.index; i++) {
|
||||
printf("%d:%d\r\n", i, user_data.triggered_watch_values[i]);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL(-120, count_value);
|
||||
TEST_ASSERT_EQUAL(3, user_data.index);
|
||||
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[0]);
|
||||
TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[1]);
|
||||
TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[2]);
|
||||
printf("remove step_notify and uninstall channels\r\n");
|
||||
TEST_ESP_OK(pcnt_unit_remove_watch_step(unit));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_remove_watch_step(unit));
|
||||
TEST_ESP_OK(pcnt_del_channel(channelA));
|
||||
TEST_ESP_OK(pcnt_unit_stop(unit));
|
||||
TEST_ESP_OK(pcnt_unit_disable(unit));
|
||||
TEST_ESP_OK(pcnt_del_unit(unit));
|
||||
}
|
||||
#endif // SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
@ -33,6 +33,12 @@ typedef enum {
|
||||
PCNT_LL_WATCH_EVENT_MAX
|
||||
} pcnt_ll_watch_event_id_t;
|
||||
|
||||
typedef enum {
|
||||
PCNT_LL_STEP_EVENT_REACH_LIMIT = PCNT_LL_WATCH_EVENT_MAX,
|
||||
PCNT_LL_STEP_EVENT_REACH_INTERVAL
|
||||
} pcnt_ll_step_event_id_t;
|
||||
|
||||
#define PCNT_LL_STEP_NOTIFY_DIR_LIMIT 1
|
||||
#define PCNT_LL_WATCH_EVENT_MASK ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
|
||||
#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
@ -467,6 +467,10 @@ config SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_RMT_GROUPS
|
||||
int
|
||||
default 1
|
||||
|
@ -303,7 +303,7 @@
|
||||
#define SOC_PCNT_THRES_POINT_PER_UNIT 2
|
||||
#define SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE 1
|
||||
#define SOC_PCNT_SUPPORT_CLEAR_SIGNAL 1
|
||||
// #define SOC_PCNT_SUPPORT_STEP_NOTIFY 1 // IDF-7984
|
||||
#define SOC_PCNT_SUPPORT_STEP_NOTIFY 1
|
||||
|
||||
/*--------------------------- RMT CAPS ---------------------------------------*/
|
||||
#define SOC_RMT_GROUPS 1U /*!< One RMT group */
|
||||
|
@ -25,6 +25,7 @@ Description of the PCNT functionality is divided into the following sections:
|
||||
- :ref:`pcnt-resource-allocation` - covers how to allocate PCNT units and channels with properly set of configurations. It also covers how to recycle the resources when they finished working.
|
||||
- :ref:`pcnt-setup-channel-actions` - covers how to configure the PCNT channel to behave on different signal edges and levels.
|
||||
- :ref:`pcnt-watch-points` - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value).
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :ref:`pcnt-step-notify` - describes how to configure PCNT watch step (i.e., tell PCNT unit to trigger an event when the count increment reaches a certain value).
|
||||
- :ref:`pcnt-register-event-callbacks` - describes how to hook your specific code to the watch point event callback function.
|
||||
- :ref:`pcnt-set-glitch-filter` - describes how to enable and set the timing parameters for the internal glitch filter.
|
||||
:SOC_PCNT_SUPPORT_CLEAR_SIGNAL: - :ref:`pcnt-set-clear-signal` - describes how to set the parameters for the external clear signal.
|
||||
@ -47,9 +48,13 @@ Install PCNT Unit
|
||||
|
||||
To install a PCNT unit, there is a configuration structure that needs to be given in advance: :cpp:type:`pcnt_unit_config_t`:
|
||||
|
||||
- :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit` specify the range for the internal hardware counter. The counter will reset to zero automatically when it crosses either the high or low limit.
|
||||
- :cpp:member:`pcnt_unit_config_t::accum_count` sets whether to create an internal accumulator for the counter. This is helpful when you want to extend the counter's width, which by default is 16 bit at most, defined in the hardware. See also :ref:`pcnt-compensate-overflow-loss` for how to use this feature to compensate the overflow loss.
|
||||
- :cpp:member:`pcnt_unit_config_t::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority.
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit` specify the range for the internal hardware counter. The counter will reset to zero automatically when it crosses either the high or low limit.
|
||||
- :cpp:member:`pcnt_unit_config_t::accum_count` sets whether to create an internal accumulator for the counter. This is helpful when you want to extend the counter's width, which by default is 16 bit at most, defined in the hardware. See also :ref:`pcnt-compensate-overflow-loss` for how to use this feature to compensate the overflow loss.
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::en_step_notify_up` Configure whether to enable watch step to count in the positive direction.
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::en_step_notify_down` Configure whether to enable watch step to count in the negative direction.
|
||||
- :cpp:member:`pcnt_unit_config_t::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority.
|
||||
|
||||
.. note::
|
||||
|
||||
@ -141,7 +146,36 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov
|
||||
|
||||
Due to the hardware limitation, after adding a watch point, you should call :cpp:func:`pcnt_unit_clear_count` to make it take effect.
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
.. _pcnt-step-notify:
|
||||
|
||||
Watch Step
|
||||
^^^^^^^^^^^
|
||||
|
||||
PCNT unit can be configured to watch a specific value increment(can be positive or negative) that you are interested in. The function of watching value increment is also called **Watch Step**. To install watch step requires enabling :cpp:member:`pcnt_unit_config_t::en_step_notify_up` or :cpp:member:`pcnt_unit_config_t::en_step_notify_down`. The step interval itself can not exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`.When the counter increment reaches step interval, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`pcnt-register-event-callbacks` for how to register event callbacks.
|
||||
|
||||
The watch step can be added and removed by :cpp:func:`pcnt_unit_add_watch_step` and :cpp:func:`pcnt_unit_remove_watch_step`. You can not add multiple watch step, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`。
|
||||
|
||||
It is recommended to remove the unused watch step by :cpp:func:`pcnt_unit_remove_watch_step` to recycle the watch step resources.
|
||||
|
||||
.. note::
|
||||
|
||||
When a watch step and a watch point are triggered at the same time, only one interrupt event will be generated.
|
||||
The step interval must be a divisor of :cpp:member:`pcnt_unit_config_t::low_limit` or :cpp:member:`pcnt_unit_config_t::high_limit`.
|
||||
|
||||
.. code:: c
|
||||
|
||||
// add positive direction watch step with 100 step intervals
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_step(pcnt_unit, 100));
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
|
||||
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
|
||||
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -152,10 +186,18 @@ When PCNT unit reaches any enabled watch point, specific event will be generated
|
||||
|
||||
You can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions.
|
||||
|
||||
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
|
||||
.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value that triggers the event.
|
||||
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**.
|
||||
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event or watch step event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
|
||||
|
||||
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
|
||||
|
||||
.. list::
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value or watch step value that triggers the event.
|
||||
:not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value that triggers the event.
|
||||
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**.
|
||||
|
||||
Registering callback function results in lazy installation of interrupt service, thus this function should only be called before the unit is enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it can return :c:macro:`ESP_ERR_INVALID_STATE` error.
|
||||
|
||||
@ -284,7 +326,10 @@ The internal hardware counter will be cleared to zero automatically when it reac
|
||||
|
||||
.. note::
|
||||
|
||||
:cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well.
|
||||
.. list::
|
||||
|
||||
- :cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well.
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - setting the watch step will also enable the accumulator.
|
||||
|
||||
.. _pcnt-power-management:
|
||||
|
||||
|
@ -25,6 +25,7 @@ PCNT 的功能从以下几个方面进行说明:
|
||||
- :ref:`pcnt-resource-allocation` - 说明如何通过配置分配 PCNT 单元和通道,以及在相应操作完成之后,如何回收单元和通道。
|
||||
- :ref:`pcnt-setup-channel-actions` - 说明如何设置通道针对不同信号沿和电平进行操作。
|
||||
- :ref:`pcnt-watch-points` - 说明如何配置观察点,即当计数达到某个数值时,命令 PCNT 单元触发某个事件。
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :ref:`pcnt-step-notify` - 说明如何配置观察步进,即当计数增量达到某个数值时,命令 PCNT 单元触发某个事件。
|
||||
- :ref:`pcnt-register-event-callbacks` - 说明如何将您的代码挂载到观察点事件的回调函数上。
|
||||
- :ref:`pcnt-set-glitch-filter` - 说明如何使能毛刺滤波器并设置其时序参数。
|
||||
:SOC_PCNT_SUPPORT_CLEAR_SIGNAL: - :ref:`pcnt-set-clear-signal` - 说明如何使能外部清零信号并设置其参数。
|
||||
@ -47,9 +48,13 @@ PCNT 单元和通道分别用 :cpp:type:`pcnt_unit_handle_t` 与 :cpp:type:`pcnt
|
||||
|
||||
安装 PCNT 单元时,需要先完成配置 :cpp:type:`pcnt_unit_config_t`:
|
||||
|
||||
- :cpp:member:`pcnt_unit_config_t::low_limit` 与 :cpp:member:`pcnt_unit_config_t::high_limit` 用于指定内部计数器的最小值和最大值。当计数器超过任一限值时,计数器将归零。
|
||||
- :cpp:member:`pcnt_unit_config_t::accum_count` 用于设置是否需要软件在硬件计数值溢出的时候进行累加保存,这有助于“拓宽”计数器的实际位宽。默认情况下,计数器的位宽最高只有 16 比特。请参考 :ref:`pcnt-compensate-overflow-loss` 了解如何利用此功能来补偿硬件计数器的溢出损失。
|
||||
- :cpp:member:`pcnt_unit_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`pcnt_unit_config_t::low_limit` 与 :cpp:member:`pcnt_unit_config_t::high_limit` 用于指定内部计数器的最小值和最大值。当计数器超过任一限值时,计数器将归零。
|
||||
- :cpp:member:`pcnt_unit_config_t::accum_count` 用于设置是否需要软件在硬件计数值溢出的时候进行累加保存,这有助于“拓宽”计数器的实际位宽。默认情况下,计数器的位宽最高只有 16 比特。请参考 :ref:`pcnt-compensate-overflow-loss` 了解如何利用此功能来补偿硬件计数器的溢出损失。
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::en_step_notify_up` 配置是否使能观察正方向步进。
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::en_step_notify_down` 配置是否使能观察负方向步进。
|
||||
- :cpp:member:`pcnt_unit_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
|
||||
|
||||
.. note::
|
||||
|
||||
@ -141,7 +146,36 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值
|
||||
|
||||
由于硬件上的限制,在添加一个新的观察点后,你需要调用 :cpp:func:`pcnt_unit_clear_count` 函数来使之生效。
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
.. _pcnt-step-notify:
|
||||
|
||||
PCNT 观察步进
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
PCNT 单元可被设置为观察一个特定的数值增量(可以是正方向或负方向),这个观察数值增量的功能被称为 **观察步进**。启用观察步进需要使能 :cpp:member:`pcnt_unit_config_t::en_step_notify_up` 或 :cpp:member:`pcnt_unit_config_t::en_step_notify_down` 选项。 步进间隔不能超过 :cpp:type:`pcnt_unit_config_t` 设置的范围,最小值和最大值分别为 :cpp:member:`pcnt_unit_config_t::low_limit` 和 :cpp:member:`pcnt_unit_config_t::high_limit`。当计数器增量到达步进间隔时,会触发一个观察事件,如果在 :cpp:func:`pcnt_unit_register_event_callbacks` 注册过事件回调函数,该事件就会通过中断发送通知。关于如何注册事件回调函数,请参考 :ref:`pcnt-register-event-callbacks`。
|
||||
|
||||
观察步进分别可以通过 :cpp:func:`pcnt_unit_add_watch_step` 和 :cpp:func:`pcnt_unit_remove_watch_step` 进行添加和删除。不能同时添加多个观察步进,否则将返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。
|
||||
|
||||
建议通过 :cpp:func:`pcnt_unit_remove_watch_step` 删除未使用的观察步进来回收资源。
|
||||
|
||||
.. note::
|
||||
|
||||
当观察步进和观察点同时被触发时,只会产生一次中断事件。
|
||||
步进间隔必须是 :cpp:member:`pcnt_unit_config_t::low_limit` 或 :cpp:member:`pcnt_unit_config_t::high_limit` 的因数。
|
||||
|
||||
.. code:: c
|
||||
|
||||
// add positive direction step notify with 100 step intervals
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_step(pcnt_unit, 100));
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
|
||||
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
.. _pcnt-register-event-callbacks:
|
||||
|
||||
|
||||
|
||||
注册事件回调函数
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
@ -152,10 +186,18 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值
|
||||
|
||||
可通过 ``user_ctx`` 将函数上下文保存到 :cpp:func:`pcnt_unit_register_event_callbacks` 中,这些数据会直接传递给回调函数。
|
||||
|
||||
驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`:
|
||||
.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点数值。
|
||||
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向** 和 **计数步长**。
|
||||
驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件或观察步进事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`:
|
||||
|
||||
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
|
||||
|
||||
驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`:
|
||||
|
||||
.. list::
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点或观察步进的数值。
|
||||
:not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点数值。
|
||||
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向** 和 **计数步长**。
|
||||
|
||||
注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。
|
||||
|
||||
@ -284,7 +326,10 @@ PCNT 内部的硬件计数器会在计数达到高/低门限的时候自动清
|
||||
|
||||
.. note::
|
||||
|
||||
:cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。
|
||||
.. list::
|
||||
|
||||
- :cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。
|
||||
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - 设置观察步进后,同时也会启用软件累加器。
|
||||
|
||||
.. _pcnt-power-management:
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user