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:
Chen Jichang 2024-06-19 21:42:03 +08:00
parent c9a504cdcb
commit d81546628a
9 changed files with 340 additions and 26 deletions

View File

@ -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
*

View File

@ -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) {

View File

@ -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

View File

@ -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))

View File

@ -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
*/

View File

@ -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

View File

@ -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 */

View File

@ -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:

View File

@ -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: