diff --git a/components/driver/.build-test-rules.yml b/components/driver/.build-test-rules.yml index d25382f32d..69fef02250 100644 --- a/components/driver/.build-test-rules.yml +++ b/components/driver/.build-test-rules.yml @@ -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 diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index c4c6e852be..5dc25f9776 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -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() diff --git a/components/driver/include/driver/mcpwm_cap.h b/components/driver/include/driver/mcpwm_cap.h new file mode 100644 index 0000000000..54aa3db59b --- /dev/null +++ b/components/driver/include/driver/mcpwm_cap.h @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_cmpr.h b/components/driver/include/driver/mcpwm_cmpr.h new file mode 100644 index 0000000000..d539f8a220 --- /dev/null +++ b/components/driver/include/driver/mcpwm_cmpr.h @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_fault.h b/components/driver/include/driver/mcpwm_fault.h new file mode 100644 index 0000000000..ddc635c679 --- /dev/null +++ b/components/driver/include/driver/mcpwm_fault.h @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_gen.h b/components/driver/include/driver/mcpwm_gen.h new file mode 100644 index 0000000000..96b31e9b96 --- /dev/null +++ b/components/driver/include/driver/mcpwm_gen.h @@ -0,0 +1,187 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_oper.h b/components/driver/include/driver/mcpwm_oper.h new file mode 100644 index 0000000000..5f89f806fc --- /dev/null +++ b/components/driver/include/driver/mcpwm_oper.h @@ -0,0 +1,159 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_prelude.h b/components/driver/include/driver/mcpwm_prelude.h new file mode 100644 index 0000000000..fd460ece38 --- /dev/null +++ b/components/driver/include/driver/mcpwm_prelude.h @@ -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" diff --git a/components/driver/include/driver/mcpwm_sync.h b/components/driver/include/driver/mcpwm_sync.h new file mode 100644 index 0000000000..7f691e9fa6 --- /dev/null +++ b/components/driver/include/driver/mcpwm_sync.h @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_timer.h b/components/driver/include/driver/mcpwm_timer.h new file mode 100644 index 0000000000..3bcec03ab5 --- /dev/null +++ b/components/driver/include/driver/mcpwm_timer.h @@ -0,0 +1,144 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/driver/mcpwm_types.h b/components/driver/include/driver/mcpwm_types.h new file mode 100644 index 0000000000..d25cff5614 --- /dev/null +++ b/components/driver/include/driver/mcpwm_types.h @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/driver/include/esp_private/mcpwm.h b/components/driver/include/esp_private/mcpwm.h new file mode 100644 index 0000000000..62f44ce435 --- /dev/null +++ b/components/driver/include/esp_private/mcpwm.h @@ -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 +#include +#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 diff --git a/components/driver/mcpwm/mcpwm_cap.c b/components/driver/mcpwm/mcpwm_cap.c new file mode 100644 index 0000000000..94667a6f14 --- /dev/null +++ b/components/driver/mcpwm/mcpwm_cap.c @@ -0,0 +1,420 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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(); + } +} diff --git a/components/driver/mcpwm/mcpwm_cmpr.c b/components/driver/mcpwm/mcpwm_cmpr.c new file mode 100644 index 0000000000..c0f9e82aad --- /dev/null +++ b/components/driver/mcpwm/mcpwm_cmpr.c @@ -0,0 +1,210 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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(); + } +} diff --git a/components/driver/mcpwm/mcpwm_com.c b/components/driver/mcpwm/mcpwm_com.c new file mode 100644 index 0000000000..7d9e555e32 --- /dev/null +++ b/components/driver/mcpwm/mcpwm_com.c @@ -0,0 +1,135 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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; +} diff --git a/components/driver/mcpwm/mcpwm_fault.c b/components/driver/mcpwm/mcpwm_fault.c new file mode 100644 index 0000000000..44936d7215 --- /dev/null +++ b/components/driver/mcpwm/mcpwm_fault.c @@ -0,0 +1,302 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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(); + } +} diff --git a/components/driver/mcpwm/mcpwm_gen.c b/components/driver/mcpwm/mcpwm_gen.c new file mode 100644 index 0000000000..41ec7c2f62 --- /dev/null +++ b/components/driver/mcpwm/mcpwm_gen.c @@ -0,0 +1,264 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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; +} diff --git a/components/driver/mcpwm/mcpwm_oper.c b/components/driver/mcpwm/mcpwm_oper.c new file mode 100644 index 0000000000..cbf3219dea --- /dev/null +++ b/components/driver/mcpwm/mcpwm_oper.c @@ -0,0 +1,363 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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(); + } +} diff --git a/components/driver/mcpwm/mcpwm_private.h b/components/driver/mcpwm/mcpwm_private.h new file mode 100644 index 0000000000..b5881280f8 --- /dev/null +++ b/components/driver/mcpwm/mcpwm_private.h @@ -0,0 +1,221 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#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 diff --git a/components/driver/mcpwm/mcpwm_sync.c b/components/driver/mcpwm/mcpwm_sync.c new file mode 100644 index 0000000000..164c36d9ee --- /dev/null +++ b/components/driver/mcpwm/mcpwm_sync.c @@ -0,0 +1,297 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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; +} diff --git a/components/driver/mcpwm/mcpwm_timer.c b/components/driver/mcpwm/mcpwm_timer.c new file mode 100644 index 0000000000..e816a7db7a --- /dev/null +++ b/components/driver/mcpwm/mcpwm_timer.c @@ -0,0 +1,364 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#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(); + } +} diff --git a/components/driver/test_apps/mcpwm/CMakeLists.txt b/components/driver/test_apps/mcpwm/CMakeLists.txt new file mode 100644 index 0000000000..ad15f86dc1 --- /dev/null +++ b/components/driver/test_apps/mcpwm/CMakeLists.txt @@ -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() diff --git a/components/driver/test_apps/mcpwm/README.md b/components/driver/test_apps/mcpwm/README.md new file mode 100644 index 0000000000..5ab630aafd --- /dev/null +++ b/components/driver/test_apps/mcpwm/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-S3 | +| ----------------- | ----- | -------- | diff --git a/components/driver/test_apps/mcpwm/main/CMakeLists.txt b/components/driver/test_apps/mcpwm/main/CMakeLists.txt new file mode 100644 index 0000000000..a6eebbfd72 --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/CMakeLists.txt @@ -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) diff --git a/components/driver/test_apps/mcpwm/main/test_app_main.c b/components/driver/test_apps/mcpwm/main/test_app_main.c new file mode 100644 index 0000000000..6f449a0046 --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_app_main.c @@ -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(); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_cap.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_cap.c new file mode 100644 index 0000000000..2a6dd37943 --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_cap.c @@ -0,0 +1,238 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#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)); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_cmpr.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_cmpr.c new file mode 100644 index 0000000000..5e0404d18b --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_cmpr.c @@ -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)); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_fault.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_fault.c new file mode 100644 index 0000000000..c516b34d0b --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_fault.c @@ -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)); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_gen.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_gen.c new file mode 100644 index 0000000000..1d397bd0e2 --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_gen.c @@ -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); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c new file mode 100644 index 0000000000..6c013e705c --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c @@ -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)); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_sync.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_sync.c new file mode 100644 index 0000000000..27cd0f7f63 --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_sync.c @@ -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])); + } +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_timer.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_timer.c new file mode 100644 index 0000000000..2c69fd7dbe --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_timer.c @@ -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); +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.c b/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.c new file mode 100644 index 0000000000..d1c933b62a --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.c @@ -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); + } +} diff --git a/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.h b/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.h new file mode 100644 index 0000000000..feb98a415f --- /dev/null +++ b/components/driver/test_apps/mcpwm/main/test_mcpwm_utils.h @@ -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); diff --git a/components/driver/test_apps/mcpwm/pytest_mcpwm.py b/components/driver/test_apps/mcpwm/pytest_mcpwm.py new file mode 100644 index 0000000000..6def6d2cf4 --- /dev/null +++ b/components/driver/test_apps/mcpwm/pytest_mcpwm.py @@ -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() diff --git a/components/driver/test_apps/mcpwm/sdkconfig.ci.iram_safe b/components/driver/test_apps/mcpwm/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..f5e68a8539 --- /dev/null +++ b/components/driver/test_apps/mcpwm/sdkconfig.ci.iram_safe @@ -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 diff --git a/components/driver/test_apps/mcpwm/sdkconfig.ci.release b/components/driver/test_apps/mcpwm/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/mcpwm/sdkconfig.ci.release @@ -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 diff --git a/components/driver/test_apps/mcpwm/sdkconfig.defaults b/components/driver/test_apps/mcpwm/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/mcpwm/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n