pcnt: added enable/disable functions

This commit is contained in:
morris 2022-04-24 15:11:23 +08:00
parent e7295c3577
commit d67888b92b
14 changed files with 340 additions and 111 deletions

View File

@ -86,7 +86,7 @@ typedef struct {
/**
* @brief Create a new PCNT unit, and return the handle
*
* @note The newly created PCNT unit is put into the stopped state.
* @note The newly created PCNT unit is put in the init state.
*
* @param[in] config PCNT unit configuration
* @param[out] ret_unit Returned PCNT unit handle
@ -102,13 +102,14 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
/**
* @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()`.
* @note A PCNT unit can't be in the enable state when this function is invoked.
* See also `pcnt_unit_disable()` for how to disable a unit.
*
* @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_ERR_INVALID_STATE: Delete the PCNT unit failed because the unit is not in init state or some PCNT channel is 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);
@ -118,29 +119,63 @@ esp_err_t pcnt_del_unit(pcnt_unit_handle_t 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()`.
* The PM lock can be uninstalled in `pcnt_del_unit()`.
* @note This function should be called when the PCNT unit is in the init state (i.e. before calling `pcnt_unit_enable()`)
*
* @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_ERR_INVALID_STATE: Set glitch filter failed because the unit is not in the init state
* - 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 Enable the PCNT unit
*
* @note This function will transit the unit state from init to enable.
* @note This function will enable the interrupt service, if it's lazy installed in `pcnt_unit_register_event_callbacks()`.
* @note This function will acquire the PM lock if it's lazy installed in `pcnt_unit_set_glitch_filter()`.
* @note Enable a PCNT unit doesn't mean to start it. See also `pcnt_unit_start()` for how to start the PCNT counter.
*
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
* @return
* - ESP_OK: Enable PCNT unit successfully
* - ESP_ERR_INVALID_ARG: Enable PCNT unit failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Enable PCNT unit failed because the unit is already enabled
* - ESP_FAIL: Enable PCNT unit failed because of other error
*/
esp_err_t pcnt_unit_enable(pcnt_unit_handle_t unit);
/**
* @brief Disable the PCNT unit
*
* @note This function will do the opposite work to the `pcnt_unit_enable()`
* @note Disable a PCNT unit doesn't mean to stop it. See also `pcnt_unit_stop()` for how to stop the PCNT counter.
*
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
* @return
* - ESP_OK: Disable PCNT unit successfully
* - ESP_ERR_INVALID_ARG: Disable PCNT unit failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Disable PCNT unit failed because the unit is not enabled yet
* - ESP_FAIL: Disable PCNT unit failed because of other error
*/
esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit);
/**
* @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 should be called when the unit is in the enable state (i.e. after calling `pcnt_unit_enable()`)
* @note This function is allowed to run within ISR context
* @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
* @note This function will be placed into IRAM if `CONFIG_PCNT_CTRL_FUNC_IN_IRAM` is on, 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_ERR_INVALID_STATE: Start PCNT unit failed because the unit is not enabled yet
* - ESP_FAIL: Start PCNT unit failed because of other error
*/
esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit);
@ -148,10 +183,8 @@ 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 This function should be called when the unit is in the enable state (i.e. after calling `pcnt_unit_enable()`)
* @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 is allowed to run within ISR context
* @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
*
@ -159,6 +192,7 @@ esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit);
* @return
* - ESP_OK: Stop PCNT unit successfully
* - ESP_ERR_INVALID_ARG: Stop PCNT unit failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Stop PCNT unit failed because the unit is not enabled yet
* - ESP_FAIL: Stop PCNT unit failed because of other error
*/
esp_err_t pcnt_unit_stop(pcnt_unit_handle_t unit);
@ -196,7 +230,8 @@ 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`.
* @note User registered callbacks are expected to be runnable within ISR context
* @note This function is only allowed to be called when the unit is in the init state (i.e. before calling `pcnt_unit_enable()`)
*
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
* @param[in] cbs Group of callback functions
@ -204,6 +239,7 @@ esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value);
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Set event callbacks failed because the unit is not in init state
* - 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);
@ -211,8 +247,6 @@ esp_err_t pcnt_unit_register_event_callbacks(pcnt_unit_handle_t unit, const pcnt
/**
* @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
@ -227,8 +261,6 @@ 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
@ -242,6 +274,8 @@ 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
*
* @note This function should be called when the unit is in init state (i.e. before calling `pcnt_unit_enable()`)
*
* @param[in] unit PCNT unit handle created by `pcnt_new_unit()`
* @param[in] config PCNT channel configuration
* @param[out] ret_chan Returned channel handle
@ -250,6 +284,7 @@ esp_err_t pcnt_unit_remove_watch_point(pcnt_unit_handle_t unit, int watch_point)
* - 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_ERR_INVALID_STATE: Create PCNT channel failed because the unit is not in the init state
* - 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);

View File

@ -72,9 +72,9 @@ typedef struct {
} pcnt_watch_point_t;
typedef enum {
PCNT_FSM_STOP,
PCNT_FSM_START,
} pcnt_lifecycle_fsm_t;
PCNT_UNIT_FSM_INIT,
PCNT_UNIT_FSM_ENABLE,
} pcnt_unit_fsm_t;
struct pcnt_unit_t {
pcnt_group_t *group; // which group the pcnt unit belongs to
@ -89,7 +89,7 @@ struct pcnt_unit_t {
#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_unit_fsm_t fsm; // record PCNT unit's driver state
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
};
@ -152,8 +152,6 @@ static void pcnt_unregister_from_group(pcnt_unit_t *unit)
static esp_err_t pcnt_destory(pcnt_unit_t *unit)
{
if (unit->pm_lock) {
// if pcnt_unit_start and pcnt_unit_stop are not called the same number of times, deleting pm_lock will return invalid state
// which in return also reflects an invalid state of PCNT driver
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(unit->pm_lock), TAG, "delete pm lock failed");
}
if (unit->intr) {
@ -206,7 +204,7 @@ esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *re
portEXIT_CRITICAL(&group->spinlock);
unit->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
unit->fsm = PCNT_FSM_STOP;
unit->fsm = PCNT_UNIT_FSM_INIT;
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
}
@ -224,23 +222,14 @@ err:
esp_err_t pcnt_del_unit(pcnt_unit_handle_t unit)
{
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
pcnt_group_t *group = unit->group;
int group_id = group->group_id;
int unit_id = unit->unit_id;
bool valid_state = true;
portENTER_CRITICAL(&unit->spinlock);
valid_state = unit->fsm == PCNT_FSM_STOP;
portEXIT_CRITICAL(&unit->spinlock);
ESP_RETURN_ON_FALSE(valid_state, ESP_ERR_INVALID_STATE, TAG, "can't delete unit as it's not stop yet");
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);
}
ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id);
// recycle memory resource
@ -253,6 +242,8 @@ esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch
pcnt_group_t *group = NULL;
uint32_t glitch_filter_thres = 0;
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// glitch filter should be set only when unit is in init state
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
group = unit->group;
if (config) {
glitch_filter_thres = esp_clk_apb_freq() / 1000000 * config->max_glitch_ns / 1000;
@ -281,57 +272,67 @@ esp_err_t pcnt_unit_set_glitch_filter(pcnt_unit_handle_t unit, const pcnt_glitch
return ESP_OK;
}
esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit)
esp_err_t pcnt_unit_enable(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;
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
// 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");
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(unit->pm_lock), TAG, "acquire pm_lock failed");
}
// enable interupt service
if (unit->intr) {
ESP_RETURN_ON_ERROR_ISR(esp_intr_enable(unit->intr), TAG, "enable interrupt service failed");
ESP_RETURN_ON_ERROR(esp_intr_enable(unit->intr), TAG, "enable interrupt service failed");
}
unit->fsm = PCNT_UNIT_FSM_ENABLE;
return ESP_OK;
}
esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit)
{
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(unit->fsm = PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not in enable state");
// disable interrupt service
if (unit->intr) {
ESP_RETURN_ON_ERROR(esp_intr_disable(unit->intr), TAG, "disable interrupt service failed");
}
// release power manager lock
if (unit->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_release(unit->pm_lock), TAG, "release APB_FREQ_MAX lock failed");
}
unit->fsm = PCNT_UNIT_FSM_INIT;
return ESP_OK;
}
esp_err_t pcnt_unit_start(pcnt_unit_handle_t unit)
{
ESP_RETURN_ON_FALSE_ISR(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE_ISR(unit->fsm == PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not enabled yet");
pcnt_group_t *group = unit->group;
// 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;
ESP_RETURN_ON_FALSE_ISR(unit->fsm == PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not enabled yet");
pcnt_group_t *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;
}
@ -361,9 +362,10 @@ esp_err_t pcnt_unit_get_count(pcnt_unit_handle_t unit, int *value)
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;
// unit event callbacks should be registered in init state
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
pcnt_group_t *group = unit->group;
int group_id = group->group_id;
int unit_id = unit->unit_id;
@ -520,6 +522,7 @@ esp_err_t pcnt_new_channel(pcnt_unit_handle_t unit, const pcnt_chan_config_t *co
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");
ESP_GOTO_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, err, TAG, "unit not in init state");
group = unit->group;
int group_id = group->group_id;
int unit_id = unit->unit_id;

View File

@ -422,10 +422,12 @@ static void setup_testbench(void)
TEST_ESP_OK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
TEST_ESP_OK(pcnt_unit_enable(pcnt_unit));
}
static void tear_testbench(void)
{
TEST_ESP_OK(pcnt_unit_disable(pcnt_unit));
TEST_ESP_OK(pcnt_del_channel(pcnt_chan));
TEST_ESP_OK(pcnt_del_unit(pcnt_unit));
}
@ -496,7 +498,7 @@ TEST_CASE("LEDC set and get frequency", "[ledc][timeout=60][ignore]")
}
static void timer_set_clk_src_and_freq_test(ledc_mode_t speed_mode, ledc_clk_cfg_t clk_src, uint32_t duty_res,
uint32_t freq_hz)
uint32_t freq_hz)
{
ledc_timer_config_t ledc_time_config = {
.speed_mode = speed_mode,

View File

@ -112,6 +112,7 @@ static void pcnt_setup_testbench(void)
TEST_ESP_OK(pcnt_new_channel(pcnt_unit_a, &chan_a_config, &pcnt_chan_a));
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan_a, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan_a, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
TEST_ESP_OK(pcnt_unit_enable(pcnt_unit_a));
// PWMB <--> PCNT UNIT1
pcnt_unit_config_t unit_b_config = {
@ -126,10 +127,13 @@ static void pcnt_setup_testbench(void)
TEST_ESP_OK(pcnt_new_channel(pcnt_unit_b, &chan_b_config, &pcnt_chan_b));
TEST_ESP_OK(pcnt_channel_set_edge_action(pcnt_chan_b, PCNT_CHANNEL_EDGE_ACTION_INCREASE, PCNT_CHANNEL_EDGE_ACTION_HOLD));
TEST_ESP_OK(pcnt_channel_set_level_action(pcnt_chan_b, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
TEST_ESP_OK(pcnt_unit_enable(pcnt_unit_b));
}
static void pcnt_tear_testbench(void)
{
TEST_ESP_OK(pcnt_unit_disable(pcnt_unit_a));
TEST_ESP_OK(pcnt_unit_disable(pcnt_unit_b));
TEST_ESP_OK(pcnt_del_channel(pcnt_chan_a));
TEST_ESP_OK(pcnt_del_channel(pcnt_chan_b));
TEST_ESP_OK(pcnt_del_unit(pcnt_unit_a));

View File

@ -1,5 +1,7 @@
set(srcs "test_app_main.c"
"test_pulse_cnt.c")
"test_pulse_cnt_simulator.c"
"test_pulse_cnt.c"
"test_pulse_cnt_iram.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE

View File

@ -13,43 +13,7 @@
#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);
}
}
#include "test_pulse_cnt_board.h"
TEST_CASE("pcnt_unit_install_uninstall", "[pcnt]")
{
@ -80,18 +44,28 @@ TEST_CASE("pcnt_unit_install_uninstall", "[pcnt]")
filter_config.max_glitch_ns = 500000;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, pcnt_unit_set_glitch_filter(units[0], &filter_config));
printf("enable pcnt units\r\n");
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
TEST_ESP_OK(pcnt_unit_enable(units[i]));
}
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]));
}
// can't uninstall unit before disable it
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_del_unit(units[0]));
printf("disable pcnt units\r\n");
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
TEST_ESP_OK(pcnt_unit_disable(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]));
@ -125,6 +99,7 @@ TEST_CASE("pcnt_channel_install_uninstall", "[pcnt]")
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]));
TEST_ESP_OK(pcnt_unit_enable(units[i]));
}
printf("start units\r\n");
@ -174,6 +149,7 @@ TEST_CASE("pcnt_channel_install_uninstall", "[pcnt]")
for (int i = 0; i < SOC_PCNT_UNITS_PER_GROUP; i++) {
// stop unit
TEST_ESP_OK(pcnt_unit_stop(units[i]));
TEST_ESP_OK(pcnt_unit_disable(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++) {
@ -257,6 +233,9 @@ TEST_CASE("pcnt_quadrature_decode_event", "[pcnt]")
// Clear internal counter, and make the watch points take effect
TEST_ESP_OK(pcnt_unit_clear_count(unit));
// start unit should fail if it's not enabled yet
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, pcnt_unit_start(unit));
TEST_ESP_OK(pcnt_unit_enable(unit));
TEST_ESP_OK(pcnt_unit_start(unit));
printf("simulating quadrature signals\r\n");
@ -300,6 +279,7 @@ TEST_CASE("pcnt_quadrature_decode_event", "[pcnt]")
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_unit_disable(unit));
TEST_ESP_OK(pcnt_del_unit(unit));
}
@ -354,6 +334,9 @@ TEST_CASE("pcnt_zero_cross_mode", "[pcnt]")
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("enable unit\r\n");
TEST_ESP_OK(pcnt_unit_enable(unit));
printf("start unit\r\n");
TEST_ESP_OK(pcnt_unit_start(unit));
@ -397,6 +380,7 @@ TEST_CASE("pcnt_zero_cross_mode", "[pcnt]")
TEST_ASSERT_EQUAL(PCNT_UNIT_ZERO_CROSS_NEG_ZERO, user_data.mode);
TEST_ESP_OK(pcnt_unit_stop(unit));
TEST_ESP_OK(pcnt_unit_disable(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));

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#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
void test_gpio_simulate_rising_edge(int gpio_sig, size_t times);
void test_gpio_simulate_quadrature_signals(int gpio_sig_a, int gpio_sig_b, size_t times);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,104 @@
/*
* 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 "freertos/semphr.h"
#include "unity.h"
#include "driver/pulse_cnt.h"
#include "driver/gpio.h"
#include "esp_spi_flash.h"
#include "esp_attr.h"
#include "soc/soc_caps.h"
#include "test_pulse_cnt_board.h"
#if CONFIG_PCNT_ISR_IRAM_SAFE
static bool IRAM_ATTR test_pcnt_iram_safe_callback(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *event_data, void *user_data)
{
uint32_t *data = (uint32_t *)user_data;
(*data)++;
// PCNT control function can still work in ISR
pcnt_unit_stop(unit);
return false;
}
static void IRAM_ATTR test_pcnt_iram_simulation(int gpio_sig)
{
// disable flash cache
spi_flash_guard_get()->start();
test_gpio_simulate_rising_edge(gpio_sig, 2);
// enable flash cache
spi_flash_guard_get()->end();
}
TEST_CASE("pcnt_iram_interrupt_safe", "[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 watch point and event callback\r\n");
TEST_ESP_OK(pcnt_unit_add_watch_point(unit, 2));
pcnt_event_callbacks_t cbs = {
.on_reach = test_pcnt_iram_safe_callback,
};
uint32_t num_of_event_triggered = 0;
TEST_ESP_OK(pcnt_unit_register_event_callbacks(unit, &cbs, &num_of_event_triggered));
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("enable unit\r\n");
TEST_ESP_OK(pcnt_unit_enable(unit));
printf("Set pcnt actions for channels\r\n");
// both channels increase on pulse edge
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_level_action(channelA, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_KEEP));
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_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));
printf("disable cache and check interrupt triggered\r\n");
TEST_ESP_OK(pcnt_unit_clear_count(unit));
// the function that will disable the flash must be placed in the IRAM
test_pcnt_iram_simulation(TEST_PCNT_GPIO_A);
// check if the interrupt has fired up
TEST_ASSERT_EQUAL(1, num_of_event_triggered);
printf("check current count value\r\n");
int cur_count = 0;
TEST_ESP_OK(pcnt_unit_get_count(unit, &cur_count));
// when the watch point got triggered, we disabled the PCNT unit in thr `test_pcnt_iram_safe_callback()`
// so the finally count value should equal to the watch point value
TEST_ASSERT_EQUAL(2, cur_count);
printf("delete channels and unit\r\n");
TEST_ESP_OK(pcnt_unit_disable(unit));
TEST_ESP_OK(pcnt_del_channel(channelA));
TEST_ESP_OK(pcnt_del_channel(channelB));
TEST_ESP_OK(pcnt_del_unit(unit));
}
#endif // CONFIG_PCNT_ISR_IRAM_SAFE

View File

@ -0,0 +1,41 @@
/*
* 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 "driver/gpio.h"
#include "esp_attr.h"
#include "test_pulse_cnt_board.h"
// helper function to simulate several rising edges on gpio
IRAM_ATTR void test_gpio_simulate_rising_edge(int gpio_sig, size_t times)
{
while (times--) {
gpio_set_level(gpio_sig, 0);
gpio_set_level(gpio_sig, 1);
}
}
// helper function to simulate several groups of quadrature signals
IRAM_ATTR void test_gpio_simulate_quadrature_signals(int gpio_sig_a, int gpio_sig_b, size_t times)
{
while (times--) {
gpio_set_level(gpio_sig_a, 1);
gpio_set_level(gpio_sig_b, 0);
vTaskDelay(1);
gpio_set_level(gpio_sig_a, 0);
gpio_set_level(gpio_sig_b, 0);
vTaskDelay(1);
gpio_set_level(gpio_sig_a, 0);
gpio_set_level(gpio_sig_b, 1);
vTaskDelay(1);
gpio_set_level(gpio_sig_a, 1);
gpio_set_level(gpio_sig_b, 1);
vTaskDelay(1);
}
}

View File

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

View File

@ -25,6 +25,8 @@ Description of the PCNT functionality is divided into into the following section
- `Set Up Channel Actions <#set-up-channel-actions>`__ - covers how to configure the PCNT channel to behave on different signal edges and levels.
- `Watch Points <#watch-points>`__ - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value).
- `Register Event Callbacks <#register-event-callbacks>`__ - describes how to hook user specific code to the watch point event callback function.
- `Set Glitch Filter <#set-glitch-filter>`__ - describes how to enable and set the timing parameters for the internal glitch filter.
- `Enable and Disable Unit <#enable-and-disable-unit>`__ - describes how to enable and disable the PCNT unit.
- `Unit IO Control <#unit-io-control>`__ - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
- `Power Management <#power-management>`__ - describes what functionality will prevent the chip from going into low power mode.
- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
@ -47,8 +49,8 @@ Unit allocation and initialization is done by calling a function :cpp:func:`pcnt
If a previously created PCNT unit is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`pcnt_del_unit`. Which in return allows the underlying unit hardware to be used for other purposes. Before deleting a PCNT unit, one should ensure the following prerequisites:
- The unit is in a stop state, in other words, the unit either is stopped by :cpp:func:`pcnt_unit_stop` or not started yet.
- The unit ought to stop watching any "watch point". See `Watch Points <#watch-points>`__ for how to removing a watch point.
- The unit is in the init state, in other words, the unit is either disabled by :cpp:func:`pcnt_unit_disable` or not enabled yet.
- The attached PCNT channels are all removed by :cpp:func:`pcnt_del_channel`.
.. code:: c
@ -71,7 +73,7 @@ To install a PCNT channel, there's a configuration structure that needs to be gi
- :cpp:member:`pcnt_chan_config_t::invert_edge_input` and :cpp:member:`pcnt_chan_config_t::invert_level_input` are used to decide whether to invert the input signals before they going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware.
- :cpp:member:`pcnt_chan_config_t::io_loop_back` is for debug only, which enables both the GPIO's input and output paths. This can help to simulate the pulse signals by function :cpp:func:`gpio_set_level` on the same GPIO.
Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e. channel resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT channels within the unit is recorded by :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT` for reference.
Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e. channel resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. The total number of available PCNT channels within the unit is recorded by :c:macro:`SOC_PCNT_CHANNELS_PER_UNIT` for reference. Note that, when install a PCNT channel for a specific unit, one should ensure the unit is in the init state, otherwise this function will return :c:macro:`ESP_ERR_INVALID_STATE` error.
If a previously created PCNT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`pcnt_del_channel`. Which in return allows the underlying channel hardware to be used for other purposes.
@ -109,7 +111,7 @@ Each PCNT unit can be configured to watch several different values that you're i
The watch point can be added and removed by :cpp:func:`pcnt_unit_add_watch_point` and :cpp:func:`pcnt_unit_remove_watch_point`. The commonly used watch points are: **zero cross**, **maximum / minimum count** and other threshold values. The number of available watch point is limited, :cpp:func:`pcnt_unit_add_watch_point` will return error :c:macro:`ESP_ERR_NOT_FOUND` if it can't find any free hardware resource to save the watch point. User can't add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`.
It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remove_watch_point` to recycle the watch point resources. Be careful when you recycle the PCNT unit by :cpp:func:`pcnt_del_unit`, the using watch points must be removed from the unit in advance.
It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remove_watch_point` to recycle the watch point resources.
.. code:: c
@ -132,6 +134,8 @@ In the callback function, the driver will fill in the event data of specific eve
- :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 will result 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.
.. code:: c
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
@ -150,11 +154,8 @@ In the callback function, the driver will fill in the event data of specific eve
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
Unit IO Control
^^^^^^^^^^^^^^^
Set Glitch Filter
~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^
The PCNT unit features filters to ignore possible short glitches in the signals. The parameters that can be configured for the glitch filter are listed in :cpp:type:`pcnt_glitch_filter_config_t`:
@ -162,6 +163,8 @@ The PCNT unit features filters to ignore possible short glitches in the signals.
User can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_set_glitch_filter` with the filter configuration provided above. Particularly, user can disable the glitch filter later by calling :cpp:func:`pcnt_unit_set_glitch_filter` with a `NULL` filter configuration.
This function should be called when the the unit is in the init state. Otherwise, it will return :c:macro:`ESP_ERR_INVALID_STATE` error.
.. note::
The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time user enables the glitch filter. For more information related to power management strategy used in PCNT driver, please see `Power Management <#power-management>`__.
@ -173,11 +176,31 @@ User can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
Enable and Disable Unit
^^^^^^^^^^^^^^^^^^^^^^^
Before doing IO control to the PCNT unit, user needs to enable it first, by calling :cpp:func:`pcnt_unit_enable`. Internally, this function will:
* switch the PCNT driver state from **init** to **enable**.
* enable the interrupt service if it has been lazy installed in :cpp:func:`pcnt_unit_register_event_callbacks`.
* acquire a proper power management lock if it has been lazy installed in :cpp:func:`pcnt_unit_set_glitch_filter`. See also `Power management <#power-management>`__ for more information.
On the contrary, calling :cpp:func:`pcnt_unit_disable` will do the opposite, that is, put the PCNT driver back to the **init** state, disable the interrupts service and release the power management lock.
.. code::c
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
Unit IO Control
^^^^^^^^^^^^^^^
Start/Stop and Clear
~~~~~~~~~~~~~~~~~~~~
Calling :cpp:func:`pcnt_unit_start` will make the PCNT unit start to work, increase or decrease counter according to pulse signals. On the contrary, calling :cpp:func:`pcnt_unit_stop` will stop the PCNT unit but retain current count value. Instead, clearing counter can only be done by calling :cpp:func:`pcnt_unit_clear_count`.
Note, :cpp:func:`pcnt_unit_start` and :cpp:func:`pcnt_unit_stop` should be called when the unit has been enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it will return :c:macro:`ESP_ERR_INVALID_STATE` error.
.. code::c
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
@ -202,7 +225,7 @@ Power Management
When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the behavior of PCNT glitch filter and leading to valid signal being treated as noise.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user enables the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is started by :cpp:func:`pcnt_unit_start`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_stop` is called. This requires that the :cpp:func:`pcnt_unit_start` and :cpp:func:`pcnt_unit_stop` should appear in pairs, otherwise the power management will be out of action.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user enables the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is enabled by :cpp:func:`pcnt_unit_enable`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_disable` is called.
IRAM Safe
^^^^^^^^^
@ -212,9 +235,7 @@ By default, the PCNT interrupt will be deferred when the Cache is disabled for r
There's a Kconfig option :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` that will:
1. Enable the interrupt being serviced even when cache is disabled
2. Place all functions that used by the ISR into IRAM [2]_
3. Place driver object into DRAM (in case it's mapped to PSRAM by accident)
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.

View File

@ -207,6 +207,7 @@ void app_main(void)
.on_reach = example_pcnt_on_reach,
};
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &pcnt_cbs, &my_timer_ctx));
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));

