From 03e936041d6e05aa1dc28aab5f72c4a3abced625 Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Tue, 25 Jun 2024 20:44:17 +0800 Subject: [PATCH] refactor(pcnt): refactor the default isr --- components/esp_driver_pcnt/src/pulse_cnt.c | 74 +++++++++++++------ .../test_apps/pulse_cnt/main/test_pulse_cnt.c | 15 ++-- docs/en/api-reference/peripherals/pcnt.rst | 28 +++---- docs/zh_CN/api-reference/peripherals/pcnt.rst | 26 +++---- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/components/esp_driver_pcnt/src/pulse_cnt.c b/components/esp_driver_pcnt/src/pulse_cnt.c index 2d4b22231b..e25dafc00b 100644 --- a/components/esp_driver_pcnt/src/pulse_cnt.c +++ b/components/esp_driver_pcnt/src/pulse_cnt.c @@ -893,35 +893,69 @@ IRAM_ATTR static void pcnt_default_isr(void *args) 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)); - // points watcher event + // watcher event uint32_t event_status = pcnt_ll_get_event_status(group->hal.dev, unit_id); + + // use flags to avoid multiple callbacks in one point + bool is_limit_event __attribute__((unused)) = false; + bool is_step_event = false; + // 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 - - portENTER_CRITICAL_ISR(&unit->spinlock); - if (unit->flags.accum_count) { - if (event_id == PCNT_LL_WATCH_EVENT_LOW_LIMIT) { + int watch_value = pcnt_ll_get_count(group->hal.dev, unit_id); + if (event_status & BIT(PCNT_LL_WATCH_EVENT_LOW_LIMIT)) { + event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_LOW_LIMIT)); + is_limit_event = true; + if (unit->flags.accum_count) { + portENTER_CRITICAL_ISR(&unit->spinlock); unit->accum_value += unit->low_limit; - } else if (event_id == PCNT_LL_WATCH_EVENT_HIGH_LIMIT) { + portEXIT_CRITICAL_ISR(&unit->spinlock); + } + watch_value = unit->low_limit; + } else if (event_status & BIT(PCNT_LL_WATCH_EVENT_HIGH_LIMIT)) { + event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_HIGH_LIMIT)); + is_limit_event = true; + if (unit->flags.accum_count) { + portENTER_CRITICAL_ISR(&unit->spinlock); unit->accum_value += unit->high_limit; + portEXIT_CRITICAL_ISR(&unit->spinlock); + } + watch_value = unit->high_limit; + } +#if SOC_PCNT_SUPPORT_STEP_NOTIFY + else if (event_status & BIT(PCNT_LL_STEP_EVENT_REACH_LIMIT)) { + event_status &= ~(BIT(PCNT_LL_STEP_EVENT_REACH_LIMIT)); + if (is_limit_event) { + continue; + } else if (unit->flags.accum_count) { + portENTER_CRITICAL_ISR(&unit->spinlock); + unit->accum_value += unit->step_limit; + portEXIT_CRITICAL_ISR(&unit->spinlock); + } + watch_value = unit->step_limit; + } else if (event_status & BIT(PCNT_LL_STEP_EVENT_REACH_INTERVAL)) { + event_status &= ~(BIT(PCNT_LL_STEP_EVENT_REACH_INTERVAL)); + is_step_event = true; + } +#endif //SOC_PCNT_SUPPORT_STEP_NOTIFY + else if (event_status & BIT(PCNT_LL_WATCH_EVENT_ZERO_CROSS)) { + event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_ZERO_CROSS)); + } else if (event_status & BIT(PCNT_LL_WATCH_EVENT_THRES0)) { + event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_THRES0)); + if (is_step_event) { + continue; + } + } else if (event_status & BIT(PCNT_LL_WATCH_EVENT_THRES1)) { + event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_THRES1)); + if (is_step_event) { + continue; } -#if SOC_PCNT_SUPPORT_STEP_NOTIFY - // zero cross event priority is higher than step limit event, ensure to accumulate the value when the zero cross is caused by step limit - if ((event_id == PCNT_LL_WATCH_EVENT_ZERO_CROSS) && (event_status & 1 << PCNT_LL_STEP_EVENT_REACH_LIMIT)) { - unit->accum_value += unit->step_limit; - } else if (event_id == PCNT_LL_STEP_EVENT_REACH_LIMIT) { - unit->accum_value += unit->step_limit; - } -#endif } - portEXIT_CRITICAL_ISR(&unit->spinlock); // invoked user registered callback if (on_reach) { pcnt_watch_event_data_t edata = { - .watch_point_value = event_id < PCNT_LL_WATCH_EVENT_MAX ? unit->watchers[event_id].watch_point_value : pcnt_ll_get_count(group->hal.dev, unit_id), + .watch_point_value = watch_value, .zero_cross_mode = pcnt_ll_get_zero_cross_mode(group->hal.dev, unit_id), }; if (on_reach(unit, &edata, unit->user_data)) { @@ -929,10 +963,6 @@ IRAM_ATTR static void pcnt_default_isr(void *args) need_yield = true; } } -#if SOC_PCNT_SUPPORT_STEP_NOTIFY - // The priority of step and step limit event is lowest. Clear the step and step limit event to ensure that in a particular point, event can only be triggered once - event_status &= ~(1 << PCNT_LL_STEP_EVENT_REACH_INTERVAL | 1 << PCNT_LL_STEP_EVENT_REACH_LIMIT); -#endif } } if (need_yield) { diff --git a/components/esp_driver_pcnt/test_apps/pulse_cnt/main/test_pulse_cnt.c b/components/esp_driver_pcnt/test_apps/pulse_cnt/main/test_pulse_cnt.c index 4fcdbeaafa..e60e15db02 100644 --- a/components/esp_driver_pcnt/test_apps/pulse_cnt/main/test_pulse_cnt.c +++ b/components/esp_driver_pcnt/test_apps/pulse_cnt/main/test_pulse_cnt.c @@ -629,7 +629,7 @@ TEST_CASE("pcnt_step_notify_event", "[pcnt]") TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, 20)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, -120)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, -30)); - TEST_ESP_OK(pcnt_unit_add_watch_step(unit, -50)); + TEST_ESP_OK(pcnt_unit_add_watch_step(unit, -25)); TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_add_watch_step(unit, -100)); TEST_ESP_OK(pcnt_unit_add_watch_point(unit, -100)); TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0)); @@ -654,11 +654,14 @@ TEST_CASE("pcnt_step_notify_event", "[pcnt]") printf("%d:%d\r\n", i, user_data.triggered_watch_values[i]); } TEST_ASSERT_EQUAL(-150, count_value); - TEST_ASSERT_EQUAL(4, user_data.index); - TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[0]); - TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[1]); - TEST_ASSERT_EQUAL(-0, user_data.triggered_watch_values[2]); - TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[3]); + TEST_ASSERT_EQUAL(7, user_data.index); + TEST_ASSERT_EQUAL(-25, user_data.triggered_watch_values[0]); + TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[1]); + TEST_ASSERT_EQUAL(-75, user_data.triggered_watch_values[2]); + TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[3]); + TEST_ASSERT_EQUAL(-0, user_data.triggered_watch_values[4]); + TEST_ASSERT_EQUAL(-25, user_data.triggered_watch_values[5]); + TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[6]); printf("add a new step interval\r\n"); TEST_ESP_OK(pcnt_unit_remove_watch_step(unit)); diff --git a/docs/en/api-reference/peripherals/pcnt.rst b/docs/en/api-reference/peripherals/pcnt.rst index a91693eb33..99e44fe7ba 100644 --- a/docs/en/api-reference/peripherals/pcnt.rst +++ b/docs/en/api-reference/peripherals/pcnt.rst @@ -153,7 +153,7 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov Watch Step ^^^^^^^^^^^ - PCNT unit can be configured to watch a specific value increment(can be positive or negative) that you are interested in. The function of watching value increment is also called **Watch Step**. To install watch step requires enabling :cpp:member:`pcnt_unit_config_t::en_step_notify_up` or :cpp:member:`pcnt_unit_config_t::en_step_notify_down`. The step interval itself can not exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`.When the counter increment reaches step interval, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`pcnt-register-event-callbacks` for how to register event callbacks. + PCNT unit can be configured to watch a specific value increment (can be positive or negative) that you are interested in. The function of watching value increment is also called **Watch Step**. To install watch step requires enabling :cpp:member:`pcnt_unit_config_t::en_step_notify_up` or :cpp:member:`pcnt_unit_config_t::en_step_notify_down`. The step interval itself can not exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`.When the counter increment reaches step interval, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`pcnt-register-event-callbacks` for how to register event callbacks. The watch step can be added and removed by :cpp:func:`pcnt_unit_add_watch_step` and :cpp:func:`pcnt_unit_remove_watch_step`. You can not add multiple watch step, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`。 @@ -161,7 +161,7 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov .. note:: - When a watch step and a watch point are triggered at the same time, only one interrupt event will be generated. + When a watch step and a watch point are triggered at the same time (i.e. at the same absolute point), the callback function only gets called by once. The step interval must be a divisor of :cpp:member:`pcnt_unit_config_t::low_limit` or :cpp:member:`pcnt_unit_config_t::high_limit`. .. code:: c @@ -186,18 +186,10 @@ When PCNT unit reaches any enabled watch point, specific event will be generated You can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions. -.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY +In the callback function, the driver will fill in the event data of specific event. For example, the watch point event or watch step event data is declared as :cpp:type:`pcnt_watch_event_data_t`: - In the callback function, the driver will fill in the event data of specific event. For example, the watch point event or watch step event data is declared as :cpp:type:`pcnt_watch_event_data_t`: - -.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY - - In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`: - -.. list:: - :SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value or watch step value that triggers the event. - :not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value that triggers the event. - - :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**. +- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the count value when the event triggered. +- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**. Registering callback function results in lazy installation of interrupt service, thus this function should only be called before the unit is enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it can return :c:macro:`ESP_ERR_INVALID_STATE` error. @@ -320,16 +312,16 @@ Compensate Overflow Loss The internal hardware counter will be cleared to zero automatically when it reaches high or low limit. If you want to compensate for that count loss and extend the counter's bit-width, you can: +.. list:: + 1. Enable :cpp:member:`pcnt_unit_config_t::accum_count` when installing the PCNT unit. - 2. Add the high/low limit as the :ref:`pcnt-watch-points`. + :SOC_PCNT_SUPPORT_STEP_NOTIFY: 2. Add the high/low limit as the :ref:`pcnt-watch-points` or add watch step as the :ref:`pcnt-step-notify`. + :not SOC_PCNT_SUPPORT_STEP_NOTIFY: 2. Add the high/low limit as the :ref:`pcnt-watch-points`. 3. Now, the returned count value from the :cpp:func:`pcnt_unit_get_count` function not only reflects the hardware's count value, but also accumulates the high/low overflow loss to it. .. note:: - .. list:: - - - :cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well. - :SOC_PCNT_SUPPORT_STEP_NOTIFY: - setting the watch step will also enable the accumulator. + :cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well. .. _pcnt-power-management: diff --git a/docs/zh_CN/api-reference/peripherals/pcnt.rst b/docs/zh_CN/api-reference/peripherals/pcnt.rst index 4876b37bd0..175010e404 100644 --- a/docs/zh_CN/api-reference/peripherals/pcnt.rst +++ b/docs/zh_CN/api-reference/peripherals/pcnt.rst @@ -161,7 +161,7 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值 .. note:: - 当观察步进和观察点同时被触发时,只会产生一次中断事件。 + 当观察步进和观察点同时被触发时,回调函数只会被调用一次。 步进间隔必须是 :cpp:member:`pcnt_unit_config_t::low_limit` 或 :cpp:member:`pcnt_unit_config_t::high_limit` 的因数。 .. code:: c @@ -186,18 +186,10 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值 可通过 ``user_ctx`` 将函数上下文保存到 :cpp:func:`pcnt_unit_register_event_callbacks` 中,这些数据会直接传递给回调函数。 -.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY +驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件或观察步进事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`: - 驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件或观察步进事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`: - -.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY - - 驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`: - -.. list:: - :SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点或观察步进的数值。 - :not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点数值。 - - :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向** 和 **计数步长**。 +- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发事件时计数器的数值。 +- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向** 和 **计数步长**。 注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`ESP_ERR_INVALID_STATE`。 @@ -320,16 +312,16 @@ PCNT 单元的滤波器可滤除信号中的短时毛刺,:cpp:type:`pcnt_glitc PCNT 内部的硬件计数器会在计数达到高/低门限的时候自动清零。如果你想补偿该计数值的溢出损失,以期进一步拓宽计数器的实际位宽,你可以: +.. list:: + 1. 在安装 PCNT 计数单元的时候使能 :cpp:member:`pcnt_unit_config_t::accum_count` 选项。 - 2. 将高/低计数门限设置为 :ref:`pcnt-watch-points`. + :SOC_PCNT_SUPPORT_STEP_NOTIFY: 2. 将高/低计数门限设置为 :ref:`pcnt-watch-points` 或添加观察步进 :ref:`pcnt-step-notify` + :not SOC_PCNT_SUPPORT_STEP_NOTIFY: 2. 将高/低计数门限设置为 :ref:`pcnt-watch-points`。 3. 现在,:cpp:func:`pcnt_unit_get_count` 函数返回的计数值就会包含硬件计数器当前的计数值,累加上计数器溢出造成的损失。 .. note:: - .. list:: - - - :cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。 - :SOC_PCNT_SUPPORT_STEP_NOTIFY: - 设置观察步进后,同时也会启用软件累加器。 + :cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。 .. _pcnt-power-management: