Skip to content

Commit

Permalink
fix(PCNT): Add zero input gpio enable flag
Browse files Browse the repository at this point in the history
The default zero input gpio num is 0. Users need to set it additionally
when not use zero input signal. It may become a breaking change.
Add an enable flag to avoid it.
  • Loading branch information
Kainarx committed Aug 23, 2023
1 parent 52bca70 commit d7d8781
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 99 deletions.
35 changes: 28 additions & 7 deletions components/driver/pcnt/include/driver/pulse_cnt.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,8 @@ typedef struct {
int high_limit; /*!< High limitation of the count unit, should be higher than 0 */
int intr_priority; /*!< PCNT interrupt priority,
if set to 0, the driver will try to allocate an interrupt with a relative low priority (1,2,3) */
#if SOC_PCNT_SUPPORT_ZERO_INPUT
int zero_input_gpio_num; /*!< GPIO number used by the clear signal, the default active level is high, input mode with pull down enabled. Set to -1 if unused */
#endif
struct {
uint32_t accum_count: 1; /*!< Whether to accumulate the count value when overflows at the high/low limit */
#if SOC_PCNT_SUPPORT_ZERO_INPUT
uint32_t invert_zero_input: 1; /*!< Invert the zero input signal and set input mode with pull up.*/
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
#endif
} flags; /*!< Extra flags */
} pcnt_unit_config_t;

Expand Down Expand Up @@ -146,6 +139,34 @@ esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit);
*/
esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config);

#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
/**
* @brief PCNT clear signal configuration
*/
typedef struct {
int clear_signal_gpio_num; /*!< GPIO number used by the clear signal, the default active level is high, input mode with pull down enabled */
struct {
uint32_t invert_clear_signal: 1; /*!< Invert the clear input signal and set input mode with pull up */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
} flags; /*!< clear signal config flags */
} pcnt_clear_signal_config_t;

/**
* @brief Set clear signal for PCNT unit
*
* @note The function of clear signal is the same as `pcnt_unit_clear_count()`. High-level Active
*
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
* @param[in] config PCNT clear signal configuration, set config to NULL means disabling the clear signal
* @return
* - ESP_OK: Set clear signal successfully
* - ESP_ERR_INVALID_ARG: Set clear signal failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Set clear signal failed because set clear signal repeatly or disable clear signal before set it
* - ESP_FAIL: Set clear signal failed because of other error
*/
esp_err_t pcnt_unit_set_clear_signal(pcnt_unit_handle_t unit, const pcnt_clear_signal_config_t *config);
#endif