View File

@ -74,6 +74,8 @@ void app_main(void)
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
ESP_LOGI(TAG, "enable pcnt unit");
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
ESP_LOGI(TAG, "clear pcnt unit");
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
ESP_LOGI(TAG, "start pcnt unit");

View File

@ -83,6 +83,8 @@ void ref_clock_init(void)
.on_reach = on_reach_watch_point,
};
TEST_ESP_OK(pcnt_unit_register_event_callbacks(s_pcnt_unit, &cbs, NULL));
// enable pcnt
TEST_ESP_OK(pcnt_unit_enable(s_pcnt_unit));
// start pcnt
TEST_ESP_OK(pcnt_unit_start(s_pcnt_unit));
@ -121,6 +123,7 @@ void ref_clock_deinit(void)
{
// Deinitialize PCNT
TEST_ESP_OK(pcnt_unit_stop(s_pcnt_unit));
TEST_ESP_OK(pcnt_unit_disable(s_pcnt_unit));
TEST_ESP_OK(pcnt_unit_remove_watch_point(s_pcnt_unit, REF_CLOCK_PRESCALER_MS * 1000));
TEST_ESP_OK(pcnt_del_channel(s_pcnt_chan));
TEST_ESP_OK(pcnt_del_unit(s_pcnt_unit));