refactor(pcnt): refactor the default isr

This commit is contained in:
Chen Jichang 2024-06-25 20:44:17 +08:00
parent d81546628a
commit 03e936041d
4 changed files with 80 additions and 63 deletions

View File

@ -893,35 +893,69 @@ IRAM_ATTR static void pcnt_default_isr(void *args)
if (intr_status & PCNT_LL_UNIT_WATCH_EVENT(unit_id)) { 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)); 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); 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 // iter on each event_id
while (event_status) { while (event_status) {
int event_id = __builtin_ffs(event_status) - 1; int watch_value = pcnt_ll_get_count(group->hal.dev, unit_id);
event_status &= (event_status - 1); // clear the right most bit if (event_status & BIT(PCNT_LL_WATCH_EVENT_LOW_LIMIT)) {
event_status &= ~(BIT(PCNT_LL_WATCH_EVENT_LOW_LIMIT));
portENTER_CRITICAL_ISR(&unit->spinlock); is_limit_event = true;
if (unit->flags.accum_count) { if (unit->flags.accum_count) {
if (event_id == PCNT_LL_WATCH_EVENT_LOW_LIMIT) { portENTER_CRITICAL_ISR(&unit->spinlock);
unit->accum_value += unit->low_limit; 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; 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 // invoked user registered callback
if (on_reach) { if (on_reach) {
pcnt_watch_event_data_t edata = { 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), .zero_cross_mode = pcnt_ll_get_zero_cross_mode(group->hal.dev, unit_id),
}; };
if (on_reach(unit, &edata, unit->user_data)) { if (on_reach(unit, &edata, unit->user_data)) {
@ -929,10 +963,6 @@ IRAM_ATTR static void pcnt_default_isr(void *args)
need_yield = true; 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) { if (need_yield) {

View File

@ -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, 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, -120));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_add_watch_step(unit, -30)); 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_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, -100));
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 0)); 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]); printf("%d:%d\r\n", i, user_data.triggered_watch_values[i]);
} }
TEST_ASSERT_EQUAL(-150, count_value); TEST_ASSERT_EQUAL(-150, count_value);
TEST_ASSERT_EQUAL(4, user_data.index); TEST_ASSERT_EQUAL(7, user_data.index);
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[0]); TEST_ASSERT_EQUAL(-25, user_data.triggered_watch_values[0]);
TEST_ASSERT_EQUAL(-100, user_data.triggered_watch_values[1]); TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[1]);
TEST_ASSERT_EQUAL(-0, user_data.triggered_watch_values[2]); TEST_ASSERT_EQUAL(-75, user_data.triggered_watch_values[2]);
TEST_ASSERT_EQUAL(-50, user_data.triggered_watch_values[3]); 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"); printf("add a new step interval\r\n");
TEST_ESP_OK(pcnt_unit_remove_watch_step(unit)); TEST_ESP_OK(pcnt_unit_remove_watch_step(unit));

View File

@ -153,7 +153,7 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov
Watch Step 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` 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:: .. 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`. 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 .. 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. 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`: - :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**.
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
.. list::
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value or watch step value that triggers the event.
:not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the watch point value that triggers the event.
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**.
Registering callback function results in lazy installation of interrupt service, thus this function should only be called before the unit is enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it can return :c:macro:`ESP_ERR_INVALID_STATE` error. 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: 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. 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. 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:: .. note::
.. list:: :cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well.
- :cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well.
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - setting the watch step will also enable the accumulator.
.. _pcnt-power-management: .. _pcnt-power-management:

View File

@ -161,7 +161,7 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值
.. note:: .. note::
当观察步进和观察点同时被触发时,只会产生一次中断事件 当观察步进和观察点同时被触发时,回调函数只会被调用一次
步进间隔必须是 :cpp:member:`pcnt_unit_config_t::low_limit`:cpp:member:`pcnt_unit_config_t::high_limit` 的因数。 步进间隔必须是 :cpp:member:`pcnt_unit_config_t::low_limit`:cpp:member:`pcnt_unit_config_t::high_limit` 的因数。
.. code:: c .. code:: c
@ -186,18 +186,10 @@ PCNT 单元可被设置为观察几个特定的数值,这些被观察的数值
可通过 ``user_ctx`` 将函数上下文保存到 :cpp:func:`pcnt_unit_register_event_callbacks` 中,这些数据会直接传递给回调函数。 可通过 ``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` - :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` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向****计数步长**
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
驱动程序会将特定事件的数据写入回调函数中,例如,观察点事件数据被声明为 :cpp:type:`pcnt_watch_event_data_t`
.. list::
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点或观察步进的数值。
:not SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_watch_event_data_t::watch_point_value` 用于保存触发该事件的观察点数值。
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` 用于保存上一次 PCNT 单元的过零模式,:cpp:type:`pcnt_unit_zero_cross_mode_t` 中列出了所有可能的过零模式。通常,不同的过零模式意味着不同的 **计数方向****计数步长**
注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`ESP_ERR_INVALID_STATE` 注册回调函数会导致中断服务延迟安装,因此回调函数只能在 PCNT 单元被 :cpp:func:`pcnt_unit_enable` 使能之前调用。否则,回调函数会返回错误 :c:macro:`ESP_ERR_INVALID_STATE`
@ -320,16 +312,16 @@ PCNT 单元的滤波器可滤除信号中的短时毛刺,:cpp:type:`pcnt_glitc
PCNT 内部的硬件计数器会在计数达到高/低门限的时候自动清零。如果你想补偿该计数值的溢出损失,以期进一步拓宽计数器的实际位宽,你可以: PCNT 内部的硬件计数器会在计数达到高/低门限的时候自动清零。如果你想补偿该计数值的溢出损失,以期进一步拓宽计数器的实际位宽,你可以:
.. list::
1. 在安装 PCNT 计数单元的时候使能 :cpp:member:`pcnt_unit_config_t::accum_count` 选项。 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` 函数返回的计数值就会包含硬件计数器当前的计数值,累加上计数器溢出造成的损失。 3. 现在,:cpp:func:`pcnt_unit_get_count` 函数返回的计数值就会包含硬件计数器当前的计数值,累加上计数器溢出造成的损失。
.. note:: .. note::
.. list:: :cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。
- :cpp:func:`pcnt_unit_clear_count` 会复位该软件累加器。
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - 设置观察步进后,同时也会启用软件累加器。
.. _pcnt-power-management: .. _pcnt-power-management: