diff --git a/components/driver/include/driver/pulse_cnt.h b/components/driver/include/driver/pulse_cnt.h index e16916a10b..6310ea7f9e 100644 --- a/components/driver/include/driver/pulse_cnt.h +++ b/components/driver/include/driver/pulse_cnt.h @@ -70,10 +70,12 @@ typedef struct { int edge_gpio_num; /*!< GPIO number used by the edge signal, input mode with pull up enabled. Set to -1 if unused */ int level_gpio_num; /*!< GPIO number used by the level signal, input mode with pull up enabled. Set to -1 if unused */ struct { - uint32_t invert_edge_input: 1; /*!< Invert the input edge signal */ - uint32_t invert_level_input: 1; /*!< Invert the input level signal */ - 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; /*!< Channel config flags */ + uint32_t invert_edge_input: 1; /*!< Invert the input edge signal */ + uint32_t invert_level_input: 1; /*!< Invert the input level signal */ + uint32_t virt_edge_io_level: 1; /*!< Virtual edge IO level, 0: low, 1: high. Only valid when edge_gpio_num is set to -1 */ + uint32_t virt_level_io_level: 1; /*!< Virtual level IO level, 0: low, 1: high. Only valid when level_gpio_num is set to -1 */ + 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; /*!< Channel config flags */ } pcnt_chan_config_t; /** diff --git a/components/driver/pulse_cnt.c b/components/driver/pulse_cnt.c index 0533e77151..2ca41f2ec6 100644 --- a/components/driver/pulse_cnt.c +++ b/components/driver/pulse_cnt.c @@ -22,6 +22,7 @@ #include "esp_rom_gpio.h" #include "soc/soc_caps.h" #include "soc/pcnt_periph.h" +#include "soc/gpio_pins.h" #include "hal/pcnt_hal.h" #include "hal/pcnt_ll.h" #include "hal/gpio_hal.h" @@ -556,13 +557,24 @@ esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *co esp_rom_gpio_connect_in_signal(config->edge_gpio_num, pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].pulse_sig, config->flags.invert_edge_input); + } else { + // using virtual IO + esp_rom_gpio_connect_in_signal(config->flags.virt_edge_io_level ? GPIO_MATRIX_CONST_ONE_INPUT : GPIO_MATRIX_CONST_ZERO_INPUT, + pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].pulse_sig, + config->flags.invert_edge_input); } + if (config->level_gpio_num >= 0) { gpio_conf.pin_bit_mask = 1ULL << config->level_gpio_num; ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config level GPIO failed"); esp_rom_gpio_connect_in_signal(config->level_gpio_num, pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].control_sig, config->flags.invert_level_input); + } else { + // using virtual IO + esp_rom_gpio_connect_in_signal(config->flags.virt_level_io_level ? GPIO_MATRIX_CONST_ONE_INPUT : GPIO_MATRIX_CONST_ZERO_INPUT, + pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].control_sig, + config->flags.invert_level_input); } channel->channel_id = channel_id; diff --git a/components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c b/components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c index 9a91039223..5b40f18d88 100644 --- a/components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c +++ b/components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c @@ -159,8 +159,58 @@ TEST_CASE("pcnt_channel_install_uninstall", "[pcnt]") } } +TEST_CASE("pcnt_multiple_units_pulse_count", "[pcnt]") +{ + printf("install pcnt units\r\n"); + pcnt_unit_config_t unit_config = { + .low_limit = -100, + .high_limit = 100, + }; + pcnt_unit_handle_t units[2]; + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(pcnt_new_unit(&unit_config, &units[i])); + } + + printf("install pcnt channels\r\n"); + const int channel_gpios[] = {TEST_PCNT_GPIO_A, TEST_PCNT_GPIO_B}; + pcnt_chan_config_t chan_config = { + .level_gpio_num = -1, + .flags.io_loop_back = true, + }; + pcnt_channel_handle_t chans[2]; + for (int i = 0; i < 2; i++) { + chan_config.edge_gpio_num = channel_gpios[i]; + TEST_ESP_OK(pcnt_new_channel(units[i], &chan_config, &chans[i])); + TEST_ESP_OK(pcnt_channel_set_edge_action(chans[i], PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD)); + TEST_ESP_OK(pcnt_channel_set_level_action(chans[i], PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP)); + } + + printf("enable and start unit\r\n"); + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(pcnt_unit_enable(units[i])); + TEST_ESP_OK(pcnt_unit_start(units[i])); + } + + // trigger 10 rising edge on GPIO + test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10); + test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_B, 10); + + int count_value = 0; + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value)); + TEST_ASSERT_EQUAL(10, count_value); + } + + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(pcnt_unit_stop(units[i])); + TEST_ESP_OK(pcnt_unit_disable(units[i])); + TEST_ESP_OK(pcnt_del_channel(chans[i])); + TEST_ESP_OK(pcnt_del_unit(units[i])); + } +} + /** - * @brief Using this context to save the triggered watchpoints in sequence + * @brief Using this context to save the triggered watch-points in sequence */ typedef struct { uint32_t index; @@ -386,3 +436,54 @@ TEST_CASE("pcnt_zero_cross_mode", "[pcnt]") TEST_ESP_OK(pcnt_del_channel(channelB)); TEST_ESP_OK(pcnt_del_unit(unit)); } + +TEST_CASE("pcnt_virtual_io", "[pcnt]") +{ + pcnt_unit_config_t unit_config = { + .low_limit = -100, + .high_limit = 100, + }; + pcnt_chan_config_t chan_config = { + .edge_gpio_num = TEST_PCNT_GPIO_A, // only detect edge signal in this case + .level_gpio_num = -1, // level signal is connected to a virtual IO internally + .flags.io_loop_back = true, + .flags.virt_level_io_level = 1, // the level input is connected to high level, internally + }; + pcnt_unit_handle_t unit = NULL; + pcnt_channel_handle_t chan = NULL; + + printf("install pcnt unit\r\n"); + TEST_ESP_OK(pcnt_new_unit(&unit_config, &unit)); + + printf("install pcnt channel\r\n"); + TEST_ESP_OK(pcnt_new_channel(unit, &chan_config, &chan)); + TEST_ESP_OK(pcnt_channel_set_edge_action(chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD)); + TEST_ESP_OK(pcnt_channel_set_level_action(chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP)); + TEST_ESP_OK(pcnt_unit_enable(unit)); + + int count_value = 0; + printf("start units\r\n"); + // start unit + TEST_ESP_OK(pcnt_unit_start(unit)); + + // 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)); + TEST_ASSERT_EQUAL(10, count_value); + + printf("update level action\r\n"); + // the counter will hold-on (i.e. freeze) if the level input is high level (which is obviously yes in this case) + TEST_ESP_OK(pcnt_channel_set_level_action(chan, PCNT_CHANNEL_LEVEL_ACTION_HOLD, PCNT_CHANNEL_LEVEL_ACTION_KEEP)); + TEST_ESP_OK(pcnt_unit_clear_count(unit)); + + // 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)); + // the count value should still be zero, because the level signal is high level, and the high level action is to hold-on the count value + TEST_ASSERT_EQUAL(0, count_value); + + TEST_ESP_OK(pcnt_unit_stop(unit)); + TEST_ESP_OK(pcnt_unit_disable(unit)); + TEST_ESP_OK(pcnt_del_channel(chan)); + TEST_ESP_OK(pcnt_del_unit(unit)); +} diff --git a/docs/en/api-reference/peripherals/pcnt.rst b/docs/en/api-reference/peripherals/pcnt.rst index 8e2486c0ae..239b44fc59 100644 --- a/docs/en/api-reference/peripherals/pcnt.rst +++ b/docs/en/api-reference/peripherals/pcnt.rst @@ -67,9 +67,10 @@ If a previously created PCNT unit is no longer needed, it's recommended to recyc Install PCNT Channel ~~~~~~~~~~~~~~~~~~~~ -To install a PCNT channel, there's a configuration structure that needs to be given in advance: :cpp:type:`pcnt_chan_config_t` as well: +To install a PCNT channel, users must initialize a :cpp:type:`pcnt_chan_config_t` structure in advance, and then call :cpp:func:`pcnt_new_channel`. The configuration fields of the :cpp:type:`pcnt_chan_config_t` structure are described below: -- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` and :cpp:member:`pcnt_chan_config_t::level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. :cpp:member:`pcnt_chan_config_t::level_gpio_num` is optional and can be assigned with `-1` if it's not used whereas the :cpp:member:`pcnt_chan_config_t::edge_gpio_num` is mandatory. +- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` and :cpp:member:`pcnt_chan_config_t::level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. Please note, either of them can be assigned to `-1` if it's not actually used, and thus it will become a **virtual IO**. For some simple pulse counting applications where one of the level/edge signals signals is fixed (i.e., never changes), users can reclaim a GPIO by setting the signal as a virtual IO on channel allocation. Setting the level/edge signal as a virtual IO will cause that signal to be internally routed to a fixed High/Low logic level, thus allowing users to save a GPIO for other purposes. +- :cpp:member:`pcnt_chan_config_t::virt_edge_io_level` and :cpp:member:`pcnt_chan_config_t::virt_level_io_level` specify the virtual IO level for **edge** and **level** input signal, to ensure a deterministic state for such control signal. Please note, they are only valid when either :cpp:member:`pcnt_chan_config_t::edge_gpio_num` or :cpp:member:`pcnt_chan_config_t::level_gpio_num` is assigned to `-1`. - :cpp:member:`pcnt_chan_config_t::invert_edge_input` and :cpp:member:`pcnt_chan_config_t::invert_level_input` are used to decide whether to invert the input signals before they going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware. - :cpp:member:`pcnt_chan_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 pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO.