Explorar el Código

pulse_cnt: new driver for PCNT peripheral

morris hace 4 años
padre
commit
ec8defaa96
Se han modificado 36 ficheros con 1832 adiciones y 378 borrados
  1. 1 1
      components/driver/CMakeLists.txt
  2. 45 9
      components/driver/Kconfig
  3. 22 16
      components/driver/gptimer.c
  4. 296 0
      components/driver/include/driver/pulse_cnt.h
  5. 6 0
      components/driver/legacy_new_driver_coexist.c
  6. 5 0
      components/driver/linker.lf
  7. 714 0
      components/driver/pulse_cnt.c
  8. 1 2
      components/driver/test_apps/gptimer/main/CMakeLists.txt
  9. 1 1
      components/driver/test_apps/gptimer/pytest_gptimer.py
  10. 18 0
      components/driver/test_apps/pulse_cnt/CMakeLists.txt
  11. 2 0
      components/driver/test_apps/pulse_cnt/README.md
  12. 6 0
      components/driver/test_apps/pulse_cnt/main/CMakeLists.txt
  13. 51 0
      components/driver/test_apps/pulse_cnt/main/test_app_main.c
  14. 408 0
      components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c
  15. 23 0
      components/driver/test_apps/pulse_cnt/pytest_pulse_cnt.py
  16. 6 0
      components/driver/test_apps/pulse_cnt/sdkconfig.ci.iram_safe
  17. 5 0
      components/driver/test_apps/pulse_cnt/sdkconfig.ci.release
  18. 2 0
      components/driver/test_apps/pulse_cnt/sdkconfig.defaults
  19. 42 29
      components/hal/esp32/include/hal/pcnt_ll.h
  20. 42 29
      components/hal/esp32s2/include/hal/pcnt_ll.h
  21. 42 29
      components/hal/esp32s3/include/hal/pcnt_ll.h
  22. 8 19
      components/hal/include/hal/pcnt_hal.h
  23. 11 22
      components/hal/include/hal/pcnt_types.h
  24. 5 13
      components/hal/pcnt_hal.c
  25. 8 21
      components/soc/esp32/include/soc/pcnt_reg.h
  26. 28 38
      components/soc/esp32/include/soc/pcnt_struct.h
  27. 5 13
      components/soc/esp32/pcnt_periph.c
  28. 3 12
      components/soc/esp32s2/include/soc/pcnt_reg.h
  29. 4 13
      components/soc/esp32s2/include/soc/pcnt_struct.h
  30. 5 13
      components/soc/esp32s2/pcnt_periph.c
  31. 0 26
      components/soc/esp32s3/include/soc/pcnt_caps.h
  32. 3 12
      components/soc/esp32s3/include/soc/pcnt_reg.h
  33. 4 13
      components/soc/esp32s3/include/soc/pcnt_struct.h
  34. 5 13
      components/soc/esp32s3/pcnt_periph.c
  35. 5 13
      components/soc/include/soc/pcnt_periph.h
  36. 0 21
      tools/ci/check_copyright_ignore.txt

+ 1 - 1
components/driver/CMakeLists.txt

@@ -48,7 +48,7 @@ if(CONFIG_SOC_RMT_SUPPORTED)
 endif()
 
 if(CONFIG_SOC_PCNT_SUPPORTED)
-    list(APPEND srcs "pcnt.c")
+    list(APPEND srcs "pcnt_legacy.c" "pulse_cnt.c")
 endif()
 
 if(CONFIG_SOC_SDMMC_HOST_SUPPORTED)

+ 45 - 9
components/driver/Kconfig

@@ -22,7 +22,7 @@ menu "Driver configurations"
     endmenu  # ADC Configuration
 
     menu "MCPWM configuration"
-
+        depends on SOC_MCPWM_SUPPORTED
         config MCPWM_ISR_IN_IRAM
             bool "Place MCPWM ISR function into IRAM"
             default n
@@ -32,7 +32,6 @@ menu "Driver configurations"
 
                 Note that if this option is selected, all user registered ISR callbacks should never
                 try to use cache as well. (with IRAM_ATTR)
-
     endmenu # MCPWM Configuration
 
     menu "SPI configuration"
@@ -158,8 +157,6 @@ menu "Driver configurations"
     endmenu # UART Configuration
 
     menu "GPIO Configuration"
-        visible if IDF_TARGET_ESP32
-
         config GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL
             bool "Support light sleep GPIO pullup/pulldown configuration for ESP32"
             depends on IDF_TARGET_ESP32
@@ -168,10 +165,10 @@ menu "Driver configurations"
                 pullup/pulldown mode in sleep.
                 If this option is selected, chip will automatically emulate the behaviour of switching,
                 and about 450B of source codes would be placed into IRAM.
-
     endmenu # GPIO Configuration
 
     menu "GDMA Configuration"
+        depends on SOC_GDMA_SUPPORTED
         config GDMA_CTRL_FUNC_IN_IRAM
             bool "Place GDMA control functions into IRAM"
             default n
@@ -202,17 +199,56 @@ menu "Driver configurations"
             bool "GPTimer ISR IRAM-Safe"
             default n
             help
-                This will ensure the GPTimer interrupt handle is IRAM-Safe, allow to avoid flash
-                cache misses, and also be able to run whilst the cache is disabled.
-                (e.g. SPI Flash write)
+                Ensure the GPTimer interrupt is IRAM-Safe by allowing the interrupt handler to be
+                executable when the cache is disabled (e.g. SPI Flash write).
 
         config GPTIMER_SUPPRESS_DEPRECATE_WARN
-            bool "Suppress leagcy driver deprecated warning"
+            bool "Suppress legacy driver deprecated warning"
             default n
             help
                 Wether to suppress the deprecation warnings when using legacy timer group driver (driver/timer.h).
                 If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
                 you can enable this option.
+
+        config GPTIMER_ENABLE_DEBUG_LOG
+            bool "Enable debug log"
+            default n
+            help
+                Wether to enable the debug log message for GPTimer driver.
+                Note that, this option only controls the GPTimer driver log, won't affect other drivers.
     endmenu # GPTimer Configuration
 
+    menu "PCNT Configuration"
+        depends on SOC_PCNT_SUPPORTED
+        config PCNT_CTRL_FUNC_IN_IRAM
+            bool "Place PCNT control functions into IRAM"
+            default n
+            help
+                Place PCNT control functions (like start/stop) into IRAM,
+                so that these functions can be IRAM-safe and able to be called in the other IRAM interrupt context.
+                Enabling this option can improve driver performance as well.
+
+        config PCNT_ISR_IRAM_SAFE
+            bool "PCNT ISR IRAM-Safe"
+            default n
+            help
+                Ensure the PCNT interrupt is IRAM-Safe by allowing the interrupt handler to be
+                executable when the cache is disabled (e.g. SPI Flash write).
+
+        config PCNT_SUPPRESS_DEPRECATE_WARN
+            bool "Suppress legacy driver deprecated warning"
+            default n
+            help
+                Wether to suppress the deprecation warnings when using legacy PCNT driver (driver/pcnt.h).
+                If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
+                you can enable this option.
+
+        config PCNT_ENABLE_DEBUG_LOG
+            bool "Enable debug log"
+            default n
+            help
+                Wether to enable the debug log message for PCNT driver.
+                Note that, this option only controls the PCNT driver log, won't affect other drivers.
+    endmenu # PCNT Configuration
+
 endmenu  # Driver configurations

+ 22 - 16
components/driver/gptimer.c

@@ -4,10 +4,14 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG // uncomment this line to enable debug logs
-
 #include <stdlib.h>
 #include <sys/lock.h>
+#include "sdkconfig.h"
+#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
 #include "freertos/FreeRTOS.h"
 #include "esp_attr.h"
 #include "esp_err.h"
@@ -90,7 +94,7 @@ static gptimer_platform_t s_platform;
 static gptimer_group_t *gptimer_acquire_group_handle(int group_id);
 static void gptimer_release_group_handle(gptimer_group_t *group);
 static esp_err_t gptimer_select_periph_clock(gptimer_t *timer, gptimer_clock_source_t src_clk, uint32_t resolution_hz);
