mcpwm: new driver implementation

This commit is contained in:
morris 2022-05-28 17:03:05 +08:00
parent fd3a1ffc21
commit 1571417679
38 changed files with 5815 additions and 2 deletions

View File

@ -8,6 +8,10 @@ components/driver/test_apps/i2s_test_apps/legacy_i2s_adc_dac:
disable:
- if: SOC_I2S_SUPPORTS_ADC_DAC != 1
components/driver/test_apps/legacy_mcpwm_driver:
disable:
- if: SOC_MCPWM_SUPPORTED != 1
components/driver/test_apps/legacy_pcnt_driver:
disable:
- if: SOC_PCNT_SUPPORTED != 1
@ -20,6 +24,10 @@ components/driver/test_apps/legacy_rtc_temp_driver:
disable:
- if: SOC_TEMP_SENSOR_SUPPORTED != 1
components/driver/test_apps/mcpwm:
disable:
- if: SOC_MCPWM_SUPPORTED != 1
components/driver/test_apps/pulse_cnt:
disable:
- if: SOC_PCNT_SUPPORTED != 1

View File

@ -32,7 +32,15 @@ if(CONFIG_SOC_ADC_DMA_SUPPORTED)
endif()
if(CONFIG_SOC_MCPWM_SUPPORTED)
list(APPEND srcs "mcpwm.c")
list(APPEND srcs "mcpwm/mcpwm_cap.c"
"mcpwm/mcpwm_cmpr.c"
"mcpwm/mcpwm_com.c"
"mcpwm/mcpwm_fault.c"
"mcpwm/mcpwm_gen.c"
"mcpwm/mcpwm_oper.c"
"mcpwm/mcpwm_sync.c"
"mcpwm/mcpwm_timer.c"
"deprecated/mcpwm_legacy.c")
endif()
if(CONFIG_SOC_DEDICATED_GPIO_SUPPORTED)
@ -88,9 +96,12 @@ if(CONFIG_SOC_TOUCH_SENSOR_SUPPORTED)
list(APPEND srcs "touch_sensor_common.c" "${target}/touch_sensor.c")
endif()
if(CONFIG_SOC_SDIO_SLAVE_SUPPORTED)
list(APPEND srcs "sdio_slave.c")
endif()
if(${target} STREQUAL "esp32")
list(APPEND srcs "dac_common.c"
"sdio_slave.c"
"deprecated/adc_i2s_deprecated.c"
"esp32/dac.c")
endif()

View File

@ -0,0 +1,194 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM capture timer configuration structure
*/
typedef struct {
int group_id; /*!< Specify from which group to allocate the capture timer */
mcpwm_capture_clock_source_t clk_src; /*!< MCPWM capture timer clock source */
} mcpwm_capture_timer_config_t;
/**
* @brief Create MCPWM capture timer
*
* @param[in] config MCPWM capture timer configuration
* @param[out] ret_cap_timer Returned MCPWM capture timer handle
* @return
* - ESP_OK: Create MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM capture timer failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM capture timer failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM capture timer failed because can't find free resource
* - ESP_FAIL: Create MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_new_capture_timer(const mcpwm_capture_timer_config_t *config, mcpwm_cap_timer_handle_t *ret_cap_timer);
/**
* @brief Delete MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
* @return
* - ESP_OK: Delete MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM capture timer failed because of invalid argument
* - ESP_FAIL: Delete MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_del_capture_timer(mcpwm_cap_timer_handle_t cap_timer);
/**
* @brief Enable MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer handle, allocated by `mcpwm_new_capture_timer()`
* @return
* - ESP_OK: Enable MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Enable MCPWM capture timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Enable MCPWM capture timer failed because timer is enabled already
* - ESP_FAIL: Enable MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_capture_timer_enable(mcpwm_cap_timer_handle_t cap_timer);
/**
* @brief Disable MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer handle, allocated by `mcpwm_new_capture_timer()`
* @return
* - ESP_OK: Disable MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Disable MCPWM capture timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Disable MCPWM capture timer failed because timer is disabled already
* - ESP_FAIL: Disable MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_capture_timer_disable(mcpwm_cap_timer_handle_t cap_timer);
/**
* @brief Start MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
* @return
* - ESP_OK: Start MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Start MCPWM capture timer failed because of invalid argument
* - ESP_FAIL: Start MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_capture_timer_start(mcpwm_cap_timer_handle_t cap_timer);
/**
* @brief Start MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
* @return
* - ESP_OK: Stop MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Stop MCPWM capture timer failed because of invalid argument
* - ESP_FAIL: Stop MCPWM capture timer failed because of other error
*/
esp_err_t mcpwm_capture_timer_stop(mcpwm_cap_timer_handle_t cap_timer);
/**
* @brief MCPWM Capture timer sync phase configuration
*/
typedef struct {
mcpwm_sync_handle_t sync_src; /*!< The sync event source */
uint32_t count_value; /*!< The count value that should lock to upon sync event */
mcpwm_timer_direction_t direction; /*!< The count direction that should lock to upon sync event */
} mcpwm_capture_timer_sync_phase_config_t;
/**
* @brief Set sync phase for MCPWM capture timer
*
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
* @param[in] config MCPWM capture timer sync phase configuration
* @return
* - ESP_OK: Set sync phase for MCPWM capture timer successfully
* - ESP_ERR_INVALID_ARG: Set sync phase for MCPWM capture timer failed because of invalid argument
* - ESP_FAIL: Set sync phase for MCPWM capture timer failed because of other error
*/
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);
/**
* @brief MCPWM capture channel configuration structure
*/
typedef struct {
int gpio_num; /*!< GPIO used capturing input signal */
uint32_t prescale; /*!< Prescale of input signal, effective frequency = cap_input_clk/prescale */
struct {
uint32_t pos_edge: 1; /*!< Whether to capture on positive edge */
uint32_t neg_edge: 1; /*!< Whether to capture on negative edge */
uint32_t pull_up: 1; /*!< Whether to pull up internally */
uint32_t pull_down: 1; /*!< Whether to pull down internally */
uint32_t invert_cap_signal: 1; /*!< Invert the input capture signal */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
} flags; /*!< Extra configuration flags for capture channel */
} mcpwm_capture_channel_config_t;
/**
* @brief Create MCPWM capture channel
*
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`, will be connected to the new capture channel
* @param[in] config MCPWM capture channel configuration
* @param[out] ret_cap_channel Returned MCPWM capture channel
* @return
* - ESP_OK: Create MCPWM capture channel successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM capture channel failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM capture channel failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM capture channel failed because can't find free resource
* - ESP_FAIL: Create MCPWM capture channel failed because of other error
*/
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);
/**
* @brief Delete MCPWM capture channel
*
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
* @return
* - ESP_OK: Delete MCPWM capture channel successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM capture channel failed because of invalid argument
* - ESP_FAIL: Delete MCPWM capture channel failed because of other error
*/
esp_err_t mcpwm_del_capture_channel(mcpwm_cap_channel_handle_t cap_channel);
/**
* @brief Group of supported MCPWM capture event callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
mcpwm_capture_event_cb_t on_cap; /*!< Callback function that would be invoked when capture event occurred */
} mcpwm_capture_event_callbacks_t;
/**
* @brief Set event callbacks for MCPWM capture channel
*
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
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);
/**
* @brief Trigger a catch by software
*
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
* @return
* - ESP_OK: Trigger software catch successfully
* - ESP_ERR_INVALID_ARG: Trigger software catch failed because of invalid argument
* - ESP_FAIL: Trigger software catch failed because of other error
*/
esp_err_t mcpwm_capture_channel_trigger_soft_catch(mcpwm_cap_channel_handle_t cap_channel);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,91 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM comparator configuration
*/
typedef struct {
struct {
uint32_t update_cmp_on_tez: 1; /*!< Whether to update compare value when timer count equals to zero (tez) */
uint32_t update_cmp_on_tep: 1; /*!< Whether to update compare value when timer count equals to peak (tep) */
uint32_t update_cmp_on_sync: 1; /*!< Whether to update compare value on sync event */
} flags; /*!< Extra configuration flags for comparator */
} mcpwm_comparator_config_t;
/**
* @brief Create MCPWM comparator
*
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`, the new comparator will be allocated from this operator
* @param[in] config MCPWM comparator configuration
* @param[out] ret_cmpr Returned MCPWM comparator
* @return
* - ESP_OK: Create MCPWM comparator successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM comparator failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM comparator failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM comparator failed because can't find free resource
* - ESP_FAIL: Create MCPWM comparator failed because of other error
*/
esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_config_t *config, mcpwm_cmpr_handle_t *ret_cmpr);
/**
* @brief Delete MCPWM comparator
*
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
* @return
* - ESP_OK: Delete MCPWM comparator successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM comparator failed because of invalid argument
* - ESP_FAIL: Delete MCPWM comparator failed because of other error
*/
esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr);
/**
* @brief Group of supported MCPWM compare event callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
mcpwm_compare_event_cb_t on_reach; /*!< ISR callback function which would be invoked when counter reaches compare value */
} mcpwm_comparator_event_callbacks_t;
/**
* @brief Set event callbacks for MCPWM comparator
*
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t mcpwm_comparator_register_event_callbacks(mcpwm_cmpr_handle_t cmpr, const mcpwm_comparator_event_callbacks_t *cbs, void *user_data);
/**
* @brief Set MCPWM comparator's compare value
*
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
* @param[in] cmp_ticks The new compare value
* @return
* - ESP_OK: Set MCPWM compare value successfully
* - ESP_ERR_INVALID_ARG: Set MCPWM compare value failed because of invalid argument (e.g. the cmp_ticks is out of range)
* - ESP_ERR_INVALID_STATE: Set MCPWM compare value failed because the operator doesn't have a timer connected
* - ESP_FAIL: Set MCPWM compare value failed because of other error
*/
esp_err_t mcpwm_comparator_set_compare_value(mcpwm_cmpr_handle_t cmpr, uint32_t cmp_ticks);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM GPIO fault configuration structure
*/
typedef struct {
int group_id; /*!< In which MCPWM group that the GPIO fault belongs to */
int gpio_num; /*!< GPIO used by the fault signal */
struct {
uint32_t active_level: 1; /*!< On which level the fault signal is treated as active */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
uint32_t pull_up: 1; /*!< Whether to pull up internally */
uint32_t pull_down: 1; /*!< Whether to pull down internally */
} flags; /*!< Extra configuration flags for GPIO fault */
} mcpwm_gpio_fault_config_t;
/**
* @brief Create MCPWM GPIO fault
*
* @param[in] config MCPWM GPIO fault configuration
* @param[out] ret_fault Returned GPIO fault handle
* @return
* - ESP_OK: Create MCPWM GPIO fault successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM GPIO fault failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM GPIO fault failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM GPIO fault failed because can't find free resource
* - ESP_FAIL: Create MCPWM GPIO fault failed because of other error
*/
esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
/**
* @brief MCPWM software fault configuration structure
*/
typedef struct {
} mcpwm_soft_fault_config_t;
/**
* @brief Create MCPWM software fault
*
* @param[in] config MCPWM software fault configuration
* @param[out] ret_fault Returned software fault handle
* @return
* - ESP_OK: Create MCPWM software fault successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM software fault failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM software fault failed because out of memory
* - ESP_FAIL: Create MCPWM software fault failed because of other error
*/
esp_err_t mcpwm_new_soft_fault(const mcpwm_soft_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
/**
* @brief Delete MCPWM fault
*
* @param[in] fault MCPWM fault handle allocated by `mcpwm_new_gpio_fault()` or `mcpwm_new_soft_fault()`
* @return
* - ESP_OK: Delete MCPWM fault successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM fault failed because of invalid argument
* - ESP_FAIL: Delete MCPWM fault failed because of other error
*/
esp_err_t mcpwm_del_fault(mcpwm_fault_handle_t fault);
/**
* @brief Activate the software fault, trigger the fault event for once
*
* @param[in] fault MCPWM soft fault, allocated by `mcpwm_new_soft_fault()`
* @return
* - ESP_OK: Trigger MCPWM software fault event successfully
* - ESP_ERR_INVALID_ARG: Trigger MCPWM software fault event failed because of invalid argument
* - ESP_FAIL: Trigger MCPWM software fault event failed because of other error
*/
esp_err_t mcpwm_soft_fault_activate(mcpwm_fault_handle_t fault);
/**
* @brief Group of supported MCPWM fault event callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
mcpwm_fault_event_cb_t on_fault_enter; /*!< ISR callback function that would be invoked when fault signal becomes active */
mcpwm_fault_event_cb_t on_fault_exit; /*!< ISR callback function that would be invoked when fault signal becomes inactive */
} mcpwm_fault_event_callbacks_t;
/**
* @brief Set event callbacks for MCPWM fault
*
* @param[in] fault MCPWM GPIO fault handle, allocated by `mcpwm_new_gpio_fault()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t mcpwm_fault_register_event_callbacks(mcpwm_fault_handle_t fault, const mcpwm_fault_event_callbacks_t *cbs, void *user_data);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,187 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM generator configuration
*/
typedef struct {
int gen_gpio_num; /*!< The GPIO number used to output the PWM signal */
struct {
uint32_t invert_pwm: 1; /*!< Whether to invert the PWM signal (done by GPIO matrix) */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
} flags; /*!< Extra configuration flags for generator */
} mcpwm_generator_config_t;
/**
* @brief Allocate MCPWM generator from given operator
*
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
* @param[in] config MCPWM generator configuration
* @param[out] ret_gen Returned MCPWM generator
* @return
* - ESP_OK: Create MCPWM generator successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM generator failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM generator failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM generator failed because can't find free resource
* - ESP_FAIL: Create MCPWM generator failed because of other error
*/
esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_config_t *config, mcpwm_gen_handle_t *ret_gen);
/**
* @brief Delete MCPWM generator
*
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
* @return
* - ESP_OK: Delete MCPWM generator successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM generator failed because of invalid argument
* - ESP_FAIL: Delete MCPWM generator failed because of other error
*/
esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen);
/**
* @brief Set force level for MCPWM generator
*
* @note The force level will be applied to the generator immediately, regardless any other events that would change the generator's behaviour.
* @note If the `hold_on` is true, the force level will retain forever, until user removes the force level by setting the force level to `-1`.
* @note If the `hold_on` is false, the force level can be overridden by the next event action.
*
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
* @param[in] level GPIO level to be applied to MCPWM generator, specially, -1 means to remove the force level
* @param[in] hold_on Whether the forced PWM level should retain (i.e. will remain unchanged until manually remove the force level)
* @return
* - ESP_OK: Set force level for MCPWM generator successfully
* - ESP_ERR_INVALID_ARG: Set force level for MCPWM generator failed because of invalid argument
* - ESP_FAIL: Set force level for MCPWM generator failed because of other error
*/
esp_err_t mcpwm_generator_set_force_level(mcpwm_gen_handle_t gen, int level, bool hold_on);
/**
* @brief Generator action on specific timer event
*/
typedef struct {
mcpwm_timer_direction_t direction; /*!< Timer direction */
mcpwm_timer_event_t event; /*!< Timer event */
mcpwm_generator_action_t action; /*!< Generator action should perform */
} mcpwm_gen_timer_event_action_t;
/**
* @brief Help macros to construct a mcpwm_gen_timer_event_action_t entry
*/
#define MCPWM_GEN_TIMER_EVENT_ACTION(dir, ev, act) \
(mcpwm_gen_timer_event_action_t) { .direction = dir, .event = ev, .action = act }
#define MCPWM_GEN_TIMER_EVENT_ACTION_END() \
(mcpwm_gen_timer_event_action_t) { .event = MCPWM_TIMER_EVENT_INVALID }
/**
* @brief Set generator actions on different MCPWM timer events
*
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
* @param[in] ev_act MCPWM timer event action list, must be terminated by `MCPWM_GEN_TIMER_EVENT_ACTION_END()`
* @return
* - ESP_OK: Set generator actions successfully
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Set generator actions failed because of timer is not connected to operator
* - ESP_FAIL: Set generator actions failed because of other error
*/
esp_err_t mcpwm_generator_set_actions_on_timer_event(mcpwm_gen_handle_t gen, mcpwm_gen_timer_event_action_t ev_act, ...);
/**
* @brief Generator action on specific comparator event
*/
typedef struct {
mcpwm_timer_direction_t direction; /*!< Timer direction */
mcpwm_cmpr_handle_t comparator; /*!< Comparator handle */
mcpwm_generator_action_t action; /*!< Generator action should perform */
} mcpwm_gen_compare_event_action_t;
/**
* @brief Help macros to construct a mcpwm_gen_compare_event_action_t entry
*/
#define MCPWM_GEN_COMPARE_EVENT_ACTION(dir, cmp, act) \
(mcpwm_gen_compare_event_action_t) { .direction = dir, .comparator = cmp, .action = act }
#define MCPWM_GEN_COMPARE_EVENT_ACTION_END() \
(mcpwm_gen_compare_event_action_t) { .comparator = NULL }
/**
* @brief Set generator actions on different MCPWM compare events
*
* @param[in] generator MCPWM generator handle, allocated by `mcpwm_new_generator()`
* @param[in] ev_act MCPWM compare event action list, must be terminated by `MCPWM_GEN_COMPARE_EVENT_ACTION_END()`
* @return
* - ESP_OK: Set generator actions successfully
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
* - ESP_FAIL: Set generator actions failed because of other error
*/
esp_err_t mcpwm_generator_set_actions_on_compare_event(mcpwm_gen_handle_t generator, mcpwm_gen_compare_event_action_t ev_act, ...);
/**
* @brief Generator action on specific brake event
*/
typedef struct {
mcpwm_timer_direction_t direction; /*!< Timer direction */
mcpwm_operator_brake_mode_t brake_mode; /*!< Brake mode */
mcpwm_generator_action_t action; /*!< Generator action should perform */
} mcpwm_gen_brake_event_action_t;
/**
* @brief Help macros to construct a mcpwm_gen_brake_event_action_t entry
*/
#define MCPWM_GEN_BRAKE_EVENT_ACTION(dir, mode, act) \
(mcpwm_gen_brake_event_action_t) { .direction = dir, .brake_mode = mode, .action = act }
#define MCPWM_GEN_BRAKE_EVENT_ACTION_END() \
(mcpwm_gen_brake_event_action_t) { .brake_mode = MCPWM_OPER_BRAKE_MODE_INVALID }
/**
* @brief Set generator actions on different MCPWM brake events
*
* @param[in] generator MCPWM generator handle, allocated by `mcpwm_new_generator()`
* @param[in] ev_act MCPWM brake event action list, must be terminated by `MCPWM_GEN_BRAKE_EVENT_ACTION_END()`
* @return
* - ESP_OK: Set generator actions successfully
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
* - ESP_FAIL: Set generator actions failed because of other error
*/
esp_err_t mcpwm_generator_set_actions_on_brake_event(mcpwm_gen_handle_t generator, mcpwm_gen_brake_event_action_t ev_act, ...);
/**
* @brief MCPWM dead time configuration structure
*/
typedef struct {
uint32_t posedge_delay_ticks; /*!< delay time applied to rising edge, 0 means no rising delay time */
uint32_t negedge_delay_ticks; /*!< delay time applied to falling edge, 0 means no falling delay time */
struct {
uint32_t invert_output: 1; /*!< Invert the signal after applied the dead time */
} flags; /*!< Extra flags for dead time configuration */
} mcpwm_dead_time_config_t;
/**
* @brief Set dead time for MCPWM generator
*
* @param[in] in_generator MCPWM generator, before adding the dead time
* @param[in] out_generator MCPWM generator, after adding the dead time
* @param[in] config MCPWM dead time configuration
* @return
* - ESP_OK: Set dead time for MCPWM generator successfully
* - ESP_ERR_INVALID_ARG: Set dead time for MCPWM generator failed because of invalid argument
* - ESP_FAIL: Set dead time for MCPWM generator failed because of other error
*/
esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t in_generator, mcpwm_gen_handle_t out_generator, const mcpwm_dead_time_config_t *config);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,159 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM operator configuration
*/
typedef struct {
int group_id; /*!< Specify from which group to allocate the MCPWM operator */
struct {
uint32_t update_gen_action_on_tez: 1; /*!< Whether to update generator action when timer counts to zero */
uint32_t update_gen_action_on_tep: 1; /*!< Whether to update generator action when timer counts to peak */
uint32_t update_gen_action_on_sync: 1; /*!< Whether to update generator action on sync event */
uint32_t update_dead_time_on_tez: 1; /*!< Whether to update dead time when timer counts to zero */
uint32_t update_dead_time_on_tep: 1; /*!< Whether to update dead time when timer counts to peak */
uint32_t update_dead_time_on_sync: 1; /*!< Whether to update dead time on sync event */
} flags; /*!< Extra configuration flags for operator */
} mcpwm_operator_config_t;
/**
* @brief Create MCPWM operator
*
* @param[in] config MCPWM operator configuration
* @param[out] ret_oper Returned MCPWM operator handle
* @return
* - ESP_OK: Create MCPWM operator successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM operator failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM operator failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM operator failed because can't find free resource
* - ESP_FAIL: Create MCPWM operator failed because of other error
*/
esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_handle_t *ret_oper);
/**
* @brief Delete MCPWM operator
*
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
* @return
* - ESP_OK: Delete MCPWM operator successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM operator failed because of invalid argument
* - ESP_FAIL: Delete MCPWM operator failed because of other error
*/
esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper);
/**
* @brief Connect MCPWM operator and timer, so that the operator can be driven by the timer
*
* @param[in] oper MCPWM operator handle, allocated by `mcpwm_new_operator()`
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @return
* - ESP_OK: Connect MCPWM operator and timer successfully
* - ESP_ERR_INVALID_ARG: Connect MCPWM operator and timer failed because of invalid argument
* - ESP_FAIL: Connect MCPWM operator and timer failed because of other error
*/
esp_err_t mcpwm_operator_connect_timer(mcpwm_oper_handle_t oper, mcpwm_timer_handle_t timer);
/**
* @brief MCPWM brake configuration structure
*/
typedef struct {
mcpwm_fault_handle_t fault; /*!< Which fault causes the operator to brake */
mcpwm_operator_brake_mode_t brake_mode; /*!< Brake mode */
struct {
uint32_t cbc_recover_on_tez: 1; /*!< Recovery CBC brake state on tez event */
uint32_t cbc_recover_on_tep: 1; /*!< Recovery CBC brake state on tep event */
} flags; /*!< Extra flags for brake configuration */
} mcpwm_brake_config_t;
/**
* @brief Set brake method for MCPWM operator
*
* @param[in] operator MCPWM operator, allocated by `mcpwm_new_operator()`
* @param[in] config MCPWM brake configuration
* @return
* - ESP_OK: Set trip for operator successfully
* - ESP_ERR_INVALID_ARG: Set trip for operator failed because of invalid argument
* - ESP_FAIL: Set trip for operator failed because of other error
*/
esp_err_t mcpwm_operator_set_brake_on_fault(mcpwm_oper_handle_t operator, const mcpwm_brake_config_t *config);
/**
* @brief Try to make the operator recover from fault
*
* @note To recover from fault or escape from trip, you make sure the fault signal has dissappeared already.
* Otherwise the recovery can't succeed.
*
* @param[in] operator MCPWM operator, allocated by `mcpwm_new_operator()`
* @param[in] fault MCPWM fault handle
* @return
* - ESP_OK: Recover from fault successfully
* - ESP_ERR_INVALID_ARG: Recover from fault failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Recover from fault failed because the fault source is still active
* - ESP_FAIL: Recover from fault failed because of other error
*/
esp_err_t mcpwm_operator_recover_from_fault(mcpwm_oper_handle_t operator, mcpwm_fault_handle_t fault);
/**
* @brief Group of supported MCPWM operator event callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
mcpwm_brake_event_cb_t on_brake_cbc; /*!< callback function when mcpwm operator brakes in CBC */
mcpwm_brake_event_cb_t on_brake_ost; /*!< callback function when mcpwm operator brakes in OST */
} mcpwm_operator_event_callbacks_t;
/**
* @brief Set event callbacks for MCPWM operator
*
* @param[in] oper MCPWM operator handle, allocated by `mcpwm_new_operator()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t mcpwm_operator_register_event_callbacks(mcpwm_oper_handle_t oper, const mcpwm_operator_event_callbacks_t *cbs, void *user_data);
/**
* @brief MCPWM carrier configuration structure
*/
typedef struct {
uint32_t frequency_hz; /*!< Carrier frequency in Hz */
uint32_t first_pulse_duration_us; /*!< The duration of the first PWM pulse, in us */
float duty_cycle; /*!< Carrier duty cycle */
struct {
uint32_t invert_before_modulate: 1; /*!< Invert the raw signal */
uint32_t invert_after_modulate: 1; /*!< Invert the modulated signal */
} flags; /*!< Extra flags for carrier configuration */
} mcpwm_carrier_config_t;
/**
* @brief Apply carrier feature for MCPWM operator
*
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
* @param[in] config MCPWM carrier specific configuration
* @return
* - ESP_OK: Set carrier for operator successfully
* - ESP_ERR_INVALID_ARG: Set carrier for operator failed because of invalid argument
* - ESP_FAIL: Set carrier for operator failed because of other error
*/
esp_err_t mcpwm_operator_apply_carrier(mcpwm_oper_handle_t oper, const mcpwm_carrier_config_t *config);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief MCPWM peripheral contains many submodules, whose drivers are scattered in different header files.
* This header file serves as a prelude, contains every thing that is needed to work with the MCPWM peripheral.
*/
#pragma once
#include "driver/mcpwm_timer.h"
#include "driver/mcpwm_oper.h"
#include "driver/mcpwm_cmpr.h"
#include "driver/mcpwm_gen.h"
#include "driver/mcpwm_fault.h"
#include "driver/mcpwm_sync.h"
#include "driver/mcpwm_cap.h"

View File

@ -0,0 +1,114 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MCPWM timer sync source configuration
*/
typedef struct {
mcpwm_timer_event_t timer_event; /*!< Timer event, upon which MCPWM timer will generate the sync signal */
struct {
uint32_t propagate_input_sync: 1; /*!< The input sync signal would be routed to its sync output */
} flags; /*!< Extra configuration flags for timer sync source */
} mcpwm_timer_sync_src_config_t;
/**
* @brief Create MCPWM timer sync source
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @param[in] config MCPWM timer sync source configuration
* @param[out] ret_sync Returned MCPWM sync handle
* @return
* - ESP_OK: Create MCPWM timer sync source successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM timer sync source failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM timer sync source failed because out of memory
* - ESP_ERR_INVALID_STATE: Create MCPWM timer sync source failed because the timer has created a sync source before
* - ESP_FAIL: Create MCPWM timer sync source failed because of other error
*/
esp_err_t mcpwm_new_timer_sync_src(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync);
/**
* @brief MCPWM GPIO sync source configuration
*/
typedef struct {
int group_id; /*!< MCPWM group ID */
int gpio_num; /*!< GPIO used by sync source */
struct {
uint32_t active_neg: 1; /*!< Whether the sync signal is active on negedge, by default, the sync signal's posedge is treated as active */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
uint32_t pull_up: 1; /*!< Whether to pull up internally */
uint32_t pull_down: 1; /*!< Whether to pull down internally */
} flags; /*!< Extra configuration flags for GPIO sync source */
} mcpwm_gpio_sync_src_config_t;
/**
* @brief Create MCPWM GPIO sync source
*
* @param[in] config MCPWM GPIO sync source configuration
* @param[out] ret_sync Returned MCPWM GPIO sync handle
* @return
* - ESP_OK: Create MCPWM GPIO sync source successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM GPIO sync source failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM GPIO sync source failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM GPIO sync source failed because can't find free resource
* - ESP_FAIL: Create MCPWM GPIO sync source failed because of other error
*/
esp_err_t mcpwm_new_gpio_sync_src(const mcpwm_gpio_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync);
/**
* @brief MCPWM software sync configuration structure
*/
typedef struct {
} mcpwm_soft_sync_config_t;
/**
* @brief Create MCPWM software sync source
*
* @param[in] config MCPWM software sync source configuration
* @param[out] ret_sync Returned software sync handle
* @return
* - ESP_OK: Create MCPWM software sync successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM software sync failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM software sync failed because out of memory
* - ESP_FAIL: Create MCPWM software sync failed because of other error
*/
esp_err_t mcpwm_new_soft_sync_src(const mcpwm_soft_sync_config_t *config, mcpwm_sync_handle_t *ret_sync);
/**
* @brief Delete MCPWM sync source
*
* @param[in] sync MCPWM sync handle, allocated by `mcpwm_new_timer_sync_src()` or `mcpwm_new_gpio_sync_src()` or `mcpwm_new_soft_sync_src()`
* @return
* - ESP_OK: Delete MCPWM sync source successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM sync source failed because of invalid argument
* - ESP_FAIL: Delete MCPWM sync source failed because of other error
*/
esp_err_t mcpwm_del_sync_src(mcpwm_sync_handle_t sync);
/**
* @brief Activate the software sync, trigger the sync event for once
*
* @param[in] sync MCPWM soft sync handle, allocated by `mcpwm_new_soft_sync_src()`
* @return
* - ESP_OK: Trigger MCPWM software sync event successfully
* - ESP_ERR_INVALID_ARG: Trigger MCPWM software sync event failed because of invalid argument
* - ESP_FAIL: Trigger MCPWM software sync event failed because of other error
*/
esp_err_t mcpwm_soft_sync_activate(mcpwm_sync_handle_t sync);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,144 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Group of supported MCPWM timer event callbacks
* @note The callbacks are all running under ISR environment
*/
typedef struct {
mcpwm_timer_event_cb_t on_full; /*!< callback function when MCPWM timer counts to peak value */
mcpwm_timer_event_cb_t on_empty; /*!< callback function when MCPWM timer counts to zero */
mcpwm_timer_event_cb_t on_stop; /*!< callback function when MCPWM timer stops */
} mcpwm_timer_event_callbacks_t;
/**
* @brief MCPWM timer configuration
*/
typedef struct {
int group_id; /*!< Specify from which group to allocate the MCPWM timer */
mcpwm_timer_clock_source_t clk_src; /*!< MCPWM timer clock source */
uint32_t resolution_hz; /*!< Counter resolution in Hz, ranges from around 300KHz to 80MHz.
The step size of each count tick equals to (1 / resolution_hz) seconds */
mcpwm_timer_count_mode_t count_mode; /*!< Count mode */
uint32_t period_ticks; /*!< Number of count ticks within a period */
struct {
uint32_t update_period_on_empty: 1; /*!< Whether to update period when timer counts to zero */
uint32_t update_period_on_sync: 1; /*!< Whether to update period on sync event */
} flags; /*!< Extra configuration flags for timer */
} mcpwm_timer_config_t;
/**
* @brief Create MCPWM timer
*
* @param[in] config MCPWM timer configuration
* @param[out] ret_timer Returned MCPWM timer handle
* @return
* - ESP_OK: Create MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Create MCPWM timer failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MCPWM timer failed because out of memory
* - ESP_ERR_NOT_FOUND: Create MCPWM timer failed because all hardware timers are used up and no more free one
* - ESP_FAIL: Create MCPWM timer failed because of other error
*/
esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle_t *ret_timer);
/**
* @brief Delete MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @return
* - ESP_OK: Delete MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Delete MCPWM timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Delete MCPWM timer failed because timer is not in init state
* - ESP_FAIL: Delete MCPWM timer failed because of other error
*/
esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer);
/**
* @brief Enable MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @return
* - ESP_OK: Enable MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Enable MCPWM timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Enable MCPWM timer failed because timer is enabled already
* - ESP_FAIL: Enable MCPWM timer failed because of other error
*/
esp_err_t mcpwm_timer_enable(mcpwm_timer_handle_t timer);
/**
* @brief Disable MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @return
* - ESP_OK: Disable MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Disable MCPWM timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Disable MCPWM timer failed because timer is disabled already
* - ESP_FAIL: Disable MCPWM timer failed because of other error
*/
esp_err_t mcpwm_timer_disable(mcpwm_timer_handle_t timer);
/**
* @brief Send specific start/stop commands to MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @param[in] command Supported command list for MCPWM timer
* @return
* - ESP_OK: Start or stop MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Start or stop MCPWM timer failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Start or stop MCPWM timer failed because timer is not enabled
* - ESP_FAIL: Start or stop MCPWM timer failed because of other error
*/
esp_err_t mcpwm_timer_start_stop(mcpwm_timer_handle_t timer, mcpwm_timer_start_stop_cmd_t command);
/**
* @brief Set event callbacks for MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @param[in] cbs Group of callback functions
* @param[in] user_data User data, which will be passed to callback functions directly
* @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 timer is not in init state
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t mcpwm_timer_register_event_callbacks(mcpwm_timer_handle_t timer, const mcpwm_timer_event_callbacks_t *cbs, void *user_data);
/**
* @brief MCPWM Timer sync phase configuration
*/
typedef struct {
mcpwm_sync_handle_t sync_src; /*!< The sync event source. Set to NULL will disable the timer being synced by others */
uint32_t count_value; /*!< The count value that should lock to upon sync event */
mcpwm_timer_direction_t direction; /*!< The count direction that should lock to upon sync event */
} mcpwm_timer_sync_phase_config_t;
/**
* @brief Set sync phase for MCPWM timer
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @param[in] config MCPWM timer sync phase configuration
* @return
* - ESP_OK: Set sync phase for MCPWM timer successfully
* - ESP_ERR_INVALID_ARG: Set sync phase for MCPWM timer failed because of invalid argument
* - ESP_FAIL: Set sync phase for MCPWM timer failed because of other error
*/
esp_err_t mcpwm_timer_set_phase_on_sync(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_phase_config_t *config);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,145 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "hal/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Type of MCPWM timer handle
*/
typedef struct mcpwm_timer_t *mcpwm_timer_handle_t;
/**
* @brief Type of MCPWM operator handle
*/
typedef struct mcpwm_oper_t *mcpwm_oper_handle_t;
/**
* @brief Type of MCPWM comparator handle
*/
typedef struct mcpwm_cmpr_t *mcpwm_cmpr_handle_t;
/**
* @brief Type of MCPWM generator handle
*/
typedef struct mcpwm_gen_t *mcpwm_gen_handle_t;
/**
* @brief Type of MCPWM fault handle
*/
typedef struct mcpwm_fault_t *mcpwm_fault_handle_t;
/**
* @brief Type of MCPWM sync handle
*/
typedef struct mcpwm_sync_t *mcpwm_sync_handle_t;
/**
* @brief Type of MCPWM capture timer handle
*/
typedef struct mcpwm_cap_timer_t *mcpwm_cap_timer_handle_t;
/**
* @brief Type of MCPWM capture channel handle
*/
typedef struct mcpwm_cap_channel_t *mcpwm_cap_channel_handle_t;
/**
* @brief MCPWM timer event data
*/
typedef struct {
uint32_t count_value; /*!< MCPWM timer count value */
mcpwm_timer_direction_t direction; /*!< MCPWM timer count direction */
} mcpwm_timer_event_data_t;
/**
* @brief MCPWM timer event callback function
*
* @param[in] timer MCPWM timer handle
* @param[in] edata MCPWM timer event data, fed by driver
* @param[in] user_ctx User data, set in `mcpwm_timer_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*mcpwm_timer_event_cb_t)(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_ctx);
/**
* @brief MCPWM brake event data
*/
typedef struct {
} mcpwm_brake_event_data_t;
/**
* @brief MCPWM operator brake event callback function
*
* @param[in] operator MCPWM operator handle
* @param[in] edata MCPWM brake event data, fed by driver
* @param[in] user_ctx User data, set in `mcpwm_operator_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*mcpwm_brake_event_cb_t)(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_ctx);
/**
* @brief MCPWM fault event data
*/
typedef struct {
} mcpwm_fault_event_data_t;
/**
* @brief MCPWM fault event callback function
*
* @param fault MCPWM fault handle
* @param ev_data MCPWM fault event data, fed by driver
* @param user_ctx User data, set in `mcpwm_fault_register_event_callbacks()`
* @return whether a task switch is needed after the callback returns
*/
typedef bool (*mcpwm_fault_event_cb_t)(mcpwm_fault_handle_t fault, const mcpwm_fault_event_data_t *ev_data, void *user_ctx);
/**
* @brief MCPWM compare event data
*/
typedef struct {
uint32_t compare_ticks; /*!< Compare value */
mcpwm_timer_direction_t direction; /*!< Count direction */
} mcpwm_compare_event_data_t;
/**
* @brief MCPWM comparator event callback function
*
* @param comparator MCPWM comparator handle
* @param edata MCPWM comparator event data, fed by driver
* @param user_ctx User data, set in `mcpwm_comparator_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*mcpwm_compare_event_cb_t)(mcpwm_cmpr_handle_t comparator, const mcpwm_compare_event_data_t *edata, void *user_ctx);
/**
* @brief MCPWM capture event data
*/
typedef struct {
uint32_t cap_value; /*!< Captured value */
mcpwm_capture_edge_t cap_edge; /*!< Capture edge */
} mcpwm_capture_event_data_t;
/**
* @brief MCPWM capture event callback function
*
* @param cap_channel MCPWM capture channel handle
* @param ev_data MCPWM capture event data, fed by driver
* @param user_ctx User data, set in `mcpwm_capture_channel_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*mcpwm_capture_event_cb_t)(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_ctx);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// DO NOT USE THESE APIS IN YOUR APPLICATIONS
// The following APIs are for internal use, public to other IDF components, but not for users' applications.
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get MCPWM timer phase
*
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
* @param[out] count_value Returned MCPWM timer phase
* @param[out] direction Returned MCPWM timer counting direction
* @return
* - ESP_OK: Get MCPWM timer status successfully
* - ESP_ERR_INVALID_ARG: Get MCPWM timer status failed because of invalid argument
* - ESP_FAIL: Get MCPWM timer status failed because of other error
*/
esp_err_t mcpwm_timer_get_phase(mcpwm_timer_handle_t timer, uint32_t *count_value, mcpwm_timer_direction_t *direction);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,420 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_destory(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");
switch (config->clk_src) {
case MCPWM_CAPTURE_CLK_SRC_APB:
cap_timer->resolution_hz = esp_clk_apb_freq();
#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);
}
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;
// 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)", cap_timer, group_id);
return ESP_OK;
err:
if (cap_timer) {
mcpwm_cap_timer_destory(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_destory(cap_timer), TAG, "destory 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;
}
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_destory(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");
// 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;
mcpwm_ll_capture_enable_channel(hal->dev, cap_chan_id, true); // enable channel
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;
*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_destory(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");
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->gpio_num >= 0) {
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);
// disable capture channel
mcpwm_ll_capture_enable_channel(group->hal.dev, cap_channel->cap_chan_id, false);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_capture_channel_destory(cap_channel), TAG, "destory capture channel failed");
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_MCWPM_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) {
// we want the interrupt servie to be enabled after allocation successfully
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
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");
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();
}
}

View File