/**
* @brief Enable the PCNT unit
*
Expand Down
68 changes: 40 additions & 28 deletions components/driver/pcnt/pulse_cnt.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ struct pcnt_unit_t {
int unit_id; // allocated unit numerical ID
int low_limit; // low limit value
int high_limit; // high limit value
int zero_input_gpio_num; // which gpio clear signal input
int clear_signal_gpio_num; // which gpio clear signal input
int accum_value; // accumulated count 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
Expand Down Expand Up @@ -235,6 +235,7 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
unit->high_limit = config->high_limit;
unit->low_limit = config->low_limit;
unit->accum_value = 0;
unit->clear_signal_gpio_num = -1;
unit->flags.accum_count = config->flags.accum_count;

// clear/pause register is shared by all units, so using group's spinlock
Expand All @@ -253,29 +254,6 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID; // invalid all watch point
}

#if SOC_PCNT_SUPPORT_ZERO_INPUT
// GPIO configuration
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
.pull_down_en = true,
.pull_up_en = false,
};

if (config->zero_input_gpio_num >= 0) {
if (config->flags.invert_zero_input) {
gpio_conf.pull_down_en = false;
gpio_conf.pull_up_en = true;
}
gpio_conf.pin_bit_mask = 1ULL << config->zero_input_gpio_num;
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config zero GPIO failed");
esp_rom_gpio_connect_in_signal(config->zero_input_gpio_num,
pcnt_periph_signals.groups[group_id].units[unit_id].clear_sig,
config->flags.invert_zero_input);
}
unit->zero_input_gpio_num = config->zero_input_gpio_num;
#endif // SOC_PCNT_SUPPORT_ZERO_INPUT

ESP_LOGD(TAG, "new pcnt unit (%d,%d) at %p, count range:[%d,%d]", group_id, unit_id, unit, unit->low_limit, unit->high_limit);
*ret_unit = unit;
return ESP_OK;
Expand All @@ -299,18 +277,52 @@ esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit)
ESP_RETURN_ON_FALSE(!unit->channels[i], ESP_ERR_INVALID_STATE, TAG, "channel %d still in working", i);
}

#if SOC_PCNT_SUPPORT_ZERO_INPUT
if (unit->zero_input_gpio_num >= 0) {
gpio_reset_pin(unit->zero_input_gpio_num);
#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
if (unit->clear_signal_gpio_num >= 0) {
gpio_reset_pin(unit->clear_signal_gpio_num);
}
#endif // SOC_PCNT_SUPPORT_ZERO_INPUT
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL

ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id);
// recycle memory resource
ESP_RETURN_ON_ERROR(pcnt_destroy(unit), TAG, "destroy pcnt unit failed");
return ESP_OK;
}

#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
esp_err_t pcnt_unit_set_clear_signal(pcnt_unit_handle_t unit, const pcnt_clear_signal_config_t *config)
{
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
pcnt_group_t *group = unit->group;
int group_id = group->group_id;
int unit_id = unit->unit_id;

if (config) {
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
.pull_down_en = true,
.pull_up_en = false,
};
if (config->flags.invert_clear_signal) {
gpio_conf.pull_down_en = false;
gpio_conf.pull_up_en = true;
}
gpio_conf.pin_bit_mask = 1ULL << config->clear_signal_gpio_num;
ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config zero signal GPIO failed");
esp_rom_gpio_connect_in_signal(config->clear_signal_gpio_num,
pcnt_periph_signals.groups[group_id].units[unit_id].clear_sig,
config->flags.invert_clear_signal);
unit->clear_signal_gpio_num = config->clear_signal_gpio_num;
} else {
ESP_RETURN_ON_FALSE(unit->clear_signal_gpio_num >= 0, ESP_ERR_INVALID_STATE, TAG, "zero signal not set yet");
gpio_reset_pin(unit->clear_signal_gpio_num);
unit->clear_signal_gpio_num = -1;
}
return ESP_OK;
}
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL

esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config)
{
pcnt_group_t *group = NULL;
Expand Down
54 changes: 28 additions & 26 deletions components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ TEST_CASE("pcnt_unit_install_uninstall", "[pcnt]")
.low_limit = -100,
.high_limit = 100,
.intr_priority = 0,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};
pcnt_unit_handle_t units[SOC_PCNT_UNITS_PER_GROUP];
int count_value = 0;
Expand Down Expand Up @@ -91,9 +88,6 @@ TEST_CASE("pcnt_channel_install_uninstall", "[pcnt]")
pcnt_unit_config_t unit_config = {
.low_limit = -100,
.high_limit = 100,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};
pcnt_chan_config_t chan_config = {
.edge_gpio_num = TEST_PCNT_GPIO_A, // only detect edge signal in this case
Expand Down Expand Up @@ -182,9 +176,6 @@ TEST_CASE("pcnt_multiple_units_pulse_count", "[pcnt]")
pcnt_unit_config_t unit_config = {
.low_limit = -100,
.high_limit = 100,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};
pcnt_unit_handle_t units[2];
for (int i = 0; i < 2; i++) {
Expand Down Expand Up @@ -250,9 +241,6 @@ TEST_CASE("pcnt_quadrature_decode_event", "[pcnt]")
pcnt_unit_config_t unit_config = {
.low_limit = -100,
.high_limit = 100,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};

printf("install pcnt unit\r\n");
Expand Down Expand Up @@ -376,9 +364,6 @@ TEST_CASE("pcnt_zero_cross_mode", "[pcnt]")
pcnt_unit_config_t unit_config = {
.low_limit = -100,
.high_limit = 100,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};

printf("install pcnt unit\r\n");
Expand Down Expand Up @@ -471,9 +456,6 @@ TEST_CASE("pcnt_virtual_io", "[pcnt]")
pcnt_unit_config_t unit_config = {
.low_limit = -100,
.high_limit = 100,
#if SOC_PCNT_SUPPORT_ZERO_INPUT
.zero_input_gpio_num = -1,
#endif
};
pcnt_chan_config_t chan_config = {
.edge_gpio_num = TEST_PCNT_GPIO_A, // only detect edge signal in this case
Expand Down Expand Up @@ -520,23 +502,24 @@ TEST_CASE("pcnt_virtual_io", "[pcnt]")
TEST_ESP_OK(pcnt_del_unit(unit));
}

#if SOC_PCNT_SUPPORT_ZERO_INPUT
#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
TEST_CASE("pcnt_zero_input_signal", "[pcnt]")
{
pcnt_unit_config_t unit_config = {
.low_limit = -1000,
.high_limit = 1000,
.zero_input_gpio_num = TEST_PCNT_GPIO_Z,
.flags.io_loop_back = 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,

pcnt_clear_signal_config_t clear_signal_config = {
.clear_signal_gpio_num = TEST_PCNT_GPIO_Z,
.flags.io_loop_back = true,
};
TEST_ESP_OK(pcnt_unit_set_glitch_filter(unit, &filter_config));

TEST_ESP_OK(pcnt_unit_set_clear_signal(unit, &clear_signal_config));

printf("install pcnt channels\r\n");
pcnt_chan_config_t chan_config = {
Expand Down Expand Up @@ -571,11 +554,30 @@ TEST_CASE("pcnt_zero_input_signal", "[pcnt]")

TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
printf("count_value=%d\r\n", count_value);
TEST_ASSERT_EQUAL(0, count_value); // 0 after rst_sig
TEST_ASSERT_EQUAL(0, count_value); // 0 after zero signal

printf("remove zero signal\r\n");
TEST_ESP_OK(pcnt_unit_set_clear_signal(unit, NULL));
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, pcnt_unit_set_clear_signal(unit, NULL));

// trigger 10 rising edge on GPIO
test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);

TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
printf("count_value=%d\r\n", count_value);
TEST_ASSERT_EQUAL(10, count_value);

printf("simulating zero input signal\r\n");
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_Z, 1));
TEST_ESP_OK(gpio_set_level(TEST_PCNT_GPIO_Z, 0));

TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
printf("count_value=%d\r\n", count_value);
TEST_ASSERT_EQUAL(10, count_value); // 10 with no zero signal

TEST_ESP_OK(pcnt_del_channel(channel));
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_ZERO_INPUT
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL
2 changes: 1 addition & 1 deletion components/soc/esp32p4/include/soc/Kconfig.soc_caps.in
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ config SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE
bool
default y

config SOC_PCNT_SUPPORT_ZERO_INPUT
config SOC_PCNT_SUPPORT_CLEAR_SIGNAL
bool
default y

Expand Down
2 changes: 1 addition & 1 deletion components/soc/esp32p4/include/soc/soc_caps.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@
#define SOC_PCNT_CHANNELS_PER_UNIT 2
#define SOC_PCNT_THRES_POINT_PER_UNIT 2
#define SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE 1
#define SOC_PCNT_SUPPORT_ZERO_INPUT 1 /*!< Support encoder with Zero phase input */
#define SOC_PCNT_SUPPORT_CLEAR_SIGNAL 1 /*!< Support clear signal input */