-IRAM_ATTR static void gptimer_default_isr(void *args);
+static void gptimer_default_isr(void *args);
 
 esp_err_t gptimer_new_timer(const gptimer_config_t *config, gptimer_handle_t *ret_timer)
 {
@@ -205,7 +209,7 @@ esp_err_t gptimer_del_timer(gptimer_handle_t timer)
 
 esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value)
 {
-    ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
 
     portENTER_CRITICAL_SAFE(&timer->spinlock);
     timer_hal_set_counter_value(&timer->hal, value);
@@ -215,7 +219,7 @@ esp_err_t gptimer_set_raw_count(gptimer_handle_t timer, unsigned long long value
 
 esp_err_t gptimer_get_raw_count(gptimer_handle_t timer, unsigned long long *value)
 {
-    ESP_RETURN_ON_FALSE(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE_ISR(timer && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
 
     portENTER_CRITICAL_SAFE(&timer->spinlock);
     *value = timer_ll_get_counter_value(timer->hal.dev, timer->timer_id);
@@ -252,9 +256,9 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
     }
 
     // enable/disable GPTimer interrupt events
-    portENTER_CRITICAL_SAFE(&group->spinlock);
+    portENTER_CRITICAL(&group->spinlock);
     timer_ll_enable_intr(timer->hal.dev, TIMER_LL_EVENT_ALARM(timer->timer_id), cbs->on_alarm != NULL); // enable timer interrupt
-    portEXIT_CRITICAL_SAFE(&group->spinlock);
+    portEXIT_CRITICAL(&group->spinlock);
 
     timer->on_alarm = cbs->on_alarm;
     timer->user_ctx = user_data;
@@ -263,11 +267,11 @@ esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer, const gptimer
 
 esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_config_t *config)
 {
-    ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
     if (config) {
         // When auto_reload is enabled, alarm_count should not be equal to reload_count
         bool valid_auto_reload = !config->flags.auto_reload_on_alarm || config->alarm_count != config->reload_count;
-        ESP_RETURN_ON_FALSE(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count");
+        ESP_RETURN_ON_FALSE_ISR(valid_auto_reload, ESP_ERR_INVALID_ARG, TAG, "reload count can't equal to alarm count");
 
         timer->reload_count = config->reload_count;
         timer->alarm_count = config->alarm_count;
@@ -292,15 +296,15 @@ esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer, const gptimer_alarm_c
 
 esp_err_t gptimer_start(gptimer_handle_t timer)
 {
-    ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
 
     // acquire power manager lock
     if (timer->pm_lock) {
-        ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
+        ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_acquire(timer->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
     }
     // interrupt interupt service
     if (timer->intr) {
-        ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
+        ESP_RETURN_ON_ERROR_ISR(esp_intr_enable(timer->intr), TAG, "enable interrupt service failed");
     }
 
     portENTER_CRITICAL_SAFE(&timer->spinlock);
@@ -314,7 +318,7 @@ esp_err_t gptimer_start(gptimer_handle_t timer)
 
 esp_err_t gptimer_stop(gptimer_handle_t timer)
 {
-    ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE_ISR(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
 
     // disable counter, alarm, autoreload
     portENTER_CRITICAL_SAFE(&timer->spinlock);
@@ -325,11 +329,11 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
 
     // disable interrupt service
     if (timer->intr) {
-        ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
+        ESP_RETURN_ON_ERROR_ISR(esp_intr_disable(timer->intr), TAG, "disable interrupt service failed");
     }
     // release power manager lock
     if (timer->pm_lock) {
-        ESP_RETURN_ON_ERROR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
+        ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_release(timer->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
     }
 
     return ESP_OK;
@@ -337,7 +341,9 @@ esp_err_t gptimer_stop(gptimer_handle_t timer)
 
 static gptimer_group_t *gptimer_acquire_group_handle(int group_id)
 {
-    // esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#if CONFIG_GPTIMER_ENABLE_DEBUG_LOG
+    esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
     bool new_group = false;
     gptimer_group_t *group = NULL;
 

+ 296 - 0
components/driver/include/driver/pulse_cnt.h

@@ -0,0 +1,296 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_err.h"
+#include "hal/pcnt_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Type of PCNT unit handle
+ */
+typedef struct pcnt_unit_t *pcnt_unit_handle_t;
+
+/**
+ * @brief Type of PCNT channel handle
+ */
+typedef struct pcnt_chan_t *pcnt_channel_handle_t;
+
+/**
+ * @brief PCNT watch event data
+ */
+typedef struct {
+    int watch_point_value;                       /*!< Watch point value that triggered the event */
+    pcnt_unit_zero_cross_mode_t zero_cross_mode; /*!< Zero cross mode */
+} pcnt_watch_event_data_t;
+
+/**
+ * @brief PCNT watch event callback prototype
+ *
+ * @note The callback function is invoked from an ISR context, so it should meet the restrictions of not calling any blocking APIs when implementing the callback.
+ *       e.g. must use ISR version of FreeRTOS APIs.
+ *
+ * @param[in] unit PCNT unit handle
+ * @param[in] edata PCNT event data, fed by the driver
+ * @param[in] user_ctx User data, passed from `pcnt_unit_register_event_callbacks()`
+ * @return Whether a high priority task has been woken up by this function
+ */
+typedef bool (*pcnt_watch_cb_t)(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx);
+
+/**
+ * @brief Group of supported PCNT callbacks
+ * @note The callbacks are all running under ISR environment
+ * @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_event_callbacks_t;
+
+/**
+ * @brief PCNT unit configuration
+ */
+typedef struct {
+    int low_limit;  /*!< Low limitation of the count unit, should be lower than 0 */
+    int high_limit; /*!< High limitation of the count unit, should be higher than 0 */
+} pcnt_unit_config_t;
+
+/**
+ * @brief PCNT channel configuration
+ */
+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;
+} pcnt_chan_config_t;
+
+/**
+ * @brief PCNT glitch filter configuration
+ */
+typedef struct {
+    uint32_t max_glitch_ns; /*!< Pulse width smaller than this threshold will be treated as glitch and ignored, in the unit of ns */
+} pcnt_glitch_filter_config_t;
+
+/**
+ * @brief Create a new PCNT unit, and return the handle
+ *
+ * @note The newly created PCNT unit is put into the stopped state.
+ *
+ * @param[in] config PCNT unit configuration
+ * @param[out] ret_unit Returned PCNT unit handle
+ * @return
+ *      - ESP_OK: Create PCNT unit successfully
+ *      - ESP_ERR_INVALID_ARG: Create PCNT unit failed because of invalid argument (e.g. high/low limit value out of the range)
+ *      - ESP_ERR_NO_MEM: Create PCNT unit failed because out of memory
+ *      - ESP_ERR_NOT_FOUND: Create PCNT unit failed because all PCNT units are used up and no more free one
+ *      - ESP_FAIL: Create PCNT unit failed because of other error
+ */
+esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit);
+
+/**
+ * @brief Delete the PCNT unit handle
+ *
+ * @note Users must ensure that the PCNT unit is stopped before deleting the unit. Users can force a working unit into the stopped state via `pcnt_unit_stop()`.
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @return
+ *      - ESP_OK: Delete the PCNT unit successfully
+ *      - ESP_ERR_INVALID_ARG: Delete the PCNT unit failed because of invalid argument
+ *      - ESP_ERR_INVALID_STATE: Delete the PCNT unit failed because corresponding PCNT channels and/or watch points are still in working
+ *      - ESP_FAIL: Delete the PCNT unit failed because of other error
+ */
+esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit);
+
+/**
+ * @brief Set glitch filter for PCNT unit
+ *
+ * @note The glitch filter module is clocked from APB, and APB frequency can be changed during DFS, which in return make the filter out of action.
+ *       So this function will lazy-install a PM lock internally when the power management is enabled. With this lock, the APB frequency won't be changed.
+ *       The PM lock can only be uninstalled in `pcnt_del_unit()`.
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[in] config PCNT filter configuration, set config to NULL means disabling the filter function
+ * @return
+ *      - ESP_OK: Set glitch filter successfully
+ *      - ESP_ERR_INVALID_ARG: Set glitch filter failed because of invalid argument (e.g. glitch width is too big)
+ *      - ESP_FAIL: Set glitch filter failed because of other error
+ */
+esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch_filter_config_t *config);
+
+/**
+ * @brief Start the PCNT unit, the counter will start to count according to the edge and/or level input signals
+ *
+ * @note This function will acquire the PM lock when power management is enabled. Also see `pcnt_unit_set_glitch_filter()` for the condition of PM lock installation.
+ * @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_stop()`.
+ * @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @return
+ *      - ESP_OK: Start PCNT unit successfully
+ *      - ESP_ERR_INVALID_ARG: Start PCNT unit failed because of invalid argument
+ *      - ESP_FAIL: Start PCNT unit failed because of other error
+ */
+esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit);
+
+/**
+ * @brief Stop PCNT from counting
+ *
+ * @note If power management is enabled, this function will release the PM lock acquired in `pcnt_unit_start()`.
+ *       Also see `pcnt_unit_set_glitch_filter()` for the condition of PM lock installation.
+ * @note The stop operation won't clear the counter. Also see `pcnt_unit_clear_count()` for how to clear pulse count value.
+ * @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_start()`.
+ * @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it is allowed to be executed when Cache is disabled
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @return
+ *      - ESP_OK: Stop PCNT unit successfully
+ *      - ESP_ERR_INVALID_ARG: Stop PCNT unit failed because of invalid argument
+ *      - ESP_FAIL: Stop PCNT unit failed because of other error
+ */
+esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit);
+
+/**
+ * @brief Clear PCNT pulse count value to zero
+ *
+ * @note It's recommended to call this function after adding a watch point by `pcnt_unit_add_watch_point()`, so that the newly added watch point is effective immediately.
+ * @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @return
+ *      - ESP_OK: Clear PCNT pulse count successfully
+ *      - ESP_ERR_INVALID_ARG: Clear PCNT pulse count failed because of invalid argument
+ *      - ESP_FAIL: Clear PCNT pulse count failed because of other error
+ */
+esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit);
+
+/**
+ * @brief Get PCNT count value
+ *
+ * @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM`, so that it's allowed to be executed when Cache is disabled
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[out] value Returned count value
+ * @return
+ *      - ESP_OK: Get PCNT pulse count successfully
+ *      - ESP_ERR_INVALID_ARG: Get PCNT pulse count failed because of invalid argument
+ *      - ESP_FAIL: Get PCNT pulse count failed because of other error
+ */
+esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value);
+
+/**
+ * @brief Set event callbacks for PCNT unit
+ *
+ * @note User can deregister the previous callback by calling this function with an empty `cbs`.
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[in] cbs Group of callback functions
+ * @param[in] user_data User data, which will be passed to callback functions directly
+ * @return
+ *      - ESP_OK: Set event callbacks successfully
+ *      - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
+ *      - ESP_FAIL: Set event callbacks failed because of other error
+ */
+esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data);
+
+/**
+ * @brief Add a watch point for PCNT unit, PCNT will generate an event when the counter value reaches the watch point value
+ *
+ * @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_remove_watch_point()`, otherwise the unit can't be deleted
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[in] watch_point Value to be watched
+ * @return
+ *      - ESP_OK: Add watch point successfully
+ *      - ESP_ERR_INVALID_ARG: Add watch point failed because of invalid argument (e.g. the value to be watched is out of the limitation set in `pcnt_unit_config_t`)
+ *      - ESP_ERR_INVALID_STATE: Add watch point failed because the same watch point has already been added
+ *      - ESP_ERR_NOT_FOUND: Add watch point failed because no more hardware watch point can be configured
+ *      - ESP_FAIL: Add watch point failed because of other error
+ */
+esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point);
+
+/**
+ * @brief Remove a watch point for PCNT unit
+ *
+ * @note The number of calls to this function should be equal to the number of calls to `pcnt_unit_add_watch_point()`, otherwise the unit can't be deleted
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[in] watch_point Watch point value
+ * @return
+ *      - ESP_OK: Remove watch point successfully
+ *      - ESP_ERR_INVALID_ARG: Remove watch point failed because of invalid argument
+ *      - ESP_ERR_INVALID_STATE: Remove watch point failed because the watch point was not added by `pcnt_unit_add_watch_point()` yet
+ *      - ESP_FAIL: Remove watch point failed because of other error
+ */
+esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point);
+
+/**
+ * @brief Create PCNT channel for specific unit, each PCNT has several channels associated with it
+ *
+ * @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
+ * @param[in] config PCNT channel configuration
+ * @param[out] ret_chan Returned channel handle
+ * @return
+ *      - ESP_OK: Create PCNT channel successfully
+ *      - ESP_ERR_INVALID_ARG: Create PCNT channel failed because of invalid argument
+ *      - ESP_ERR_NO_MEM: Create PCNT channel failed because of insufficient memory
+ *      - ESP_ERR_NOT_FOUND: Create PCNT channel failed because all PCNT channels are used up and no more free one
+ *      - ESP_FAIL: Create PCNT channel failed because of other error
+ */
+esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *config, pcnt_channel_handle_t *ret_chan);
+
+/**
+ * @brief Delete the PCNT channel
+ *
+ * @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
+ * @return
+ *      - ESP_OK: Delete the PCNT channel successfully
+ *      - ESP_ERR_INVALID_ARG: Delete the PCNT channel failed because of invalid argument
+ *      - ESP_FAIL: Delete the PCNT channel failed because of other error
+ */
+esp_err_t pcnt_del_channel(pcnt_channel_handle_t chan);
+
+/**
+ * @brief Set channel actions when edge signal changes (e.g. falling or rising edge occurred).
+ *        The edge signal is input from the `edge_gpio_num` configured in `pcnt_chan_config_t`.
+ *        We use these actions to control when and how to change the counter value.
+ *
+ * @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
+ * @param[in] pos_act Action on posedge signal
+ * @param[in] neg_act Action on negedge signal
+ * @return
+ *      - ESP_OK: Set edge action for PCNT channel successfully
+ *      - ESP_ERR_INVALID_ARG: Set edge action for PCNT channel failed because of invalid argument
+ *      - ESP_FAIL: Set edge action for PCNT channel failed because of other error
+ */
+esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act);
+
+/**
+ * @brief Set channel actions when level signal changes (e.g. signal level goes from high to low).
+ *        The level signal is input from the `level_gpio_num` configured in `pcnt_chan_config_t`.
+ *        We use these actions to control when and how to change the counting mode.
+ *
+ * @param[in] chan PCNT channel handle created by `pcnt_new_channel()`
+ * @param[in] high_act Action on high level signal
+ * @param[in] low_act Action on low level signal
+ * @return
+ *      - ESP_OK: Set level action for PCNT channel successfully
+ *      - ESP_ERR_INVALID_ARG: Set level action for PCNT channel failed because of invalid argument
+ *      - ESP_FAIL: Set level action for PCNT channel failed because of other error
+ */
+esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act);
+
+#ifdef __cplusplus
+}
+#endif

+ 6 - 0
components/driver/legacy_new_driver_coexist.c

@@ -9,3 +9,9 @@
  *        the legacy timer group driver (deprecated/driver/timer.h) and the new gptimer driver (driver/gptimer.h).
  */
 int timer_group_driver_init_count = 0;
+
+/**
+ * @brief This count is used to prevent the coexistence of
+ *        the legacy pcnt driver (deprecated/driver/pcnt.h) and the new pulse_cnt driver (driver/pulse_cnt.h).
+ */
+int pcnt_driver_init_count = 0;

+ 5 - 0
components/driver/linker.lf

@@ -18,3 +18,8 @@ entries:
         gdma: gdma_stop (noflash)
         gdma: gdma_append (noflash)
         gdma: gdma_reset (noflash)
+    if PCNT_CTRL_FUNC_IN_IRAM = y:
+        pulse_cnt: pcnt_unit_start (noflash)
+        pulse_cnt: pcnt_unit_stop (noflash)
+        pulse_cnt: pcnt_unit_clear_count (noflash)
+        pulse_cnt: pcnt_unit_get_count (noflash)

+ 714 - 0
components/driver/pulse_cnt.c

@@ -0,0 +1,714 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdint.h>
+#include <sys/lock.h>
+#include "sdkconfig.h"
+#if CONFIG_PCNT_ENABLE_DEBUG_LOG
+// The local log level must be defined before including esp_log.h
+// Set the maximum log level for this source file
+#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+#endif
+#include "freertos/FreeRTOS.h"
+#include "esp_heap_caps.h"
+#include "esp_intr_alloc.h"
+#include "esp_attr.h"
+#include "esp_log.h"
+#include "esp_check.h"
+#include "esp_pm.h"
+#include "esp_rom_gpio.h"
+#include "soc/soc_caps.h"
+#include "soc/pcnt_periph.h"
+#include "hal/pcnt_hal.h"
+#include "hal/pcnt_ll.h"
+#include "hal/gpio_hal.h"
+#include "esp_private/esp_clk.h"
+#include "esp_private/periph_ctrl.h"
+#include "driver/gpio.h"
+#include "driver/pulse_cnt.h"
+
+// If ISR handler is allowed to run whilst cache is disabled,
+// Make sure all the code and related variables used by the handler are in the SRAM
+#if CONFIG_PCNT_ISR_IRAM_SAFE
+#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
+#define PCNT_MEM_ALLOC_CAPS   (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
+#else
+#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
+#define PCNT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
+#endif //CONFIG_PCNT_ISR_IRAM_SAFE
+
+#define PCNT_PM_LOCK_NAME_LEN_MAX 16
+
+static const char *TAG = "pcnt";
+
+typedef struct pcnt_platform_t pcnt_platform_t;
+typedef struct pcnt_group_t pcnt_group_t;
+typedef struct pcnt_unit_t pcnt_unit_t;
+typedef struct pcnt_chan_t pcnt_chan_t;
+
+struct pcnt_platform_t {
+    _lock_t mutex;                         // platform level mutex lock
+    pcnt_group_t *groups[SOC_PCNT_GROUPS]; // pcnt group pool
+    int group_ref_counts[SOC_PCNT_GROUPS]; // reference count used to protect group install/uninstall
+};
+
+struct pcnt_group_t {
+    int group_id;          // Group ID, index from 0
+    portMUX_TYPE spinlock; // to protect per-group register level concurrent access
+    pcnt_hal_context_t hal;
+    pcnt_unit_t *units[SOC_PCNT_UNITS_PER_GROUP]; // array of PCNT units
+};
+
+typedef struct {
+    pcnt_ll_watch_event_id_t event_id; // event type
+    int watch_point_value;             // value to be watched
+} pcnt_watch_point_t;
+
+typedef enum {
+    PCNT_FSM_STOP,
+    PCNT_FSM_START,
+} pcnt_lifecycle_fsm_t;
+
+struct pcnt_unit_t {
+    pcnt_group_t *group;                                  // which group the pcnt unit belongs to
+    portMUX_TYPE spinlock;                                // Spinlock, stop one unit from accessing different parts of a same register concurrently
+    uint32_t unit_id;                                     // allocated unit numerical ID
+    int low_limit;                                        // low limit value
+    int high_limit;                                       // high limit 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
+    esp_pm_lock_handle_t pm_lock;                         // PM lock, for glitch filter, as that module can only be functional under APB
+#if CONFIG_PM_ENABLE
+    char pm_lock_name[PCNT_PM_LOCK_NAME_LEN_MAX]; // pm lock name
+#endif
+    pcnt_lifecycle_fsm_t fsm; // access to fsm should be protect by spinlock, as fsm can also accessed from ISR handler
+    pcnt_watch_cb_t on_reach; // user registered callback function
+    void *user_data;          // user data registered by user, which would be passed to the right callback function
+};
+
+struct pcnt_chan_t {
+    pcnt_unit_t *unit;   // pointer to the PCNT unit where it derives from
+    uint32_t channel_id; // channel ID, index from 0
+    int edge_gpio_num;
+    int level_gpio_num;
+};
+
+// pcnt driver platform, it's always a singleton
+static pcnt_platform_t s_platform;
+
+static pcnt_group_t *pcnt_acquire_group_handle(int group_id);
+static void pcnt_release_group_handle(pcnt_group_t *group);
+static void pcnt_default_isr(void *args);
+
+esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit)
+{
+    esp_err_t ret = ESP_OK;
+    pcnt_group_t *group = NULL;
+    pcnt_unit_t *unit = NULL;
+    int group_id = -1;
+    int unit_id = -1;
+    ESP_GOTO_ON_FALSE(config && ret_unit, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+    ESP_GOTO_ON_FALSE(config->low_limit < 0 && config->high_limit > 0 && config->low_limit >= PCNT_LL_MIN_LIN && config->high_limit <= PCNT_LL_MAX_LIM,
+                      ESP_ERR_INVALID_ARG, err, TAG, "invalid limit range:[%d,%d]", config->low_limit, config->high_limit);
+
+    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");
+
+    for (int i = 0; (i < SOC_PCNT_GROUPS) && (unit_id < 0); i++) {
+        group = pcnt_acquire_group_handle(i);
+        ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group (%d)", i);
+        // loop to search free unit in the group
+        portENTER_CRITICAL(&group->spinlock);
+        for (int j = 0; j < SOC_PCNT_UNITS_PER_GROUP; j++) {
+            if (!group->units[j]) {
+                group_id = i;
+                unit_id = j;
+                group->units[j] = unit;
+                break;
+            }
+        }
+        portEXIT_CRITICAL(&group->spinlock);
+        if (unit_id < 0) {
+            pcnt_release_group_handle(group);
+            group = NULL;
+        }
+    }
+
+    ESP_GOTO_ON_FALSE(unit_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free unit");
+    unit->group = group;
+    unit->unit_id = unit_id;
+
+    // some events are enabled by default, disable them all
+    pcnt_ll_disable_all_events(group->hal.dev, unit_id);
+    // disable filter by default
+    pcnt_ll_enable_glitch_filter(group->hal.dev, unit_id, false);
+    // set default high/low limitation value
+    // note: limit value takes effect only after counter clear
+    pcnt_ll_set_high_limit_value(group->hal.dev, unit_id, config->high_limit);
+    pcnt_ll_set_low_limit_value(group->hal.dev, unit_id, config->low_limit);
+    unit->high_limit = config->high_limit;
+    unit->low_limit = config->low_limit;
+
+    // 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);
+    pcnt_ll_clear_count(group->hal.dev, unit_id);
+    pcnt_ll_enable_intr(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id), false);
+    pcnt_ll_clear_intr_status(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
+    portEXIT_CRITICAL(&group->spinlock);
+
+    unit->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
+    unit->fsm = PCNT_FSM_STOP;
+    for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
+        unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID; // invalid all watch point
+    }
+    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;
+
+err:
+    if (unit) {
+        free(unit);
+    }
+    if (group) {
+        pcnt_release_group_handle(group);
+    }
+    return ret;
+}
+
+esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE(unit->fsm == PCNT_FSM_STOP, ESP_ERR_INVALID_STATE, TAG, "can't delete unit as it's not in stop state");
+    for (int i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++) {
+        ESP_RETURN_ON_FALSE(!unit->channels[i], ESP_ERR_INVALID_STATE, TAG, "channel %d still in working", i);
+    }
+    for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
+        ESP_RETURN_ON_FALSE(unit->watchers[i].event_id == PCNT_LL_WATCH_EVENT_INVALID, ESP_ERR_INVALID_STATE, TAG,
+                            "watch point %d still in working", unit->watchers[i].watch_point_value);
+    }
+    group = unit->group;
+    int group_id = group->group_id;
+    int unit_id = unit->unit_id;
+
+    portENTER_CRITICAL(&group->spinlock);
+    group->units[unit_id] = NULL;
+    portEXIT_CRITICAL(&group->spinlock);
+
+    if (unit->intr) {
+        esp_intr_free(unit->intr);
+        ESP_LOGD(TAG, "uninstall interrupt service for unit (%d,%d)", group_id, unit_id);
+    }
+    if (unit->pm_lock) {
+        esp_pm_lock_delete(unit->pm_lock);
+        ESP_LOGD(TAG, "uninstall APB_FREQ_MAX lock for unit (%d,%d)", group_id, unit_id);
+    }
+    free(unit);
+    ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id);
+    // unit has a reference on group, release it now
+    pcnt_release_group_handle(group);
+
+    return ESP_OK;
+}
+
+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;
+    uint32_t glitch_filter_thres = 0;
+    ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+    if (config) {
+        glitch_filter_thres = esp_clk_apb_freq() / 1000000 * config->max_glitch_ns / 1000;
+        ESP_RETURN_ON_FALSE(glitch_filter_thres <= PCNT_LL_MAX_GLITCH_WIDTH, ESP_ERR_INVALID_ARG, TAG, "glitch width out of range");
+
+        // The filter module is working against APB clock, so lazy install PM lock
+#if CONFIG_PM_ENABLE
+        if (!unit->pm_lock) {
+            sprintf(unit->pm_lock_name, "pcnt_%d_%d", group->group_id, unit->unit_id); // e.g. pcnt_0_0
+            ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, unit->pm_lock_name, &unit->pm_lock), TAG, "install pm lock failed");
+            ESP_LOGD(TAG, "install APB_FREQ_MAX lock for unit (%d,%d)", group->group_id, unit->unit_id);
+        }
+#endif
+    }
+
+    // filter control bit is mixed with other PCNT control bits in the same register
+    portENTER_CRITICAL(&unit->spinlock);
+    if (config) {
+        pcnt_ll_set_glitch_filter_thres(group->hal.dev, unit->unit_id, glitch_filter_thres);
+        pcnt_ll_enable_glitch_filter(group->hal.dev, unit->unit_id, true);
+    } else {
+        pcnt_ll_enable_glitch_filter(group->hal.dev, unit->unit_id, false);
+    }
+    portEXIT_CRITICAL(&unit->spinlock);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+
+    // acquire power manager lock
+    if (unit->pm_lock) {
+        ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_acquire(unit->pm_lock), TAG, "acquire APB_FREQ_MAX lock failed");
+    }
+    // enable interupt service
+    if (unit->intr) {
+        ESP_RETURN_ON_ERROR_ISR(esp_intr_enable(unit->intr), TAG, "enable interrupt service failed");
+    }
+
+    // all PCNT units share the same register to control counter
+    portENTER_CRITICAL_SAFE(&group->spinlock);
+    pcnt_ll_start_count(group->hal.dev, unit->unit_id);
+    portEXIT_CRITICAL_SAFE(&group->spinlock);
+
+    portENTER_CRITICAL_SAFE(&unit->spinlock);
+    unit->fsm = PCNT_FSM_START;
+    portEXIT_CRITICAL_SAFE(&unit->spinlock);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+
+    // all PCNT units share the same register to control counter
+    portENTER_CRITICAL_SAFE(&group->spinlock);
+    pcnt_ll_stop_count(group->hal.dev, unit->unit_id);
+    portEXIT_CRITICAL_SAFE(&group->spinlock);
+
+    portENTER_CRITICAL_SAFE(&unit->spinlock);
+    unit->fsm = PCNT_FSM_STOP;
+    portEXIT_CRITICAL_SAFE(&unit->spinlock);
+
+    // disable interrupt service
+    if (unit->intr) {
+        ESP_RETURN_ON_ERROR_ISR(esp_intr_disable(unit->intr), TAG, "disable interrupt service failed");
+    }
+    // release power manager lock
+    if (unit->pm_lock) {
+        ESP_RETURN_ON_ERROR_ISR(esp_pm_lock_release(unit->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_clear_count(pcnt_unit_handle_t unit)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+
+    // all PCNT units share the same register to control counter
+    portENTER_CRITICAL_SAFE(&group->spinlock);
+    pcnt_ll_clear_count(group->hal.dev, unit->unit_id);
+    portEXIT_CRITICAL_SAFE(&group->spinlock);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE_ISR(unit && value, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+    *value = pcnt_ll_get_count(group->hal.dev, unit->unit_id);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt_event_callbacks_t *cbs, void *user_data)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(unit && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+    int group_id = group->group_id;
+    int unit_id = unit->unit_id;
+
+#if CONFIG_PCNT_ISR_IRAM_SAFE
+    if (cbs->on_reach) {
+        ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_reach), ESP_ERR_INVALID_ARG, TAG, "on_reach callback not in IRAM");
+    }
+    if (user_data) {
+        ESP_RETURN_ON_FALSE(esp_ptr_in_dram(user_data) ||
+                            esp_ptr_in_diram_dram(user_data) ||
+                            esp_ptr_in_rtc_dram_fast(user_data),
+                            ESP_ERR_INVALID_ARG, TAG, "user context not in DRAM");
+    }
+#endif
+
+    // lazy install interrupt service
+    if (!unit->intr) {
+        int isr_flags = PCNT_INTR_ALLOC_FLAGS;
+        ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(pcnt_periph_signals.groups[group_id].irq, isr_flags,
+                            (uint32_t)pcnt_ll_get_intr_status_reg(group->hal.dev), PCNT_LL_UNIT_WATCH_EVENT(unit_id),
+                            pcnt_default_isr, unit, &unit->intr),
+                            TAG, "install interrupt service failed");
+    }
+    // enable/disable PCNT interrupt events
+    portENTER_CRITICAL(&group->spinlock);
+    pcnt_ll_enable_intr(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id), cbs->on_reach != NULL);
+    portEXIT_CRITICAL(&group->spinlock);
+
+    unit->on_reach = cbs->on_reach;
+    unit->user_data = user_data;
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_add_watch_point(pcnt_unit_handle_t unit, int watch_point)
+{
+    esp_err_t ret = ESP_OK;
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    ESP_RETURN_ON_FALSE(watch_point <= unit->high_limit && watch_point >= unit->low_limit,
+                        ESP_ERR_INVALID_ARG, TAG, "watch_point out of limit");
+    group = unit->group;
+
+    // event enable/disable is mixed with other control function in the same register
+    portENTER_CRITICAL(&unit->spinlock);
+    // zero cross watch point
+    if (watch_point == 0) {
+        if (unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
+            ret = ESP_ERR_INVALID_STATE; // zero cross event watcher has been installed already
+        } else {
+            unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].event_id = PCNT_LL_WATCH_EVENT_ZERO_CROSS;
+            unit->watchers[PCNT_LL_WATCH_EVENT_ZERO_CROSS].watch_point_value = 0;
+            pcnt_ll_enable_zero_cross_event(group->hal.dev, unit->unit_id, true);
+        }
+    }
+    // high limit watch point
+    else if (watch_point == unit->high_limit) {
+        if (unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
+            ret = ESP_ERR_INVALID_STATE; // high limit event watcher has been installed already
+        } else {
+            unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].event_id = PCNT_LL_WATCH_EVENT_HIGH_LIMIT;
+            unit->watchers[PCNT_LL_WATCH_EVENT_HIGH_LIMIT].watch_point_value = unit->high_limit;
+            pcnt_ll_enable_high_limit_event(group->hal.dev, unit->unit_id, true);
+        }
+    }
+    // low limit watch point
+    else if (watch_point == unit->low_limit) {
+        if (unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id != PCNT_LL_WATCH_EVENT_INVALID) {
+            ret = ESP_ERR_INVALID_STATE; // low limit event watcher has been installed already
+        } else {
+            unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].event_id = PCNT_LL_WATCH_EVENT_LOW_LIMIT;
+            unit->watchers[PCNT_LL_WATCH_EVENT_LOW_LIMIT].watch_point_value = unit->low_limit;
+            pcnt_ll_enable_low_limit_event(group->hal.dev, unit->unit_id, true);
+        }
+    }
+    // other threshold watch point
+    else {
+        int thres_num = SOC_PCNT_THRES_POINT_PER_UNIT - 1;
+        switch (thres_num) {
+        case 1:
+            if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].event_id == PCNT_LL_WATCH_EVENT_INVALID) {
+                unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].event_id = PCNT_LL_WATCH_EVENT_THRES1;
+                unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].watch_point_value = watch_point;
+                pcnt_ll_set_thres_value(group->hal.dev, unit->unit_id, 1, watch_point);
+                pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 1, true);
+                break;
+            } else if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES1].watch_point_value == watch_point) {
+                ret = ESP_ERR_INVALID_STATE;
+                break;
+            }
+        /* fall-through */
+        case 0:
+            if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].event_id == PCNT_LL_WATCH_EVENT_INVALID) {
+                unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].event_id = PCNT_LL_WATCH_EVENT_THRES0;
+                unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].watch_point_value = watch_point;
+                pcnt_ll_set_thres_value(group->hal.dev, unit->unit_id, 0, watch_point);
+                pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 0, true);
+                break;
+            } else if (unit->watchers[PCNT_LL_WATCH_EVENT_THRES0].watch_point_value == watch_point) {
+                ret = ESP_ERR_INVALID_STATE;
+                break;
+            }
+        /* fall-through */
+        default:
+            ret = ESP_ERR_NOT_FOUND; // no free threshold watch point available
+            break;
+        }
+    }
+    portEXIT_CRITICAL(&unit->spinlock);
+    ESP_RETURN_ON_ERROR(ret, TAG, "add watchpoint %d failed", watch_point);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    group = unit->group;
+    pcnt_ll_watch_event_id_t event_id = PCNT_LL_WATCH_EVENT_INVALID;
+
+    // event enable/disable is mixed with other control function in the same register
+    portENTER_CRITICAL(&unit->spinlock);
+    for (int i = 0; i < PCNT_LL_WATCH_EVENT_MAX; i++) {
+        if (unit->watchers[i].event_id != PCNT_LL_WATCH_EVENT_INVALID && unit->watchers[i].watch_point_value == watch_point) {
+            event_id = unit->watchers[i].event_id;
+            unit->watchers[i].event_id = PCNT_LL_WATCH_EVENT_INVALID;
+            break;
+        }
+    }
+    switch (event_id) {
+    case PCNT_LL_WATCH_EVENT_ZERO_CROSS:
+        pcnt_ll_enable_zero_cross_event(group->hal.dev, unit->unit_id, false);
+        break;
+    case PCNT_LL_WATCH_EVENT_LOW_LIMIT:
+        pcnt_ll_enable_low_limit_event(group->hal.dev, unit->unit_id, false);
+        break;
+    case PCNT_LL_WATCH_EVENT_HIGH_LIMIT:
+        pcnt_ll_enable_high_limit_event(group->hal.dev, unit->unit_id, false);
+        break;
+    case PCNT_LL_WATCH_EVENT_THRES0:
+        pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 0, false);
+        break;
+    case PCNT_LL_WATCH_EVENT_THRES1:
+        pcnt_ll_enable_thres_event(group->hal.dev, unit->unit_id, 1, false);
+        break;
+    default:
+        break;
+    }
+    portEXIT_CRITICAL(&unit->spinlock);
+    ESP_RETURN_ON_FALSE(event_id != PCNT_LL_WATCH_EVENT_INVALID, ESP_ERR_INVALID_STATE, TAG, "watch point %d not added yet", watch_point);
+    return ESP_OK;
+}
+
+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;
+    pcnt_chan_t *channel = NULL;
+    pcnt_group_t *group = NULL;
+    ESP_GOTO_ON_FALSE(unit && config && ret_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+    group = unit->group;
+    int group_id = group->group_id;
+    int unit_id = unit->unit_id;
+
+    channel = heap_caps_calloc(1, sizeof(pcnt_chan_t), PCNT_MEM_ALLOC_CAPS);
+    ESP_GOTO_ON_FALSE(channel, ESP_ERR_NO_MEM, err, TAG, "no mem for channel");
+
+    // search for a free channel
+    int channel_id = -1;
+    portENTER_CRITICAL(&unit->spinlock);
+    for (int i = 0; i < SOC_PCNT_CHANNELS_PER_UNIT; i++) {
+        if (!unit->channels[i]) {
+            channel_id = i;
+            unit->channels[channel_id] = channel;
+            break;
+        }
+    }
+    portEXIT_CRITICAL(&unit->spinlock);
+    ESP_GOTO_ON_FALSE(channel_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free channel in unit (%d,%d)", group_id, unit_id);
+
+    // 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 = false,
+        .pull_up_en = true,
+    };
+    if (config->edge_gpio_num >= 0) {
+        gpio_conf.pin_bit_mask = 1ULL << config->edge_gpio_num;
+        ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config edge GPIO failed");
+        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);
+    }
+    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);
+    }
+
+    channel->channel_id = channel_id;
+    channel->unit = unit;
+    channel->edge_gpio_num = config->edge_gpio_num;
+    channel->level_gpio_num = config->level_gpio_num;
+    ESP_LOGD(TAG, "new pcnt channel(%d,%d,%d) at %p", group_id, unit_id, channel_id, channel);
+
+    *ret_chan = channel;
+    return ESP_OK;
+err:
+    if (channel) {
+        free(channel);
+    }
+    return ret;
+}
+
+esp_err_t pcnt_del_channel(pcnt_channel_handle_t chan)
+{
+    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    pcnt_unit_t *unit = chan->unit;
+    pcnt_group_t *group = unit->group;
+    int group_id = group->group_id;
+    int unit_id = unit->unit_id;
+    int channel_id = chan->channel_id;
+
+    portENTER_CRITICAL(&unit->spinlock);
+    unit->channels[channel_id] = NULL;
+    portEXIT_CRITICAL(&unit->spinlock);
+
+    if (chan->level_gpio_num >= 0) {
+        gpio_reset_pin(chan->level_gpio_num);
+    }
+    if (chan->edge_gpio_num >= 0) {
+        gpio_reset_pin(chan->edge_gpio_num);
+    }
+
+    free(chan);
+    ESP_LOGD(TAG, "del pcnt channel(%d,%d,%d)", group_id, unit_id, channel_id);
+    return ESP_OK;
+}
+
+esp_err_t pcnt_channel_set_edge_action(pcnt_channel_handle_t chan, pcnt_channel_edge_action_t pos_act, pcnt_channel_edge_action_t neg_act)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    pcnt_unit_t *unit = chan->unit;
+    group = unit->group;
+
+    // mode control bits are mixed with other PCNT control bits in a same register
+    portENTER_CRITICAL(&unit->spinlock);
+    pcnt_ll_set_edge_action(group->hal.dev, unit->unit_id, chan->channel_id, pos_act, neg_act);
+    portEXIT_CRITICAL(&unit->spinlock);
+
+    return ESP_OK;
+}
+
+esp_err_t pcnt_channel_set_level_action(pcnt_channel_handle_t chan, pcnt_channel_level_action_t high_act, pcnt_channel_level_action_t low_act)
+{
+    pcnt_group_t *group = NULL;
+    ESP_RETURN_ON_FALSE(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
+    pcnt_unit_t *unit = chan->unit;
+    group = unit->group;
+
+    // mode control bits are mixed with other PCNT control bits in a same register
+    portENTER_CRITICAL(&unit->spinlock);
+    pcnt_ll_set_level_action(group->hal.dev, unit->unit_id, chan->channel_id, high_act, low_act);
+    portEXIT_CRITICAL(&unit->spinlock);
+
+    return ESP_OK;
+}
+
+static pcnt_group_t *pcnt_acquire_group_handle(int group_id)
+{
+#if CONFIG_PCNT_ENABLE_DEBUG_LOG
+    esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif
+    bool new_group = false;
+    pcnt_group_t *group = NULL;
+
+    // prevent install pcnt group concurrently
+    _lock_acquire(&s_platform.mutex);
+    if (!s_platform.groups[group_id]) {
+        group = heap_caps_calloc(1, sizeof(pcnt_group_t), PCNT_MEM_ALLOC_CAPS);
+        if (group) {
+            new_group = true;
+            s_platform.groups[group_id] = group; // register to platform
+            // initialize pcnt group members
+            group->group_id = group_id;
+            group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
+            // enable APB access pcnt registers
+            periph_module_enable(pcnt_periph_signals.groups[group_id].module);
+            periph_module_reset(pcnt_periph_signals.groups[group_id].module);
+            // initialize HAL context
+            pcnt_hal_init(&group->hal, group_id);
+        }
+    } else {
+        group = s_platform.groups[group_id];
+    }
+    if (group) {
+        // someone acquired the group handle means we have a new object that refer to this group
+        s_platform.group_ref_counts[group_id]++;
+    }
+    _lock_release(&s_platform.mutex);
+
+    if (new_group) {
+        ESP_LOGD(TAG, "new group (%d) at %p", group_id, group);
+    }
+
+    return group;
+}
+
+static void pcnt_release_group_handle(pcnt_group_t *group)
+{
+    int group_id = group->group_id;
+    bool do_deinitialize = false;
+
+    _lock_acquire(&s_platform.mutex);
+    s_platform.group_ref_counts[group_id]--;
+    if (s_platform.group_ref_counts[group_id] == 0) {
+        assert(s_platform.groups[group_id]);
+        do_deinitialize = true;
+        s_platform.groups[group_id] = NULL; // deregister from platform
+        periph_module_disable(pcnt_periph_signals.groups[group_id].module);
+    }
+    _lock_release(&s_platform.mutex);
+
+    if (do_deinitialize) {
+        free(group);
+        ESP_LOGD(TAG, "del group (%d)", group_id);
+    }
+}
+
+IRAM_ATTR static void pcnt_default_isr(void *args)
+{
+    bool need_yield = false;
+    pcnt_unit_t *unit = (pcnt_unit_t *)args;
+    int unit_id = unit->unit_id;
+    pcnt_group_t *group = unit->group;
+    pcnt_watch_cb_t on_reach = unit->on_reach;
+
+    uint32_t intr_status = pcnt_ll_get_intr_status(group->hal.dev);
+    if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(unit_id)) {
+        pcnt_ll_clear_intr_status(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id));
+        uint32_t event_status = pcnt_ll_get_event_status(group->hal.dev, unit_id);
+        // iter on each event_id
+        while (event_status) {
+            int event_id = __builtin_ffs(event_status) - 1;
+            event_status &= (event_status - 1); // clear the right most bit
+
+            // invoked user registered callback
+            if (on_reach) {
+                pcnt_watch_event_data_t edata = {
+                    .watch_point_value = unit->watchers[event_id].watch_point_value,
+                    .zero_cross_mode = pcnt_ll_get_zero_cross_mode(group->hal.dev, unit_id),
+                };
+                if (on_reach(unit, &edata, unit->user_data)) {
+                    // check if we need to yield for high priority task
+                    need_yield = true;
+                }
+            }
+        }
+    }
+    if (need_yield) {
+        portYIELD_FROM_ISR();
+    }
+}
+
+/**
+ * @brief This function will be called during start up, to check that pulse_cnt driver is not running along with the legacy pcnt driver
+ */
+__attribute__((constructor))
+static void check_pulse_cnt_driver_conflict(void)
+{
+    extern int pcnt_driver_init_count;
+    pcnt_driver_init_count++;
+    if (pcnt_driver_init_count > 1) {
+        ESP_EARLY_LOGE(TAG, "CONFLICT! The pulse_cnt driver can't work along with the legacy pcnt driver");
+        abort();
+    }
+}

+ 1 - 2
components/driver/test_apps/gptimer/main/CMakeLists.txt

@@ -2,7 +2,6 @@ set(srcs "test_app_main.c"
          "test_gptimer.c"
          "test_gptimer_iram.c")
 
-idf_component_register(SRCS ${srcs}
-                       PRIV_REQUIRES driver unity spi_flash)
+idf_component_register(SRCS ${srcs})
 
 target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_gptimer" "-u test_app_include_gptimer_iram")

+ 1 - 1
components/driver/test_apps/gptimer/pytest_gptimer.py

@@ -12,6 +12,6 @@ from pytest_embedded import Dut
     'release',
 ], indirect=True)
 def test_gptimer(dut: Dut) -> None:
-    dut.expect('Press ENTER to see the list of tests')
+    dut.expect_exact('Press ENTER to see the list of tests')
     dut.write('*')
     dut.expect_unity_test_output()

+ 18 - 0
components/driver/test_apps/pulse_cnt/CMakeLists.txt

@@ -0,0 +1,18 @@
+# This is the project CMakeLists.txt file for the test subproject
+cmake_minimum_required(VERSION 3.5)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(pcnt_test)
+
+if(CONFIG_COMPILER_DUMP_RTL_FILES)
+    add_custom_target(check_test_app_sections ALL
+                      COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
+                      --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
+                      --elf-file ${CMAKE_BINARY_DIR}/pcnt_test.elf
+                      find-refs
+                      --from-sections=.iram0.text
+                      --to-sections=.flash.text,.flash.rodata
+                      --exit-code
+                      DEPENDS ${elf}
+                      )
+endif()