@ -0,0 +1,210 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_cmpr.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static void mcpwm_comparator_default_isr(void *args);
static esp_err_t mcpwm_comparator_register_to_operator(mcpwm_cmpr_t *cmpr, mcpwm_oper_t *oper)
{
int cmpr_id = -1;
portENTER_CRITICAL(&oper->spinlock);
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
if (!oper->comparators[i]) {
oper->comparators[i] = cmpr;
cmpr_id = i;
break;
}
}
portEXIT_CRITICAL(&oper->spinlock);
ESP_RETURN_ON_FALSE(cmpr_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free comparator in operator (%d,%d)", oper->group->group_id, oper->oper_id);
cmpr->cmpr_id = cmpr_id;
cmpr->operator = oper;
return ESP_OK;
}
static void mcpwm_comparator_unregister_from_operator(mcpwm_cmpr_t *cmpr)
{
mcpwm_oper_t *oper = cmpr->operator;
int cmpr_id = cmpr->cmpr_id;
portENTER_CRITICAL(&oper->spinlock);
oper->comparators[cmpr_id] = NULL;
portEXIT_CRITICAL(&oper->spinlock);
}
static esp_err_t mcpwm_comparator_destory(mcpwm_cmpr_t *cmpr)
{
if (cmpr->intr) {
ESP_RETURN_ON_ERROR(esp_intr_free(cmpr->intr), TAG, "uninstall interrupt service failed");
}
if (cmpr->operator) {
mcpwm_comparator_unregister_from_operator(cmpr);
}
free(cmpr);
return ESP_OK;
}
esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_config_t *config, mcpwm_cmpr_handle_t *ret_cmpr)
{
esp_err_t ret = ESP_OK;
mcpwm_cmpr_t *cmpr = NULL;
ESP_GOTO_ON_FALSE(oper && config && ret_cmpr, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
cmpr = heap_caps_calloc(1, sizeof(mcpwm_cmpr_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(cmpr, ESP_ERR_NO_MEM, err, TAG, "no mem for comparator");
ESP_GOTO_ON_ERROR(mcpwm_comparator_register_to_operator(cmpr, oper), err, TAG, "register comparator failed");
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
int cmpr_id = cmpr->cmpr_id;
mcpwm_ll_operator_enable_update_compare_on_tez(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_tez);
mcpwm_ll_operator_enable_update_compare_on_tep(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_tep);
mcpwm_ll_operator_enable_update_compare_on_sync(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_sync);
// fill in other comparator members
cmpr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
*ret_cmpr = cmpr;
ESP_LOGD(TAG, "new comparator (%d,%d,%d) at %p", group->group_id, oper_id, cmpr_id, cmpr);
return ESP_OK;
err:
if (cmpr) {
mcpwm_comparator_destory(cmpr);
}
return ret;
}
esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr)
{
ESP_RETURN_ON_FALSE(cmpr, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *operator= cmpr->operator;
mcpwm_group_t *group = operator->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = operator->oper_id;
int cmpr_id = cmpr->cmpr_id;
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id), false);
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id));
portEXIT_CRITICAL(&group->spinlock);
ESP_LOGD(TAG, "del comparator (%d,%d,%d)", group->group_id, oper_id, cmpr_id);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_comparator_destory(cmpr), TAG, "destory comparator failed");
return ESP_OK;
}
esp_err_t mcpwm_comparator_set_compare_value(mcpwm_cmpr_handle_t cmpr, uint32_t cmp_ticks)
{
ESP_RETURN_ON_FALSE(cmpr, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *oper = cmpr->operator;
mcpwm_group_t *group = oper->group;
mcpwm_timer_t *timer = oper->timer;
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_STATE, TAG, "timer and operator are not connected");
ESP_RETURN_ON_FALSE(cmp_ticks < timer->peak_ticks, ESP_ERR_INVALID_ARG, TAG, "compare value out of range");
portENTER_CRITICAL_SAFE(&cmpr->spinlock);
mcpwm_ll_operator_set_compare_value(group->hal.dev, oper->oper_id, cmpr->cmpr_id, cmp_ticks);
portEXIT_CRITICAL_SAFE(&cmpr->spinlock);
cmpr->compare_ticks = cmp_ticks;
return ESP_OK;
}
esp_err_t mcpwm_comparator_register_event_callbacks(mcpwm_cmpr_handle_t cmpr, const mcpwm_comparator_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(cmpr && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *oper = cmpr->operator;
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int group_id = group->group_id;
int oper_id = oper->oper_id;
int cmpr_id = cmpr->cmpr_id;
#if CONFIG_MCWPM_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 (!cmpr->intr) {
// we want the interrupt servie to be enabled after allocation successfully
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
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_CMP_EQUAL(oper_id, cmpr_id),
mcpwm_comparator_default_isr, cmpr, &cmpr->intr), TAG, "install interrupt service for comparator failed");
}
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id), cbs->on_reach != NULL);
portEXIT_CRITICAL(&group->spinlock);
cmpr->on_reach = cbs->on_reach;
cmpr->user_data = user_data;
return ESP_OK;
}
static void IRAM_ATTR mcpwm_comparator_default_isr(void *args)
{
mcpwm_cmpr_t *cmpr = (mcpwm_cmpr_t *)args;
mcpwm_oper_t *oper = cmpr->operator;
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
int cmpr_id = cmpr->cmpr_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_CMP_EQUAL(oper_id, cmpr_id));
mcpwm_compare_event_data_t edata = {
.compare_ticks = cmpr->compare_ticks,
// .direction = TODO
};
if (status & MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id)) {
mcpwm_compare_event_cb_t cb = cmpr->on_reach;
if (cb) {
if (cb(cmpr, &edata, cmpr->user_data)) {
need_yield = true;
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,135 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <sys/lock.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 "esp_log.h"
#include "esp_check.h"
#include "esp_private/periph_ctrl.h"
#include "soc/mcpwm_periph.h"
#include "hal/mcpwm_ll.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
typedef struct {
_lock_t mutex; // platform level mutex lock
mcpwm_group_t *groups[SOC_MCPWM_GROUPS]; // array of MCPWM group instances
int group_ref_counts[SOC_MCPWM_GROUPS]; // reference count used to protect group install/uninstall
} mcpwm_platform_t;
static mcpwm_platform_t s_platform; // singleton platform
mcpwm_group_t *mcpwm_acquire_group_handle(int group_id)
{
bool new_group = false;
mcpwm_group_t *group = NULL;
// prevent install mcpwm group concurrently
_lock_acquire(&s_platform.mutex);
if (!s_platform.groups[group_id]) {
group = heap_caps_calloc(1, sizeof(mcpwm_group_t), MCPWM_MEM_ALLOC_CAPS);
if (group) {
new_group = true;
s_platform.groups[group_id] = group;
group->group_id = group_id;
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
// enable APB to access MCPWM registers
periph_module_enable(mcpwm_periph_signals.groups[group_id].module);
periph_module_reset(mcpwm_periph_signals.groups[group_id].module);
// initialize HAL context
mcpwm_hal_init_config_t hal_config = {
.group_id = group_id
};
mcpwm_hal_context_t *hal = &group->hal;
mcpwm_hal_init(hal, &hal_config);
// disable all interrupts and clear pending status
mcpwm_ll_intr_enable(hal->dev, UINT32_MAX, false);
mcpwm_ll_intr_clear_status(hal->dev, UINT32_MAX);
}
} else { // group already install
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;
}
void mcpwm_release_group_handle(mcpwm_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) {
do_deinitialize = true;
s_platform.groups[group_id] = NULL; // deregister from platfrom
// hal layer deinitialize
mcpwm_hal_deinit(&group->hal);
periph_module_disable(mcpwm_periph_signals.groups[group_id].module);
free(group);
}
_lock_release(&s_platform.mutex);
if (do_deinitialize) {
ESP_LOGD(TAG, "del group(%d)", group_id);
}
}
esp_err_t mcpwm_select_periph_clock(mcpwm_group_t *group, mcpwm_timer_clock_source_t clk_src)
{
esp_err_t ret = ESP_OK;
uint32_t periph_src_clk_hz = 0;
bool clock_selection_conflict = false;
bool do_clock_init = false;
// check if we need to update the group clock source, group clock source is shared by all mcpwm objects
portENTER_CRITICAL(&group->spinlock);
if (group->clk_src == 0) {
group->clk_src = clk_src;
do_clock_init = true;
} else {
clock_selection_conflict = (group->clk_src != clk_src);
}
portEXIT_CRITICAL(&group->spinlock);
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
"group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src);
if (do_clock_init) {
// [clk_tree] ToDo: replace the following switch-case table by clock_tree APIs
switch (clk_src) {
case MCPWM_TIMER_CLK_SRC_DEFAULT:
periph_src_clk_hz = 160000000;
#if CONFIG_PM_ENABLE
sprintf(group->pm_lock_name, "mcpwm_%d", group->group_id); // e.g. mcpwm_0
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, group->pm_lock_name, &group->pm_lock);
ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
ESP_LOGD(TAG, "install ESP_PM_APB_FREQ_MAX lock for MCPWM group(%d)", group->group_id);
#endif // CONFIG_PM_ENABLE
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not supported", clk_src);
break;
}
mcpwm_ll_group_set_clock_prescale(group->hal.dev, MCPWM_PERIPH_CLOCK_PRE_SCALE);
group->resolution_hz = periph_src_clk_hz / MCPWM_PERIPH_CLOCK_PRE_SCALE;
ESP_LOGD(TAG, "group (%d) clock resolution:%uHz", group->group_id, group->resolution_hz);
}
return ret;
}

View File

@ -0,0 +1,302 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_fault.h"
#include "driver/gpio.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static void mcpwm_gpio_fault_default_isr(void *args);
static esp_err_t mcpwm_del_gpio_fault(mcpwm_fault_handle_t fault);
static esp_err_t mcpwm_del_soft_fault(mcpwm_fault_handle_t fault);
static esp_err_t mcpwm_gpio_fault_register_to_group(mcpwm_gpio_fault_t *fault, 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);
int fault_id = -1;
portENTER_CRITICAL(&group->spinlock);
for (int i = 0; i < SOC_MCPWM_GPIO_FAULTS_PER_GROUP; i++) {
if (!group->gpio_faults[i]) {
fault_id = i;
group->gpio_faults[i] = fault;
break;
}
}
portEXIT_CRITICAL(&group->spinlock);
if (fault_id < 0) {
mcpwm_release_group_handle(group);
group = NULL;
} else {
fault->base.group = group;
fault->fault_id = fault_id;
}
ESP_RETURN_ON_FALSE(fault_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free gpio fault in group (%d)", group_id);
return ESP_OK;
}
static void mcpwm_gpio_fault_unregister_from_group(mcpwm_gpio_fault_t *fault)
{
mcpwm_group_t *group = fault->base.group;
int fault_id = fault->fault_id;
portENTER_CRITICAL(&group->spinlock);
group->gpio_faults[fault_id] = NULL;
portEXIT_CRITICAL(&group->spinlock);
// fault has a reference on group, release it now
mcpwm_release_group_handle(group);
}
static esp_err_t mcpwm_gpio_fault_destory(mcpwm_gpio_fault_t *fault)
{
if (fault->intr) {
ESP_RETURN_ON_ERROR(esp_intr_free(fault->intr), TAG, "uninstall interrupt service failed");
}
if (fault->base.group) {
mcpwm_gpio_fault_unregister_from_group(fault);
}
free(fault);
return ESP_OK;
}
esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fault_handle_t *ret_fault)
{
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
mcpwm_gpio_fault_t *fault = NULL;
ESP_GOTO_ON_FALSE(config && ret_fault, 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);
fault = heap_caps_calloc(1, sizeof(mcpwm_gpio_fault_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(fault, ESP_ERR_NO_MEM, err, TAG, "no mem for gpio fault");
ESP_GOTO_ON_ERROR(mcpwm_gpio_fault_register_to_group(fault, config->group_id), err, TAG, "register gpio fault failed");
mcpwm_group_t *group = fault->base.group;
int group_id = group->group_id;
mcpwm_hal_context_t *hal = &group->hal;
int fault_id = fault->fault_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
.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 fault GPIO failed");
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group_id].gpio_faults[fault_id].fault_sig, 0);
// set fault detection polarity
// different gpio faults share the same config register, using a group level spin lock
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_fault_set_active_level(hal->dev, fault_id, config->flags.active_level);
portEXIT_CRITICAL(&group->spinlock);
// enable fault detection
mcpwm_ll_fault_enable_detection(hal->dev, fault_id, true);
// fill in other operator members
fault->base.type = MCPWM_FAULT_TYPE_GPIO;
fault->gpio_num = config->gpio_num;
fault->base.del = mcpwm_del_gpio_fault;
*ret_fault = &fault->base;
ESP_LOGD(TAG, "new gpio fault (%d,%d) at %p, GPIO: %d", group_id, fault_id, fault, config->gpio_num);
return ESP_OK;
err:
if (fault) {
mcpwm_gpio_fault_destory(fault);
}
return ret;
}
static esp_err_t mcpwm_del_gpio_fault(mcpwm_fault_handle_t fault)
{
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
mcpwm_group_t *group = fault->group;
mcpwm_hal_context_t *hal = &group->hal;
int fault_id = gpio_fault->fault_id;
ESP_LOGD(TAG, "del GPIO fault (%d,%d)", group->group_id, fault_id);
gpio_reset_pin(gpio_fault->gpio_num);
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_MASK(fault_id), false);
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_FAULT_MASK(fault_id));
portEXIT_CRITICAL(&group->spinlock);
// disable fault detection
mcpwm_ll_fault_enable_detection(hal->dev, fault_id, false);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_gpio_fault_destory(gpio_fault), TAG, "destory GPIO fault failed");
return ESP_OK;
}
esp_err_t mcpwm_new_soft_fault(const mcpwm_soft_fault_config_t *config, mcpwm_fault_handle_t *ret_fault)
{
esp_err_t ret = ESP_OK;
mcpwm_soft_fault_t *soft_fault = NULL;
ESP_GOTO_ON_FALSE(config && ret_fault, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
soft_fault = heap_caps_calloc(1, sizeof(mcpwm_soft_fault_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(soft_fault, ESP_ERR_NO_MEM, err, TAG, "no mem for soft fault");
// fill in other fault members
soft_fault->base.type = MCPWM_FAULT_TYPE_SOFT;
soft_fault->base.del = mcpwm_del_soft_fault;
*ret_fault = &soft_fault->base;
ESP_LOGD(TAG, "new soft fault at %p", soft_fault);
return ESP_OK;
err:
if (soft_fault) {
free(soft_fault);
}
return ret;
}
static esp_err_t mcpwm_del_soft_fault(mcpwm_fault_handle_t fault)
{
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
ESP_LOGD(TAG, "del soft fault %p", soft_fault);
free(soft_fault);
return ESP_OK;
}
esp_err_t mcpwm_soft_fault_activate(mcpwm_fault_handle_t fault)
{
ESP_RETURN_ON_FALSE(fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(fault->type == MCPWM_FAULT_TYPE_SOFT, ESP_ERR_INVALID_ARG, TAG, "not a valid soft fault");
mcpwm_group_t *group = fault->group;
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
mcpwm_oper_t *operator = soft_fault->operator;
ESP_RETURN_ON_FALSE(operator, ESP_ERR_INVALID_STATE, TAG, "no operator is assigned to the fault");
switch (operator->brake_mode_on_soft_fault) {
case MCPWM_OPER_BRAKE_MODE_CBC:
mcpwm_ll_brake_trigger_soft_cbc(group->hal.dev, operator->oper_id);
break;
case MCPWM_OPER_BRAKE_MODE_OST:
mcpwm_ll_brake_trigger_soft_ost(group->hal.dev, operator->oper_id);
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "unknown brake mode:%d", operator->brake_mode_on_soft_fault);
break;
}
return ESP_OK;
}
esp_err_t mcpwm_del_fault(mcpwm_fault_handle_t fault)
{
ESP_RETURN_ON_FALSE(fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return fault->del(fault);
}
esp_err_t mcpwm_fault_register_event_callbacks(mcpwm_fault_handle_t fault, const mcpwm_fault_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(fault && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(fault->type == MCPWM_FAULT_TYPE_GPIO, ESP_ERR_INVALID_ARG, TAG, "only gpio fault can register event callback");
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
mcpwm_group_t *group = fault->group;
int group_id = group->group_id;
mcpwm_hal_context_t *hal = &group->hal;
int fault_id = gpio_fault->fault_id;
#if CONFIG_MCWPM_ISR_IRAM_SAFE
if (cbs->on_fault_enter) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_fault_enter), ESP_ERR_INVALID_ARG, TAG, "on_fault_enter callback not in IRAM");
}
if (cbs->on_fault_exit) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_fault_exit), ESP_ERR_INVALID_ARG, TAG, "on_fault_exit 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 (!gpio_fault->intr) {
// we want the interrupt servie to be enabled after allocation successfully
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ESP_INTR_FLAG_INTRDISABLED;
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_FAULT_MASK(fault_id),
mcpwm_gpio_fault_default_isr, gpio_fault, &gpio_fault->intr), TAG, "install interrupt service for gpio fault failed");
}
// different mcpwm events share the same interrupt control register
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_ENTER(fault_id), cbs->on_fault_enter != NULL);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_EXIT(fault_id), cbs->on_fault_exit != NULL);
portEXIT_CRITICAL(&group->spinlock);
gpio_fault->on_fault_enter = cbs->on_fault_enter;
gpio_fault->on_fault_exit = cbs->on_fault_exit;
gpio_fault->user_data = user_data;
return ESP_OK;
}
static void IRAM_ATTR mcpwm_gpio_fault_default_isr(void *args)
{
mcpwm_gpio_fault_t *fault = (mcpwm_gpio_fault_t *)args;
mcpwm_group_t *group = fault->base.group;
mcpwm_hal_context_t *hal = &group->hal;
int fault_id = fault->fault_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_FAULT_MASK(fault_id));
mcpwm_fault_event_data_t edata = {
// TODO
};
if (status & MCPWM_LL_EVENT_FAULT_ENTER(fault_id)) {
mcpwm_fault_event_cb_t cb = fault->on_fault_enter;
if (cb) {
if (cb(&fault->base, &edata, fault->user_data)) {
need_yield = true;
}
}
}
if (status & MCPWM_LL_EVENT_FAULT_EXIT(fault_id)) {
mcpwm_fault_event_cb_t cb = fault->on_fault_exit;
if (cb) {
if (cb(&fault->base, &edata, fault->user_data)) {
need_yield = true;
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2022 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_err.h"
#include "esp_log.h"
#include "soc/soc_caps.h"
#include "soc/mcpwm_periph.h"
#include "hal/mcpwm_ll.h"
#include "driver/gpio.h"
#include "driver/mcpwm_gen.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static esp_err_t mcpwm_generator_register_to_operator(mcpwm_gen_t *gen, mcpwm_oper_t *oper)
{
int gen_id = -1;
portENTER_CRITICAL(&oper->spinlock);
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
if (!oper->generators[i]) {
oper->generators[i] = gen;
gen_id = i;
break;
}
}
portEXIT_CRITICAL(&oper->spinlock);
ESP_RETURN_ON_FALSE(gen_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free generator in operator (%d,%d)", oper->group->group_id, oper->oper_id);
gen->gen_id = gen_id;
gen->operator = oper;
return ESP_OK;
}
static void mcpwm_generator_unregister_from_operator(mcpwm_gen_t *gen)
{
mcpwm_oper_t *oper = gen->operator;
int gen_id = gen->gen_id;
portENTER_CRITICAL(&oper->spinlock);
oper->generators[gen_id] = NULL;
portEXIT_CRITICAL(&oper->spinlock);
}
static esp_err_t mcpwm_generator_destory(mcpwm_gen_t *gen)
{
if (gen->operator) {
mcpwm_generator_unregister_from_operator(gen);
}
free(gen);
return ESP_OK;
}
esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_config_t *config, mcpwm_gen_handle_t *ret_gen)
{
esp_err_t ret = ESP_OK;
mcpwm_gen_t *gen = NULL;
ESP_GOTO_ON_FALSE(oper && config && ret_gen, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
gen = heap_caps_calloc(1, sizeof(mcpwm_gen_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(gen, ESP_ERR_NO_MEM, err, TAG, "no mem for generator");
ESP_GOTO_ON_ERROR(mcpwm_generator_register_to_operator(gen, oper), err, TAG, "register generator failed");
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
int gen_id = gen->gen_id;
// reset generator
mcpwm_hal_generator_reset(hal, oper_id, gen_id);
// GPIO configuration
gpio_config_t gpio_conf = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_OUTPUT | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0), // also enable the input path if `io_loop_back` is enabled
.pin_bit_mask = (1ULL << config->gen_gpio_num),
.pull_down_en = false,
.pull_up_en = true,
};
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config gen GPIO failed");
esp_rom_gpio_connect_out_signal(config->gen_gpio_num,
mcpwm_periph_signals.groups[group->group_id].operators[oper_id].generators[gen_id].pwm_sig,
config->flags.invert_pwm, 0);
// fill in other generator members
gen->gen_gpio_num = config->gen_gpio_num;
gen->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
*ret_gen = gen;
ESP_LOGD(TAG, "new generator (%d,%d,%d) at %p, GPIO %d", group->group_id, oper_id, gen_id, gen, gen->gen_gpio_num);
return ESP_OK;
err:
if (gen) {
mcpwm_generator_destory(gen);
}
return ret;
}
esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen)
{
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *oper = gen->operator;
mcpwm_group_t *group = oper->group;
ESP_LOGD(TAG, "del generator (%d,%d,%d)", group->group_id, oper->oper_id, gen->gen_id);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_generator_destory(gen), TAG, "destory generator failed");
return ESP_OK;
}
esp_err_t mcpwm_generator_set_force_level(mcpwm_gen_handle_t gen, int level, bool hold_on)
{
ESP_RETURN_ON_FALSE(gen && level <= 1, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *oper = gen->operator;
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
int gen_id = gen->gen_id;
if (level < 0) { // to remove the force level
if (hold_on) {
mcpwm_ll_gen_disable_continue_force_action(hal->dev, oper_id, gen_id);
} else {
mcpwm_ll_gen_disable_noncontinue_force_action(hal->dev, oper_id, gen_id);
}
} else { // to enable the force output level
if (hold_on) {
mcpwm_ll_gen_set_continue_force_level(hal->dev, oper_id, gen_id, level);
} else {
mcpwm_ll_gen_set_noncontinue_force_level(hal->dev, oper_id, gen_id, level);
mcpwm_ll_gen_trigger_noncontinue_force_action(hal->dev, oper_id, gen_id);
}
}
return ESP_OK;
}
esp_err_t mcpwm_generator_set_actions_on_timer_event(mcpwm_gen_handle_t gen, mcpwm_gen_timer_event_action_t ev_act, ...)
{
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *operator= gen->operator;
mcpwm_group_t *group = operator->group;
mcpwm_timer_t *timer = operator->timer;
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_STATE, TAG, "no timer is connected to the operator");
mcpwm_gen_timer_event_action_t ev_act_itor = ev_act;
bool invalid_utep = false;
bool invalid_dtez = false;
va_list it;
va_start(it, ev_act);
while (ev_act_itor.event != MCPWM_TIMER_EVENT_INVALID) {
invalid_utep = (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) &&
(ev_act_itor.direction == MCPWM_TIMER_DIRECTION_UP) &&
(ev_act_itor.event == MCPWM_TIMER_EVENT_FULL);
invalid_dtez = (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) &&
(ev_act_itor.direction == MCPWM_TIMER_DIRECTION_DOWN) &&
(ev_act_itor.event == MCPWM_TIMER_EVENT_EMPTY);
if (invalid_utep || invalid_dtez) {
va_end(it);
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "UTEP and DTEZ can't be reached under MCPWM_TIMER_COUNT_MODE_UP_DOWN mode");
}
mcpwm_ll_generator_set_action_on_timer_event(group->hal.dev, operator->oper_id, gen->gen_id,
ev_act_itor.direction, ev_act_itor.event, ev_act_itor.action);
ev_act_itor = va_arg(it, mcpwm_gen_timer_event_action_t);
}
va_end(it);
return ESP_OK;
}
esp_err_t mcpwm_generator_set_actions_on_compare_event(mcpwm_gen_handle_t gen, mcpwm_gen_compare_event_action_t ev_act, ...)
{
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *operator= gen->operator;
mcpwm_group_t *group = operator->group;
mcpwm_gen_compare_event_action_t ev_act_itor = ev_act;
va_list it;
va_start(it, ev_act);
while (ev_act_itor.comparator) {
mcpwm_ll_generator_set_action_on_compare_event(group->hal.dev, operator->oper_id, gen->gen_id,
ev_act_itor.direction, ev_act_itor.comparator->cmpr_id, ev_act_itor.action);
ev_act_itor = va_arg(it, mcpwm_gen_compare_event_action_t);
}
va_end(it);
return ESP_OK;
}
esp_err_t mcpwm_generator_set_actions_on_brake_event(mcpwm_gen_handle_t gen, mcpwm_gen_brake_event_action_t ev_act, ...)
{
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_oper_t *operator= gen->operator;
mcpwm_group_t *group = operator->group;
mcpwm_gen_brake_event_action_t ev_act_itor = ev_act;
va_list it;
va_start(it, ev_act);
while (ev_act_itor.brake_mode != MCPWM_OPER_BRAKE_MODE_INVALID) {
mcpwm_ll_generator_set_action_on_brake_event(group->hal.dev, operator->oper_id, gen->gen_id,
ev_act_itor.direction, ev_act_itor.brake_mode, ev_act_itor.action);
ev_act_itor = va_arg(it, mcpwm_gen_brake_event_action_t);
}
va_end(it);
return ESP_OK;
}
esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t in_generator, mcpwm_gen_handle_t out_generator, const mcpwm_dead_time_config_t *config)
{
ESP_RETURN_ON_FALSE(in_generator && out_generator && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(in_generator->operator == out_generator->operator, ESP_ERR_INVALID_ARG, TAG, "in/out generator are not derived from the same operator");
ESP_RETURN_ON_FALSE(config->negedge_delay_ticks < MCPWM_LL_MAX_DEAD_DELAY && config->posedge_delay_ticks < MCPWM_LL_MAX_DEAD_DELAY,
ESP_ERR_INVALID_ARG, TAG, "delay time out of range");
mcpwm_oper_t *operator= in_generator->operator;
mcpwm_group_t *group = operator->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = operator->oper_id;
// Note: to better understand the following code, you should read the deadtime module topology diagram in the TRM
// check if we want to bypass the deadtime module
bool bypass = (config->negedge_delay_ticks == 0) && (config->posedge_delay_ticks == 0);
// check is we want to delay on the both edge
bool delay_on_both_edge = config->posedge_delay_ticks && config->negedge_delay_ticks;
int out_path_id = -1;
if (bypass) {
// out path is same to the input path of generator
out_path_id = in_generator->gen_id;
} else if (config->negedge_delay_ticks) {
out_path_id = 1; // FED path
} else {
out_path_id = 0; // RED path
}
bool swap_path = out_path_id != out_generator->gen_id;
mcpwm_ll_deadtime_bypass_path(hal->dev, oper_id, out_path_id, bypass); // S0/1
if (!bypass) {
if (config->posedge_delay_ticks) {
mcpwm_ll_deadtime_red_select_generator(hal->dev, oper_id, in_generator->gen_id); // S4
} else {
mcpwm_ll_deadtime_fed_select_generator(hal->dev, oper_id, in_generator->gen_id); // S5
}
mcpwm_ll_deadtime_enable_deb(hal->dev, oper_id, delay_on_both_edge); // S8
mcpwm_ll_deadtime_invert_outpath(hal->dev, oper_id, out_path_id, config->flags.invert_output); // S2/3
mcpwm_ll_deadtime_swap_out_path(hal->dev, oper_id, out_generator->gen_id, swap_path); // S6/S7
}
// set delay time
if (config->posedge_delay_ticks) {
mcpwm_ll_deadtime_set_rising_delay(hal->dev, oper_id, config->posedge_delay_ticks);
}
if (config->negedge_delay_ticks) {
mcpwm_ll_deadtime_set_falling_delay(hal->dev, oper_id, config->negedge_delay_ticks);
}
ESP_LOGD(TAG, "operator (%d,%d) dead time (R:%u,F:%u), topology code:%x", group->group_id, oper_id,
config->posedge_delay_ticks, config->negedge_delay_ticks, mcpwm_ll_deadtime_get_switch_topology(hal->dev, oper_id));
return ESP_OK;
}

View File

@ -0,0 +1,363 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_oper.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static void mcpwm_operator_default_isr(void *args);
static esp_err_t mcpwm_operator_register_to_group(mcpwm_oper_t *oper, 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);
int oper_id = -1;
portENTER_CRITICAL(&group->spinlock);
for (int i = 0; i < SOC_MCPWM_OPERATORS_PER_GROUP; i++) {
if (!group->operators[i]) {
oper_id = i;
group->operators[i] = oper;
break;
}
}
portEXIT_CRITICAL(&group->spinlock);
if (oper_id < 0) {
mcpwm_release_group_handle(group);
group = NULL;
} else {
oper->group = group;
oper->oper_id = oper_id;
}
ESP_RETURN_ON_FALSE(oper_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free operators in group (%d)", group_id);
return ESP_OK;
}
static void mcpwm_operator_unregister_from_group(mcpwm_oper_t *oper)
{
mcpwm_group_t *group = oper->group;
int oper_id = oper->oper_id;
portENTER_CRITICAL(&group->spinlock);
group->operators[oper_id] = NULL;
portEXIT_CRITICAL(&group->spinlock);
// operator has a reference on group, release it now
mcpwm_release_group_handle(group);
}
static esp_err_t mcpwm_operator_destory(mcpwm_oper_t *oper)
{
if (oper->intr) {
ESP_RETURN_ON_ERROR(esp_intr_free(oper->intr), TAG, "uninstall interrupt service failed");
}
if (oper->group) {
mcpwm_operator_unregister_from_group(oper);
}
free(oper);
return ESP_OK;
}
esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_handle_t *ret_oper)
{
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
mcpwm_oper_t *operator= NULL;
ESP_GOTO_ON_FALSE(config && ret_oper, 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);
operator= heap_caps_calloc(1, sizeof(mcpwm_oper_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(operator, ESP_ERR_NO_MEM, err, TAG, "no mem for operator");
ESP_GOTO_ON_ERROR(mcpwm_operator_register_to_group(operator, config->group_id), err, TAG, "register operator failed");
mcpwm_group_t *group = operator->group;
int group_id = group->group_id;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = operator->oper_id;
// reset MCPWM operator
mcpwm_hal_operator_reset(hal, oper_id);
// set the time point that the generator can update the action
mcpwm_ll_operator_enable_update_action_on_tez(hal->dev, oper_id, config->flags.update_gen_action_on_tez);
mcpwm_ll_operator_enable_update_action_on_tep(hal->dev, oper_id, config->flags.update_gen_action_on_tep);
mcpwm_ll_operator_enable_update_action_on_sync(hal->dev, oper_id, config->flags.update_gen_action_on_sync);
// set the time point that the deadtime can update the delay parameter
mcpwm_ll_deadtime_enable_update_delay_on_tez(hal->dev, oper_id, config->flags.update_dead_time_on_tez);
mcpwm_ll_deadtime_enable_update_delay_on_tep(hal->dev, oper_id, config->flags.update_dead_time_on_tep);
mcpwm_ll_deadtime_enable_update_delay_on_sync(hal->dev, oper_id, config->flags.update_dead_time_on_sync);
// set the clock source for dead time submodule, the resolution is the same to the MCPWM group
mcpwm_ll_operator_set_deadtime_clock_src(hal->dev, oper_id, MCPWM_LL_DEADTIME_CLK_SRC_GROUP);
operator->deadtime_resolution_hz = group->resolution_hz;
// fill in other operator members
operator->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
*ret_oper = operator;
ESP_LOGD(TAG, "new operator (%d,%d) at %p", group_id, oper_id, operator);
return ESP_OK;
err:
if (operator) {
mcpwm_operator_destory(operator);
}
return ret;
}
esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper)
{
ESP_RETURN_ON_FALSE(oper, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
ESP_RETURN_ON_FALSE(!oper->comparators[i], ESP_ERR_INVALID_STATE, TAG, "comparator still in working");
}
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
ESP_RETURN_ON_FALSE(!oper->generators[i], ESP_ERR_INVALID_STATE, TAG, "generator still in working");
}
ESP_RETURN_ON_FALSE(!oper->soft_fault, ESP_ERR_INVALID_STATE, TAG, "soft fault still in working");
mcpwm_group_t *group = oper->group;
int oper_id = oper->oper_id;
mcpwm_hal_context_t *hal = &group->hal;
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_MASK(oper_id), false);
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_OPER_MASK(oper_id));
portEXIT_CRITICAL(&group->spinlock);
ESP_LOGD(TAG, "del operator (%d,%d)", group->group_id, oper_id);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_operator_destory(oper), TAG, "destory operator failed");
return ESP_OK;
}
esp_err_t mcpwm_operator_connect_timer(mcpwm_oper_handle_t oper, mcpwm_timer_handle_t timer)
{
ESP_RETURN_ON_FALSE(oper && timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(oper->group == timer->group, ESP_ERR_INVALID_ARG, TAG, "operator and timer should reside in the same group");
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
// connect operator and timer
mcpwm_ll_operator_connect_timer(hal->dev, oper->oper_id, timer->timer_id);
// change the the clock source of deadtime submodule to use MCPWM timer
mcpwm_ll_operator_set_deadtime_clock_src(hal->dev, oper->oper_id, MCPWM_LL_DEADTIME_CLK_SRC_TIMER);
oper->deadtime_resolution_hz = timer->resolution_hz;
oper->timer = timer;
ESP_LOGD(TAG, "connect operator (%d) and timer (%d) in group (%d)", oper->oper_id, timer->timer_id, group->group_id);
return ESP_OK;
}
esp_err_t mcpwm_operator_apply_carrier(mcpwm_oper_handle_t oper, const mcpwm_carrier_config_t *config)
{
ESP_RETURN_ON_FALSE(oper, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
uint32_t real_frequency = 0;
uint32_t real_fpd = 0;
float real_duty = 0.0;
if (config && config->frequency_hz) {
uint8_t pre_scale = group->resolution_hz / 8 / config->frequency_hz;
mcpwm_ll_carrier_set_prescale(hal->dev, oper_id, pre_scale);
real_frequency = group->resolution_hz / 8 / pre_scale;
uint8_t duty = (uint8_t)(config->duty_cycle * 8);
mcpwm_ll_carrier_set_duty(hal->dev, oper_id, duty);
real_duty = (float) duty / 8.0F;
uint8_t first_pulse_ticks = (uint8_t)(config->first_pulse_duration_us * real_frequency / 1000000UL);
ESP_RETURN_ON_FALSE(first_pulse_ticks > 0 && first_pulse_ticks <= MCPWM_LL_MAX_CARRIER_ONESHOT,
ESP_ERR_INVALID_ARG, TAG, "invalid first pulse duration");
mcpwm_ll_carrier_set_first_pulse_width(hal->dev, oper_id, first_pulse_ticks);
real_fpd = first_pulse_ticks * 1000000UL / real_frequency;
mcpwm_ll_carrier_in_invert(hal->dev, oper_id, config->flags.invert_before_modulate);
mcpwm_ll_carrier_out_invert(hal->dev, oper_id, config->flags.invert_after_modulate);
}
mcpwm_ll_carrier_enable(hal->dev, oper_id, real_frequency > 0);
if (real_frequency > 0) {
ESP_LOGD(TAG, "enable carrier modulation for operator(%d,%d), freq=%uHz, duty=%.2f, FPD=%dus",
group->group_id, oper_id, real_frequency, real_duty, real_fpd);
} else {
ESP_LOGD(TAG, "disable carrier for operator (%d,%d)", group->group_id, oper_id);
}
return ESP_OK;
}
esp_err_t mcpwm_operator_register_event_callbacks(mcpwm_oper_handle_t oper, const mcpwm_operator_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(oper && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int group_id = group->group_id;
int oper_id = oper->oper_id;
#if CONFIG_MCWPM_ISR_IRAM_SAFE
if (cbs->on_brake_cbc) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_brake_cbc), ESP_ERR_INVALID_ARG, TAG, "on_brake_cbc callback not in IRAM");
}
if (cbs->on_brake_ost) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_brake_ost), ESP_ERR_INVALID_ARG, TAG, "on_brake_ost 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 (!oper->intr) {
// we want the interrupt servie to be enabled after allocation successfully
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
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_OPER_MASK(oper_id),
mcpwm_operator_default_isr, oper, &oper->intr), TAG, "install interrupt service for operator failed");
}
// enable/disable interrupt events
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_BRAKE_CBC(oper_id), cbs->on_brake_cbc != NULL);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_BRAKE_OST(oper_id), cbs->on_brake_ost != NULL);
portEXIT_CRITICAL(&group->spinlock);
oper->on_brake_cbc = cbs->on_brake_cbc;
oper->on_brake_ost = cbs->on_brake_ost;
oper->user_data = user_data;
return ESP_OK;
}
esp_err_t mcpwm_operator_set_brake_on_fault(mcpwm_oper_handle_t operator, const mcpwm_brake_config_t *config)
{
ESP_RETURN_ON_FALSE(operator && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = operator->group;
mcpwm_fault_t *fault = config->fault;
int oper_id = operator->oper_id;
mcpwm_ll_brake_enable_cbc_refresh_on_tez(group->hal.dev, oper_id, config->flags.cbc_recover_on_tez);
mcpwm_ll_fault_enable_cbc_refresh_on_tep(group->hal.dev, oper_id, config->flags.cbc_recover_on_tep);
switch (fault->type) {
case MCPWM_FAULT_TYPE_GPIO: {
ESP_RETURN_ON_FALSE(group == fault->group, ESP_ERR_INVALID_ARG, TAG, "fault and operator not in the same group");
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
mcpwm_ll_brake_enable_cbc_mode(group->hal.dev, oper_id, gpio_fault->fault_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_CBC);
mcpwm_ll_brake_enable_oneshot_mode(group->hal.dev, oper_id, gpio_fault->fault_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_OST);
operator->brake_mode_on_gpio_fault[gpio_fault->fault_id] = config->brake_mode;
break;
}
case MCPWM_FAULT_TYPE_SOFT: {
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
ESP_RETURN_ON_FALSE(!soft_fault->operator || soft_fault->operator == operator, ESP_ERR_INVALID_STATE, TAG, "soft fault already used by another operator");
soft_fault->operator = operator;
soft_fault->base.group = operator->group;
mcpwm_ll_brake_enable_soft_cbc(group->hal.dev, oper_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_CBC);
mcpwm_ll_brake_enable_soft_ost(group->hal.dev, oper_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_OST);
operator->brake_mode_on_soft_fault = config->brake_mode;
break;
}
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "unknown fault type:%d", fault->type);
break;
}
return ESP_OK;
}
esp_err_t mcpwm_operator_recover_from_fault(mcpwm_oper_handle_t operator, mcpwm_fault_handle_t fault)
{
ESP_RETURN_ON_FALSE(operator && fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = operator->group;
mcpwm_operator_brake_mode_t brake_mode;
// check the brake mode on the fault event
switch (fault->type) {
case MCPWM_FAULT_TYPE_GPIO: {
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
brake_mode = operator->brake_mode_on_gpio_fault[gpio_fault->fault_id];
break;
}
case MCPWM_FAULT_TYPE_SOFT:
brake_mode = operator->brake_mode_on_soft_fault;
break;
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "unknown fault type:%d", fault->type);
break;
}
bool fault_signal_is_active = false;
if (brake_mode == MCPWM_OPER_BRAKE_MODE_OST) {
fault_signal_is_active = mcpwm_ll_ost_brake_active(group->hal.dev, operator->oper_id);
// OST brake can't recover automatically, need to manually recovery the operator
if (!fault_signal_is_active) {
mcpwm_ll_brake_clear_ost(group->hal.dev, operator->oper_id);
}
} else {
fault_signal_is_active = mcpwm_ll_cbc_brake_active(group->hal.dev, operator->oper_id);
// CBC brake can recover automatically after deactivating the fault signal
}
ESP_RETURN_ON_FALSE(!fault_signal_is_active, ESP_ERR_INVALID_STATE, TAG, "recover fail, fault signal still active");
return ESP_OK;
}
static void IRAM_ATTR mcpwm_operator_default_isr(void *args)
{
mcpwm_oper_t *oper = (mcpwm_oper_t *)args;
mcpwm_group_t *group = oper->group;
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_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_OPER_MASK(oper_id));
mcpwm_brake_event_data_t edata = {};
if (status & MCPWM_LL_EVENT_OPER_BRAKE_CBC(oper_id)) {
mcpwm_brake_event_cb_t cb = oper->on_brake_cbc;
if (cb) {
if (cb(oper, &edata, oper->user_data)) {
need_yield = true;
}
}
}
if (status & MCPWM_LL_EVENT_OPER_BRAKE_OST(oper_id)) {
mcpwm_brake_event_cb_t cb = oper->on_brake_ost;
if (cb) {
if (cb(oper, &edata, oper->user_data)) {
need_yield = true;
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,221 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "esp_err.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "soc/soc_caps.h"
#include "hal/mcpwm_hal.h"
#include "hal/mcpwm_types.h"
#include "driver/mcpwm_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#if CONFIG_MCPWM_ISR_IRAM_SAFE
#define MCPWM_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define MCPWM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif
#if CONFIG_MCPWM_ISR_IRAM_SAFE
#define MCPWM_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM)
#else
#define MCPWM_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_INTRDISABLED)
#endif
#define MCPWM_PERIPH_CLOCK_PRE_SCALE (2)
#define MCPWM_PM_LOCK_NAME_LEN_MAX 16
typedef struct mcpwm_group_t mcpwm_group_t;
typedef struct mcpwm_timer_t mcpwm_timer_t;
typedef struct mcpwm_cap_timer_t mcpwm_cap_timer_t;
typedef struct mcpwm_oper_t mcpwm_oper_t;
typedef struct mcpwm_cmpr_t mcpwm_cmpr_t;
typedef struct mcpwm_gen_t mcpwm_gen_t;
typedef struct mcpwm_fault_t mcpwm_fault_t;
typedef struct mcpwm_gpio_fault_t mcpwm_gpio_fault_t;
typedef struct mcpwm_soft_fault_t mcpwm_soft_fault_t;
typedef struct mcpwm_sync_t mcpwm_sync_t;
typedef struct mcpwm_gpio_sync_src_t mcpwm_gpio_sync_src_t;
typedef struct mcpwm_timer_sync_src_t mcpwm_timer_sync_src_t;
typedef struct mcpwm_soft_sync_src_t mcpwm_soft_sync_src_t;
typedef struct mcpwm_cap_channel_t mcpwm_cap_channel_t;
struct mcpwm_group_t {
int group_id; // group ID, index from 0
mcpwm_hal_context_t hal; // HAL instance is at group level
portMUX_TYPE spinlock; // group level spinlock
uint32_t resolution_hz; // MCPWM group clock resolution
esp_pm_lock_handle_t pm_lock; // power management lock
mcpwm_timer_clock_source_t clk_src; // source clock
mcpwm_cap_timer_t *cap_timer; // mcpwm capture timers
mcpwm_timer_t *timers[SOC_MCPWM_TIMERS_PER_GROUP]; // mcpwm timer array
mcpwm_oper_t *operators[SOC_MCPWM_OPERATORS_PER_GROUP]; // mcpwm operator array
mcpwm_gpio_fault_t *gpio_faults[SOC_MCPWM_GPIO_FAULTS_PER_GROUP]; // mcpwm fault detectors array
mcpwm_gpio_sync_src_t *gpio_sync_srcs[SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP]; // mcpwm gpio sync array
#if CONFIG_PM_ENABLE
char pm_lock_name[MCPWM_PM_LOCK_NAME_LEN_MAX]; // pm lock name
#endif
};
typedef enum {
MCPWM_TIMER_FSM_INIT,
MCPWM_TIMER_FSM_ENABLE,
} mcpwm_timer_fsm_t;
struct mcpwm_timer_t {
int timer_id; // timer ID, index from 0
mcpwm_group_t *group; // which group the timer belongs to
mcpwm_timer_fsm_t fsm; // driver FSM
portMUX_TYPE spinlock; // spin lock
intr_handle_t intr; // interrupt handle
uint32_t resolution_hz; // resolution of the timer
uint32_t peak_ticks; // peak ticks that the timer could reach to
mcpwm_timer_sync_src_t *sync_src; // timer sync_src
mcpwm_timer_count_mode_t count_mode; // count mode
mcpwm_timer_event_cb_t on_full; // callback function when MCPWM timer counts to peak value
mcpwm_timer_event_cb_t on_empty; // callback function when MCPWM timer counts to zero
mcpwm_timer_event_cb_t on_stop; // callback function when MCPWM timer stops
void *user_data; // user data which would be passed to the timer callbacks
};
struct mcpwm_oper_t {
int oper_id; // operator ID, index from 0
mcpwm_group_t *group; // which group the timer belongs to
mcpwm_timer_t *timer; // which timer is connected to this operator
portMUX_TYPE spinlock; // spin lock
intr_handle_t intr; // interrupt handle
mcpwm_gen_t *generators[SOC_MCPWM_GENERATORS_PER_OPERATOR]; // mcpwm generator array
mcpwm_cmpr_t *comparators[SOC_MCPWM_COMPARATORS_PER_OPERATOR]; // mcpwm comparator array
mcpwm_soft_fault_t *soft_fault; // mcpwm software fault
mcpwm_operator_brake_mode_t brake_mode_on_soft_fault; // brake mode on software triggered fault
mcpwm_operator_brake_mode_t brake_mode_on_gpio_fault[SOC_MCPWM_GPIO_FAULTS_PER_GROUP]; // brake mode on GPIO triggered faults
uint32_t deadtime_resolution_hz; // resolution of deadtime submodule
mcpwm_brake_event_cb_t on_brake_cbc; // callback function which would be invoked when mcpwm operator goes into trip zone
mcpwm_brake_event_cb_t on_brake_ost; // callback function which would be invoked when mcpwm operator goes into trip zone
void *user_data; // user data which would be passed to the trip zone callback
};
struct mcpwm_cmpr_t {
int cmpr_id; // comparator ID, index from 0
mcpwm_oper_t *operator; // which operator that the comparator resides in
intr_handle_t intr; // interrupt handle
portMUX_TYPE spinlock; // spin lock
uint32_t compare_ticks; // compare value of this comparator
mcpwm_compare_event_cb_t on_reach; // ISR callback function which would be invoked on timer counter reaches compare value
void *user_data; // user data which would be passed to the comparator callbacks
};
struct mcpwm_gen_t {
int gen_id; // generator ID, index from 0
mcpwm_oper_t *operator; // which operator that the generator resides in
int gen_gpio_num; // GPIO number used by the generator
portMUX_TYPE spinlock; // spin lock
};
typedef enum {
MCPWM_FAULT_TYPE_GPIO, // external GPIO fault
MCPWM_FAULT_TYPE_SOFT, // software fault
} mcpwm_fault_type_t;
struct mcpwm_fault_t {
mcpwm_group_t *group; // which group the fault belongs to
mcpwm_fault_type_t type; // fault type
esp_err_t (*del)(mcpwm_fault_t *fault);
};
struct mcpwm_gpio_fault_t {
mcpwm_fault_t base; // base class
int fault_id; // fault detector ID, index from 0
int gpio_num; // GPIO number of fault detector
intr_handle_t intr; // interrupt handle
mcpwm_fault_event_cb_t on_fault_enter; // ISR callback function that would be invoked when fault signal got triggered
mcpwm_fault_event_cb_t on_fault_exit; // ISR callback function that would be invoked when fault signal got clear
void *user_data; // user data which would be passed to the isr_cb
};
struct mcpwm_soft_fault_t {
mcpwm_fault_t base; // base class
mcpwm_oper_t *operator; // the operator where the soft fault allocated from
};
typedef enum {
MCPWM_SYNC_TYPE_TIMER, // sync event generated by MCPWM timer count event
MCPWM_SYNC_TYPE_GPIO, // sync event generated by GPIO
MCPWM_SYNC_TYPE_SOFT, // sync event generated by software
} mcpwm_sync_src_type_t;
struct mcpwm_sync_t {
mcpwm_group_t *group; // which group the sync_src belongs to
mcpwm_sync_src_type_t type; // sync_src type
esp_err_t (*del)(mcpwm_sync_t *sync_src);
};
struct mcpwm_gpio_sync_src_t {
mcpwm_sync_t base; // base class
int sync_id; // sync signal ID
int gpio_num; // GPIO number
};
struct mcpwm_timer_sync_src_t {
mcpwm_sync_t base; // base class
mcpwm_timer_t *timer; // timer handle, where this sync_src allocated from
};
typedef enum {
MCPWM_SOFT_SYNC_FROM_NONE, // the software sync event generator has not been assigned
MCPWM_SOFT_SYNC_FROM_TIMER, // the software sync event is generated by MCPWM timer
MCPWM_SOFT_SYNC_FROM_CAP, // the software sync event is generated by MCPWM capture timer
} mcpwm_soft_sync_source_t;
struct mcpwm_soft_sync_src_t {
mcpwm_sync_t base; // base class
mcpwm_soft_sync_source_t soft_sync_from; // where the software sync event is generated by
union {
mcpwm_timer_t *timer; // soft sync is generated by which MCPWM timer
mcpwm_cap_timer_t *cap_timer; // soft sync is generated by which MCPWM capture timer
};
};
typedef enum {
MCPWM_CAP_TIMER_FSM_INIT,
MCPWM_CAP_TIMER_FSM_ENABLE,
} mcpwm_cap_timer_fsm_t;
struct mcpwm_cap_timer_t {
mcpwm_group_t *group; // which group the capture timer belongs to
portMUX_TYPE spinlock; // spin lock, to prevent concurrently accessing capture timer level resources, including registers
uint32_t resolution_hz; // resolution of capture timer
mcpwm_cap_timer_fsm_t fsm; // driver FSM
esp_pm_lock_handle_t pm_lock; // power management lock
mcpwm_cap_channel_t *cap_channels[SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER]; // capture channel array
};
struct mcpwm_cap_channel_t {
int cap_chan_id; // capture channel ID, index from 0
mcpwm_cap_timer_t *cap_timer; // which capture timer that the channel resides in
uint32_t prescale; // prescale of capture signal
int gpio_num; // GPIO number used by the channel
intr_handle_t intr; // Interrupt handle
mcpwm_capture_event_cb_t on_cap; // Callback function which would be invoked in capture interrupt routine
void *user_data; // user data which would be passed to the capture callback
};
mcpwm_group_t *mcpwm_acquire_group_handle(int group_id);
void mcpwm_release_group_handle(mcpwm_group_t *group);
esp_err_t mcpwm_select_periph_clock(mcpwm_group_t *group, mcpwm_timer_clock_source_t clk_src);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,297 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_sync.h"
#include "driver/gpio.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static esp_err_t mcpwm_del_timer_sync_src(mcpwm_sync_t *sync_src);
static esp_err_t mcpwm_del_gpio_sync_src(mcpwm_sync_t *sync_src);
static esp_err_t mcpwm_del_soft_sync_src(mcpwm_sync_t *sync_src);
static esp_err_t mcpwm_timer_sync_src_register_to_timer(mcpwm_timer_sync_src_t *timer_sync_src, mcpwm_timer_t *timer)
{
bool new_sync = false;
portENTER_CRITICAL(&timer->spinlock);
if (!timer->sync_src) {
new_sync = true;
timer->sync_src = timer_sync_src;
}
portEXIT_CRITICAL(&timer->spinlock);
ESP_RETURN_ON_FALSE(new_sync, ESP_ERR_INVALID_STATE, TAG, "timer sync_src already installed for timer (%d,%d)",
timer->group->group_id, timer->timer_id);
timer_sync_src->timer = timer;
return ESP_OK;
}
static void mcpwm_timer_sync_src_unregister_from_timer(mcpwm_timer_sync_src_t *timer_sync_src)
{
mcpwm_timer_t *timer = timer_sync_src->timer;
portENTER_CRITICAL(&timer->spinlock);
timer->sync_src = NULL;
portEXIT_CRITICAL(&timer->spinlock);
}
static esp_err_t mcpwm_timer_sync_src_destory(mcpwm_timer_sync_src_t *timer_sync_src)
{
if (timer_sync_src->timer) {
mcpwm_timer_sync_src_unregister_from_timer(timer_sync_src);
}
free(timer_sync_src);
return ESP_OK;
}
esp_err_t mcpwm_new_timer_sync_src(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync)
{
esp_err_t ret = ESP_OK;
mcpwm_timer_sync_src_t *timer_sync_src = NULL;
ESP_GOTO_ON_FALSE(timer && config && ret_sync, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
timer_sync_src = heap_caps_calloc(1, sizeof(mcpwm_timer_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(timer_sync_src, ESP_ERR_NO_MEM, err, TAG, "no mem for timer sync_src");
ESP_GOTO_ON_ERROR(mcpwm_timer_sync_src_register_to_timer(timer_sync_src, timer), err, TAG, "register timer sync_src failed");
mcpwm_group_t *group = timer->group;
mcpwm_hal_context_t *hal = &group->hal;
int timer_id = timer->timer_id;
if (config->flags.propagate_input_sync) {
mcpwm_ll_timer_propagate_input_sync(hal->dev, timer_id);
} else {
switch (config->timer_event) {
case MCPWM_TIMER_EVENT_EMPTY:
mcpwm_ll_timer_sync_out_on_timer_event(hal->dev, timer_id, MCPWM_TIMER_EVENT_EMPTY);
break;
case MCPWM_TIMER_EVENT_FULL:
mcpwm_ll_timer_sync_out_on_timer_event(hal->dev, timer_id, MCPWM_TIMER_EVENT_FULL);
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown timer sync event:%d", config->timer_event);
break;
}
}
timer_sync_src->base.group = group;
timer_sync_src->base.type = MCPWM_SYNC_TYPE_TIMER;
timer_sync_src->base.del = mcpwm_del_timer_sync_src;
*ret_sync = &timer_sync_src->base;
ESP_LOGD(TAG, "new timer sync_src at %p in timer (%d,%d), event:%c", timer_sync_src, group->group_id, timer_id, "EP?"[config->timer_event]);
return ESP_OK;
err:
if (timer_sync_src) {
mcpwm_timer_sync_src_destory(timer_sync_src);
}
return ret;
}
static esp_err_t mcpwm_del_timer_sync_src(mcpwm_sync_t *sync_src)
{
mcpwm_timer_sync_src_t *timer_sync_src = __containerof(sync_src, mcpwm_timer_sync_src_t, base);
mcpwm_timer_t *timer = timer_sync_src->timer;
int timer_id = timer->timer_id;
mcpwm_group_t *group = sync_src->group;
mcpwm_ll_timer_disable_sync_out(group->hal.dev, timer_id);
ESP_LOGD(TAG, "del timer sync_src in timer (%d,%d)", group->group_id, timer_id);
ESP_RETURN_ON_ERROR(mcpwm_timer_sync_src_destory(timer_sync_src), TAG, "destory timer sync_src failed");
return ESP_OK;
}
static esp_err_t mcpwm_gpio_sync_src_register_to_group(mcpwm_gpio_sync_src_t *gpio_sync_src, 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);
int sync_id = -1;
portENTER_CRITICAL(&group->spinlock);
for (int i = 0; i < SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP; i++) {
if (!group->gpio_sync_srcs[i]) {
sync_id = i;
group->gpio_sync_srcs[i] = gpio_sync_src;
break;
}
}
portEXIT_CRITICAL(&group->spinlock);
if (sync_id < 0) {
mcpwm_release_group_handle(group);
group = NULL;
} else {
gpio_sync_src->base.group = group;
gpio_sync_src->sync_id = sync_id;
}
ESP_RETURN_ON_FALSE(sync_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free gpio sync_src in group (%d)", group_id);
return ESP_OK;
}
static void mcpwm_gpio_sync_src_unregister_from_group(mcpwm_gpio_sync_src_t *gpio_sync_src)
{
mcpwm_group_t *group = gpio_sync_src->base.group;
int sync_id = gpio_sync_src->sync_id;
portENTER_CRITICAL(&group->spinlock);
group->gpio_sync_srcs[sync_id] = NULL;
portEXIT_CRITICAL(&group->spinlock);
// sync_src has a reference on group, release it now
mcpwm_release_group_handle(group);
}
static esp_err_t mcpwm_gpio_sync_src_destory(mcpwm_gpio_sync_src_t *gpio_sync_src)
{
if (gpio_sync_src->base.group) {
mcpwm_gpio_sync_src_unregister_from_group(gpio_sync_src);
}
free(gpio_sync_src);
return ESP_OK;
}
esp_err_t mcpwm_new_gpio_sync_src(const mcpwm_gpio_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync)
{
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
mcpwm_gpio_sync_src_t *gpio_sync_src = NULL;
ESP_GOTO_ON_FALSE(config && ret_sync, 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);
gpio_sync_src = heap_caps_calloc(1, sizeof(mcpwm_gpio_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(gpio_sync_src, ESP_ERR_NO_MEM, err, TAG, "no mem for gpio sync_src");
ESP_GOTO_ON_ERROR(mcpwm_gpio_sync_src_register_to_group(gpio_sync_src, config->group_id), err, TAG, "register gpio sync_src failed");
mcpwm_group_t *group = gpio_sync_src->base.group;
int group_id = group->group_id;
int sync_id = gpio_sync_src->sync_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
.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 sync GPIO failed");
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group_id].gpio_synchros[sync_id].sync_sig, 0);
// different ext sync share the same config register, using a group level spin lock
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_invert_gpio_sync_input(group->hal.dev, sync_id, config->flags.active_neg);
portEXIT_CRITICAL(&group->spinlock);
// fill in other operator members
gpio_sync_src->base.type = MCPWM_SYNC_TYPE_GPIO;
gpio_sync_src->gpio_num = config->gpio_num;
gpio_sync_src->base.del = mcpwm_del_gpio_sync_src;
*ret_sync = &gpio_sync_src->base;
ESP_LOGD(TAG, "new gpio sync_src (%d,%d) at %p, GPIO:%d", group_id, sync_id, gpio_sync_src, config->gpio_num);
return ESP_OK;
err:
if (gpio_sync_src) {
mcpwm_gpio_sync_src_destory(gpio_sync_src);
}
return ret;
}
static esp_err_t mcpwm_del_gpio_sync_src(mcpwm_sync_t *sync_src)
{
mcpwm_gpio_sync_src_t *gpio_sync_src = __containerof(sync_src, mcpwm_gpio_sync_src_t, base);
mcpwm_group_t *group = sync_src->group;
ESP_LOGD(TAG, "del gpio sync_src (%d,%d)", group->group_id, gpio_sync_src->sync_id);
gpio_reset_pin(gpio_sync_src->gpio_num);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_gpio_sync_src_destory(gpio_sync_src), TAG, "destory GPIO sync_src failed");
return ESP_OK;
}
esp_err_t mcpwm_new_soft_sync_src(const mcpwm_soft_sync_config_t *config, mcpwm_sync_handle_t *ret_sync)
{
esp_err_t ret = ESP_OK;
mcpwm_soft_sync_src_t *soft_sync = NULL;
ESP_GOTO_ON_FALSE(config && ret_sync, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
soft_sync = heap_caps_calloc(1, sizeof(mcpwm_soft_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(soft_sync, ESP_ERR_NO_MEM, err, TAG, "no mem for soft sync");
// fill in other sync member
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_NONE;
soft_sync->base.type = MCPWM_SYNC_TYPE_SOFT;
soft_sync->base.del = mcpwm_del_soft_sync_src;
*ret_sync = &soft_sync->base;
ESP_LOGD(TAG, "new soft sync at %p", soft_sync);
return ESP_OK;
err:
if (soft_sync) {
free(soft_sync);
}
return ret;
}
static esp_err_t mcpwm_del_soft_sync_src(mcpwm_sync_t *sync_src)
{
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_src, mcpwm_soft_sync_src_t, base);
ESP_LOGD(TAG, "del soft sync %p", soft_sync);
free(soft_sync);
return ESP_OK;
}
esp_err_t mcpwm_del_sync_src(mcpwm_sync_handle_t sync_src)
{
ESP_RETURN_ON_FALSE(sync_src, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return sync_src->del(sync_src);
}
esp_err_t mcpwm_soft_sync_activate(mcpwm_sync_handle_t sync_src)
{
ESP_RETURN_ON_FALSE(sync_src, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(sync_src->type == MCPWM_SYNC_TYPE_SOFT, ESP_ERR_INVALID_ARG, TAG, "not a valid soft sync");
mcpwm_group_t *group = sync_src->group;
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_src, mcpwm_soft_sync_src_t, base);
switch (soft_sync->soft_sync_from) {
case MCPWM_SOFT_SYNC_FROM_TIMER: {
mcpwm_timer_t *timer = soft_sync->timer;
mcpwm_ll_timer_trigger_soft_sync(group->hal.dev, timer->timer_id);
break;
}
case MCPWM_SOFT_SYNC_FROM_CAP: {
mcpwm_ll_capture_trigger_sw_sync(group->hal.dev);
break;
}
default:
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "no soft sync generator is assigned");
break;
}
return ESP_OK;
}

View File

@ -0,0 +1,364 @@
/*
* SPDX-FileCopyrightText: 2022 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_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_timer.h"
#include "esp_private/mcpwm.h"
#include "mcpwm_private.h"
static const char *TAG = "mcpwm";
static void mcpwm_timer_default_isr(void *args);
static esp_err_t mcpwm_timer_register_to_group(mcpwm_timer_t *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);
int timer_id = -1;
portENTER_CRITICAL(&group->spinlock);
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
if (!group->timers[i]) {
timer_id = i;
group->timers[i] = timer;
break;
}
}
portEXIT_CRITICAL(&group->spinlock);
if (timer_id < 0) {
mcpwm_release_group_handle(group);
group = NULL;
} else {
timer->group = group;
timer->timer_id = timer_id;
}
ESP_RETURN_ON_FALSE(timer_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free timer in group (%d)", group_id);
return ESP_OK;
}
static void mcpwm_timer_unregister_from_group(mcpwm_timer_t *timer)
{
mcpwm_group_t *group = timer->group;
int timer_id = timer->timer_id;
portENTER_CRITICAL(&group->spinlock);
group->timers[timer_id] = NULL;
portEXIT_CRITICAL(&group->spinlock);
// timer has a reference on group, release it now
mcpwm_release_group_handle(group);
}
static esp_err_t mcpwm_timer_destory(mcpwm_timer_t *timer)
{
if (timer->intr) {
ESP_RETURN_ON_ERROR(esp_intr_free(timer->intr), TAG, "uninstall interrupt service failed");
}
if (timer->group) {
mcpwm_timer_unregister_from_group(timer);
}
free(timer);
return ESP_OK;
}
esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle_t *ret_timer)
{
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
esp_log_level_set(TAG, ESP_LOG_DEBUG);
#endif
esp_err_t ret = ESP_OK;
mcpwm_timer_t *timer = NULL;
ESP_GOTO_ON_FALSE(config && ret_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);
timer = heap_caps_calloc(1, sizeof(mcpwm_timer_t), MCPWM_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for timer");
ESP_GOTO_ON_ERROR(mcpwm_timer_register_to_group(timer, config->group_id), err, TAG, "register timer failed");
mcpwm_group_t *group = timer->group;
int group_id = group->group_id;
mcpwm_hal_context_t *hal = &group->hal;
int timer_id = timer->timer_id;
// select the clock source
ESP_GOTO_ON_ERROR(mcpwm_select_periph_clock(group, config->clk_src), err, TAG, "set group clock failed");
// reset the timer to a determined state
mcpwm_hal_timer_reset(hal, timer_id);
// set timer resolution
uint32_t prescale = group->resolution_hz / config->resolution_hz;
mcpwm_ll_timer_set_clock_prescale(hal->dev, timer_id, prescale);
timer->resolution_hz = group->resolution_hz / prescale;
if (timer->resolution_hz != config->resolution_hz) {
ESP_LOGW(TAG, "adjust timer resolution to %uHz", timer->resolution_hz);
}
// set the peak tickes that the timer can reach to
timer->count_mode = config->count_mode;
uint32_t peak_ticks = config->period_ticks;
if (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) {
peak_ticks /= 2; // in symmetric mode, peak_ticks = period_ticks / 2
}
timer->peak_ticks = peak_ticks;
mcpwm_ll_timer_set_peak(hal->dev, timer_id, peak_ticks, timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN);
// set count direction
mcpwm_ll_timer_set_count_mode(hal->dev, timer_id, timer->count_mode);
// what time is allowed to update the period
mcpwm_ll_timer_enable_update_period_on_sync(hal->dev, timer_id, config->flags.update_period_on_sync);
mcpwm_ll_timer_enable_update_period_on_tez(hal->dev, timer_id, config->flags.update_period_on_empty);
// fill in other timer specific members
timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
timer->fsm = MCPWM_TIMER_FSM_INIT;
*ret_timer = timer;
ESP_LOGD(TAG, "new timer(%d,%d) at %p, resolution:%uHz, peak:%u, count_mod:%c",
group_id, timer_id, timer, timer->resolution_hz, timer->peak_ticks, "SUDB"[timer->count_mode]);
return ESP_OK;
err:
if (timer) {
mcpwm_timer_destory(timer);
}
return ret;
}
esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// check child resources are in free state
ESP_RETURN_ON_FALSE(!timer->sync_src, ESP_ERR_INVALID_STATE, TAG, "timer sync_src still in working");
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
mcpwm_group_t *group = timer->group;
int timer_id = timer->timer_id;
mcpwm_hal_context_t *hal = &group->hal;
// disable and clear the pending interrupt
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_MASK(timer_id), false);
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_TIMER_MASK(timer_id));
portEXIT_CRITICAL(&group->spinlock);
ESP_LOGD(TAG, "del timer (%d,%d)", group->group_id, timer_id);
// recycle memory resource
ESP_RETURN_ON_ERROR(mcpwm_timer_destory(timer), TAG, "destory timer failed");
return ESP_OK;
}
esp_err_t mcpwm_timer_register_event_callbacks(mcpwm_timer_handle_t timer, const mcpwm_timer_event_callbacks_t *cbs, void *user_data)
{
ESP_RETURN_ON_FALSE(timer && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
mcpwm_group_t *group = timer->group;
int group_id = group->group_id;
int timer_id = timer->timer_id;
mcpwm_hal_context_t *hal = &group->hal;
#if CONFIG_MCWPM_ISR_IRAM_SAFE
if (cbs->on_empty) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_empty), ESP_ERR_INVALID_ARG, TAG, "on_empty callback not in IRAM");
}
if (cbs->on_full) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_full), ESP_ERR_INVALID_ARG, TAG, "on_full callback not in IRAM");
}
if (cbs->on_stop) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_stop), ESP_ERR_INVALID_ARG, TAG, "on_stop 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 (!timer->intr) {
int isr_flags = MCPWM_INTR_ALLOC_FLAG;
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_TIMER_MASK(timer_id),
mcpwm_timer_default_isr, timer, &timer->intr), TAG, "install interrupt service for timer failed");
}
// enable/disable interrupt events
portENTER_CRITICAL(&group->spinlock);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_FULL(timer_id), cbs->on_full != NULL);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_EMPTY(timer_id), cbs->on_empty != NULL);
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_STOP(timer_id), cbs->on_stop != NULL);
portEXIT_CRITICAL(&group->spinlock);
timer->on_stop = cbs->on_stop;
timer->on_full = cbs->on_full;
timer->on_empty = cbs->on_empty;
timer->user_data = user_data;
return ESP_OK;
}
esp_err_t mcpwm_timer_get_phase(mcpwm_timer_handle_t timer, uint32_t *count_value, mcpwm_timer_direction_t *direction)
{
ESP_RETURN_ON_FALSE(timer && count_value && direction, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = timer->group;
int timer_id = timer->timer_id;
mcpwm_hal_context_t *hal = &group->hal;
portENTER_CRITICAL(&timer->spinlock);
*count_value = mcpwm_ll_timer_get_count_value(hal->dev, timer_id);
*direction = mcpwm_ll_timer_get_count_direction(hal->dev, timer_id);
portEXIT_CRITICAL(&timer->spinlock);
return ESP_OK;
}
esp_err_t mcpwm_timer_enable(mcpwm_timer_handle_t timer)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
mcpwm_group_t *group = timer->group;
if (timer->intr) {
ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt failed");
}
if (group->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(group->pm_lock), TAG, "acquire pm lock failed");
}
timer->fsm = MCPWM_TIMER_FSM_ENABLE;
return ESP_OK;
}
esp_err_t mcpwm_timer_disable(mcpwm_timer_handle_t timer)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
mcpwm_group_t *group = timer->group;
if (timer->intr) {
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt failed");
}
if (group->pm_lock) {
ESP_RETURN_ON_ERROR(esp_pm_lock_release(group->pm_lock), TAG, "acquire pm lock failed");
}
timer->fsm = MCPWM_TIMER_FSM_INIT;
return ESP_OK;
}
esp_err_t mcpwm_timer_start_stop(mcpwm_timer_handle_t timer, mcpwm_timer_start_stop_cmd_t command)
{
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
mcpwm_group_t *group = timer->group;
portENTER_CRITICAL_SAFE(&timer->spinlock);
mcpwm_ll_timer_set_start_stop_command(group->hal.dev, timer->timer_id, command);
portEXIT_CRITICAL_SAFE(&timer->spinlock);
return ESP_OK;
}
esp_err_t mcpwm_timer_set_phase_on_sync(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_phase_config_t *config)
{
ESP_RETURN_ON_FALSE(timer && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcpwm_group_t *group = timer->group;
mcpwm_hal_context_t *hal = &group->hal;
int group_id = group->group_id;
int timer_id = timer->timer_id;
mcpwm_sync_handle_t sync_source = config->sync_src;
// enable sync feature and set sync phase
if (sync_source) {
ESP_RETURN_ON_FALSE(config->count_value < MCPWM_LL_MAX_COUNT_VALUE, ESP_ERR_INVALID_ARG, TAG, "invalid sync count value");
switch (sync_source->type) {
case MCPWM_SYNC_TYPE_TIMER: {
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "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_timer_set_timer_sync_input(hal->dev, timer_id, timer_sync_src->timer->timer_id);
ESP_LOGD(TAG, "enable sync to timer (%d,%d) for timer (%d,%d)",
group_id, timer_sync_src->timer->timer_id, group_id, timer_id);
break;
}
case MCPWM_SYNC_TYPE_GPIO: {
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "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_timer_set_gpio_sync_input(hal->dev, timer_id, gpio_sync_src->sync_id);
ESP_LOGD(TAG, "enable sync to gpio (%d) for timer (%d,%d)",
gpio_sync_src->gpio_num, group_id, timer_id);
break;
}
case MCPWM_SYNC_TYPE_SOFT: {
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_source, mcpwm_soft_sync_src_t, base);
if (soft_sync->soft_sync_from == MCPWM_SOFT_SYNC_FROM_TIMER && soft_sync->timer != timer) {
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "soft sync already used by another timer");
}
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_TIMER;
soft_sync->timer = timer;
soft_sync->base.group = group;
break;
}
}
mcpwm_ll_timer_set_sync_phase_direction(hal->dev, timer_id, config->direction);
mcpwm_ll_timer_set_sync_phase_value(hal->dev, timer_id, config->count_value);
mcpwm_ll_timer_enable_sync_input(hal->dev, timer_id, true);
} else { // disable sync feature
mcpwm_ll_timer_enable_sync_input(hal->dev, timer_id, false);
ESP_LOGD(TAG, "disable sync for timer (%d,%d)", group_id, timer_id);
}
return ESP_OK;
}
static void IRAM_ATTR mcpwm_timer_default_isr(void *args)
{
mcpwm_timer_t *timer = (mcpwm_timer_t *)args;
mcpwm_group_t *group = timer->group;
mcpwm_hal_context_t *hal = &group->hal;
int timer_id = timer->timer_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_TIMER_MASK(timer_id));
mcpwm_timer_event_data_t edata = {
.direction = mcpwm_ll_timer_get_count_direction(hal->dev, timer_id),
.count_value = mcpwm_ll_timer_get_count_value(hal->dev, timer_id),
};
if (status & MCPWM_LL_EVENT_TIMER_STOP(timer_id)) {
mcpwm_timer_event_cb_t cb = timer->on_stop;
if (cb) {
if (cb(timer, &edata, timer->user_data)) {
need_yield = true;
}
}
}
if (status & MCPWM_LL_EVENT_TIMER_FULL(timer_id)) {
mcpwm_timer_event_cb_t cb = timer->on_full;
if (cb) {
if (cb(timer, &edata, timer->user_data)) {
need_yield = true;
}
}
}
if (status & MCPWM_LL_EVENT_TIMER_EMPTY(timer_id)) {
mcpwm_timer_event_cb_t cb = timer->on_empty;
if (cb) {
if (cb(timer, &edata, timer->user_data)) {
need_yield = true;
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}

View File

@ -0,0 +1,18 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mcpwm_test)
if(CONFIG_COMPILER_DUMP_RTL_FILES)
add_custom_target(check_test_app_sections ALL
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
--rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
--elf-file ${CMAKE_BINARY_DIR}/mcpwm_test.elf
find-refs
--from-sections=.iram0.text
--to-sections=.flash.text,.flash.rodata
--exit-code
DEPENDS ${elf}
)
endif()

View File

@ -0,0 +1,2 @@
| Supported Targets | ESP32 | ESP32-S3 |
| ----------------- | ----- | -------- |

View File

@ -0,0 +1,14 @@
set(srcs "test_app_main.c"
"test_mcpwm_cap.c"
"test_mcpwm_cmpr.c"
"test_mcpwm_fault.c"
"test_mcpwm_gen.c"
"test_mcpwm_oper.c"
"test_mcpwm_sync.c"
"test_mcpwm_timer.c"
"test_mcpwm_utils.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
idf_component_register(SRCS ${srcs}
WHOLE_ARCHIVE)

View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_heap_caps.h"
// Some resources are lazy allocated in GPTimer driver, the threshold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (-300)
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
// __ __ ____ ______ ____ __ _____ _
// | \/ |/ ___| _ \ \ / / \/ | |_ _|__ ___| |_
// | |\/| | | | |_) \ \ /\ / /| |\/| | | |/ _ \/ __| __|
// | | | | |___| __/ \ V V / | | | | | | __/\__ \ |_
// |_| |_|\____|_| \_/\_/ |_| |_| |_|\___||___/\__|
printf(" __ __ ____ ______ ____ __ _____ _\r\n");
printf("| \\/ |/ ___| _ \\ \\ / / \\/ | |_ _|__ ___| |_\r\n");
printf("| |\\/| | | | |_) \\ \\ /\\ / /| |\\/| | | |/ _ \\/ __| __|\r\n");
printf("| | | | |___| __/ \\ V V / | | | | | | __/\\__ \\ |_\r\n");
printf("|_| |_|\\____|_| \\_/\\_/ |_| |_| |_|\\___||___/\\__|\r\n");
unity_run_menu();
}

View File

@ -0,0 +1,238 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_private/esp_clk.h"
#include "driver/mcpwm_cap.h"
#include "driver/mcpwm_sync.h"
#include "driver/gpio.h"
#include "test_mcpwm_utils.h"
TEST_CASE("mcpwm_capture_install_uninstall", "[mcpwm]")
{
printf("install mcpwm capture timers\r\n");
mcpwm_capture_timer_config_t cap_timer_config = {
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
};
int total_cap_timers = SOC_MCPWM_GROUPS * SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP;
mcpwm_cap_timer_handle_t cap_timers[total_cap_timers];
int k = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
cap_timer_config.group_id = i;
for (int j = 0; j < SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[k++]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[0]));
}
printf("install mcpwm capture channels\r\n");
mcpwm_capture_channel_config_t cap_chan_config = {
.gpio_num = 0,
.prescale = 2,
.flags.pos_edge = true,
.flags.pull_up = true,
};
mcpwm_cap_channel_handle_t cap_channels[total_cap_timers][SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER];
for (int i = 0; i < total_cap_timers; i++) {
for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][j]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][0]));
}
printf("uninstall mcpwm capture channels and timers\r\n");
for (int i = 0; i < total_cap_timers; i++) {
for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channels[i][j]));
}
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timers[i]));
}
}
TEST_MCPWM_CALLBACK_ATTR
static bool test_capture_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_data)
{
uint32_t *cap_value = (uint32_t *)user_data;
if (edata->cap_edge == MCPWM_CAP_EDGE_NEG) {
cap_value[1] = edata->cap_value;
} else {
cap_value[0] = edata->cap_value;
}
return false;
}
TEST_CASE("mcpwm_capture_ext_gpio", "[mcpwm]")
{
printf("install mcpwm capture timer\r\n");
mcpwm_cap_timer_handle_t cap_timer = NULL;
mcpwm_capture_timer_config_t cap_timer_config = {
.clk_src = MCPWM_CAPTURE_CLK_SRC_APB,
.group_id = 0,
};
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
const int cap_gpio = 0;
// put the GPIO into a preset state
gpio_set_level(cap_gpio, 0);
printf("install mcpwm capture channel\r\n");
mcpwm_cap_channel_handle_t pps_channel;
mcpwm_capture_channel_config_t cap_chan_config = {
.gpio_num = cap_gpio,
.prescale = 1,
.flags.pos_edge = true,
.flags.neg_edge = true,
.flags.io_loop_back = true, // so we can use GPIO functions to simulate the external capture signal
.flags.pull_up = true,
};
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &pps_channel));
printf("install callback for capture channel\r\n");
mcpwm_capture_event_callbacks_t cbs = {
.on_cap = test_capture_callback,
};
uint32_t cap_value[2] = {0};
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(pps_channel, &cbs, cap_value));
printf("enable and start capture timer\r\n");
TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));
printf("simulate GPIO capture signal\r\n");
gpio_set_level(cap_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(100));
gpio_set_level(cap_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(100));
printf("capture value: Pos=%u, Neg=%u\r\n", cap_value[0], cap_value[1]);
// Capture timer is clocked from APB by default
uint32_t clk_src_res = esp_clk_apb_freq();
TEST_ASSERT_UINT_WITHIN(100000, clk_src_res / 10, cap_value[1] - cap_value[0]);
printf("uninstall capture channel and timer\r\n");
TEST_ESP_OK(mcpwm_del_capture_channel(pps_channel));
TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
}
typedef struct {
uint32_t cap_data[2];
int cap_data_index;
} test_soft_catch_user_data_t;
TEST_MCPWM_CALLBACK_ATTR
static bool soft_cap_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
{
test_soft_catch_user_data_t *cbdata = (test_soft_catch_user_data_t *)user_data;
cbdata->cap_data[cbdata->cap_data_index++] = data->cap_value;
return false;
}
TEST_CASE("mcpwm_capture_software_catch", "[mcpwm]")
{
printf("install mcpwm capture timer\r\n");
mcpwm_cap_timer_handle_t cap_timer = NULL;
mcpwm_capture_timer_config_t cap_timer_config = {
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
.group_id = 0,
};
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
printf("install mcpwm capture channel\r\n");
mcpwm_cap_channel_handle_t cap_channel = NULL;
mcpwm_capture_channel_config_t cap_chan_config = {
.gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
.prescale = 2,
};
test_soft_catch_user_data_t test_callback_data = {};
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));
printf("register event callback for capture channel\r\n");
mcpwm_capture_event_callbacks_t cbs = {
.on_cap = soft_cap_callback,
};
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &test_callback_data));
printf("enable and start capture timer\r\n");
TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));
printf("trigger software catch\r\n");
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
vTaskDelay(pdMS_TO_TICKS(10));
// check user data
TEST_ASSERT_EQUAL(2, test_callback_data.cap_data_index);
uint32_t delta = test_callback_data.cap_data[1] - test_callback_data.cap_data[0];
esp_rom_printf("duration=%u ticks\r\n", delta);
// Capture timer is clocked from APB by default
uint32_t clk_src_res = esp_clk_apb_freq();
TEST_ASSERT_UINT_WITHIN(80000, clk_src_res / 100, delta);
printf("uninstall capture channel and timer\r\n");
TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
}
TEST_MCPWM_CALLBACK_ATTR
static bool test_capture_after_sync_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
{
uint32_t *cap_data = (uint32_t *)user_data;
*cap_data = data->cap_value;
return false;
}
TEST_CASE("mcpwm_capture_timer_sync_phase_lock", "[mcpwm]")
{
mcpwm_capture_timer_config_t cap_timer_config = {
.group_id = 0,
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
};
mcpwm_cap_timer_handle_t cap_timer = NULL;
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
mcpwm_sync_handle_t soft_sync = NULL;
mcpwm_soft_sync_config_t soft_sync_config = {};
TEST_ESP_OK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync));
mcpwm_capture_timer_sync_phase_config_t sync_config = {
.count_value = 1000,
.direction = MCPWM_TIMER_DIRECTION_UP,
.sync_src = soft_sync,
};
TEST_ESP_OK(mcpwm_capture_timer_set_phase_on_sync(cap_timer, &sync_config));
mcpwm_cap_channel_handle_t cap_channel = NULL;
mcpwm_capture_channel_config_t cap_chan_config = {
.gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
.prescale = 1,
};
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));
mcpwm_capture_event_callbacks_t cbs = {
.on_cap = test_capture_after_sync_callback,
};
uint32_t cap_data;
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &cap_data));
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
vTaskDelay(pdMS_TO_TICKS(10));
printf("capture data before sync: %u\r\n", cap_data);
TEST_ESP_OK(mcpwm_soft_sync_activate(soft_sync));
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
vTaskDelay(pdMS_TO_TICKS(10));
printf("capture data after sync: %u\r\n", cap_data);
TEST_ASSERT_EQUAL(1000, cap_data);
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
TEST_ESP_OK(mcpwm_del_sync_src(soft_sync));
}

View File

@ -0,0 +1,113 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "driver/mcpwm_timer.h"
#include "driver/mcpwm_oper.h"
#include "driver/mcpwm_cmpr.h"
TEST_CASE("mcpwm_comparator_install_uninstall", "[mcpwm]")
{
mcpwm_timer_handle_t timer;
mcpwm_oper_handle_t operator;
mcpwm_cmpr_handle_t comparators[SOC_MCPWM_COMPARATORS_PER_OPERATOR];
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1 * 1000 * 1000,
.period_ticks = 10 * 1000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
printf("install timer and operator");
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
printf("install comparator\r\n");
mcpwm_comparator_config_t comparator_config = {};
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparators[i]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_comparator(operator, &comparator_config, &comparators[0]));
printf("connect MCPWM timer and operators\r\n");
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
printf("uninstall timer, operator and comparators\r\n");
// can't delete operator if the comparators are still in working
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_operator(operator));
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
TEST_ESP_OK(mcpwm_del_comparator(comparators[i]));
}
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
static bool test_compare_on_reach(mcpwm_cmpr_handle_t cmpr, const mcpwm_compare_event_data_t *ev_data, void *user_data)
{
uint32_t *counts = (uint32_t *)user_data;
(*counts)++;
return false;
}
TEST_CASE("mcpwm_comparator_event_callback", "[mcpwm]")
{
mcpwm_timer_handle_t timer;
mcpwm_oper_handle_t operator;
mcpwm_cmpr_handle_t comparator;
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1 * 1000 * 1000,
.period_ticks = 10 * 1000, // 10ms
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_comparator_config_t comparator_config = {};
printf("install timer, operator and comparator");
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator));
// set compare value before connecting timer and operator will fail
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_comparator_set_compare_value(comparator, 5000));
printf("connect MCPWM timer and operators\r\n");
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
// compare ticks can't exceed the timer's period ticks
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mcpwm_comparator_set_compare_value(comparator, 20 * 1000));
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator, 5 * 1000));
printf("register compare event callback\r\n");
uint32_t compare_counts = 0;
mcpwm_comparator_event_callbacks_t cbs = {
.on_reach = test_compare_on_reach,
};
TEST_ESP_OK(mcpwm_comparator_register_event_callbacks(comparator, &cbs, &compare_counts));
printf("start timer\r\n");
TEST_ESP_OK(mcpwm_timer_enable(timer));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(1000));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
printf("compare_counts=%u\r\n", compare_counts);
// the timer period is 10ms, the expected compare_counts = 1s/10ms = 100
TEST_ASSERT_INT_WITHIN(1, 100, compare_counts);
printf("uninstall timer, operator and comparator\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_comparator(comparator));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}

View File

@ -0,0 +1,93 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "driver/mcpwm_fault.h"
#include "driver/mcpwm_oper.h"
#include "driver/gpio.h"
TEST_CASE("mcpwm_fault_install_uninstall", "[mcpwm]")
{
printf("install and uninstall gpio faults\r\n");
mcpwm_gpio_fault_config_t gpio_fault_config = {
.gpio_num = 0,
};
int total_gpio_faults = SOC_MCPWM_GPIO_FAULTS_PER_GROUP * SOC_MCPWM_GROUPS;
mcpwm_fault_handle_t gpio_faults[total_gpio_faults];
int fault_itor = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
gpio_fault_config.group_id = i;
for (int j = 0; j < SOC_MCPWM_GPIO_FAULTS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_faults[fault_itor++]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_faults[0]));
}
for (int i = 0; i < total_gpio_faults; i++) {
TEST_ESP_OK(mcpwm_del_fault(gpio_faults[i]));
}
printf("install and uninstall software fault\r\n");
mcpwm_soft_fault_config_t soft_fault_config = {};
mcpwm_fault_handle_t soft_fault = NULL;
TEST_ESP_OK(mcpwm_new_soft_fault(&soft_fault_config, &soft_fault));
TEST_ESP_OK(mcpwm_del_fault(soft_fault));
}
static bool test_fault_enter_callback(mcpwm_fault_handle_t detector, const mcpwm_fault_event_data_t *status, void *user_data)
{
TaskHandle_t task_handle = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup = pdFALSE;
esp_rom_printf("fault found\r\n");
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
static bool test_fault_exit_callback(mcpwm_fault_handle_t detector, const mcpwm_fault_event_data_t *status, void *user_data)
{
TaskHandle_t task_handle = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup = pdFALSE;
esp_rom_printf("fault relieved\r\n");
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("mcpwm_gpio_fault_event_callbacks", "[mcpwm]")
{
printf("create gpio fault\r\n");
const int fault_gpio = 0;
mcpwm_fault_handle_t fault = NULL;
mcpwm_gpio_fault_config_t gpio_fault_config = {
.group_id = 0,
.gpio_num = fault_gpio,
.flags.active_level = true, // active on high level
.flags.pull_down = true,
.flags.io_loop_back = true, // for debug, so that we can use gpio_set_level to mimic a fault source
};
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &fault));
// put fault GPIO into a safe state
gpio_set_level(fault_gpio, 0);
printf("register callback for the gpio fault\r\n");
mcpwm_fault_event_callbacks_t cbs = {
.on_fault_enter = test_fault_enter_callback,
.on_fault_exit = test_fault_exit_callback,
};
TaskHandle_t task_to_notify = xTaskGetCurrentTaskHandle();
TEST_ESP_OK(mcpwm_fault_register_event_callbacks(fault, &cbs, task_to_notify));
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
printf("trigget a fault event\r\n");
gpio_set_level(fault_gpio, 1);
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)));
printf("remove the fault source\r\n");
gpio_set_level(fault_gpio, 0);
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)));
TEST_ESP_OK(mcpwm_del_fault(fault));
}

View File

@ -0,0 +1,646 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "driver/mcpwm_timer.h"
#include "driver/mcpwm_oper.h"
#include "driver/mcpwm_cmpr.h"
#include "driver/mcpwm_gen.h"
#include "driver/gpio.h"
TEST_CASE("mcpwm_generator_install_uninstall", "[mcpwm]")
{
mcpwm_operator_config_t oper_config = {
.group_id = 0,
};
mcpwm_oper_handle_t oper = NULL;
printf("create a MCPWM operator\r\n");
TEST_ESP_OK(mcpwm_new_operator(&oper_config, &oper));
printf("create MCPWM generators from that operator\r\n");
mcpwm_gen_handle_t gens[SOC_MCPWM_GENERATORS_PER_OPERATOR];
mcpwm_generator_config_t gen_config = {
.gen_gpio_num = 0,
};
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
TEST_ESP_OK(mcpwm_new_generator(oper, &gen_config, &gens[i]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_generator(oper, &gen_config, &gens[0]));
printf("delete generators and operator\r\n");
// can't delete operator if the generator is till in working
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_operator(oper));
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
TEST_ESP_OK(mcpwm_del_generator(gens[i]));
}
TEST_ESP_OK(mcpwm_del_operator(oper));
}
TEST_CASE("mcpwm_generator_force_level_hold_on", "[mcpwm]")
{
// The operator can even work without the timer
printf("create operator and generator\r\n");
mcpwm_oper_handle_t operator = NULL;
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
mcpwm_gen_handle_t generator = NULL;
const int gen_gpio = 0;
mcpwm_generator_config_t generator_config = {
.gen_gpio_num = gen_gpio,
.flags.io_loop_back = true, // loop back for test
};
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
printf("add force level to the generator, hold on");
for (int i = 0; i < 10; i++) {
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
}
printf("remove the force level\r\n");
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, -1, true));
printf("delete generator and operator\r\n");
TEST_ESP_OK(mcpwm_del_generator(generator));
TEST_ESP_OK(mcpwm_del_operator(operator));
}
TEST_CASE("mcpwm_generator_force_level_recovery", "[mcpwm]")
{
printf("create mcpwm timer\r\n");
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.resolution_hz = 1000000,
.period_ticks = 50000,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
TEST_ESP_OK(mcpwm_timer_enable(timer));
printf("create operator\r\n");
mcpwm_oper_handle_t operator = NULL;
mcpwm_operator_config_t operator_config = {
.group_id = 0,
.flags.update_gen_action_on_tez = true,
};
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
printf("create generator\r\n");
mcpwm_gen_handle_t generator = NULL;
const int gen_gpio = 0;
mcpwm_generator_config_t generator_config = {
.gen_gpio_num = gen_gpio,
.flags.io_loop_back = true, // loop back for test
};
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
printf("add force level to the generator, and recovery by events");
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, false));
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, false));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, false));
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
// generator should output high level on tez event, the previous force level should disappear
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(200));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, false));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
// generator should output low level on tez event, the previous force level should disappear
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(200));
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
printf("delete generator, operator and timer\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(generator));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
TEST_CASE("mcpwm_generator_action_on_timer_event", "[mcpwm]")
{
const int generator_gpio = 0;
printf("create timer and operator\r\n");
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.period_ticks = 1000,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
TEST_ESP_OK(mcpwm_timer_enable(timer));
mcpwm_operator_config_t oper_config = {
.group_id = 0,
};
mcpwm_oper_handle_t oper = NULL;
TEST_ESP_OK(mcpwm_new_operator(&oper_config, &oper));
printf("connect timer and operator\r\n");
TEST_ESP_OK(mcpwm_operator_connect_timer(oper, timer));
printf("create generator\r\n");
mcpwm_generator_config_t gen_config = {
.gen_gpio_num = generator_gpio,
.flags.io_loop_back = 1, // so that we can read the GPIO value by GPIO driver
};
mcpwm_gen_handle_t gen = NULL;
TEST_ESP_OK(mcpwm_new_generator(oper, &gen_config, &gen));
printf("set generator to output high on timer full\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_KEEP),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
printf("start timer\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
printf("stop timer on full\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_FULL));
TEST_ASSERT_EQUAL(1, gpio_get_level(generator_gpio));
printf("set generator to output low on timer full\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_KEEP),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
printf("start timer\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
printf("stop timer on full\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_FULL));
TEST_ASSERT_EQUAL(0, gpio_get_level(generator_gpio));
printf("delete timer, operator, generator\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(gen));
TEST_ESP_OK(mcpwm_del_operator(oper));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
typedef void (*set_gen_actions_cb_t)(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb);
static void mcpwm_gen_action_test_template(uint32_t timer_resolution, uint32_t period, mcpwm_timer_count_mode_t count_mode,
uint32_t cmpa, uint32_t cmpb, int gpioa, int gpiob, set_gen_actions_cb_t set_generator_actions)
{
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.count_mode = count_mode,
.resolution_hz = timer_resolution,
.period_ticks = period,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t operator = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
TEST_ESP_OK(mcpwm_timer_enable(timer));
mcpwm_cmpr_handle_t comparator_a = NULL;
mcpwm_cmpr_handle_t comparator_b = NULL;
mcpwm_comparator_config_t comparator_config = {
.flags.update_cmp_on_tez = true,
};
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_a));
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_b));
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_a, cmpa));
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_b, cmpb));
mcpwm_gen_handle_t generator_a = NULL;
mcpwm_gen_handle_t generator_b = NULL;
mcpwm_generator_config_t generator_config = {
.gen_gpio_num = gpioa,
};
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_a));
generator_config.gen_gpio_num = gpiob;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_b));
set_generator_actions(generator_a, generator_b, comparator_a, comparator_b);
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(generator_a));
TEST_ESP_OK(mcpwm_del_generator(generator_b));
TEST_ESP_OK(mcpwm_del_comparator(comparator_a));
TEST_ESP_OK(mcpwm_del_comparator(comparator_b));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
static void single_edge_active_high(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void single_edge_active_low(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void pulse_placement(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
}
static void dual_edge_active_low_asym(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
}
static void dual_edge_active_low_sym(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void dual_edge_complementary(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
TEST_CASE("mcpwm_generator_action_on_compare_event", "[mcpwm]")
{
printf("[Asymmetric, SingleEdge, ActiveHigh]\r\n");
// PWMA: high = [1->350], low = [351->499,0]
// PWMB: high = [1->200], low = [201->499,0]
mcpwm_gen_action_test_template(1000000, 500, MCPWM_TIMER_COUNT_MODE_UP, 350, 200, 0, 2, single_edge_active_high);
printf("[Asymmetric, SingleEdge, ActiveLow]\r\n");
// PWMA: low = [0->300], high = [301->499]
// PWMB: low = [0->150], high = [151->499]
mcpwm_gen_action_test_template(1000000, 500, MCPWM_TIMER_COUNT_MODE_UP, 300, 150, 0, 2, single_edge_active_low);
printf("[Asymmetric, PulsePlacement]\r\n");
// PWMA: low = [0->200], high = [201->400], low = [401->599]
// PWMB: high = [0->599], low = [0->599]
mcpwm_gen_action_test_template(1000000, 600, MCPWM_TIMER_COUNT_MODE_UP, 200, 400, 0, 2, pulse_placement);
printf("[Asymmetric, DualEdge, ActiveLow]\r\n");
// PWMA: low = [0->250], high = [251->599, 600->450], low = [451->1]
// PWMB: low = [0->599], low = [600->1]
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 250, 450, 0, 2, dual_edge_active_low_asym);
printf("[Symmetric, DualEdge, ActiveLow]\r\n");
// PWMA: low = [0->400], high = [401->599, 600->400], low = [399->1]
// PWMB: low = [0->500], high = [501->599, 600->500], low = [499->1]
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 400, 500, 0, 2, dual_edge_active_low_sym);
printf("[Symmetric, DualEdge, Complementary]\r\n");
// PWMA: low = [0->350], high = [351->599, 600->350], low = [349->1]
// PWMB: low = [0->400], high = [401->599, 600->400], low = [399->1]
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 350, 400, 0, 2, dual_edge_complementary);
}
typedef void (*set_dead_time_cb_t)(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb);
static void mcpwm_deadtime_test_template(uint32_t timer_resolution, uint32_t period, uint32_t cmpa, uint32_t cmpb, int gpioa, int gpiob,
set_gen_actions_cb_t set_generator_actions, set_dead_time_cb_t set_dead_time)
{
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = timer_resolution,
.period_ticks = period,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t operator = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
TEST_ESP_OK(mcpwm_timer_enable(timer));
mcpwm_cmpr_handle_t comparator_a = NULL;
mcpwm_cmpr_handle_t comparator_b = NULL;
mcpwm_comparator_config_t comparator_config = {
.flags.update_cmp_on_tez = true,
};
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_a));
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_b));
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_a, cmpa));
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_b, cmpb));
mcpwm_gen_handle_t generator_a = NULL;
mcpwm_gen_handle_t generator_b = NULL;
mcpwm_generator_config_t generator_config = {
.gen_gpio_num = gpioa,
};
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_a));
generator_config.gen_gpio_num = gpiob;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_b));
set_generator_actions(generator_a, generator_b, comparator_a, comparator_b);
set_dead_time(generator_a, generator_b);
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(generator_a));
TEST_ESP_OK(mcpwm_del_generator(generator_b));
TEST_ESP_OK(mcpwm_del_comparator(comparator_a));
TEST_ESP_OK(mcpwm_del_comparator(comparator_b));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
static void ahc_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void ahc_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 50,
.negedge_delay_ticks = 0
};
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
dead_time_config.posedge_delay_ticks = 0;
dead_time_config.negedge_delay_ticks = 100;
dead_time_config.flags.invert_output = true;
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
}
static void alc_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void alc_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 50,
.negedge_delay_ticks = 0,
.flags.invert_output = true
};
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
dead_time_config.posedge_delay_ticks = 0;
dead_time_config.negedge_delay_ticks = 100;
dead_time_config.flags.invert_output = false;
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
}
static void ah_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void ah_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 50,
.negedge_delay_ticks = 0,
};
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
dead_time_config.posedge_delay_ticks = 0;
dead_time_config.negedge_delay_ticks = 100;
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
}
static void al_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void al_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 50,
.negedge_delay_ticks = 0,
.flags.invert_output = true
};
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
dead_time_config.posedge_delay_ticks = 0;
dead_time_config.negedge_delay_ticks = 100;
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
}
static void reda_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void reda_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 50,
.negedge_delay_ticks = 0,
};
// apply deadtime to generator_a
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
// bypass deadtime module for generator_b
dead_time_config.posedge_delay_ticks = 0;
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
}
static void fedb_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void fedb_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 0,
.negedge_delay_ticks = 0,
};
// generator_a bypass the deadtime module (no delay)
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
// apply dead time to generator_b
dead_time_config.negedge_delay_ticks = 50;
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
}
static void redfedb_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
}
static void redfedb_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 0,
.negedge_delay_ticks = 0,
};
// generator_a bypass the deadtime module (no delay)
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
// apply dead time on both edge for generator_b
dead_time_config.negedge_delay_ticks = 50;
dead_time_config.posedge_delay_ticks = 50;
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
}
TEST_CASE("mcpwm_generator_deadtime_classical_configuration", "[mcpwm]")
{
printf("Active High Complementary\r\n");
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, ahc_set_generator_actions, ahc_set_dead_time);
printf("Active Low Complementary\r\n");
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, alc_set_generator_actions, alc_set_dead_time);
printf("Active High\r\n");
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, ah_set_generator_actions, ah_set_dead_time);
printf("Active Low\r\n");
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, al_set_generator_actions, al_set_dead_time);
printf("RED on A, Bypass B\r\n");
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, reda_only_set_generator_actions, reda_only_set_dead_time);
printf("Bypass A, FED on B\r\n");
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, fedb_only_set_generator_actions, fedb_only_set_dead_time);
printf("Bypass A, RED + FED on B\r\n");
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, redfedb_only_set_generator_actions, redfedb_only_set_dead_time);
}

View File

@ -0,0 +1,380 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "driver/mcpwm_oper.h"
#include "driver/mcpwm_timer.h"
#include "driver/mcpwm_gen.h"
#include "driver/mcpwm_fault.h"
#include "driver/gpio.h"
TEST_CASE("mcpwm_operator_install_uninstall", "[mcpwm]")
{
const int total_operators = SOC_MCPWM_OPERATORS_PER_GROUP * SOC_MCPWM_GROUPS;
mcpwm_timer_handle_t timers[SOC_MCPWM_GROUPS];
mcpwm_oper_handle_t operators[total_operators];
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1 * 1000 * 1000,
.period_ticks = 10 * 1000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_operator_config_t operator_config = {
};
printf("install one MCPWM timer for each group\r\n");
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
timer_config.group_id = i;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
}
printf("install MCPWM operators for each group\r\n");
int k = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
operator_config.group_id = i;
for (int j = 0; j < SOC_MCPWM_OPERATORS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operators[k++]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_operator(&operator_config, &operators[0]));
}
printf("connect MCPWM timer and operators\r\n");
k = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
for (int j = 0; j < SOC_MCPWM_OPERATORS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_operator_connect_timer(operators[k++], timers[i]));
}
}
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mcpwm_operator_connect_timer(operators[0], timers[1]));
printf("uninstall operators and timers\r\n");
for (int i = 0; i < total_operators; i++) {
TEST_ESP_OK(mcpwm_del_operator(operators[i]));
}
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
}
}
TEST_CASE("mcpwm_operator_carrier", "[mcpwm]")
{
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 20000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t operator = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
mcpwm_generator_config_t generator_config = {
.gen_gpio_num = 0,
};
mcpwm_gen_handle_t generator = NULL;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
printf("add carrier to PWM wave\r\n");
mcpwm_carrier_config_t carrier_config = {
.frequency_hz = 1000000, // 1MHz carrier
.duty_cycle = 0.5,
.first_pulse_duration_us = 10,
};
TEST_ESP_OK(mcpwm_operator_apply_carrier(operator, &carrier_config));
TEST_ESP_OK(mcpwm_timer_enable(timer));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
printf("remove carrier from PWM wave\r\n");
carrier_config.frequency_hz = 0;
TEST_ESP_OK(mcpwm_operator_apply_carrier(operator, &carrier_config));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(200));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_generator(generator));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
static bool test_cbc_brake_on_gpio_fault_callback(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_data)
{
esp_rom_printf("cbc brake\r\n");
return false;
}
static bool test_ost_brake_on_gpio_fault_callback(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_data)
{
esp_rom_printf("ost brake\r\n");
return false;
}
TEST_CASE("mcpwm_operator_brake_on_gpio_fault", "[mcpwm]")
{
printf("install timer\r\n");
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.group_id = 0,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 20000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
printf("install operator\r\n");
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t operator = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
printf("set brake event callbacks for operator\r\n");
mcpwm_operator_event_callbacks_t cbs = {
.on_brake_cbc = test_cbc_brake_on_gpio_fault_callback,
.on_brake_ost = test_ost_brake_on_gpio_fault_callback,
};
TEST_ESP_OK(mcpwm_operator_register_event_callbacks(operator, &cbs, NULL));
printf("install gpio fault\r\n");
mcpwm_gpio_fault_config_t gpio_fault_config = {
.group_id = 0,
.flags.active_level = 1,
.flags.io_loop_back = true,
.flags.pull_down = true,
};
mcpwm_fault_handle_t gpio_cbc_fault = NULL;
mcpwm_fault_handle_t gpio_ost_fault = NULL;
const int cbc_fault_gpio = 4;
const int ost_fault_gpio = 5;
gpio_fault_config.gpio_num = cbc_fault_gpio;
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_cbc_fault));
gpio_fault_config.gpio_num = ost_fault_gpio;
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_ost_fault));
// put fault GPIO into a safe state
gpio_set_level(cbc_fault_gpio, 0);
gpio_set_level(ost_fault_gpio, 0);
printf("set brake mode on fault\r\n");
mcpwm_brake_config_t brake_config = {
.fault = gpio_cbc_fault,
.brake_mode = MCPWM_OPER_BRAKE_MODE_CBC,
.flags.cbc_recover_on_tez = true,
};
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
brake_config.fault = gpio_ost_fault;
brake_config.brake_mode = MCPWM_OPER_BRAKE_MODE_OST;
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
printf("create generators\r\n");
const int gen_a_gpio = 0;
const int gen_b_gpio = 2;
mcpwm_gen_handle_t gen_a = NULL;
mcpwm_gen_handle_t gen_b = NULL;
mcpwm_generator_config_t generator_config = {
.flags.io_loop_back = true,
};
generator_config.gen_gpio_num = gen_a_gpio;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_a));
generator_config.gen_gpio_num = gen_b_gpio;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_b));
printf("set generator actions on timer event\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_a,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_b,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
printf("set generator actions on brake event\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_a,
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_b,
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
printf("enable and start timer\r\n");
TEST_ESP_OK(mcpwm_timer_enable(timer));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
printf("trigger GPIO fault signal, brake in CBC mode\r\n");
for (int i = 0; i < 10; i++) {
gpio_set_level(cbc_fault_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_a_gpio));
// remove the fault signal
gpio_set_level(cbc_fault_gpio, 0);
// recovery
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, gpio_cbc_fault));
vTaskDelay(pdMS_TO_TICKS(40));
// should recovery automatically
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
}
printf("trigger GPIO fault signal, brake in OST mode\r\n");
for (int i = 0; i < 10; i++) {
gpio_set_level(ost_fault_gpio, 1);
vTaskDelay(pdMS_TO_TICKS(10));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
// can't recover because fault signal is still active
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_operator_recover_from_fault(operator, gpio_ost_fault));
// remove the fault signal
gpio_set_level(ost_fault_gpio, 0);
vTaskDelay(pdMS_TO_TICKS(40));
// for ost brake, the generator can't recover before we manually recover it
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
// now it's safe to recover the operator
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, gpio_ost_fault));
vTaskDelay(pdMS_TO_TICKS(40));
// should recovery now
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
}
printf("delete all mcpwm objects\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_fault(gpio_cbc_fault));
TEST_ESP_OK(mcpwm_del_fault(gpio_ost_fault));
TEST_ESP_OK(mcpwm_del_generator(gen_a));
TEST_ESP_OK(mcpwm_del_generator(gen_b));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}
TEST_CASE("mcpwm_operator_brake_on_soft_fault", "[mcpwm]")
{
printf("install timer\r\n");
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.group_id = 0,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 20000,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
printf("install operator\r\n");
mcpwm_operator_config_t operator_config = {
.group_id = 0,
};
mcpwm_oper_handle_t operator = NULL;
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
printf("install soft fault\r\n");
mcpwm_soft_fault_config_t soft_fault_config = {};
mcpwm_fault_handle_t soft_fault = NULL;
TEST_ESP_OK(mcpwm_new_soft_fault(&soft_fault_config, &soft_fault));
printf("set brake mode on fault\r\n");
mcpwm_brake_config_t brake_config = {
.fault = soft_fault,
.brake_mode = MCPWM_OPER_BRAKE_MODE_CBC,
.flags.cbc_recover_on_tez = true,
};
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
printf("create generators\r\n");
const int gen_a_gpio = 0;
const int gen_b_gpio = 2;
mcpwm_gen_handle_t gen_a = NULL;
mcpwm_gen_handle_t gen_b = NULL;
mcpwm_generator_config_t generator_config = {
.flags.io_loop_back = true,
};
generator_config.gen_gpio_num = gen_a_gpio;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_a));
generator_config.gen_gpio_num = gen_b_gpio;
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_b));
printf("set generator actions on timer event\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_a,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_b,
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
printf("set generator actions on brake event\r\n");
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_a,
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_b,
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, MCPWM_GEN_ACTION_HIGH),
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
printf("enable and start timer\r\n");
TEST_ESP_OK(mcpwm_timer_enable(timer));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
printf("trigger soft fault signal, brake in CBC mode\r\n");
for (int i = 0; i < 1; i++) {
// stop the timer, so the operator can't recover from fault automatically
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(40));
// check initial generator output
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
TEST_ESP_OK(mcpwm_soft_fault_activate(soft_fault));
// check generate output on fault event
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_a_gpio));
// start the timer, so that operator can recover at a specific event (e.g. tez)
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
// recover on tez
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, soft_fault));
vTaskDelay(pdMS_TO_TICKS(40));
// the generator output should be recoverd automatically
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
}
printf("change the brake mode to ost\r\n");
brake_config.brake_mode = MCPWM_OPER_BRAKE_MODE_OST;
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
printf("trigger soft fault signal, brake in OST mode\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
for (int i = 0; i < 10; i++) {
// check initial generator output
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
TEST_ESP_OK(mcpwm_soft_fault_activate(soft_fault));
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
vTaskDelay(pdMS_TO_TICKS(40));
// don't recover without a manual recover
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, soft_fault));
vTaskDelay(pdMS_TO_TICKS(10));
// should recovery now
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
}
printf("delete all mcpwm objects\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_fault(soft_fault));
TEST_ESP_OK(mcpwm_del_generator(gen_a));
TEST_ESP_OK(mcpwm_del_generator(gen_b));
TEST_ESP_OK(mcpwm_del_operator(operator));
TEST_ESP_OK(mcpwm_del_timer(timer));
}

View File

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "driver/mcpwm_timer.h"
#include "driver/mcpwm_sync.h"
#include "driver/gpio.h"
#include "esp_private/mcpwm.h"
#include "test_mcpwm_utils.h"
TEST_CASE("mcpwm_sync_source_install_uninstall", "[mcpwm]")
{
printf("install timer sync_src\r\n");
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1MHz
.period_ticks = 200,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
const int total_timers = SOC_MCPWM_TIMERS_PER_GROUP * SOC_MCPWM_GROUPS;
mcpwm_timer_handle_t timers[total_timers];
int k = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
timer_config.group_id = i;
for (int j = 0; j < SOC_MCPWM_TIMERS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[k++]));
}
}
mcpwm_timer_sync_src_config_t timer_sync_src_config = {
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
};
mcpwm_sync_handle_t timer_syncs[total_timers];
for (int i = 0; i < total_timers; i++) {
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[i], &timer_sync_src_config, &timer_syncs[i]));
}
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_new_timer_sync_src(timers[0], &timer_sync_src_config, &timer_syncs[0]));
printf("install gpio sync_src\r\n");
mcpwm_gpio_sync_src_config_t gpio_sync_config = {
.gpio_num = 0,
};
const int total_gpio_sync_srcs = SOC_MCPWM_GROUPS * SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP;
mcpwm_sync_handle_t gpio_sync_srcs[total_gpio_sync_srcs];
k = 0;
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
gpio_sync_config.group_id = i;
for (int j = 0; j < SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP; j++) {
TEST_ESP_OK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_srcs[k++]));
}
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_srcs[0]));
printf("delete synchors\r\n");
for (int i = 0; i < total_gpio_sync_srcs; i++) {
TEST_ESP_OK(mcpwm_del_sync_src(gpio_sync_srcs[i]));
}
for (int i = 0; i < total_timers; i++) {
TEST_ESP_OK(mcpwm_del_sync_src(timer_syncs[i]));
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
}
}
TEST_CASE("mcpwm_soft_sync_timer_phase_lock", "[mcpwm]")
{
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.group_id = 0,
.resolution_hz = 1000000, // 1MHz
.period_ticks = 200,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
};
mcpwm_timer_handle_t timer = NULL;
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
TEST_ESP_OK(mcpwm_timer_enable(timer));
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_STOP_FULL));
vTaskDelay(pdMS_TO_TICKS(10));
check_mcpwm_timer_phase(&timer, 1, timer_config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
printf("install soft sync source\r\n");
mcpwm_sync_handle_t soft_sync = NULL;
mcpwm_soft_sync_config_t soft_sync_config = {};
TEST_ESP_OK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync));
mcpwm_timer_sync_phase_config_t sync_phase_config = {
.count_value = 77,
.direction = MCPWM_TIMER_DIRECTION_UP,
.sync_src = soft_sync,
};
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timer, &sync_phase_config));
TEST_ESP_OK(mcpwm_soft_sync_activate(soft_sync));
check_mcpwm_timer_phase(&timer, 1, 77, MCPWM_TIMER_DIRECTION_UP);
TEST_ESP_OK(mcpwm_timer_disable(timer));
TEST_ESP_OK(mcpwm_del_timer(timer));
TEST_ESP_OK(mcpwm_del_sync_src(soft_sync));
}
TEST_CASE("mcpwm_gpio_sync_timer_phase_lock", "[mcpwm]")
{
// GPIO
// |
// v
// timer0-->timer1-->timer2
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.group_id = 0,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 500,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_sync_src_config_t sync_config = {
.flags.propagate_input_sync = 1, // reuse the input sync source as the output sync trigger
};
mcpwm_timer_handle_t timers[SOC_MCPWM_TIMERS_PER_GROUP];
mcpwm_sync_handle_t sync_srcs[SOC_MCPWM_TIMERS_PER_GROUP];
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[i], &sync_config, &sync_srcs[i]));
}
mcpwm_timer_sync_phase_config_t sync_phase_config = {
.count_value = 100,
.direction = MCPWM_TIMER_DIRECTION_UP,
};
mcpwm_sync_handle_t gpio_sync_src;
const int gpio_num = 0;
mcpwm_gpio_sync_src_config_t gpio_sync_config = {
.group_id = 0,
.gpio_num = gpio_num,
.flags.io_loop_back = true, // so that we can use gpio driver to simulate the sync signal
.flags.pull_down = true, // internally pull down
};
TEST_ESP_OK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_src));
// put the GPIO into initial state
gpio_set_level(gpio_num, 0);
for (int i = 1; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
sync_phase_config.sync_src = sync_srcs[i - 1];
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
}
sync_phase_config.sync_src = gpio_sync_src;
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[0], &sync_phase_config));
// simulate an GPIO sync singal
gpio_set_level(gpio_num, 1);
gpio_set_level(gpio_num, 0);
check_mcpwm_timer_phase(timers, SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP, 100, MCPWM_TIMER_DIRECTION_UP);
TEST_ESP_OK(mcpwm_del_sync_src(gpio_sync_src));
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
TEST_ESP_OK(mcpwm_del_sync_src(sync_srcs[i]));
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
}
}
TEST_CASE("mcpwm_timer_sync_timer_phase_lock", "[mcpwm]")
{
// +->timer1
// |
// timer0---+
// |
// +->timer2
mcpwm_timer_config_t timer_config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.group_id = 0,
.resolution_hz = 1000000, // 1MHz, 1us per tick
.period_ticks = 500,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
};
mcpwm_timer_handle_t timers[SOC_MCPWM_TIMERS_PER_GROUP];
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
}
mcpwm_timer_sync_src_config_t sync_config = {
.timer_event = MCPWM_TIMER_EVENT_FULL,
};
mcpwm_sync_handle_t sync_src;
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[0], &sync_config, &sync_src));
mcpwm_timer_sync_phase_config_t sync_phase_config = {
.count_value = 50,
.direction = MCPWM_TIMER_DIRECTION_DOWN,
.sync_src = sync_src,
};
for (int i = 1; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
}
TEST_ESP_OK(mcpwm_timer_enable(timers[0]));
TEST_ESP_OK(mcpwm_timer_start_stop(timers[0], MCPWM_TIMER_START_STOP_FULL));
vTaskDelay(pdMS_TO_TICKS(10));
check_mcpwm_timer_phase(&timers[1], 2, 50, MCPWM_TIMER_DIRECTION_DOWN);
TEST_ESP_OK(mcpwm_timer_disable(timers[0]));
TEST_ESP_OK(mcpwm_del_sync_src(sync_src));
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
}
}

View File

@ -0,0 +1,186 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "driver/mcpwm_timer.h"
#include "esp_private/mcpwm.h"
#include "test_mcpwm_utils.h"
TEST_CASE("mcpwm_timer_start_stop", "[mcpwm]")
{
mcpwm_timer_config_t config = {
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000, // 1MHz
.period_ticks = 400,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
};
const int num_timers = SOC_MCPWM_TIMERS_PER_GROUP * SOC_MCPWM_GROUPS;
printf("create mcpwm timer instances\r\n");
mcpwm_timer_handle_t timers[num_timers];
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
for (int j = 0; j < SOC_MCPWM_TIMERS_PER_GROUP; j++) {
config.group_id = i;
TEST_ESP_OK(mcpwm_new_timer(&config, &timers[i * SOC_MCPWM_TIMERS_PER_GROUP + j]));
}
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_timer(&config, &timers[0]));
}
// can't do start/stop control before enable
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_timer_start_stop(timers[0], MCPWM_TIMER_START_NO_STOP));
printf("enable timers\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_enable(timers[i]));
}
printf("start timer and then stop when empty\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_STOP_EMPTY));
}
vTaskDelay(pdMS_TO_TICKS(10));
check_mcpwm_timer_phase(timers, num_timers, 0, MCPWM_TIMER_DIRECTION_UP);
printf("start timer and then stop when full\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_STOP_FULL));
}
vTaskDelay(pdMS_TO_TICKS(10));
check_mcpwm_timer_phase(timers, num_timers, config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
printf("start freely and stop manually when full\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(10));
// stop at next counter full
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_STOP_FULL));
vTaskDelay(pdMS_TO_TICKS(10));
}
check_mcpwm_timer_phase(timers, num_timers, config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
printf("start freely and stop manually when empty\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
vTaskDelay(pdMS_TO_TICKS(10));
// stop at next counter empty
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_STOP_EMPTY));
vTaskDelay(pdMS_TO_TICKS(10));
}
check_mcpwm_timer_phase(timers, num_timers, 0, MCPWM_TIMER_DIRECTION_UP);
// can't delete timer before disable
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_timer(timers[0]));
printf("disable timers\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_disable(timers[i]));
}
printf("delete timers\r\n");
for (int i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
}
}
#define TEST_MCPWM_TIMER_EVENT_BIT_FULL (1 << 0)
#define TEST_MCPWM_TIMER_EVENT_BIT_EMPTY (1 << 1)
#define TEST_MCPWM_TIMER_EVENT_BIT_STOP (1 << 2)
typedef struct {
EventGroupHandle_t event_group;
uint32_t expected_full_counts;
uint32_t expected_empty_counts;
uint32_t accumulate_full_counts;
uint32_t accumulate_empty_counts;
} test_mcpwm_timer_user_data_t;
static bool test_on_stop(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
{
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
BaseType_t high_task_wakeup = pdFALSE;
esp_rom_printf("timer stopped at %u\r\n", edata->count_value);
TEST_ASSERT_EQUAL(0, edata->count_value);
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_STOP, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
static bool test_on_full(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
{
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
BaseType_t high_task_wakeup = pdFALSE;
udata->accumulate_full_counts++;
if (udata->accumulate_full_counts >= udata->expected_full_counts) {
udata->accumulate_full_counts = 0;
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_FULL, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
static bool test_on_empty(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
{
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
BaseType_t high_task_wakeup = pdFALSE;
udata->accumulate_empty_counts++;
if (udata->accumulate_empty_counts >= udata->expected_empty_counts) {
udata->accumulate_empty_counts = 0;
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
TEST_CASE("mcpwm_timer_event_callbacks", "[mcpwm]")
{
EventGroupHandle_t event_group = xEventGroupCreate();
EventBits_t bits = 0;
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1us per tick
.period_ticks = 20 * 1000, // 20ms, 50Hz
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
mcpwm_timer_handle_t timer = NULL;
printf("create mcpwm timer\r\n");
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
printf("register event callbacks\r\n");
mcpwm_timer_event_callbacks_t cbs = {
.on_stop = test_on_stop,
.on_full = test_on_full,
.on_empty = test_on_empty,
};
test_mcpwm_timer_user_data_t udata = {
.event_group = event_group,
.expected_empty_counts = 50,
.expected_full_counts = 50,
};
TEST_ESP_OK(mcpwm_timer_register_event_callbacks(timer, &cbs, &udata));
printf("enable timer\r\n");
TEST_ESP_OK(mcpwm_timer_enable(timer));
printf("start timer\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
printf("wait for full and empty events\r\n");
bits = xEventGroupWaitBits(event_group, TEST_MCPWM_TIMER_EVENT_BIT_FULL | TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, pdTRUE, pdTRUE, pdMS_TO_TICKS(1050));
TEST_ASSERT_EQUAL(TEST_MCPWM_TIMER_EVENT_BIT_FULL | TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, bits);
printf("stop timer and wait for event\r\n");
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
bits = xEventGroupWaitBits(event_group, TEST_MCPWM_TIMER_EVENT_BIT_STOP, pdTRUE, pdTRUE, pdMS_TO_TICKS(50));
TEST_ASSERT_EQUAL(TEST_MCPWM_TIMER_EVENT_BIT_STOP, bits);
printf("disable timer\r\n");
TEST_ESP_OK(mcpwm_timer_disable(timer));
printf("delete timer\r\n");
TEST_ESP_OK(mcpwm_del_timer(timer));
vEventGroupDelete(event_group);
}

View File

@ -0,0 +1,20 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "esp_private/mcpwm.h"
#include "test_mcpwm_utils.h"
void check_mcpwm_timer_phase(mcpwm_timer_handle_t *timers, size_t num_timers,
uint32_t expected_count, mcpwm_timer_direction_t expected_direction)
{
uint32_t count_value;
mcpwm_timer_direction_t direction;
for (size_t i = 0; i < num_timers; i++) {
TEST_ESP_OK(mcpwm_timer_get_phase(timers[i], &count_value, &direction));
TEST_ASSERT_INT_WITHIN(1, expected_count, count_value);
TEST_ASSERT_EQUAL(expected_direction, direction);
}
}

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include "esp_attr.h"
#include "driver/mcpwm_types.h"
#if CONFIG_MCPWM_ISR_IRAM_SAFE
#define TEST_MCPWM_CALLBACK_ATTR IRAM_ATTR
#else
#define TEST_MCPWM_CALLBACK_ATTR
#endif // CONFIG_MCPWM_ISR_IRAM_SAFE
void check_mcpwm_timer_phase(mcpwm_timer_handle_t *timers, size_t num_timers,
uint32_t expected_count, mcpwm_timer_direction_t expected_direction);

View File

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s3
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'release',
'iram_safe',
],
indirect=True,
)
def test_mcpwm(dut: Dut) -> None:
dut.expect('Press ENTER to see the list of tests')
dut.write('*')
dut.expect_unity_test_output()

View File

@ -0,0 +1,5 @@
CONFIG_COMPILER_DUMP_RTL_FILES=y
CONFIG_MCPWM_ISR_IRAM_SAFE=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

@ -0,0 +1,5 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@ -0,0 +1,2 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT=n