/*--------------------------- RMT CAPS ---------------------------------------*/
#define SOC_RMT_GROUPS 1U /*!< One RMT group */
Expand Down
59 changes: 41 additions & 18 deletions docs/en/api-reference/peripherals/pcnt.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ Functional Overview

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).
- :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.
- :ref:`pcnt-enable-disable-unit` - describes how to enable and disable the PCNT unit.
- :ref:`pcnt-unit-io-control` - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
- :ref:`pcnt-power-management` - describes what functionality will prevent the chip from going into low power mode.
- :ref:`pcnt-iram-safe` - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
- :ref:`pcnt-thread-safe` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`pcnt-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. list::

- :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).
- :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 zero signal.
- :ref:`pcnt-enable-disable-unit` - describes how to enable and disable the PCNT unit.
- :ref:`pcnt-unit-io-control` - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
- :ref:`pcnt-power-management` - describes what functionality will prevent the chip from going into low power mode.
- :ref:`pcnt-iram-safe` - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
- :ref:`pcnt-thread-safe` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`pcnt-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.

.. _pcnt-resource-allocation:

Expand All @@ -52,12 +55,6 @@ To install a PCNT unit, there's a configuration structure that needs to be given

Since all PCNT units share the same interrupt source, when installing multiple PCNT units make sure that the interrupt priority :cpp:member:`pcnt_unit_config_t::intr_priority` is the same for each unit.