+ 2 - 0
components/driver/test_apps/pulse_cnt/README.md

@@ -0,0 +1,2 @@
+| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
+| ----------------- | ----- | -------- | -------- |

+ 6 - 0
components/driver/test_apps/pulse_cnt/main/CMakeLists.txt

@@ -0,0 +1,6 @@
+set(srcs "test_app_main.c"
+         "test_pulse_cnt.c")
+
+idf_component_register(SRCS ${srcs})
+
+target_link_libraries(${COMPONENT_LIB} INTERFACE "-u test_app_include_pulse_cnt")

+ 51 - 0
components/driver/test_apps/pulse_cnt/main/test_app_main.c

@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "unity.h"
+#include "unity_test_runner.h"
+#include "esp_heap_caps.h"
+
+// Some resources are lazy allocated in pulse_cnt driver, the threshold is left for that case
+#define TEST_MEMORY_LEAK_THRESHOLD (-300)
+
+static size_t before_free_8bit;
+static size_t before_free_32bit;
+
+static void check_leak(size_t before_free, size_t after_free, const char *type)
+{
+    ssize_t delta = after_free - before_free;
+    printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
+    TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
+}
+
+void setUp(void)
+{
+    before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+}
+
+void tearDown(void)
+{
+    size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
+    size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
+    check_leak(before_free_8bit, after_free_8bit, "8BIT");
+    check_leak(before_free_32bit, after_free_32bit, "32BIT");
+}
+
+void app_main(void)
+{
+    //  ____   ____ _   _ _____   _____         _
+    // |  _ \ / ___| \ | |_   _| |_   _|__  ___| |_
+    // | |_) | |   |  \| | | |     | |/ _ \/ __| __|
+    // |  __/| |___| |\  | | |     | |  __/\__ \ |_
+    // |_|    \____|_| \_| |_|     |_|\___||___/\__|
+    printf(" ____   ____ _   _ _____   _____         _\r\n");
+    printf("|  _ \\ / ___| \\ | |_   _| |_   _|__  ___| |_\r\n");
+    printf("| |_) | |   |  \\| | | |     | |/ _ \\/ __| __|\r\n");
+    printf("|  __/| |___| |\\  | | |     | |  __/\\__ \\ |_\r\n");
+    printf("|_|    \\____|_| \\_| |_|     |_|\\___||___/\\__|\r\n");
+    unity_run_menu();
+}

+ 408 - 0
components/driver/test_apps/pulse_cnt/main/test_pulse_cnt.c

@@ -0,0 +1,408 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include "sdkconfig.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "unity.h"
+#include "driver/pulse_cnt.h"
+#include "driver/gpio.h"
+#include "soc/soc_caps.h"
+#include "esp_attr.h"
+
+#define TEST_PCNT_GPIO_A 0
+#define TEST_PCNT_GPIO_B 2
+
+#if CONFIG_PCNT_ISR_IRAM_SAFE
+#define TEST_PCNT_CALLBACK_ATTR IRAM_ATTR
+#else
+#define TEST_PCNT_CALLBACK_ATTR
+#endif // CONFIG_PCNT_ISR_IRAM_SAFE
+
+// helper function to simulate several rising edges on gpio
+static void test_gpio_simulate_rising_edge(int gpio_sig, size_t times)
+{
+    while (times--) {
+        TEST_ESP_OK(gpio_set_level(gpio_sig, 0));
+        TEST_ESP_OK(gpio_set_level(gpio_sig, 1));
+    }
+}
+
+// helper function to simulate several groups of quadrature signals
+static void test_gpio_simulate_quadrature_signals(int gpio_sig_a, int gpio_sig_b, size_t times)
+{
+    while (times--) {
+        TEST_ESP_OK(gpio_set_level(gpio_sig_a, 1));
+        TEST_ESP_OK(gpio_set_level(gpio_sig_b, 0));
+        vTaskDelay(1);
+        TEST_ESP_OK(gpio_set_level(gpio_sig_a, 0));
+        TEST_ESP_OK(gpio_set_level(gpio_sig_b, 0));
+        vTaskDelay(1);
+        TEST_ESP_OK(gpio_set_level(gpio_sig_a, 0));
+        TEST_ESP_OK(gpio_set_level(gpio_sig_b, 1));
+        vTaskDelay(1);
+        TEST_ESP_OK(gpio_set_level(gpio_sig_a, 1));
+        TEST_ESP_OK(gpio_set_level(gpio_sig_b, 1));
+        vTaskDelay(1);
+    }
+}
+
+TEST_CASE("pcnt_unit_install_uninstall", "[pcnt]")
+{
+    pcnt_unit_config_t unit_config = {
+        .low_limit = -100,
+        .high_limit = 100,
+    };
+    pcnt_unit_handle_t units[SOC_PCNT_UNITS_PER_GROUP];
+    int count_value = 0;
+
+    printf("install pcnt units and check initial count\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_new_unit(&unit_config, &units[i]));
+        TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
+        TEST_ASSERT_EQUAL(0, count_value);
+    }
+    // no more free pcnt units
+    TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_new_unit(&unit_config, &units[0]));
+
+    printf("set glitch filter\r\n");
+    pcnt_glitch_filter_config_t filter_config = {
+        .max_glitch_ns = 1000,
+    };
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_unit_set_glitch_filter(units[i], &filter_config));
+    }
+    // invalid glitch configuration
+    filter_config.max_glitch_ns = 500000;
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_set_glitch_filter(units[0], &filter_config));
+
+    printf("start pcnt units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_unit_start(units[i]));
+    }
+    // can't uninstall unit before stop it
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_del_unit(units[0]));
+
+    printf("stop pcnt units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_unit_stop(units[i]));
+    }
+
+    printf("uninstall pcnt units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_del_unit(units[i]));
+    }
+}
+
+TEST_CASE("pcnt_channel_install_uninstall", "[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,
+        .flags.io_loop_back = true,
+    };
+    pcnt_unit_handle_t units[SOC_PCNT_UNITS_PER_GROUP];
+    pcnt_channel_handle_t chans[SOC_PCNT_UNITS_PER_GROUP][SOC_PCNT_CHANNELS_PER_UNIT];
+
+    printf("install pcnt units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_new_unit(&unit_config, &units[i]));
+    }
+
+    printf("install pcnt channels\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        for (int j = 0; j < SOC_PCNT_CHANNELS_PER_UNIT; j++) {
+            TEST_ESP_OK(pcnt_new_channel(units[i], &chan_config, &chans[i][j]));
+            TEST_ESP_OK(pcnt_channel_set_edge_action(chans[i][j], PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+            TEST_ESP_OK(pcnt_channel_set_level_action(chans[i][j], PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
+        }
+        TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_new_channel(units[i], &chan_config, &chans[i][0]));
+    }
+
+    printf("start units\r\n");
+    int count_value = 0;
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        // start unit
+        TEST_ESP_OK(pcnt_unit_start(units[i]));
+        // trigger 10 rising edge on GPIO0
+        test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);
+        TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
+        // each channel increases to the same unit counter
+        TEST_ASSERT_EQUAL(10 * SOC_PCNT_CHANNELS_PER_UNIT, count_value);
+    }
+
+    printf("clear counts\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_unit_clear_count(units[i]));
+        TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
+        TEST_ASSERT_EQUAL(0, count_value);
+    }
+
+    printf("stop unit\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        // stop unit
+        TEST_ESP_OK(pcnt_unit_stop(units[i]));
+    }
+
+    // trigger 10 rising edge on GPIO0 shouldn't increase the counter
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 10);
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        TEST_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
+        TEST_ASSERT_EQUAL(0, count_value);
+    }
+
+    printf("restart units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        // start unit
+        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_ESP_OK(pcnt_unit_get_count(units[i], &count_value));
+        // each channel increases to the same unit counter
+        TEST_ASSERT_EQUAL(10 * SOC_PCNT_CHANNELS_PER_UNIT, count_value);
+    }
+
+    printf("uninstall channels and units\r\n");
+    for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
+        // stop unit
+        TEST_ESP_OK(pcnt_unit_stop(units[i]));
+        // can't uninstall unit when channel is still alive
+        TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_del_unit(units[i]));
+        for (int j = 0; j < SOC_PCNT_CHANNELS_PER_UNIT; j++) {
+            TEST_ESP_OK(pcnt_del_channel(chans[i][j]));
+        }
+        TEST_ESP_OK(pcnt_del_unit(units[i]));
+    }
+}
+
+/**
+ * @brief Using this context to save the triggered watchpoints in sequence
+ */
+typedef struct {
+    uint32_t index;
+    int triggered_watch_values[8];
+} test_pcnt_quadrature_context_t;
+
+TEST_PCNT_CALLBACK_ATTR
+static bool test_pcnt_quadrature_reach_watch_point(pcnt_unit_handle_t handle, pcnt_watch_event_data_t *event_data, void *user_data)
+{
+    test_pcnt_quadrature_context_t *user_ctx = (test_pcnt_quadrature_context_t *)user_data;
+    user_ctx->triggered_watch_values[user_ctx->index++] = event_data->watch_point_value;
+    return false;
+}
+
+TEST_CASE("pcnt_quadrature_decode_event", "[pcnt]")
+{
+    pcnt_unit_config_t unit_config = {
+        .low_limit = -100,
+        .high_limit = 100
+    };
+
+    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_INCREASE));
+    TEST_ESP_OK(pcnt_channel_set_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
+    // switch edge gpio and level gpio, the assign to another channel in the same unit
+    pcnt_channel_handle_t channelB = NULL;
+    channel_config.edge_gpio_num = TEST_PCNT_GPIO_B;
+    channel_config.level_gpio_num = TEST_PCNT_GPIO_A;
+    TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelB));
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_DECREASE));
+    TEST_ESP_OK(pcnt_channel_set_level_action(channelB, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
+
+    // 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 = {}
+    };
+    TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &user_data));
+
+    printf("add watchpoints\r\n");
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0));
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 100));
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -100));
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 50));
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -50));
+    TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, pcnt_unit_add_watch_point(unit, 33));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_point(unit, 50));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_point(unit, 100));
+
+    // Clear internal counter, and make the watch points take effect
+    TEST_ESP_OK(pcnt_unit_clear_count(unit));
+    TEST_ESP_OK(pcnt_unit_start(unit));
+
+    printf("simulating quadrature signals\r\n");
+    test_gpio_simulate_quadrature_signals(TEST_PCNT_GPIO_A, TEST_PCNT_GPIO_B, 30);
+    // simply wait for done
+    vTaskDelay(pdMS_TO_TICKS(100));
+
+    int count_value;
+    printf("checking count value\r\n");
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    printf("count_value=%d\r\n", count_value);
+    TEST_ASSERT_EQUAL(-20, count_value); // 0-30*4+100
+    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("simulating quadrature signals in another direction\r\n");
+    user_data.index = 0;
+    test_gpio_simulate_quadrature_signals(TEST_PCNT_GPIO_B, TEST_PCNT_GPIO_A, 40);
+    // simply wait for done
+    vTaskDelay(pdMS_TO_TICKS(100));
+    printf("checking count value\r\n");
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    printf("count_value=%d\r\n", count_value);
+    TEST_ASSERT_EQUAL(40, count_value); // -20+40*4-100
+    TEST_ASSERT_EQUAL(4, user_data.index);
+    TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[0]);
+    TEST_ASSERT_EQUAL(50, user_data.triggered_watch_values[1]);
+    TEST_ASSERT_EQUAL(100, user_data.triggered_watch_values[2]);
+    TEST_ASSERT_EQUAL(0, user_data.triggered_watch_values[3]);
+
+    printf("remove watchpoints and uninstall channels\r\n");
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 0));
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 100));
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, -100));
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 50));
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, -50));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_remove_watch_point(unit, 50));
+    TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_remove_watch_point(unit, 33));
+    TEST_ESP_OK(pcnt_del_channel(channelA));
+    TEST_ESP_OK(pcnt_del_channel(channelB));
+    TEST_ESP_OK(pcnt_unit_stop(unit));
+    TEST_ESP_OK(pcnt_del_unit(unit));
+}
+
+typedef struct {
+    pcnt_unit_zero_cross_mode_t mode;
+} test_pcnt_zero_cross_context_t;
+
+TEST_PCNT_CALLBACK_ATTR
+static bool test_pcnt_on_zero_cross(pcnt_unit_handle_t handle, pcnt_watch_event_data_t *event_data, void *user_data)
+{
+    test_pcnt_zero_cross_context_t *user_ctx = (test_pcnt_zero_cross_context_t *)user_data;
+    user_ctx->mode = event_data->zero_cross_mode;
+    return false;
+}
+
+TEST_CASE("pcnt_zero_cross_mode", "[pcnt]")
+{
+    pcnt_unit_config_t unit_config = {
+        .low_limit = -100,
+        .high_limit = 100
+    };
+
+    printf("install pcnt unit\r\n");
+    pcnt_unit_handle_t unit = NULL;
+    TEST_ESP_OK(pcnt_new_unit(&unit_config, &unit));
+
+    printf("add watchpoint to detect zero cross\r\n");
+    TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0));
+
+    printf("register callback for zero cross event\r\n");
+    pcnt_event_callbacks_t cbs = {
+        .on_reach = test_pcnt_on_zero_cross,
+    };
+    test_pcnt_zero_cross_context_t user_data = {};
+    TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &user_data));
+
+    printf("install pcnt channels\r\n");
+    pcnt_chan_config_t channel_config = {
+        .edge_gpio_num = TEST_PCNT_GPIO_A,
+        .level_gpio_num = -1,
+        .flags.io_loop_back = true,
+    };
+    pcnt_channel_handle_t channelA = NULL;
+    pcnt_channel_handle_t channelB = NULL;
+    TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelA));
+    TEST_ESP_OK(pcnt_new_channel(unit, &channel_config, &channelB));
+
+    printf("Initialize pcnt actions for channels\r\n");
+    // only channel will increase the counter, 0->1
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    TEST_ESP_OK(pcnt_channel_set_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    TEST_ESP_OK(pcnt_channel_set_level_action(channelB, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
+
+    printf("start unit\r\n");
+    TEST_ESP_OK(pcnt_unit_start(unit));
+
+    int count_value = 0;
+    printf("counter goes 0->1\r\n");
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    TEST_ASSERT_EQUAL(1, count_value);
+
+    printf("counter goes 1->-1\r\n");
+    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_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    TEST_ASSERT_EQUAL(-1, count_value);
+    TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_POS_NEG, user_data.mode);
+
+    printf("counter goes -1->1\r\n");
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 1);
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    TEST_ASSERT_EQUAL(1, count_value);
+    TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_NEG_POS, user_data.mode);
+
+    printf("counter goes 1->0->-1\r\n");
+    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_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 2);
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    TEST_ASSERT_EQUAL(-1, count_value);
+    TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_POS_ZERO, user_data.mode);
+
+    printf("counter goes -1->0->1\r\n");
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelA, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    TEST_ESP_OK(pcnt_channel_set_edge_action(channelB, PCNT_CHANNEL_EDGE_ACTION_HOLD, PCNT_CHANNEL_EDGE_ACTION_HOLD));
+    test_gpio_simulate_rising_edge(TEST_PCNT_GPIO_A, 2);
+    TEST_ESP_OK(pcnt_unit_get_count(unit, &count_value));
+    TEST_ASSERT_EQUAL(1, count_value);
+    TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_NEG_ZERO, user_data.mode);
+
+    TEST_ESP_OK(pcnt_unit_stop(unit));
+    TEST_ESP_OK(pcnt_unit_remove_watch_point(unit, 0));
+    TEST_ESP_OK(pcnt_del_channel(channelA));
+    TEST_ESP_OK(pcnt_del_channel(channelB));
+    TEST_ESP_OK(pcnt_del_unit(unit));
+}
+
+void test_app_include_pulse_cnt(void)
+{
+}

+ 23 - 0
components/driver/test_apps/pulse_cnt/pytest_pulse_cnt.py

@@ -0,0 +1,23 @@
+# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: CC0-1.0
+
+import pytest
+from pytest_embedded import Dut
+
+
+@pytest.mark.esp32
+@pytest.mark.esp32s2
+@pytest.mark.esp32s3
+@pytest.mark.generic
+@pytest.mark.parametrize(
+    'config',
+    [
+        'iram_safe',
+        'release',
+    ],
+    indirect=True,
+)
+def test_gptimer(dut: Dut) -> None:
+    dut.expect_exact('Press ENTER to see the list of tests')
+    dut.write('*')
+    dut.expect_unity_test_output()

+ 6 - 0
components/driver/test_apps/pulse_cnt/sdkconfig.ci.iram_safe

@@ -0,0 +1,6 @@
+CONFIG_COMPILER_DUMP_RTL_FILES=y
+CONFIG_PCNT_CTRL_FUNC_IN_IRAM=y
+CONFIG_PCNT_ISR_IRAM_SAFE=y
+
+# silent the error check, as the error string are stored in rodata, causing RTL check failure
+CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y

+ 5 - 0
components/driver/test_apps/pulse_cnt/sdkconfig.ci.release

@@ -0,0 +1,5 @@
+CONFIG_PM_ENABLE=y
+CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

+ 2 - 0
components/driver/test_apps/pulse_cnt/sdkconfig.defaults

@@ -0,0 +1,2 @@
+CONFIG_FREERTOS_HZ=1000
+CONFIG_ESP_TASK_WDT=n

+ 42 - 29
components/hal/esp32/include/hal/pcnt_ll.h

@@ -1,16 +1,8 @@
-// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 /*******************************************************************************
  * NOTICE
@@ -22,6 +14,7 @@
 
 #pragma once
 
+#include <limits.h>
 #include <stdlib.h>
 #include <stdbool.h>
 #include "soc/pcnt_struct.h"
@@ -31,19 +24,23 @@
 extern "C" {
 #endif
 
-#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
+#define PCNT_LL_GET_HW(num)      (((num) == 0) ? (&PCNT) : NULL)
 #define PCNT_LL_MAX_GLITCH_WIDTH 1023
+#define PCNT_LL_MAX_LIM          SHRT_MAX
+#define PCNT_LL_MIN_LIN          SHRT_MIN
 
 typedef enum {
-    PCNT_LL_EVENT_THRES1,
-    PCNT_LL_EVENT_THRES0,
-    PCNT_LL_EVENT_LOW_LIMIT,
-    PCNT_LL_EVENT_HIGH_LIMIT,
-    PCNT_LL_EVENT_ZERO_CROSS,
-    PCNT_LL_EVENT_MAX
-} pcnt_ll_event_id_t;
-
-#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
+    PCNT_LL_WATCH_EVENT_INVALID = -1,
+    PCNT_LL_WATCH_EVENT_THRES1,
+    PCNT_LL_WATCH_EVENT_THRES0,
+    PCNT_LL_WATCH_EVENT_LOW_LIMIT,
+    PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
+    PCNT_LL_WATCH_EVENT_ZERO_CROSS,
+    PCNT_LL_WATCH_EVENT_MAX
+} pcnt_ll_watch_event_id_t;
+
+#define PCNT_LL_WATCH_EVENT_MASK          ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
+#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
 
 /**
  * @brief Set PCNT channel edge action
@@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
  * @param hw Peripheral PCNT hardware instance address.
  * @return Interrupt status word
  */
-__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
+__attribute__((always_inline))
+static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
 {
     return hw->int_st.val;
 }
@@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
  * @param hw Peripheral PCNT hardware instance address.
  * @param status value to clear interrupt status
  */
-__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
+__attribute__((always_inline))
+static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
 {
     hw->int_clr.val = status;
 }
@@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
  */
 static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
 {
-    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
+    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
 }
 
 /**
@@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
 }
 
 /**
- * @brief Get PCNT count sign
+ * @brief Get PCNT zero cross mode
  *
  * @param hw Peripheral PCNT hardware instance address.
  * @param unit PCNT unit number
- * @return Count sign
+ * @return Zero cross mode
  */
-static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
+__attribute__((always_inline))
+static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val & 0x03;
 }
@@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
  * @param unit PCNT unit number
  * @return Event status word
  */
+__attribute__((always_inline))
 static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val >> 2;
@@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
     hw->conf_unit[unit].conf0.filter_en = enable;
 }
 
+/**
+ * @brief Get interrupt status register address.
+ *
+ * @param hw Beginning address of the peripheral registers.
+ *
+ * @return Interrupt status register address
+ */
+static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
+{
+    return &hw->int_st.val;
+}
+
 #ifdef __cplusplus
 }
 #endif

+ 42 - 29
components/hal/esp32s2/include/hal/pcnt_ll.h

@@ -1,16 +1,8 @@
-// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 /*******************************************************************************
  * NOTICE
@@ -22,6 +14,7 @@
 
 #pragma once
 
+#include <limits.h>
 #include <stdlib.h>
 #include <stdbool.h>
 #include "soc/pcnt_struct.h"
@@ -31,19 +24,23 @@
 extern "C" {
 #endif
 
-#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
+#define PCNT_LL_GET_HW(num)      (((num) == 0) ? (&PCNT) : NULL)
 #define PCNT_LL_MAX_GLITCH_WIDTH 1023
+#define PCNT_LL_MAX_LIM          SHRT_MAX
+#define PCNT_LL_MIN_LIN          SHRT_MIN
 
 typedef enum {
-    PCNT_LL_EVENT_THRES1,
-    PCNT_LL_EVENT_THRES0,
-    PCNT_LL_EVENT_LOW_LIMIT,
-    PCNT_LL_EVENT_HIGH_LIMIT,
-    PCNT_LL_EVENT_ZERO_CROSS,
-    PCNT_LL_EVENT_MAX
-} pcnt_ll_event_id_t;
-
-#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
+    PCNT_LL_WATCH_EVENT_INVALID = -1,
+    PCNT_LL_WATCH_EVENT_THRES1,
+    PCNT_LL_WATCH_EVENT_THRES0,
+    PCNT_LL_WATCH_EVENT_LOW_LIMIT,
+    PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
+    PCNT_LL_WATCH_EVENT_ZERO_CROSS,
+    PCNT_LL_WATCH_EVENT_MAX
+} pcnt_ll_watch_event_id_t;
+
+#define PCNT_LL_WATCH_EVENT_MASK          ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
+#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
 
 /**
  * @brief Set PCNT channel edge action
@@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
  * @param hw Peripheral PCNT hardware instance address.
  * @return Interrupt status word
  */
-__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
+__attribute__((always_inline))
+static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
 {
     return hw->int_st.val;
 }
@@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
  * @param hw Peripheral PCNT hardware instance address.
  * @param status value to clear interrupt status
  */
-__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
+__attribute__((always_inline))
+static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
 {
     hw->int_clr.val = status;
 }
@@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
  */
 static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
 {
-    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
+    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
 }
 
 /**
@@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
 }
 
 /**
- * @brief Get PCNT count sign
+ * @brief Get PCNT zero cross mode
  *
  * @param hw Peripheral PCNT hardware instance address.
  * @param unit PCNT unit number
- * @return Count sign
+ * @return Zero cross mode
  */
-static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
+__attribute__((always_inline))
+static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val & 0x03;
 }
@@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
  * @param unit PCNT unit number
  * @return Event status word
  */
+__attribute__((always_inline))
 static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val >> 2;
@@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
     hw->conf_unit[unit].conf0.filter_en_un = enable;
 }
 
+/**
+ * @brief Get interrupt status register address.
+ *
+ * @param hw Beginning address of the peripheral registers.
+ *
+ * @return Interrupt status register address
+ */
+static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
+{
+    return &hw->int_st.val;
+}
+
 #ifdef __cplusplus
 }
 #endif

+ 42 - 29
components/hal/esp32s3/include/hal/pcnt_ll.h

@@ -1,16 +1,8 @@
-// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 /*******************************************************************************
  * NOTICE
@@ -22,6 +14,7 @@
 
 #pragma once
 
+#include <limits.h>
 #include <stdlib.h>
 #include <stdbool.h>
 #include "soc/pcnt_struct.h"
@@ -31,19 +24,23 @@
 extern "C" {
 #endif
 
-#define PCNT_LL_GET_HW(num) (((num) == 0) ? (&PCNT) : NULL)
+#define PCNT_LL_GET_HW(num)      (((num) == 0) ? (&PCNT) : NULL)
 #define PCNT_LL_MAX_GLITCH_WIDTH 1023
+#define PCNT_LL_MAX_LIM          SHRT_MAX
+#define PCNT_LL_MIN_LIN          SHRT_MIN
 
 typedef enum {
-    PCNT_LL_EVENT_THRES1,
-    PCNT_LL_EVENT_THRES0,
-    PCNT_LL_EVENT_LOW_LIMIT,
-    PCNT_LL_EVENT_HIGH_LIMIT,
-    PCNT_LL_EVENT_ZERO_CROSS,
-    PCNT_LL_EVENT_MAX
-} pcnt_ll_event_id_t;
-
-#define PCNT_LL_EVENT_MASK ((1 << PCNT_LL_EVENT_MAX) - 1)
+    PCNT_LL_WATCH_EVENT_INVALID = -1,
+    PCNT_LL_WATCH_EVENT_THRES1,
+    PCNT_LL_WATCH_EVENT_THRES0,
+    PCNT_LL_WATCH_EVENT_LOW_LIMIT,
+    PCNT_LL_WATCH_EVENT_HIGH_LIMIT,
+    PCNT_LL_WATCH_EVENT_ZERO_CROSS,
+    PCNT_LL_WATCH_EVENT_MAX
+} pcnt_ll_watch_event_id_t;
+
+#define PCNT_LL_WATCH_EVENT_MASK          ((1 << PCNT_LL_WATCH_EVENT_MAX) - 1)
+#define PCNT_LL_UNIT_WATCH_EVENT(unit_id) (1 << (unit_id))
 
 /**
  * @brief Set PCNT channel edge action
@@ -156,7 +153,8 @@ static inline void pcnt_ll_enable_intr(pcnt_dev_t *hw, uint32_t unit_mask, bool
  * @param hw Peripheral PCNT hardware instance address.
  * @return Interrupt status word
  */
-__attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
+__attribute__((always_inline))
+static inline uint32_t pcnt_ll_get_intr_status(pcnt_dev_t *hw)
 {
     return hw->int_st.val;
 }
@@ -167,7 +165,8 @@ __attribute__((always_inline)) static inline uint32_t pcnt_ll_get_intr_status(pc
  * @param hw Peripheral PCNT hardware instance address.
  * @param status value to clear interrupt status
  */
-__attribute__((always_inline)) static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
+__attribute__((always_inline))
+static inline void pcnt_ll_clear_intr_status(pcnt_dev_t *hw, uint32_t status)
 {
     hw->int_clr.val = status;
 }
@@ -233,7 +232,7 @@ static inline void pcnt_ll_enable_thres_event(pcnt_dev_t *hw, uint32_t unit, uin
  */
 static inline void pcnt_ll_disable_all_events(pcnt_dev_t *hw, uint32_t unit)
 {
-    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_EVENT_MASK << 11);
+    hw->conf_unit[unit].conf0.val &= ~(PCNT_LL_WATCH_EVENT_MASK << 11);
 }
 
 /**
@@ -344,13 +343,14 @@ static inline uint32_t pcnt_ll_get_unit_status(pcnt_dev_t *hw, uint32_t unit)
 }
 
 /**
- * @brief Get PCNT count sign
+ * @brief Get PCNT zero cross mode
  *
  * @param hw Peripheral PCNT hardware instance address.
  * @param unit PCNT unit number
- * @return Count sign
+ * @return Zero cross mode
  */
-static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint32_t unit)
+__attribute__((always_inline))
+static inline pcnt_unit_zero_cross_mode_t pcnt_ll_get_zero_cross_mode(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val & 0x03;
 }
@@ -362,6 +362,7 @@ static inline pcnt_unit_count_sign_t pcnt_ll_get_count_sign(pcnt_dev_t *hw, uint
  * @param unit PCNT unit number
  * @return Event status word
  */
+__attribute__((always_inline))
 static inline uint32_t pcnt_ll_get_event_status(pcnt_dev_t *hw, uint32_t unit)
 {
     return hw->status_unit[unit].val >> 2;
@@ -404,6 +405,18 @@ static inline void pcnt_ll_enable_glitch_filter(pcnt_dev_t *hw, uint32_t unit, b
     hw->conf_unit[unit].conf0.filter_en_un = enable;
 }
 
+/**
+ * @brief Get interrupt status register address.
+ *
+ * @param hw Beginning address of the peripheral registers.
+ *
+ * @return Interrupt status register address
+ */
+static inline volatile void *pcnt_ll_get_intr_status_reg(pcnt_dev_t *hw)
+{
+    return &hw->int_st.val;
+}
+
 #ifdef __cplusplus
 }
 #endif

+ 8 - 19
components/hal/include/hal/pcnt_hal.h

@@ -1,16 +1,8 @@
-// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 /*******************************************************************************
  * NOTICE
@@ -18,22 +10,19 @@
  * See readme.md in hal/include/hal/readme.md
  ******************************************************************************/
 
-// The HAL layer for PCNT.
-// There is no parameter check in the hal layer, so the caller must ensure the correctness of the parameters.
-
 #pragma once
 
-#include "soc/pcnt_struct.h"
-
 #ifdef __cplusplus
 extern "C" {
 #endif
 
+typedef struct pcnt_dev_t *pcnt_soc_handle_t; // PCNT SOC layer handle
+
 /**
  * Context that should be maintained by both the driver and the HAL
  */
 typedef struct {
-    pcnt_dev_t *dev; /*!< PCNT peripheral register base address */
+    pcnt_soc_handle_t dev; // PCNT SOC layer handle
 } pcnt_hal_context_t;
 
 /**

+ 11 - 22
components/hal/include/hal/pcnt_types.h

@@ -1,16 +1,8 @@
-// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 #pragma once
 
@@ -20,7 +12,6 @@ extern "C" {
 
 /**
  * @brief PCNT channel action on control level
- *
  */
 typedef enum {
     PCNT_CHANNEL_LEVEL_ACTION_KEEP,    /*!< Keep current count mode */
@@ -30,7 +21,6 @@ typedef enum {
 
 /**
  * @brief PCNT channel action on signal edge
- *
  */
 typedef enum {
     PCNT_CHANNEL_EDGE_ACTION_HOLD,     /*!< Hold current count value */
@@ -39,15 +29,14 @@ typedef enum {
 } pcnt_channel_edge_action_t;
 
 /**
- * @brief PCNT unit counter value's sign
- *
+ * @brief PCNT unit zero cross mode
  */
 typedef enum {
-    PCNT_UNIT_COUNT_SIGN_ZERO_POS, /*!< positive value to zero */
-    PCNT_UNIT_COUNT_SIGN_ZERO_NEG, /*!< negative value to zero */
-    PCNT_UNIT_COUNT_SIGN_NEG,      /*!< counter value negative */
-    PCNT_UNIT_COUNT_SIGN_POS,      /*!< counter value positive */
-} pcnt_unit_count_sign_t;
+    PCNT_UNIT_ZERO_CROSS_POS_ZERO, /*!< start from positive value, end to zero, i.e. +N->0 */
+    PCNT_UNIT_ZERO_CROSS_NEG_ZERO, /*!< start from negative value, end to zero, i.e. -N->0 */
+    PCNT_UNIT_ZERO_CROSS_NEG_POS,  /*!< start from negative value, end to positive value, i.e. -N->+M */
+    PCNT_UNIT_ZERO_CROSS_POS_NEG,  /*!< start from positive value, end to negative value, i.e. +N->-M */
+} pcnt_unit_zero_cross_mode_t;
 
 #ifdef __cplusplus
 }

+ 5 - 13
components/hal/pcnt_hal.c

@@ -1,16 +1,8 @@
-// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 // The HAL layer for PCNT (common part)
 

+ 8 - 21
components/soc/esp32/include/soc/pcnt_reg.h

@@ -1,21 +1,13 @@
-// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
 
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-#ifndef _SOC_PCNT_REG_H_
-#define _SOC_PCNT_REG_H_
+#include <stdint.h>
+#include "soc/soc.h"
 
-
-#include "soc.h"
 #define PCNT_U0_CONF0_REG          (DR_REG_PCNT_BASE + 0x0000)
 /* PCNT_CH1_LCTRL_MODE_U0 : R/W ;bitpos:[31:30] ;default: 2'd0 ; */
 /*description: This register is used to control the mode of channel1's low control
@@ -1517,8 +1509,3 @@
 #define PCNT_DATE_M  ((PCNT_DATE_V)<<(PCNT_DATE_S))
 #define PCNT_DATE_V  0xFFFFFFFF
 #define PCNT_DATE_S  0
-
-
-
-
-#endif /*_SOC_PCNT_REG_H_ */

+ 28 - 38
components/soc/esp32/include/soc/pcnt_struct.h

@@ -1,18 +1,9 @@
-// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-#ifndef _SOC_PCNT_STRUCT_H_
-#define _SOC_PCNT_STRUCT_H_
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
 
 #include <stdint.h>
 
@@ -20,8 +11,8 @@
 extern "C" {
 #endif
 
-typedef volatile struct pcnt_dev_s {
-    struct {
+typedef struct pcnt_dev_t {
+    volatile struct {
         union {
             struct {
                 uint32_t filter_thres:  10;         /*This register is used to filter pulse whose width is smaller than this value for unit0.*/
@@ -44,27 +35,27 @@ typedef volatile struct pcnt_dev_s {
         } conf0;
         union {
             struct {
-                uint32_t cnt_thres0:16;             /*This register is used to configure thres0 value for unit0.*/
-                uint32_t cnt_thres1:16;             /*This register is used to configure thres1 value for unit0.*/
+                uint32_t cnt_thres0: 16;            /*This register is used to configure thres0 value for unit0.*/
+                uint32_t cnt_thres1: 16;            /*This register is used to configure thres1 value for unit0.*/
             };
             uint32_t val;
         } conf1;
         union {
             struct {
-                uint32_t cnt_h_lim:16;              /*This register is used to configure thr_h_lim value for unit0.*/
-                uint32_t cnt_l_lim:16;              /*This register is used to configure thr_l_lim value for unit0.*/
+                uint32_t cnt_h_lim: 16;             /*This register is used to configure thr_h_lim value for unit0.*/
+                uint32_t cnt_l_lim: 16;             /*This register is used to configure thr_l_lim value for unit0.*/
             };
             uint32_t val;
         } conf2;
     } conf_unit[8];
-    union {
+    volatile union {
         struct {
             uint32_t cnt_val   : 16;                /*This register stores the current pulse count value for unit0.*/
             uint32_t reserved16: 16;
         };
         uint32_t val;
     } cnt_unit[8];
-    union {
+    volatile union {
         struct {
             uint32_t cnt_thr_event_u0: 1;           /*This is the interrupt raw bit for channel0 event.*/
             uint32_t cnt_thr_event_u1: 1;           /*This is the interrupt raw bit for channel1 event.*/
@@ -78,7 +69,7 @@ typedef volatile struct pcnt_dev_s {
         };
         uint32_t val;
     } int_raw;
-    union {
+    volatile union {
         struct {
             uint32_t cnt_thr_event_u0: 1;            /*This is the  interrupt status bit for channel0 event.*/
             uint32_t cnt_thr_event_u1: 1;            /*This is the  interrupt status bit for channel1 event.*/
@@ -92,7 +83,7 @@ typedef volatile struct pcnt_dev_s {
         };
         uint32_t val;
     } int_st;
-    union {
+    volatile union {
         struct {
             uint32_t cnt_thr_event_u0: 1;           /*This is the  interrupt enable bit for channel0 event.*/
             uint32_t cnt_thr_event_u1: 1;           /*This is the  interrupt enable bit for channel1 event.*/
@@ -106,7 +97,7 @@ typedef volatile struct pcnt_dev_s {
         };
         uint32_t val;
     } int_ena;
-    union {
+    volatile union {
         struct {
             uint32_t cnt_thr_event_u0: 1;           /*Set this bit to clear channel0 event interrupt.*/
             uint32_t cnt_thr_event_u1: 1;           /*Set this bit to clear channel1 event interrupt.*/
@@ -120,19 +111,19 @@ typedef volatile struct pcnt_dev_s {
         };
         uint32_t val;
     } int_clr;
-    union {
+    volatile union {
         struct {
-            uint32_t cnt_mode:2;                    /*0: positive value to zero; 1: negative value to zero; 2: counter value negative ; 3: counter value positive*/
-            uint32_t thres1_lat:1;                  /* counter value equals to thresh1*/
-            uint32_t thres0_lat:1;                  /* counter value equals to thresh0*/
-            uint32_t l_lim_lat:1;                   /* counter value reaches h_lim*/
-            uint32_t h_lim_lat:1;                   /* counter value reaches l_lim*/
-            uint32_t zero_lat:1;                    /* counter value equals zero*/
-            uint32_t reserved7:25;
+            uint32_t cnt_mode: 2;                   /*0: positive value to zero; 1: negative value to zero; 2: counter value negative ; 3: counter value positive*/
+            uint32_t thres1_lat: 1;                 /* counter value equals to thresh1*/
+            uint32_t thres0_lat: 1;                 /* counter value equals to thresh0*/
+            uint32_t l_lim_lat: 1;                  /* counter value reaches h_lim*/
+            uint32_t h_lim_lat: 1;                  /* counter value reaches l_lim*/
+            uint32_t zero_lat: 1;                   /* counter value equals zero*/
+            uint32_t reserved7: 25;
         };
         uint32_t val;
     } status_unit[8];
-    union {
+    volatile union {
         struct {
             uint32_t cnt_rst_u0:   1;               /*Set this bit to clear unit0's counter.*/
             uint32_t cnt_pause_u0: 1;               /*Set this bit to pause unit0's counter.*/
@@ -173,12 +164,11 @@ typedef volatile struct pcnt_dev_s {
     uint32_t reserved_f0;
     uint32_t reserved_f4;
     uint32_t reserved_f8;
-    uint32_t date;                                  /**/
+    volatile uint32_t date;
 } pcnt_dev_t;
+
 extern pcnt_dev_t PCNT;
 
 #ifdef __cplusplus
 }
 #endif
-
-#endif  /* _SOC_PCNT_STRUCT_H_ */

+ 5 - 13
components/soc/esp32/pcnt_periph.c

@@ -1,16 +1,8 @@
-// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 #include "soc/pcnt_periph.h"
 #include "soc/gpio_sig_map.h"

+ 3 - 12
components/soc/esp32s2/include/soc/pcnt_reg.h

@@ -1,16 +1,7 @@
-/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
  *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
+ * SPDX-License-Identifier: Apache-2.0
  */
 #pragma once
 

+ 4 - 13
components/soc/esp32s2/include/soc/pcnt_struct.h

@@ -1,16 +1,7 @@
-/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
  *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
+ * SPDX-License-Identifier: Apache-2.0
  */
 #pragma once
 
@@ -388,7 +379,7 @@ typedef union {
 } pcnt_date_reg_t;
 
 
-typedef struct {
+typedef struct pcnt_dev_t {
     volatile struct {
         pcnt_un_conf0_reg_t conf0;
         pcnt_un_conf1_reg_t conf1;

+ 5 - 13
components/soc/esp32s2/pcnt_periph.c

@@ -1,16 +1,8 @@
-// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 #include "soc/pcnt_periph.h"
 #include "soc/gpio_sig_map.h"

+ 0 - 26
components/soc/esp32s3/include/soc/pcnt_caps.h

@@ -1,26 +0,0 @@
-// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#pragma once
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define SOC_PCNT_PORT_NUM      (1)
-#define SOC_PCNT_UNIT_NUM      (4)
-
-#ifdef __cplusplus
-}
-#endif

+ 3 - 12
components/soc/esp32s3/include/soc/pcnt_reg.h

@@ -1,16 +1,7 @@
-/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
  *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
+ * SPDX-License-Identifier: Apache-2.0
  */
 #pragma once
 

+ 4 - 13
components/soc/esp32s3/include/soc/pcnt_struct.h

@@ -1,16 +1,7 @@
-/** Copyright 2021 Espressif Systems (Shanghai) PTE LTD
+/*
+ * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
  *
- *  Licensed under the Apache License, Version 2.0 (the "License");
- *  you may not use this file except in compliance with the License.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
+ * SPDX-License-Identifier: Apache-2.0
  */
 #pragma once
 
@@ -388,7 +379,7 @@ typedef union {
 } pcnt_date_reg_t;
 
 
-typedef struct {
+typedef struct pcnt_dev_t {
     volatile struct {
         pcnt_un_conf0_reg_t conf0;
         pcnt_un_conf1_reg_t conf1;

+ 5 - 13
components/soc/esp32s3/pcnt_periph.c

@@ -1,16 +1,8 @@
-// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 #include "soc/pcnt_periph.h"
 #include "soc/gpio_sig_map.h"

+ 5 - 13
components/soc/include/soc/pcnt_periph.h

@@ -1,16 +1,8 @@
-// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
+/*
+ * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
 
 #pragma once
 

+ 0 - 21
tools/ci/check_copyright_ignore.txt

@@ -818,7 +818,6 @@ components/hal/esp32/include/hal/dac_ll.h
 components/hal/esp32/include/hal/i2c_ll.h
 components/hal/esp32/include/hal/interrupt_controller_ll.h
 components/hal/esp32/include/hal/mpu_ll.h
-components/hal/esp32/include/hal/pcnt_ll.h
 components/hal/esp32/include/hal/rtc_cntl_ll.h
 components/hal/esp32/include/hal/rtc_io_ll.h
 components/hal/esp32/include/hal/rwdt_ll.h
@@ -901,7 +900,6 @@ components/hal/esp32s2/include/hal/interrupt_controller_ll.h
 components/hal/esp32s2/include/hal/memprot_ll.h
 components/hal/esp32s2/include/hal/memprot_peri_ll.h
 components/hal/esp32s2/include/hal/mpu_ll.h
-components/hal/esp32s2/include/hal/pcnt_ll.h
 components/hal/esp32s2/include/hal/rtc_cntl_ll.h
 components/hal/esp32s2/include/hal/rtc_io_ll.h
 components/hal/esp32s2/include/hal/sha_ll.h
@@ -927,7 +925,6 @@ components/hal/esp32s3/include/hal/i2c_ll.h
 components/hal/esp32s3/include/hal/interrupt_controller_ll.h
 components/hal/esp32s3/include/hal/memprot_ll.h
 components/hal/esp32s3/include/hal/mpu_ll.h
-components/hal/esp32s3/include/hal/pcnt_ll.h
 components/hal/esp32s3/include/hal/rtc_cntl_ll.h
 components/hal/esp32s3/include/hal/rwdt_ll.h
 components/hal/esp32s3/include/hal/sha_ll.h
@@ -958,8 +955,6 @@ components/hal/include/hal/mcpwm_hal.h
 components/hal/include/hal/mcpwm_types.h
 components/hal/include/hal/mpu_hal.h
 components/hal/include/hal/mpu_types.h
-components/hal/include/hal/pcnt_hal.h
-components/hal/include/hal/pcnt_types.h
 components/hal/include/hal/rmt_types.h
 components/hal/include/hal/rtc_io_types.h
 components/hal/include/hal/sdio_slave_hal.h
@@ -987,7 +982,6 @@ components/hal/interrupt_controller_hal.c
 components/hal/ledc_hal_iram.c
 components/hal/mcpwm_hal.c
 components/hal/mpu_hal.c
-components/hal/pcnt_hal.c
 components/hal/platform_port/include/hal/assert.h
 components/hal/platform_port/include/hal/check.h
 components/hal/platform_port/include/hal/log.h
@@ -1315,8 +1309,6 @@ components/soc/esp32/include/soc/ledc_reg.h
 components/soc/esp32/include/soc/ledc_struct.h
 components/soc/esp32/include/soc/mmu.h
 components/soc/esp32/include/soc/nrx_reg.h
-components/soc/esp32/include/soc/pcnt_reg.h
-components/soc/esp32/include/soc/pcnt_struct.h
 components/soc/esp32/include/soc/pid.h
 components/soc/esp32/include/soc/reset_reasons.h
 components/soc/esp32/include/soc/rmt_reg.h
@@ -1352,7 +1344,6 @@ components/soc/esp32/include/soc/wdev_reg.h
 components/soc/esp32/interrupts.c
 components/soc/esp32/ledc_periph.c
 components/soc/esp32/mcpwm_periph.c
-components/soc/esp32/pcnt_periph.c
 components/soc/esp32/rmt_periph.c
 components/soc/esp32/sdio_slave_periph.c
 components/soc/esp32/sdmmc_periph.c
@@ -1511,8 +1502,6 @@ components/soc/esp32s2/include/soc/ledc_struct.h
 components/soc/esp32s2/include/soc/memprot_defs.h
 components/soc/esp32s2/include/soc/mmu.h
 components/soc/esp32s2/include/soc/nrx_reg.h
-components/soc/esp32s2/include/soc/pcnt_reg.h
-components/soc/esp32s2/include/soc/pcnt_struct.h
 components/soc/esp32s2/include/soc/reset_reasons.h
 components/soc/esp32s2/include/soc/rtc_cntl_reg.h
 components/soc/esp32s2/include/soc/rtc_cntl_struct.h
@@ -1556,7 +1545,6 @@ components/soc/esp32s2/include/soc/usbh_struct.h
 components/soc/esp32s2/include/soc/wdev_reg.h
 components/soc/esp32s2/interrupts.c
 components/soc/esp32s2/ledc_periph.c
-components/soc/esp32s2/pcnt_periph.c
 components/soc/esp32s2/rmt_periph.c
 components/soc/esp32s2/sigmadelta_periph.c
 components/soc/esp32s2/spi_periph.c
@@ -1605,9 +1593,6 @@ components/soc/esp32s3/include/soc/ledc_struct.h
 components/soc/esp32s3/include/soc/mmu.h
 components/soc/esp32s3/include/soc/mpu_caps.h
 components/soc/esp32s3/include/soc/nrx_reg.h
-components/soc/esp32s3/include/soc/pcnt_caps.h
-components/soc/esp32s3/include/soc/pcnt_reg.h
-components/soc/esp32s3/include/soc/pcnt_struct.h
 components/soc/esp32s3/include/soc/peri_backup_reg.h
 components/soc/esp32s3/include/soc/peri_backup_struct.h
 components/soc/esp32s3/include/soc/reset_reasons.h
@@ -1663,7 +1648,6 @@ components/soc/esp32s3/include/soc/world_controller_struct.h
 components/soc/esp32s3/interrupts.c
 components/soc/esp32s3/ledc_periph.c
 components/soc/esp32s3/mcpwm_periph.c
-components/soc/esp32s3/pcnt_periph.c
 components/soc/esp32s3/rmt_periph.c
 components/soc/esp32s3/rtc_io_periph.c
 components/soc/esp32s3/sdio_slave_periph.c
@@ -1683,7 +1667,6 @@ components/soc/include/soc/i2c_periph.h
 components/soc/include/soc/interrupts.h
 components/soc/include/soc/ledc_periph.h
 components/soc/include/soc/mcpwm_periph.h
-components/soc/include/soc/pcnt_periph.h
 components/soc/include/soc/rmt_periph.h
 components/soc/include/soc/rtc_cntl_periph.h
 components/soc/include/soc/rtc_periph.h
@@ -2223,10 +2206,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro
 examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c
 examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c
 examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c
-examples/peripherals/pcnt/pulse_count_event/main/pcnt_event_example_main.c
-examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/include/rotary_encoder.h
-examples/peripherals/pcnt/rotary_encoder/components/rotary_encoder/src/rotary_encoder_pcnt_ec11.c
-examples/peripherals/pcnt/rotary_encoder/main/rotary_encoder_example_main.c
 examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h
 examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_nec.c
 examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c