mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
d8e5b2ac41
MCPWM group clock pre scale was originally fixed to 2, which is inconvenient. Set group clock prescale dynamically. Now the maximum resolution of the MCPWM timer is up to 160MHz(when the prescale set to 1). And add a resulotion config for MCPWM capture.
494 lines
21 KiB
C
494 lines
21 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <sys/cdefs.h>
|
|
#include "sdkconfig.h"
|
|
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
|
// The local log level must be defined before including esp_log.h
|
|
// Set the maximum log level for this source file
|
|
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
|
#endif
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "esp_attr.h"
|
|
#include "esp_check.h"
|
|
#include "esp_private/esp_clk.h"
|
|
#include "esp_clk_tree.h"
|
|
#include "esp_err.h"
|
|
#include "esp_log.h"
|
|
#include "esp_memory_utils.h"
|
|
#include "soc/soc_caps.h"
|
|
#include "soc/mcpwm_periph.h"
|
|
#include "hal/mcpwm_ll.h"
|
|
#include "driver/mcpwm_cap.h"
|
|
#include "driver/gpio.h"
|
|
#include "mcpwm_private.h"
|
|
|
|
static const char *TAG = "mcpwm";
|
|
|
|
static void mcpwm_capture_default_isr(void *args);
|
|
|
|
static esp_err_t mcpwm_cap_timer_register_to_group(mcpwm_cap_timer_t *cap_timer, int group_id)
|
|
{
|
|
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
|
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
|
|
|
bool new_timer = false;
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
if (!group->cap_timer) {
|
|
group->cap_timer = cap_timer;
|
|
new_timer = true;
|
|
}
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
|
|
if (!new_timer) {
|
|
mcpwm_release_group_handle(group);
|
|
group = NULL;
|
|
} else {
|
|
cap_timer->group = group;
|
|
}
|
|
ESP_RETURN_ON_FALSE(new_timer, ESP_ERR_NOT_FOUND, TAG, "no free cap timer in group (%d)", group_id);
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void mcpwm_cap_timer_unregister_from_group(mcpwm_cap_timer_t *cap_timer)
|
|
{
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
group->cap_timer = NULL;
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
|
|
// capture timer has a reference on group, release it now
|
|
mcpwm_release_group_handle(group);
|
|
}
|
|
|
|
static esp_err_t mcpwm_cap_timer_destroy(mcpwm_cap_timer_t *cap_timer)
|
|
{
|
|
if (cap_timer->pm_lock) {
|
|
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(cap_timer->pm_lock), TAG, "delete pm_lock failed");
|
|
}
|
|
if (cap_timer->group) {
|
|
mcpwm_cap_timer_unregister_from_group(cap_timer);
|
|
}
|
|
free(cap_timer);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_new_capture_timer(const mcpwm_capture_timer_config_t *config, mcpwm_cap_timer_handle_t *ret_cap_timer)
|
|
{
|
|
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
|
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
|
#endif
|
|
esp_err_t ret = ESP_OK;
|
|
mcpwm_cap_timer_t *cap_timer = NULL;
|
|
ESP_GOTO_ON_FALSE(config && ret_cap_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
|
err, TAG, "invalid group ID:%d", config->group_id);
|
|
|
|
cap_timer = heap_caps_calloc(1, sizeof(mcpwm_cap_timer_t), MCPWM_MEM_ALLOC_CAPS);
|
|
ESP_GOTO_ON_FALSE(cap_timer, ESP_ERR_NO_MEM, err, TAG, "no mem for capture timer");
|
|
|
|
ESP_GOTO_ON_ERROR(mcpwm_cap_timer_register_to_group(cap_timer, config->group_id), err, TAG, "register timer failed");
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
int group_id = group->group_id;
|
|
|
|
mcpwm_capture_clock_source_t clk_src = config->clk_src ? config->clk_src : MCPWM_CAPTURE_CLK_SRC_DEFAULT;
|
|
#if SOC_MCPWM_CAPTURE_CLK_FROM_GROUP
|
|
// capture timer clock source is same as the MCPWM group
|
|
ESP_GOTO_ON_ERROR(mcpwm_select_periph_clock(group, (soc_module_clk_t)clk_src), err, TAG, "set group clock failed");
|
|
uint32_t periph_src_clk_hz = 0;
|
|
ESP_GOTO_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &periph_src_clk_hz), err, TAG, "get clock source freq failed");
|
|
ESP_LOGD(TAG, "periph_src_clk_hz %"PRIu32"", periph_src_clk_hz);
|
|
// when resolution_hz set to zero, use default resolution_hz
|
|
uint32_t resolution_hz = config->resolution_hz ? config->resolution_hz : periph_src_clk_hz / MCPWM_GROUP_CLOCK_DEFAULT_PRESCALE;
|
|
|
|
ESP_GOTO_ON_ERROR(mcpwm_set_prescale(group, resolution_hz, MCPWM_LL_MAX_CAPTURE_TIMER_PRESCALE, NULL), err, TAG, "set prescale failed");
|
|
cap_timer->resolution_hz = group->resolution_hz;
|
|
if (cap_timer->resolution_hz != resolution_hz) {
|
|
ESP_LOGW(TAG, "adjust cap_timer resolution to %"PRIu32"Hz", cap_timer->resolution_hz);
|
|
}
|
|
#else
|
|
// capture timer has independent clock source selection
|
|
switch (clk_src) {
|
|
case MCPWM_CAPTURE_CLK_SRC_APB:
|
|
cap_timer->resolution_hz = esp_clk_apb_freq();
|
|
if (config->resolution_hz) {
|
|
ESP_LOGW(TAG, "cap_timer resolution is not adjustable in current target, still %"PRIu32"Hz", cap_timer->resolution_hz);
|
|
}
|
|
#if CONFIG_PM_ENABLE
|
|
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "mcpwm_cap_timer", &cap_timer->pm_lock);
|
|
ESP_GOTO_ON_ERROR(ret, err, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
|
|
#endif // CONFIG_PM_ENABLE
|
|
break;
|
|
default:
|
|
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid clock source:%d", config->clk_src);
|
|
}
|
|
#endif
|
|
|
|
// fill in other capture timer specific members
|
|
cap_timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
|
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_INIT;
|
|
*ret_cap_timer = cap_timer;
|
|
ESP_LOGD(TAG, "new capture timer at %p, in group (%d), resolution %"PRIu32, cap_timer, group_id, cap_timer->resolution_hz);
|
|
return ESP_OK;
|
|
|
|
err:
|
|
if (cap_timer) {
|
|
mcpwm_cap_timer_destroy(cap_timer);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t mcpwm_del_capture_timer(mcpwm_cap_timer_handle_t cap_timer)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
|
for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; i++) {
|
|
ESP_RETURN_ON_FALSE(!cap_timer->cap_channels[i], ESP_ERR_INVALID_STATE, TAG, "cap channel still in working");
|
|
}
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
|
|
ESP_LOGD(TAG, "del capture timer in group %d", group->group_id);
|
|
// recycle memory resource
|
|
ESP_RETURN_ON_ERROR(mcpwm_cap_timer_destroy(cap_timer), TAG, "destroy capture timer failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_enable(mcpwm_cap_timer_handle_t cap_timer)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
|
if (cap_timer->pm_lock) {
|
|
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(cap_timer->pm_lock), TAG, "acquire pm_lock failed");
|
|
}
|
|
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_ENABLE;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_disable(mcpwm_cap_timer_handle_t cap_timer)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
|
|
if (cap_timer->pm_lock) {
|
|
ESP_RETURN_ON_ERROR(esp_pm_lock_release(cap_timer->pm_lock), TAG, "release pm_lock failed");
|
|
}
|
|
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_INIT;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_start(mcpwm_cap_timer_handle_t cap_timer)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
|
|
portENTER_CRITICAL_SAFE(&cap_timer->spinlock);
|
|
mcpwm_ll_capture_enable_timer(group->hal.dev, true);
|
|
portEXIT_CRITICAL_SAFE(&cap_timer->spinlock);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_stop(mcpwm_cap_timer_handle_t cap_timer)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
|
|
portENTER_CRITICAL_SAFE(&cap_timer->spinlock);
|
|
mcpwm_ll_capture_enable_timer(group->hal.dev, false);
|
|
portEXIT_CRITICAL_SAFE(&cap_timer->spinlock);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_get_resolution(mcpwm_cap_timer_handle_t cap_timer, uint32_t *out_resolution)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer && out_resolution, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
*out_resolution = cap_timer->resolution_hz;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t mcpwm_capture_channel_register_to_timer(mcpwm_cap_channel_t *cap_channel, mcpwm_cap_timer_t *cap_timer)
|
|
{
|
|
int cap_chan_id = -1;
|
|
portENTER_CRITICAL(&cap_timer->spinlock);
|
|
for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; i++) {
|
|
if (!cap_timer->cap_channels[i]) {
|
|
cap_timer->cap_channels[i] = cap_channel;
|
|
cap_chan_id = i;
|
|
break;
|
|
}
|
|
}
|
|
portEXIT_CRITICAL(&cap_timer->spinlock);
|
|
ESP_RETURN_ON_FALSE(cap_chan_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free channel in the timer (%d)", cap_timer->group->group_id);
|
|
|
|
cap_channel->cap_chan_id = cap_chan_id;
|
|
cap_channel->cap_timer = cap_timer;
|
|
return ESP_OK;
|
|
}
|
|
|
|
static void mcpwm_capture_channel_unregister_from_timer(mcpwm_cap_channel_t *cap_chan)
|
|
{
|
|
mcpwm_cap_timer_t *cap_timer = cap_chan->cap_timer;
|
|
int cap_chan_id = cap_chan->cap_chan_id;
|
|
|
|
portENTER_CRITICAL(&cap_timer->spinlock);
|
|
cap_timer->cap_channels[cap_chan_id] = NULL;
|
|
portEXIT_CRITICAL(&cap_timer->spinlock);
|
|
}
|
|
|
|
static esp_err_t mcpwm_capture_channel_destroy(mcpwm_cap_channel_t *cap_chan)
|
|
{
|
|
if (cap_chan->intr) {
|
|
ESP_RETURN_ON_ERROR(esp_intr_free(cap_chan->intr), TAG, "delete interrupt service failed");
|
|
}
|
|
if (cap_chan->cap_timer) {
|
|
mcpwm_capture_channel_unregister_from_timer(cap_chan);
|
|
}
|
|
free(cap_chan);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_channel_config_t *config, mcpwm_cap_channel_handle_t *ret_cap_channel)
|
|
{
|
|
esp_err_t ret = ESP_OK;
|
|
mcpwm_cap_channel_t *cap_chan = NULL;
|
|
ESP_GOTO_ON_FALSE(cap_timer && config && ret_cap_channel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
|
ESP_GOTO_ON_FALSE(config->prescale && config->prescale <= MCPWM_LL_MAX_CAPTURE_PRESCALE, ESP_ERR_INVALID_ARG, err, TAG, "invalid prescale");
|
|
if (config->intr_priority) {
|
|
ESP_RETURN_ON_FALSE(1 << (config->intr_priority) & MCPWM_ALLOW_INTR_PRIORITY_MASK, ESP_ERR_INVALID_ARG,
|
|
TAG, "invalid interrupt priority:%d", config->intr_priority);
|
|
}
|
|
|
|
// create instance firstly, then install onto platform
|
|
cap_chan = calloc(1, sizeof(mcpwm_cap_channel_t));
|
|
ESP_GOTO_ON_FALSE(cap_chan, ESP_ERR_NO_MEM, err, TAG, "no mem for capture channel");
|
|
|
|
ESP_GOTO_ON_ERROR(mcpwm_capture_channel_register_to_timer(cap_chan, cap_timer), err, TAG, "register channel failed");
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
mcpwm_hal_context_t *hal = &group->hal;
|
|
int cap_chan_id = cap_chan->cap_chan_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
|
|
ESP_GOTO_ON_ERROR(mcpwm_check_intr_priority(group, config->intr_priority), err, TAG, "set group intrrupt priority failed");
|
|
|
|
mcpwm_ll_capture_enable_negedge(hal->dev, cap_chan_id, config->flags.neg_edge);
|
|
mcpwm_ll_capture_enable_posedge(hal->dev, cap_chan_id, config->flags.pos_edge);
|
|
mcpwm_ll_invert_input(hal->dev, cap_chan_id, config->flags.invert_cap_signal);
|
|
mcpwm_ll_capture_set_prescale(hal->dev, cap_chan_id, config->prescale);
|
|
|
|
if (config->gpio_num >= 0) {
|
|
// 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
|
|
.pin_bit_mask = (1ULL << config->gpio_num),
|
|
.pull_down_en = config->flags.pull_down,
|
|
.pull_up_en = config->flags.pull_up,
|
|
};
|
|
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config capture GPIO failed");
|
|
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group->group_id].captures[cap_chan_id].cap_sig, 0);
|
|
}
|
|
|
|
cap_chan->gpio_num = config->gpio_num;
|
|
cap_chan->fsm = MCPWM_CAP_CHAN_FSM_INIT;
|
|
cap_chan->flags.reset_io_at_exit = !config->flags.keep_io_conf_at_exit && config->gpio_num >= 0;
|
|
*ret_cap_channel = cap_chan;
|
|
ESP_LOGD(TAG, "new capture channel (%d,%d) at %p", group->group_id, cap_chan_id, cap_chan);
|
|
return ESP_OK;
|
|
err:
|
|
if (cap_chan) {
|
|
mcpwm_capture_channel_destroy(cap_chan);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t mcpwm_del_capture_channel(mcpwm_cap_channel_handle_t cap_channel)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_channel->fsm == MCPWM_CAP_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state");
|
|
mcpwm_cap_timer_t *cap_timer = cap_channel->cap_timer;
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
mcpwm_hal_context_t *hal = &group->hal;
|
|
int cap_chan_id = cap_channel->cap_chan_id;
|
|
|
|
ESP_LOGD(TAG, "del capture channel (%d,%d)", group->group_id, cap_channel->cap_chan_id);
|
|
if (cap_channel->flags.reset_io_at_exit) {
|
|
gpio_reset_pin(cap_channel->gpio_num);
|
|
}
|
|
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id), false);
|
|
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id));
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
|
|
// recycle memory resource
|
|
ESP_RETURN_ON_ERROR(mcpwm_capture_channel_destroy(cap_channel), TAG, "destroy capture channel failed");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_channel_enable(mcpwm_cap_channel_handle_t cap_channel)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_channel->fsm == MCPWM_CAP_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state");
|
|
mcpwm_hal_context_t *hal = &cap_channel->cap_timer->group->hal;
|
|
|
|
// enable interrupt service
|
|
if (cap_channel->intr) {
|
|
ESP_RETURN_ON_ERROR(esp_intr_enable(cap_channel->intr), TAG, "enable interrupt service failed");
|
|
}
|
|
// enable channel
|
|
mcpwm_ll_capture_enable_channel(hal->dev, cap_channel->cap_chan_id, true);
|
|
|
|
cap_channel->fsm = MCPWM_CAP_CHAN_FSM_ENABLE;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_channel_disable(mcpwm_cap_channel_handle_t cap_channel)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_channel->fsm == MCPWM_CAP_CHAN_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state");
|
|
mcpwm_hal_context_t *hal = &cap_channel->cap_timer->group->hal;
|
|
|
|
// disable channel
|
|
mcpwm_ll_capture_enable_channel(hal->dev, cap_channel->cap_chan_id, false);
|
|
|
|
// disable interrupt service
|
|
if (cap_channel->intr) {
|
|
ESP_RETURN_ON_ERROR(esp_intr_disable(cap_channel->intr), TAG, "disable interrupt service failed");
|
|
}
|
|
|
|
cap_channel->fsm = MCPWM_CAP_CHAN_FSM_INIT;
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_channel_register_event_callbacks(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_callbacks_t *cbs, void *user_data)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
mcpwm_group_t *group = cap_channel->cap_timer->group;
|
|
mcpwm_hal_context_t *hal = &group->hal;
|
|
int group_id = group->group_id;
|
|
int cap_chan_id = cap_channel->cap_chan_id;
|
|
|
|
#if CONFIG_MCPWM_ISR_IRAM_SAFE
|
|
if (cbs->on_cap) {
|
|
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_cap), ESP_ERR_INVALID_ARG, TAG, "on_cap 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 (!cap_channel->intr) {
|
|
ESP_RETURN_ON_FALSE(cap_channel->fsm == MCPWM_CAP_CHAN_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state");
|
|
int isr_flags = MCPWM_INTR_ALLOC_FLAG;
|
|
isr_flags |= mcpwm_get_intr_priority_flag(group);
|
|
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
|
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_CAPTURE(cap_chan_id),
|
|
mcpwm_capture_default_isr, cap_channel, &cap_channel->intr), TAG, "install interrupt service for cap channel failed");
|
|
}
|
|
|
|
portENTER_CRITICAL(&group->spinlock);
|
|
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id), cbs->on_cap != NULL);
|
|
portEXIT_CRITICAL(&group->spinlock);
|
|
|
|
cap_channel->on_cap = cbs->on_cap;
|
|
cap_channel->user_data = user_data;
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_channel_trigger_soft_catch(mcpwm_cap_channel_handle_t cap_channel)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
ESP_RETURN_ON_FALSE(cap_channel->fsm == MCPWM_CAP_CHAN_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not enabled yet");
|
|
mcpwm_cap_timer_t *cap_timer = cap_channel->cap_timer;
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
|
|
// note: soft capture can also triggers the interrupt routine
|
|
mcpwm_ll_trigger_soft_capture(group->hal.dev, cap_channel->cap_chan_id);
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t mcpwm_capture_timer_set_phase_on_sync(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_timer_sync_phase_config_t *config)
|
|
{
|
|
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
|
// capture timer only support count up
|
|
ESP_RETURN_ON_FALSE(config->direction == MCPWM_TIMER_DIRECTION_UP, ESP_ERR_INVALID_ARG, TAG, "invalid sync direction");
|
|
mcpwm_group_t *group = cap_timer->group;
|
|
mcpwm_sync_t *sync_source = config->sync_src;
|
|
|
|
// a non-NULL sync_src means to enable sync feature
|
|
if (sync_source) {
|
|
switch (sync_source->type) {
|
|
case MCPWM_SYNC_TYPE_GPIO: {
|
|
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "capture timer and sync source are not in the same group");
|
|
mcpwm_gpio_sync_src_t *gpio_sync_src = __containerof(sync_source, mcpwm_gpio_sync_src_t, base);
|
|
mcpwm_ll_capture_set_gpio_sync(group->hal.dev, gpio_sync_src->sync_id);
|
|
ESP_LOGD(TAG, "enable sync to GPIO (%d,%d) for cap timer (%d)",
|
|
group->group_id, gpio_sync_src->sync_id, group->group_id);
|
|
break;
|
|
}
|
|
case MCPWM_SYNC_TYPE_TIMER: {
|
|
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "capture timer and sync source are not in the same group");
|
|
mcpwm_timer_sync_src_t *timer_sync_src = __containerof(sync_source, mcpwm_timer_sync_src_t, base);
|
|
mcpwm_ll_capture_set_timer_sync(group->hal.dev, timer_sync_src->timer->timer_id);
|
|
ESP_LOGD(TAG, "enable sync to pwm timer (%d,%d) for cap timer (%d)",
|
|
group->group_id, timer_sync_src->timer->timer_id, group->group_id);
|
|
break;
|
|
}
|
|
case MCPWM_SYNC_TYPE_SOFT: {
|
|
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_source, mcpwm_soft_sync_src_t, base);
|
|
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_CAP;
|
|
soft_sync->cap_timer = cap_timer;
|
|
soft_sync->base.group = group;
|
|
break;
|
|
}
|
|
}
|
|
mcpwm_ll_capture_enable_timer_sync(group->hal.dev, true);
|
|
mcpwm_ll_capture_set_sync_phase_value(group->hal.dev, config->count_value);
|
|
} else { // disable sync feature
|
|
mcpwm_ll_capture_enable_timer_sync(group->hal.dev, false);
|
|
ESP_LOGD(TAG, "disable sync for cap timer (%d)", group->group_id);
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
IRAM_ATTR static void mcpwm_capture_default_isr(void *args)
|
|
{
|
|
mcpwm_cap_channel_t *cap_chan = (mcpwm_cap_channel_t *)args;
|
|
mcpwm_group_t *group = cap_chan->cap_timer->group;
|
|
mcpwm_hal_context_t *hal = &group->hal;
|
|
int cap_id = cap_chan->cap_chan_id;
|
|
bool need_yield = false;
|
|
|
|
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
|
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_CAPTURE(cap_id));
|
|
|
|
// read capture value and pass to user
|
|
mcpwm_capture_event_data_t data = {
|
|
.cap_value = mcpwm_ll_capture_get_value(hal->dev, cap_id),
|
|
.cap_edge = mcpwm_ll_capture_get_edge(hal->dev, cap_id),
|
|
};
|
|
if (status & MCPWM_LL_EVENT_CAPTURE(cap_id)) {
|
|
mcpwm_capture_event_cb_t cb = cap_chan->on_cap;
|
|
if (cb) {
|
|
if (cb(cap_chan, &data, cap_chan->user_data)) {
|
|
need_yield = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (need_yield) {
|
|
portYIELD_FROM_ISR();
|
|
}
|
|
}
|