.. only:: SOC_PCNT_SUPPORT_ZERO_INPUT

- :cpp:member:`pcnt_unit_config_t::zero_input_gpio_num` specify the GPIO numbers used by **zero** type signal. The default active level is high, and the input mode is pull-down enabled. Please note, it can be assigned to `-1` if it's not actually used, and GPIO will not be initialized.
- :cpp:member:`pcnt_unit_config_t::invert_zero_input` is used to decide whether to invert the input signal before it going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware. The input mode is pull-up enabled when the input signal is invert.
- :cpp:member:`pcnt_unit_config_t::io_loop_back` is for debug only, which enables both the GPIO's input and output paths. This can help to simulate the zreo pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO.

Unit allocation and initialization is done by calling a function :cpp:func:`pcnt_new_unit` with :cpp:type:`pcnt_unit_config_t` as an input parameter. The function will return a PCNT unit handle only when it runs correctly. Specifically, when there are no more free PCNT units in the pool (i.e. unit resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT units is recorded by :c:macro:`SOC_PCNT_UNITS_PER_GROUP` for reference.

If a previously created PCNT unit is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`pcnt_del_unit`. Which in return allows the underlying unit hardware to be used for other purposes. Before deleting a PCNT unit, one should ensure the following prerequisites:
Expand Down Expand Up @@ -204,7 +201,33 @@ This function should be called when the unit is in the init state. Otherwise, it
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
.. _pcnt-enable-disable-unit:
.. only:: SOC_PCNT_SUPPORT_CLEAR_SIGNAL

.. _pcnt-set-clear-signal:

Set Clear Signal
^^^^^^^^^^^^^^^^

The PCNT unit can receive a zero signal from the GPIO. The parameters that can be configured for the zero signal are listed in :cpp:type:`pcnt_clear_signal_config_t`:

- :cpp:member:`pcnt_clear_signal_config_t::zero_input_gpio_num` specify the GPIO numbers used by **zero** signal. The default active level is high, and the input mode is pull-down enabled.
- :cpp:member:`pcnt_clear_signal_config_t::invert_zero_input` is used to decide whether to invert the input signal before it going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware. The input mode is pull-up enabled when the input signal is invert.
- :cpp:member:`pcnt_clear_signal_config_t::io_loop_back` is for debug only, which enables both the GPIO's input and output paths. This can help to simulate the zreo pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO.

This signal acts in the same way as calling :cpp:func:`pcnt_unit_clear_count`, but is not subject to software latency, and is suitable for use in situations with high latency requirements.

.. code:: c
pcnt_clear_signal_config_t clear_signal_config = {
.clear_signal_gpio_num = PCNT_CLEAR_SIGNAL_GPIO,
};
ESP_ERROR_CHECK(pcnt_unit_set_clear_signal(pcnt_unit, &clear_signal_config));
.. _pcnt-enable-disable-unit:

.. only:: not SOC_PCNT_SUPPORT_CLEAR_SIGNAL

.. _pcnt-enable-disable-unit:

Enable and Disable Unit
^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
Loading

0 comments on commit d7d8781

Please sign in to comment.