mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
7638235311
In esp32p4, the MCPWM has a new feature, mainly ETM-oriented. Each operator has two event comparators, can generate an event when the count value of the timer that operator connects is equal to the value of event comparator.
854 lines
34 KiB
C
854 lines
34 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2021-2023 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 "soc/gpio_pins.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"
|
|
#include "esp_memory_utils.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 || CONFIG_PCNT_CTRL_FUNC_IN_IRAM
|
|
#define PCNT_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
|
#else
|
|
#define PCNT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
|
#endif
|
|
|
|
#if CONFIG_PCNT_ISR_IRAM_SAFE
|
|
#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
|
#else
|
|
#define PCNT_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED)
|
|
#endif
|
|
|
|
#define PCNT_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED
|
|
|
|
#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
|
|
int intr_priority; // PCNT interrupt priority
|
|
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_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
|
|
portMUX_TYPE spinlock; // Spinlock, stop one unit from accessing different parts of a same register concurrently
|
|
int unit_id; // allocated unit numerical ID
|
|
int low_limit; // low limit value
|
|
int high_limit; // high limit value
|
|
int clear_signal_gpio_num; // which gpio clear signal input
|
|
int accum_value; // accumulated count 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_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
|
|
struct {
|
|
uint32_t accum_count: 1; /*!< Whether to accumulate the count value when overflows at the high/low limit */
|
|
} flags;
|
|
};
|
|
|
|
struct pcnt_chan_t {
|
|
pcnt_unit_t *unit; // pointer to the PCNT unit where it derives from
|
|
int 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);
|
|
|
|
static esp_err_t pcnt_register_to_group(pcnt_unit_t *unit)
|
|
{
|
|
pcnt_group_t *group = NULL;
|
|
int unit_id = -1;
|
|
for (int i = 0; i < SOC_PCNT_GROUPS; i++) {
|
|
group = pcnt_acquire_group_handle(i);
|
|
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, 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]) {
|
|
unit_id = j;
|
|
group->units[j] = unit;
|
|
break;
|
|
}
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
if (unit_id < 0) {
|
|
pcnt_release_group_handle(group);
|
|
} else {
|
|
unit->group = group;
|
|
unit->unit_id = unit_id;
|
|
break;
|
|
}
|
|
}
|
|
ESP_RETURN_ON_FALSE(unit_id != -1, ESP_ERR_NOT_FOUND, TAG, "no free unit");
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void pcnt_unregister_from_group(pcnt_unit_t *unit)
|
|
{
|
|
pcnt_group_t *group = unit->group;
|
|
int unit_id = unit->unit_id;
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
group->units[unit_id] = NULL;
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
// unit has a reference on group, release it now
|
|
pcnt_release_group_handle(group);
|
|
}
|
|
|
|
static esp_err_t pcnt_destroy(pcnt_unit_t *unit)
|
|
{
|
|
if (unit->pm_lock) {
|
|
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(unit->pm_lock), TAG, "delete pm lock failed");
|
|
}
|
|
if (unit->intr) {
|
|
ESP_RETURN_ON_ERROR(esp_intr_free(unit->intr), TAG, "delete interrupt service failed");
|
|
}
|
|
if (unit->group) {
|
|
pcnt_unregister_from_group(unit);
|
|
}
|
|
free(unit);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t pcnt_new_unit(const pcnt_unit_config_t *config, pcnt_unit_handle_t *ret_unit)
|
|
{
|
|
#if CONFIG_PCNT_ENABLE_DEBUG_LOG
|
|
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
|
#endif
|
|
esp_err_t ret = ESP_OK;
|
|
pcnt_unit_t *unit = NULL;
|
|
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);
|
|
if (config->intr_priority) {
|
|
ESP_GOTO_ON_FALSE(1 << (config->intr_priority) & PCNT_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG, err,
|
|
TAG, "invalid interrupt priority:%d", config->intr_priority);
|
|
}
|
|
|
|
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");
|
|
// register unit to the group (because one group can have several units)
|
|
ESP_GOTO_ON_ERROR(pcnt_register_to_group(unit), err, TAG, "register unit failed");
|
|
pcnt_group_t *group = unit->group;
|
|
int group_id = group->group_id;
|
|
int unit_id = unit->unit_id;
|
|
|
|
// if interrupt priority specified before, it cannot be changed until the group is released
|
|
// check if the new priority specified consistents with the old one
|
|
bool intr_priority_conflict = false;
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
if (group->intr_priority == -1) {
|
|
group->intr_priority = config->intr_priority;
|
|
} else if (config->intr_priority != 0) {
|
|
intr_priority_conflict = (group->intr_priority != config->intr_priority);
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
ESP_GOTO_ON_FALSE(!intr_priority_conflict, ESP_ERR_INVALID_STATE, err, TAG, "intr_priority conflict, already is %d but attempt to %d", group->intr_priority, config->intr_priority);
|
|
|
|
// to accumulate count value, we should install the interrupt handler first, and in the ISR we do the accumulation
|
|
bool to_install_isr = (config->flags.accum_count == 1);
|
|
if (to_install_isr) {
|
|
int isr_flags = PCNT_INTR_ALLOC_FLAGS;
|
|
if (group->intr_priority) {
|
|
isr_flags |= 1 << (group->intr_priority);
|
|
} else {
|
|
isr_flags |= PCNT_ALLOW_INTR_PRIORITY_MASK;
|
|
}
|
|
ESP_GOTO_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), err,
|
|
TAG, "install interrupt service failed");
|
|
}
|
|
|
|
// 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;
|
|
unit->accum_value = 0;
|
|
unit->clear_signal_gpio_num = -1;
|
|
unit->flags.accum_count = config->flags.accum_count;
|
|
|
|
// 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);
|
|
// enable the interrupt if we want to accumulate the counter in the ISR
|
|
pcnt_ll_enable_intr(group->hal.dev, PCNT_LL_UNIT_WATCH_EVENT(unit_id), to_install_isr);
|
|
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_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
|
|
}
|
|
|
|
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) {
|
|
pcnt_destroy(unit);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
|
if (unit->clear_signal_gpio_num >= 0) {
|
|
gpio_reset_pin(unit->clear_signal_gpio_num);
|
|
}
|
|
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
|
|
|
ESP_LOGD(TAG, "del unit (%d,%d)", group_id, unit_id);
|
|
// recycle memory resource
|
|
ESP_RETURN_ON_ERROR(pcnt_destroy(unit), TAG, "destroy pcnt unit failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
#if SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
|
esp_err_t pcnt_unit_set_clear_signal(pcnt_unit_handle_t unit, const pcnt_clear_signal_config_t *config)
|
|
{
|
|
ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
pcnt_group_t *group = unit->group;
|
|
int group_id = group->group_id;
|
|
int unit_id = unit->unit_id;
|
|
|
|
if (config) {
|
|
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 = true,
|
|
.pull_up_en = false,
|
|
};
|
|
if (config->flags.invert_clear_signal) {
|
|
gpio_conf.pull_down_en = false;
|
|
gpio_conf.pull_up_en = true;
|
|
}
|
|
gpio_conf.pin_bit_mask = 1ULL << config->clear_signal_gpio_num;
|
|
ESP_RETURN_ON_ERROR(gpio_config(&gpio_conf), TAG, "config zero signal GPIO failed");
|
|
esp_rom_gpio_connect_in_signal(config->clear_signal_gpio_num,
|
|
pcnt_periph_signals.groups[group_id].units[unit_id].clear_sig,
|
|
config->flags.invert_clear_signal);
|
|
unit->clear_signal_gpio_num = config->clear_signal_gpio_num;
|
|
} else {
|
|
ESP_RETURN_ON_FALSE(unit->clear_signal_gpio_num >= 0, ESP_ERR_INVALID_STATE, TAG, "zero signal not set yet");
|
|
gpio_reset_pin(unit->clear_signal_gpio_num);
|
|
unit->clear_signal_gpio_num = -1;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
#endif // SOC_PCNT_SUPPORT_CLEAR_SIGNAL
|
|
|
|
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");
|
|
// 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;
|
|
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_enable(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");
|
|
|
|
// acquire power manager lock
|
|
if (unit->pm_lock) {
|
|
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(unit->pm_lock), TAG, "acquire pm_lock failed");
|
|
}
|
|
// enable interrupt service
|
|
if (unit->intr) {
|
|
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);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t pcnt_unit_stop(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_stop_count(group->hal.dev, unit->unit_id);
|
|
portEXIT_CRITICAL_SAFE(&group->spinlock);
|
|
|
|
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);
|
|
|
|
// reset the accumulated count as well
|
|
portENTER_CRITICAL_SAFE(&unit->spinlock);
|
|
unit->accum_value = 0;
|
|
portEXIT_CRITICAL_SAFE(&unit->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;
|
|
|
|
// the accum_value is also accessed by the ISR, so adding a critical section
|
|
portENTER_CRITICAL_SAFE(&unit->spinlock);
|
|
*value = pcnt_ll_get_count(group->hal.dev, unit->unit_id) + unit->accum_value;
|
|
portEXIT_CRITICAL_SAFE(&unit->spinlock);
|
|
|
|
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)
|
|
{
|
|
ESP_RETURN_ON_FALSE(unit && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
// unit event callbacks should be registered in init state
|
|
pcnt_group_t *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_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
|
}
|
|
#endif
|
|
|
|
// lazy install interrupt service
|
|
if (!unit->intr) {
|
|
ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "unit not in init state");
|
|
int isr_flags = PCNT_INTR_ALLOC_FLAGS;
|
|
if (group->intr_priority) {
|
|
isr_flags |= 1 << (group->intr_priority);
|
|
} else {
|
|
isr_flags |= PCNT_ALLOW_INTR_PRIORITY_MASK;
|
|
}
|
|
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");
|
|
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;
|
|
|
|
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);
|
|
} else {
|
|
// using virtual IO
|
|
esp_rom_gpio_connect_in_signal(config->flags.virt_edge_io_level ? GPIO_MATRIX_CONST_ONE_INPUT : GPIO_MATRIX_CONST_ZERO_INPUT,
|
|
pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].pulse_sig,
|
|
config->flags.invert_edge_input);
|
|
}
|
|
|
|
if (config->level_gpio_num >= 0) {
|
|
gpio_conf.pin_bit_mask = 1ULL << config->level_gpio_num;
|
|
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config level GPIO failed");
|
|
esp_rom_gpio_connect_in_signal(config->level_gpio_num,
|
|
pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].control_sig,
|
|
config->flags.invert_level_input);
|
|
} else {
|
|
// using virtual IO
|
|
esp_rom_gpio_connect_in_signal(config->flags.virt_level_io_level ? GPIO_MATRIX_CONST_ONE_INPUT : GPIO_MATRIX_CONST_ZERO_INPUT,
|
|
pcnt_periph_signals.groups[group_id].units[unit_id].channels[channel_id].control_sig,
|
|
config->flags.invert_level_input);
|
|
}
|
|
|
|
channel->channel_id = channel_id;
|
|
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)
|
|
{
|
|
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->intr_priority = -1;
|
|
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));
|
|
|
|
// points watcher event
|
|
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
|
|
|
|
portENTER_CRITICAL_ISR(&unit->spinlock);
|
|
if (unit->flags.accum_count) {
|
|
if (event_id == PCNT_LL_WATCH_EVENT_LOW_LIMIT) {
|
|
unit->accum_value += unit->low_limit;
|
|
} else if (event_id == PCNT_LL_WATCH_EVENT_HIGH_LIMIT) {
|
|
unit->accum_value += unit->high_limit;
|
|
}
|
|
}
|
|
portEXIT_CRITICAL_ISR(&unit->spinlock);
|
|
|
|
// 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();
|
|
}
|
|
}
|