mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-05 05:34:32 +02:00
Merge branch 'feature/mcpwm_new_driver' into 'master'
⛵ MCPWM Driver-NG Closes IDF-2471, IDF-2895, IDF-20, and IDF-3945 See merge request espressif/esp-idf!11480
This commit is contained in:
@@ -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
|
||||
|
@@ -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()
|
||||
|
@@ -352,6 +352,20 @@ menu "Driver Configurations"
|
||||
cache misses, and also be able to run whilst the cache is disabled.
|
||||
(e.g. SPI Flash write)
|
||||
|
||||
config MCPWM_SUPPRESS_DEPRECATE_WARN
|
||||
bool "Suppress leagcy driver deprecated warning"
|
||||
default n
|
||||
help
|
||||
Wether to suppress the deprecation warnings when using legacy MCPWM driver (driver/mcpwm.h).
|
||||
If you want to continue using the legacy driver, and don't want to see related deprecation warnings,
|
||||
you can enable this option.
|
||||
|
||||
config MCPWM_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
default n
|
||||
help
|
||||
Wether to enable the debug log message for MCPWM driver.
|
||||
Note that, this option only controls the MCPWM driver log, won't affect other drivers.
|
||||
endmenu # MCPWM Configuration
|
||||
|
||||
menu "I2S Configuration"
|
||||
|
@@ -9,298 +9,16 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_bit_defs.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/mcpwm_types.h"
|
||||
#include "driver/mcpwm_types_legacy.h"
|
||||
|
||||
#if !CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN
|
||||
#warning "legacy MCPWM driver is deprecated, please migrate to the new driver (include driver/mcpwm_prelude.h)"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief IO signals for the MCPWM
|
||||
*
|
||||
* - 6 MCPWM output pins that generate PWM signals
|
||||
* - 3 MCPWM fault input pins to detect faults like overcurrent, overvoltage, etc.
|
||||
* - 3 MCPWM sync input pins to synchronize MCPWM outputs signals
|
||||
* - 3 MCPWM capture input pins to gather feedback from controlled motors, using e.g. hall sensors
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM0A = 0, /*!<PWM0A output pin*/
|
||||
MCPWM0B, /*!<PWM0B output pin*/
|
||||
MCPWM1A, /*!<PWM1A output pin*/
|
||||
MCPWM1B, /*!<PWM1B output pin*/
|
||||
MCPWM2A, /*!<PWM2A output pin*/
|
||||
MCPWM2B, /*!<PWM2B output pin*/
|
||||
MCPWM_SYNC_0, /*!<SYNC0 input pin*/
|
||||
MCPWM_SYNC_1, /*!<SYNC1 input pin*/
|
||||
MCPWM_SYNC_2, /*!<SYNC2 input pin*/
|
||||
MCPWM_FAULT_0, /*!<FAULT0 input pin*/
|
||||
MCPWM_FAULT_1, /*!<FAULT1 input pin*/
|
||||
MCPWM_FAULT_2, /*!<FAULT2 input pin*/
|
||||
MCPWM_CAP_0 = 84, /*!<CAP0 input pin*/
|
||||
MCPWM_CAP_1, /*!<CAP1 input pin*/
|
||||
MCPWM_CAP_2, /*!<CAP2 input pin*/
|
||||
} mcpwm_io_signals_t;
|
||||
|
||||
/**
|
||||
* @brief pin number for MCPWM
|
||||
*/
|
||||
typedef struct {
|
||||
int mcpwm0a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm0b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm1a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm1b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm2a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm2b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm_sync0_in_num; /*!<SYNC0 in pin*/
|
||||
int mcpwm_sync1_in_num; /*!<SYNC1 in pin*/
|
||||
int mcpwm_sync2_in_num; /*!<SYNC2 in pin*/
|
||||
int mcpwm_fault0_in_num; /*!<FAULT0 in pin*/
|
||||
int mcpwm_fault1_in_num; /*!<FAULT1 in pin*/
|
||||
int mcpwm_fault2_in_num; /*!<FAULT2 in pin*/
|
||||
int mcpwm_cap0_in_num; /*!<CAP0 in pin*/
|
||||
int mcpwm_cap1_in_num; /*!<CAP1 in pin*/
|
||||
int mcpwm_cap2_in_num; /*!<CAP2 in pin*/
|
||||
} mcpwm_pin_config_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM unit
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_UNIT_0, /*!<MCPWM unit0 selected*/
|
||||
MCPWM_UNIT_1, /*!<MCPWM unit1 selected*/
|
||||
MCPWM_UNIT_MAX, /*!<Max number of MCPWM units*/
|
||||
} mcpwm_unit_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM timer
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_TIMER_0, /*!<Select MCPWM timer0*/
|
||||
MCPWM_TIMER_1, /*!<Select MCPWM timer1*/
|
||||
MCPWM_TIMER_2, /*!<Select MCPWM timer2*/
|
||||
MCPWM_TIMER_MAX, /*!<Max number of timers in a unit*/
|
||||
} mcpwm_timer_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM operator
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_GEN_A, /*!<Select MCPWMXA, where 'X' is operator number*/
|
||||
MCPWM_GEN_B, /*!<Select MCPWMXB, where 'X' is operator number*/
|
||||
MCPWM_GEN_MAX, /*!<Num of generators to each operator of MCPWM*/
|
||||
} mcpwm_generator_t;
|
||||
|
||||
//definitions and macros to be back-compatible before IDFv4.1
|
||||
#define MCPWM_OPR_A MCPWM_GEN_A ///< @deprecated
|
||||
#define MCPWM_OPR_B MCPWM_GEN_B ///< @deprecated
|
||||
#define MCPWM_OPR_MAX MCPWM_GEN_MAX ///< @deprecated
|
||||
typedef mcpwm_generator_t mcpwm_operator_t; ///< @deprecated
|
||||
|
||||
/**
|
||||
* @brief MCPWM carrier output inversion, high frequency carrier signal active with MCPWM signal is high
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_CARRIER_OUT_IVT_DIS, /*!<Enable carrier output inversion*/
|
||||
MCPWM_CARRIER_OUT_IVT_EN, /*!<Disable carrier output inversion*/
|
||||
} mcpwm_carrier_out_ivt_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select fault signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_F0, /*!<Select F0 as input*/
|
||||
MCPWM_SELECT_F1, /*!<Select F1 as input*/
|
||||
MCPWM_SELECT_F2, /*!<Select F2 as input*/
|
||||
} mcpwm_fault_signal_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select sync signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_NO_INPUT, /*!<No sync input selected*/
|
||||
MCPWM_SELECT_TIMER0_SYNC, /*!<Select software sync signal from timer0 as input*/
|
||||
MCPWM_SELECT_TIMER1_SYNC, /*!<Select software sync signal from timer1 as input*/
|
||||
MCPWM_SELECT_TIMER2_SYNC, /*!<Select software sync signal from timer2 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC0, /*!<Select GPIO SYNC0 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC1, /*!<Select GPIO SYNC1 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC2, /*!<Select GPIO SYNC2 as input*/
|
||||
} mcpwm_sync_signal_t;
|
||||
|
||||
// backward compatibility
|
||||
#define MCPWM_SELCT_SYNC0 MCPWM_SELCT_GPIO_SYNC0
|
||||
#define MCPWM_SELCT_SYNC1 MCPWM_SELCT_GPIO_SYNC1
|
||||
#define MCPWM_SELCT_SYNC2 MCPWM_SELCT_GPIO_SYNC2
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer sync event trigger
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SWSYNC_SOURCE_SYNCIN, /*!<the input sync signal will be routed to its sync output path*/
|
||||
MCPWM_SWSYNC_SOURCE_TEZ, /*!<sync signal generated when timer counts to zero*/
|
||||
MCPWM_SWSYNC_SOURCE_TEP, /*!<sync signal generated when timer counts to peak*/
|
||||
MCPWM_SWSYNC_SOURCE_DISABLED, /*!<timer does not generate sync signals*/
|
||||
} mcpwm_timer_sync_trigger_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select triggering level of fault signal
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_LOW_LEVEL_TGR, /*!<Fault condition occurs when fault input signal goes from high to low*/
|
||||
MCPWM_HIGH_LEVEL_TGR, /*!<Fault condition occurs when fault input signal goes low to high*/
|
||||
} mcpwm_fault_input_level_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select capture starts from which edge
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_NEG_EDGE = BIT(0), /*!<Capture the negative edge*/
|
||||
MCPWM_POS_EDGE = BIT(1), /*!<Capture the positive edge*/
|
||||
MCPWM_BOTH_EDGE = BIT(1) | BIT(0), /*!<Capture both edges*/
|
||||
} mcpwm_capture_on_edge_t;
|
||||
|
||||
/**
|
||||
* @brief Select type of MCPWM counter
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_FREEZE_COUNTER, /*!<Counter freeze */
|
||||
MCPWM_UP_COUNTER, /*!<For asymmetric MCPWM*/
|
||||
MCPWM_DOWN_COUNTER, /*!<For asymmetric MCPWM*/
|
||||
MCPWM_UP_DOWN_COUNTER, /*!<For symmetric MCPWM, frequency is half of MCPWM frequency set*/
|
||||
MCPWM_COUNTER_MAX, /*!<Maximum counter mode*/
|
||||
} mcpwm_counter_type_t;
|
||||
|
||||
/**
|
||||
* @brief Select type of MCPWM duty cycle mode
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_DUTY_MODE_0 = 0, /*!<Active high duty, i.e. duty cycle proportional to high time for asymmetric MCPWM*/
|
||||
MCPWM_DUTY_MODE_1, /*!<Active low duty, i.e. duty cycle proportional to low time for asymmetric MCPWM, out of phase(inverted) MCPWM*/
|
||||
MCPWM_HAL_GENERATOR_MODE_FORCE_LOW,
|
||||
MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH,
|
||||
MCPWM_DUTY_MODE_MAX, /*!<Num of duty cycle modes*/
|
||||
} mcpwm_duty_type_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM deadtime types, used to generate deadtime, RED refers to rising edge delay and FED refers to falling edge delay
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_DEADTIME_BYPASS = 0, /*!<Bypass the deadtime*/
|
||||
MCPWM_BYPASS_RED, /*!<MCPWMXA Out = MCPWMXA In with no delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_BYPASS_FED, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB Out = MCPWMXB In with no delay*/
|
||||
MCPWM_ACTIVE_HIGH_MODE, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_ACTIVE_LOW_MODE, /*!<MCPWMXA Out = MCPWMXA In with compliment of rising edge delay, MCPWMXB Out = MCPWMXA In with compliment of falling edge delay*/
|
||||
MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB = MCPWMXA In with compliment of falling edge delay*/
|
||||
MCPWM_ACTIVE_LOW_COMPLIMENT_MODE, /*!<MCPWMXA Out = MCPWMXA In with compliment of rising edge delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_ACTIVE_RED_FED_FROM_PWMXA, /*!<MCPWMXA Out = MCPWMXB Out = MCPWMXA In with rising edge delay as well as falling edge delay*/
|
||||
MCPWM_ACTIVE_RED_FED_FROM_PWMXB, /*!<MCPWMXA Out = MCPWMXB Out = MCPWMXB In with rising edge delay as well as falling edge delay*/
|
||||
MCPWM_DEADTIME_TYPE_MAX, /*!<Maximum number of supported dead time modes*/
|
||||
} mcpwm_deadtime_type_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select action to be taken on the output when event happens
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_ACTION_NO_CHANGE = 0, /*!<No change in the output*/
|
||||
MCPWM_ACTION_FORCE_LOW, /*!<Make output low*/
|
||||
MCPWM_ACTION_FORCE_HIGH, /*!<Make output high*/
|
||||
MCPWM_ACTION_TOGGLE, /*!<Make output toggle*/
|
||||
} mcpwm_output_action_t;
|
||||
|
||||
/// @deprecated MCPWM select action to be taken on MCPWMXA when fault occurs
|
||||
typedef mcpwm_output_action_t mcpwm_action_on_pwmxa_t;
|
||||
#define MCPWM_NO_CHANGE_IN_MCPWMXA MCPWM_ACTION_NO_CHANGE /*!< @deprecated No change in MCPWMXA output*/
|
||||
#define MCPWM_FORCE_MCPWMXA_LOW MCPWM_ACTION_FORCE_LOW /*!< @deprecated Make MCPWMXA output low*/
|
||||
#define MCPWM_FORCE_MCPWMXA_HIGH MCPWM_ACTION_FORCE_HIGH /*!< @deprecated Make MCPWMXA output high*/
|
||||
#define MCPWM_TOG_MCPWMXA MCPWM_ACTION_TOGGLE /*!< @deprecated Make MCPWMXA output toggle*/
|
||||
|
||||
/// @deprecated MCPWM select action to be taken on MCPWMXB when fault occurs
|
||||
typedef mcpwm_output_action_t mcpwm_action_on_pwmxb_t;
|
||||
#define MCPWM_NO_CHANGE_IN_MCPWMXB MCPWM_ACTION_NO_CHANGE /*!< @deprecated No change in MCPWMXB output*/
|
||||
#define MCPWM_FORCE_MCPWMXB_LOW MCPWM_ACTION_FORCE_LOW /*!< @deprecated Make MCPWMXB output low*/
|
||||
#define MCPWM_FORCE_MCPWMXB_HIGH MCPWM_ACTION_FORCE_HIGH /*!< @deprecated Make MCPWMXB output high*/
|
||||
#define MCPWM_TOG_MCPWMXB MCPWM_ACTION_TOGGLE /*!< @deprecated Make MCPWMXB output toggle*/
|
||||
|
||||
/**
|
||||
* @brief MCPWM select capture signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_CAP0, /*!<Select CAP0 as input*/
|
||||
MCPWM_SELECT_CAP1, /*!<Select CAP1 as input*/
|
||||
MCPWM_SELECT_CAP2, /*!<Select CAP2 as input*/
|
||||
} mcpwm_capture_signal_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture channel ID alias
|
||||
*/
|
||||
typedef mcpwm_capture_signal_t mcpwm_capture_channel_id_t;
|
||||
|
||||
/**
|
||||
* @brief event data that will be passed into ISR callback
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_capture_on_edge_t cap_edge; /*!<Which signal edge is detected*/
|
||||
uint32_t cap_value; /*!<Corresponding timestamp when event occurs. Clock rate = APB(usually 80M)*/
|
||||
} cap_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Type of capture event callback
|
||||
* @param mcpwm MCPWM unit(0-1)
|
||||
* @param cap_channel capture channel ID
|
||||
* @param edata Capture event data, contains capture edge and capture value, fed by the driver
|
||||
* @param user_data User registered data, passed from `mcpwm_capture_config_t`
|
||||
*
|
||||
* @note Since this an ISR callback so do not do anything that may block and call APIs that is designed to be used within ISR(usually has '_ISR' postfix)
|
||||
*
|
||||
* @return Whether a task switch is needed after the callback function returns,
|
||||
* this is usually due to the callback wakes up some high priority task.
|
||||
*
|
||||
*/
|
||||
typedef bool (*cap_isr_cb_t)(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* @brief MCPWM config structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t frequency; /*!<Set frequency of MCPWM in Hz*/
|
||||
float cmpr_a; /*!<Set % duty cycle for operator a(MCPWMXA), i.e for 62.3% duty cycle, duty_a = 62.3*/
|
||||
float cmpr_b; /*!<Set % duty cycle for operator b(MCPWMXB), i.e for 48% duty cycle, duty_b = 48.0*/
|
||||
mcpwm_duty_type_t duty_mode; /*!<Set type of duty cycle*/
|
||||
mcpwm_counter_type_t counter_mode; /*!<Set type of MCPWM counter*/
|
||||
} mcpwm_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM carrier configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t carrier_period; /*!<Set carrier period = (carrier_period + 1)*800ns, carrier_period should be < 16*/
|
||||
uint8_t carrier_duty; /*!<Set carrier duty cycle, carrier_duty should be less than 8 (increment every 12.5%)*/
|
||||
uint8_t pulse_width_in_os; /*!<Set pulse width of first pulse in one shot mode = (carrier period)*(pulse_width_in_os + 1), should be less then 16*/
|
||||
mcpwm_carrier_out_ivt_t carrier_ivt_mode; /*!<Invert output of carrier*/
|
||||
} mcpwm_carrier_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM config capture structure
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_capture_on_edge_t cap_edge; /*!<Set capture edge*/
|
||||
uint32_t cap_prescale; /*!<Prescale of capture signal, ranging from 1 to 256*/
|
||||
cap_isr_cb_t capture_cb; /*!<User defined capture event callback, running under interrupt context */
|
||||
void *user_data; /*!<User defined ISR callback function args*/
|
||||
} mcpwm_capture_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM config sync structure
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_sync_signal_t sync_sig; /*!<Set sync input signal that will cause timer to sync*/
|
||||
uint32_t timer_val; /*!<Counter value to be set after sync, in 0 ~ 999, unit: 1 / 1000 * peak*/
|
||||
mcpwm_timer_direction_t count_direction; /*!<Counting direction to be set after sync */
|
||||
} mcpwm_sync_config_t;
|
||||
|
||||
/**
|
||||
* @brief This function initializes each gpio signal for MCPWM
|
||||
*
|
307
components/driver/deprecated/driver/mcpwm_types_legacy.h
Normal file
307
components/driver/deprecated/driver/mcpwm_types_legacy.h
Normal file
@@ -0,0 +1,307 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_bit_defs.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief IO signals for the MCPWM
|
||||
*
|
||||
* - 6 MCPWM output pins that generate PWM signals
|
||||
* - 3 MCPWM fault input pins to detect faults like over-current, over-voltage, etc.
|
||||
* - 3 MCPWM sync input pins to synchronize MCPWM outputs signals
|
||||
* - 3 MCPWM capture input pins to gather feedback from controlled motors, using e.g. hall sensors
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM0A = 0, /*!<PWM0A output pin*/
|
||||
MCPWM0B, /*!<PWM0B output pin*/
|
||||
MCPWM1A, /*!<PWM1A output pin*/
|
||||
MCPWM1B, /*!<PWM1B output pin*/
|
||||
MCPWM2A, /*!<PWM2A output pin*/
|
||||
MCPWM2B, /*!<PWM2B output pin*/
|
||||
MCPWM_SYNC_0, /*!<SYNC0 input pin*/
|
||||
MCPWM_SYNC_1, /*!<SYNC1 input pin*/
|
||||
MCPWM_SYNC_2, /*!<SYNC2 input pin*/
|
||||
MCPWM_FAULT_0, /*!<FAULT0 input pin*/
|
||||
MCPWM_FAULT_1, /*!<FAULT1 input pin*/
|
||||
MCPWM_FAULT_2, /*!<FAULT2 input pin*/
|
||||
MCPWM_CAP_0 = 84, /*!<CAP0 input pin*/
|
||||
MCPWM_CAP_1, /*!<CAP1 input pin*/
|
||||
MCPWM_CAP_2, /*!<CAP2 input pin*/
|
||||
} mcpwm_io_signals_t;
|
||||
|
||||
/**
|
||||
* @brief pin number for MCPWM
|
||||
*/
|
||||
typedef struct {
|
||||
int mcpwm0a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm0b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm1a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm1b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm2a_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm2b_out_num; /*!<MCPWM0A out pin*/
|
||||
int mcpwm_sync0_in_num; /*!<SYNC0 in pin*/
|
||||
int mcpwm_sync1_in_num; /*!<SYNC1 in pin*/
|
||||
int mcpwm_sync2_in_num; /*!<SYNC2 in pin*/
|
||||
int mcpwm_fault0_in_num; /*!<FAULT0 in pin*/
|
||||
int mcpwm_fault1_in_num; /*!<FAULT1 in pin*/
|
||||
int mcpwm_fault2_in_num; /*!<FAULT2 in pin*/
|
||||
int mcpwm_cap0_in_num; /*!<CAP0 in pin*/
|
||||
int mcpwm_cap1_in_num; /*!<CAP1 in pin*/
|
||||
int mcpwm_cap2_in_num; /*!<CAP2 in pin*/
|
||||
} mcpwm_pin_config_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM unit
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_UNIT_0, /*!<MCPWM unit0 selected*/
|
||||
MCPWM_UNIT_1, /*!<MCPWM unit1 selected*/
|
||||
MCPWM_UNIT_MAX, /*!<Max number of MCPWM units*/
|
||||
} mcpwm_unit_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM timer
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_TIMER_0, /*!<Select MCPWM timer0*/
|
||||
MCPWM_TIMER_1, /*!<Select MCPWM timer1*/
|
||||
MCPWM_TIMER_2, /*!<Select MCPWM timer2*/
|
||||
MCPWM_TIMER_MAX, /*!<Max number of timers in a unit*/
|
||||
} mcpwm_timer_t;
|
||||
|
||||
/**
|
||||
* @brief Select MCPWM operator
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_GEN_A, /*!<Select MCPWMXA, where 'X' is operator number*/
|
||||
MCPWM_GEN_B, /*!<Select MCPWMXB, where 'X' is operator number*/
|
||||
MCPWM_GEN_MAX, /*!<Num of generators to each operator of MCPWM*/
|
||||
} mcpwm_generator_t;
|
||||
|
||||
//definitions and macros to be back-compatible before IDFv4.1
|
||||
#define MCPWM_OPR_A MCPWM_GEN_A ///< @deprecated
|
||||
#define MCPWM_OPR_B MCPWM_GEN_B ///< @deprecated
|
||||
#define MCPWM_OPR_MAX MCPWM_GEN_MAX ///< @deprecated
|
||||
typedef mcpwm_generator_t mcpwm_operator_t; ///< @deprecated
|
||||
|
||||
/**
|
||||
* @brief MCPWM carrier output inversion, high frequency carrier signal active with MCPWM signal is high
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_CARRIER_OUT_IVT_DIS, /*!<Enable carrier output inversion*/
|
||||
MCPWM_CARRIER_OUT_IVT_EN, /*!<Disable carrier output inversion*/
|
||||
} mcpwm_carrier_out_ivt_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select fault signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_F0, /*!<Select F0 as input*/
|
||||
MCPWM_SELECT_F1, /*!<Select F1 as input*/
|
||||
MCPWM_SELECT_F2, /*!<Select F2 as input*/
|
||||
} mcpwm_fault_signal_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select sync signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_NO_INPUT, /*!<No sync input selected*/
|
||||
MCPWM_SELECT_TIMER0_SYNC, /*!<Select software sync signal from timer0 as input*/
|
||||
MCPWM_SELECT_TIMER1_SYNC, /*!<Select software sync signal from timer1 as input*/
|
||||
MCPWM_SELECT_TIMER2_SYNC, /*!<Select software sync signal from timer2 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC0, /*!<Select GPIO SYNC0 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC1, /*!<Select GPIO SYNC1 as input*/
|
||||
MCPWM_SELECT_GPIO_SYNC2, /*!<Select GPIO SYNC2 as input*/
|
||||
} mcpwm_sync_signal_t;
|
||||
|
||||
// backward compatibility
|
||||
#define MCPWM_SELCT_SYNC0 MCPWM_SELCT_GPIO_SYNC0
|
||||
#define MCPWM_SELCT_SYNC1 MCPWM_SELCT_GPIO_SYNC1
|
||||
#define MCPWM_SELCT_SYNC2 MCPWM_SELCT_GPIO_SYNC2
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer sync event trigger
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SWSYNC_SOURCE_SYNCIN, /*!<the input sync signal will be routed to its sync output path*/
|
||||
MCPWM_SWSYNC_SOURCE_TEZ, /*!<sync signal generated when timer counts to zero*/
|
||||
MCPWM_SWSYNC_SOURCE_TEP, /*!<sync signal generated when timer counts to peak*/
|
||||
MCPWM_SWSYNC_SOURCE_DISABLED, /*!<timer does not generate sync signals*/
|
||||
} mcpwm_timer_sync_trigger_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select triggering level of fault signal
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_LOW_LEVEL_TGR, /*!<Fault condition occurs when fault input signal goes from high to low*/
|
||||
MCPWM_HIGH_LEVEL_TGR, /*!<Fault condition occurs when fault input signal goes low to high*/
|
||||
} mcpwm_fault_input_level_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select capture starts from which edge
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_NEG_EDGE = BIT(0), /*!<Capture the negative edge*/
|
||||
MCPWM_POS_EDGE = BIT(1), /*!<Capture the positive edge*/
|
||||
MCPWM_BOTH_EDGE = BIT(1) | BIT(0), /*!<Capture both edges*/
|
||||
} mcpwm_capture_on_edge_t;
|
||||
|
||||
/**
|
||||
* @brief Select type of MCPWM counter
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_FREEZE_COUNTER, /*!<Counter freeze */
|
||||
MCPWM_UP_COUNTER, /*!<For asymmetric MCPWM*/
|
||||
MCPWM_DOWN_COUNTER, /*!<For asymmetric MCPWM*/
|
||||
MCPWM_UP_DOWN_COUNTER, /*!<For symmetric MCPWM, frequency is half of MCPWM frequency set*/
|
||||
MCPWM_COUNTER_MAX, /*!<Maximum counter mode*/
|
||||
} mcpwm_counter_type_t;
|
||||
|
||||
/**
|
||||
* @brief Select type of MCPWM duty cycle mode
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_DUTY_MODE_0 = 0, /*!<Active high duty, i.e. duty cycle proportional to high time for asymmetric MCPWM*/
|
||||
MCPWM_DUTY_MODE_1, /*!<Active low duty, i.e. duty cycle proportional to low time for asymmetric MCPWM, out of phase(inverted) MCPWM*/
|
||||
MCPWM_DUTY_MODE_FORCE_LOW, /*!< Forced to output low level */
|
||||
MCPWM_DUTY_MODE_FORCE_HIGH, /*!< Forced to output high level */
|
||||
MCPWM_DUTY_MODE_MAX, /*!<Num of duty cycle modes*/
|
||||
} mcpwm_duty_type_t;
|
||||
|
||||
#define MCPWM_HAL_GENERATOR_MODE_FORCE_LOW MCPWM_DUTY_MODE_FORCE_LOW /*!< @deprecated Forced to output low level */
|
||||
#define MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH MCPWM_DUTY_MODE_FORCE_HIGH /*!< @deprecated Forced to output low level */
|
||||
|
||||
/**
|
||||
* @brief MCPWM deadtime types, used to generate deadtime, RED refers to rising edge delay and FED refers to falling edge delay
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_DEADTIME_BYPASS = 0, /*!<Bypass the deadtime*/
|
||||
MCPWM_BYPASS_RED, /*!<MCPWMXA Out = MCPWMXA In with no delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_BYPASS_FED, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB Out = MCPWMXB In with no delay*/
|
||||
MCPWM_ACTIVE_HIGH_MODE, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_ACTIVE_LOW_MODE, /*!<MCPWMXA Out = MCPWMXA In with compliment of rising edge delay, MCPWMXB Out = MCPWMXA In with compliment of falling edge delay*/
|
||||
MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, /*!<MCPWMXA Out = MCPWMXA In with rising edge delay, MCPWMXB = MCPWMXA In with compliment of falling edge delay*/
|
||||
MCPWM_ACTIVE_LOW_COMPLIMENT_MODE, /*!<MCPWMXA Out = MCPWMXA In with compliment of rising edge delay, MCPWMXB Out = MCPWMXA In with falling edge delay*/
|
||||
MCPWM_ACTIVE_RED_FED_FROM_PWMXA, /*!<MCPWMXA Out = MCPWMXB Out = MCPWMXA In with rising edge delay as well as falling edge delay*/
|
||||
MCPWM_ACTIVE_RED_FED_FROM_PWMXB, /*!<MCPWMXA Out = MCPWMXB Out = MCPWMXB In with rising edge delay as well as falling edge delay*/
|
||||
MCPWM_DEADTIME_TYPE_MAX, /*!<Maximum number of supported dead time modes*/
|
||||
} mcpwm_deadtime_type_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM select action to be taken on the output when event happens
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_ACTION_NO_CHANGE = 0, /*!<No change in the output*/
|
||||
MCPWM_ACTION_FORCE_LOW, /*!<Make output low*/
|
||||
MCPWM_ACTION_FORCE_HIGH, /*!<Make output high*/
|
||||
MCPWM_ACTION_TOGGLE, /*!<Make output toggle*/
|
||||
} mcpwm_output_action_t;
|
||||
|
||||
/// @deprecated MCPWM select action to be taken on MCPWMXA when fault occurs
|
||||
typedef mcpwm_output_action_t mcpwm_action_on_pwmxa_t;
|
||||
#define MCPWM_NO_CHANGE_IN_MCPWMXA MCPWM_ACTION_NO_CHANGE /*!< @deprecated No change in MCPWMXA output*/
|
||||
#define MCPWM_FORCE_MCPWMXA_LOW MCPWM_ACTION_FORCE_LOW /*!< @deprecated Make MCPWMXA output low*/
|
||||
#define MCPWM_FORCE_MCPWMXA_HIGH MCPWM_ACTION_FORCE_HIGH /*!< @deprecated Make MCPWMXA output high*/
|
||||
#define MCPWM_TOG_MCPWMXA MCPWM_ACTION_TOGGLE /*!< @deprecated Make MCPWMXA output toggle*/
|
||||
|
||||
/// @deprecated MCPWM select action to be taken on MCPWMXB when fault occurs
|
||||
typedef mcpwm_output_action_t mcpwm_action_on_pwmxb_t;
|
||||
#define MCPWM_NO_CHANGE_IN_MCPWMXB MCPWM_ACTION_NO_CHANGE /*!< @deprecated No change in MCPWMXB output*/
|
||||
#define MCPWM_FORCE_MCPWMXB_LOW MCPWM_ACTION_FORCE_LOW /*!< @deprecated Make MCPWMXB output low*/
|
||||
#define MCPWM_FORCE_MCPWMXB_HIGH MCPWM_ACTION_FORCE_HIGH /*!< @deprecated Make MCPWMXB output high*/
|
||||
#define MCPWM_TOG_MCPWMXB MCPWM_ACTION_TOGGLE /*!< @deprecated Make MCPWMXB output toggle*/
|
||||
|
||||
/**
|
||||
* @brief MCPWM select capture signal input
|
||||
*/
|
||||
typedef enum {
|
||||
MCPWM_SELECT_CAP0, /*!<Select CAP0 as input*/
|
||||
MCPWM_SELECT_CAP1, /*!<Select CAP1 as input*/
|
||||
MCPWM_SELECT_CAP2, /*!<Select CAP2 as input*/
|
||||
} mcpwm_capture_signal_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture channel ID alias
|
||||
*/
|
||||
typedef mcpwm_capture_signal_t mcpwm_capture_channel_id_t;
|
||||
|
||||
/**
|
||||
* @brief event data that will be passed into ISR callback
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_capture_on_edge_t cap_edge; /*!<Which signal edge is detected*/
|
||||
uint32_t cap_value; /*!<Corresponding timestamp when event occurs. Clock rate = APB(usually 80M)*/
|
||||
} cap_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Type of capture event callback
|
||||
* @param mcpwm MCPWM unit(0-1)
|
||||
* @param cap_channel capture channel ID
|
||||
* @param edata Capture event data, contains capture edge and capture value, fed by the driver
|
||||
* @param user_data User registered data, passed from `mcpwm_capture_config_t`
|
||||
*
|
||||
* @note Since this an ISR callback so do not do anything that may block and call APIs that is designed to be used within ISR(usually has '_ISR' postfix)
|
||||
*
|
||||
* @return Whether a task switch is needed after the callback function returns,
|
||||
* this is usually due to the callback wakes up some high priority task.
|
||||
*
|
||||
*/
|
||||
typedef bool (*cap_isr_cb_t)(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata,
|
||||
void *user_data);
|
||||
|
||||
/**
|
||||
* @brief MCPWM config structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t frequency; /*!<Set frequency of MCPWM in Hz*/
|
||||
float cmpr_a; /*!<Set % duty cycle for operator a(MCPWMXA), i.e for 62.3% duty cycle, duty_a = 62.3*/
|
||||
float cmpr_b; /*!<Set % duty cycle for operator b(MCPWMXB), i.e for 48% duty cycle, duty_b = 48.0*/
|
||||
mcpwm_duty_type_t duty_mode; /*!<Set type of duty cycle*/
|
||||
mcpwm_counter_type_t counter_mode; /*!<Set type of MCPWM counter*/
|
||||
} mcpwm_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM carrier configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t carrier_period; /*!<Set carrier period = (carrier_period + 1)*800ns, carrier_period should be < 16*/
|
||||
uint8_t carrier_duty; /*!<Set carrier duty cycle, carrier_duty should be less than 8 (increment every 12.5%)*/
|
||||
uint8_t pulse_width_in_os; /*!<Set pulse width of first pulse in one shot mode = (carrier period)*(pulse_width_in_os + 1), should be less then 16*/
|
||||
mcpwm_carrier_out_ivt_t carrier_ivt_mode; /*!<Invert output of carrier*/
|
||||
} mcpwm_carrier_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM config capture structure
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_capture_on_edge_t cap_edge; /*!<Set capture edge*/
|
||||
uint32_t cap_prescale; /*!<Prescale of capture signal, ranging from 1 to 256*/
|
||||
cap_isr_cb_t capture_cb; /*!<User defined capture event callback, running under interrupt context */
|
||||
void *user_data; /*!<User defined ISR callback function args*/
|
||||
} mcpwm_capture_config_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM config sync structure
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_sync_signal_t sync_sig; /*!<Set sync input signal that will cause timer to sync*/
|
||||
uint32_t timer_val; /*!<Counter value to be set after sync, in 0 ~ 999, unit: 1 / 1000 * peak*/
|
||||
mcpwm_timer_direction_t count_direction; /*!<Counting direction to be set after sync */
|
||||
} mcpwm_sync_config_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -14,16 +14,17 @@
|
||||
#include "esp_err.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_hal.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_types_legacy.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
static const char *TAG = "mcpwm(legacy)";
|
||||
|
||||
_Static_assert(MCPWM_UNIT_MAX == SOC_MCPWM_GROUPS, "MCPWM unit number not equal to chip capabilities");
|
||||
|
||||
@@ -326,11 +327,11 @@ esp_err_t mcpwm_set_duty_type(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, m
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_NO_CHANGE);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, gen, MCPWM_ACTION_FORCE_HIGH);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_LOW) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_LOW) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_HIGH) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, gen, MCPWM_ACTION_FORCE_HIGH);
|
||||
@@ -345,11 +346,11 @@ esp_err_t mcpwm_set_duty_type(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, m
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_NO_CHANGE);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_LOW) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_LOW) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_HIGH) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, gen, MCPWM_ACTION_FORCE_HIGH);
|
||||
@@ -364,14 +365,14 @@ esp_err_t mcpwm_set_duty_type(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, m
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, gen, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_LOW) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_LOW) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
mcpwm_ll_generator_set_action_on_compare_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, gen, MCPWM_ACTION_FORCE_LOW);
|
||||
} else if (duty_type == MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH) {
|
||||
} else if (duty_type == MCPWM_DUTY_MODE_FORCE_HIGH) {
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_HIGH);
|
||||
mcpwm_ll_generator_set_action_on_timer_event(hal->dev, op, gen, MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_ACTION_FORCE_HIGH);
|
||||
@@ -463,13 +464,13 @@ uint32_t mcpwm_get_duty_in_us(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, m
|
||||
esp_err_t mcpwm_set_signal_high(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen)
|
||||
{
|
||||
//the driver currently always use the timer x for operator x
|
||||
return mcpwm_set_duty_type(mcpwm_num, timer_num, gen, MCPWM_HAL_GENERATOR_MODE_FORCE_HIGH);
|
||||
return mcpwm_set_duty_type(mcpwm_num, timer_num, gen, MCPWM_DUTY_MODE_FORCE_HIGH);
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_set_signal_low(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, mcpwm_generator_t gen)
|
||||
{
|
||||
//the driver currently always use the timer x for operator x
|
||||
return mcpwm_set_duty_type(mcpwm_num, timer_num, gen, MCPWM_HAL_GENERATOR_MODE_FORCE_LOW);
|
||||
return mcpwm_set_duty_type(mcpwm_num, timer_num, gen, MCPWM_DUTY_MODE_FORCE_LOW);
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_carrier_enable(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num)
|
||||
@@ -943,3 +944,19 @@ esp_err_t mcpwm_set_timer_sync_output(mcpwm_unit_t mcpwm_num, mcpwm_timer_t time
|
||||
mcpwm_critical_exit(mcpwm_num);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that this legacy mcpwm driver is not running along with the new MCPWM driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_mcpwm_driver_conflict(void)
|
||||
{
|
||||
// This function was declared as weak here. The new MCPWM driver has the implementation.
|
||||
// So if the new MCPWM driver is not linked in, then `mcpwm_acquire_group_handle()` should be NULL at runtime.
|
||||
extern __attribute__((weak)) void *mcpwm_acquire_group_handle(int group_id);
|
||||
if ((void *)mcpwm_acquire_group_handle != NULL) {
|
||||
ESP_EARLY_LOGE(TAG, "CONFLICT! driver_ng is not allowed to be used with the legacy driver");
|
||||
abort();
|
||||
}
|
||||
ESP_EARLY_LOGW(TAG, "legacy driver is deprecated, please migrate to `driver/mcpwm_prelude.h`");
|
||||
}
|
194
components/driver/include/driver/mcpwm_cap.h
Normal file
194
components/driver/include/driver/mcpwm_cap.h
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture timer configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< Specify from which group to allocate the capture timer */
|
||||
mcpwm_capture_clock_source_t clk_src; /*!< MCPWM capture timer clock source */
|
||||
} mcpwm_capture_timer_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM capture timer
|
||||
*
|
||||
* @param[in] config MCPWM capture timer configuration
|
||||
* @param[out] ret_cap_timer Returned MCPWM capture timer handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM capture timer failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM capture timer failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_capture_timer(const mcpwm_capture_timer_config_t *config, mcpwm_cap_timer_handle_t *ret_cap_timer);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_capture_timer(mcpwm_cap_timer_handle_t cap_timer);
|
||||
|
||||
/**
|
||||
* @brief Enable MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer handle, allocated by `mcpwm_new_capture_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Enable MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Enable MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Enable MCPWM capture timer failed because timer is enabled already
|
||||
* - ESP_FAIL: Enable MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_timer_enable(mcpwm_cap_timer_handle_t cap_timer);
|
||||
|
||||
/**
|
||||
* @brief Disable MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer handle, allocated by `mcpwm_new_capture_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Disable MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Disable MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Disable MCPWM capture timer failed because timer is disabled already
|
||||
* - ESP_FAIL: Disable MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_timer_disable(mcpwm_cap_timer_handle_t cap_timer);
|
||||
|
||||
/**
|
||||
* @brief Start MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Start MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Start MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_FAIL: Start MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_timer_start(mcpwm_cap_timer_handle_t cap_timer);
|
||||
|
||||
/**
|
||||
* @brief Start MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Stop MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Stop MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_FAIL: Stop MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_timer_stop(mcpwm_cap_timer_handle_t cap_timer);
|
||||
|
||||
/**
|
||||
* @brief MCPWM Capture timer sync phase configuration
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_sync_handle_t sync_src; /*!< The sync event source */
|
||||
uint32_t count_value; /*!< The count value that should lock to upon sync event */
|
||||
mcpwm_timer_direction_t direction; /*!< The count direction that should lock to upon sync event */
|
||||
} mcpwm_capture_timer_sync_phase_config_t;
|
||||
|
||||
/**
|
||||
* @brief Set sync phase for MCPWM capture timer
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`
|
||||
* @param[in] config MCPWM capture timer sync phase configuration
|
||||
* @return
|
||||
* - ESP_OK: Set sync phase for MCPWM capture timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set sync phase for MCPWM capture timer failed because of invalid argument
|
||||
* - ESP_FAIL: Set sync phase for MCPWM capture timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_timer_set_phase_on_sync(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_timer_sync_phase_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture channel configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
int gpio_num; /*!< GPIO used capturing input signal */
|
||||
uint32_t prescale; /*!< Prescale of input signal, effective frequency = cap_input_clk/prescale */
|
||||
struct {
|
||||
uint32_t pos_edge: 1; /*!< Whether to capture on positive edge */
|
||||
uint32_t neg_edge: 1; /*!< Whether to capture on negative edge */
|
||||
uint32_t pull_up: 1; /*!< Whether to pull up internally */
|
||||
uint32_t pull_down: 1; /*!< Whether to pull down internally */
|
||||
uint32_t invert_cap_signal: 1; /*!< Invert the input capture signal */
|
||||
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
|
||||
} flags; /*!< Extra configuration flags for capture channel */
|
||||
} mcpwm_capture_channel_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM capture channel
|
||||
*
|
||||
* @param[in] cap_timer MCPWM capture timer, allocated by `mcpwm_new_capture_timer()`, will be connected to the new capture channel
|
||||
* @param[in] config MCPWM capture channel configuration
|
||||
* @param[out] ret_cap_channel Returned MCPWM capture channel
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM capture channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM capture channel failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM capture channel failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM capture channel failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM capture channel failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_channel_config_t *config, mcpwm_cap_channel_handle_t *ret_cap_channel);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM capture channel
|
||||
*
|
||||
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM capture channel successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM capture channel failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM capture channel failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_capture_channel(mcpwm_cap_channel_handle_t cap_channel);
|
||||
|
||||
/**
|
||||
* @brief Group of supported MCPWM capture event callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_capture_event_cb_t on_cap; /*!< Callback function that would be invoked when capture event occurred */
|
||||
} mcpwm_capture_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for MCPWM capture channel
|
||||
*
|
||||
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_channel_register_event_callbacks(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Trigger a catch by software
|
||||
*
|
||||
* @param[in] cap_channel MCPWM capture channel handle, allocated by `mcpwm_new_capture_channel()`
|
||||
* @return
|
||||
* - ESP_OK: Trigger software catch successfully
|
||||
* - ESP_ERR_INVALID_ARG: Trigger software catch failed because of invalid argument
|
||||
* - ESP_FAIL: Trigger software catch failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_capture_channel_trigger_soft_catch(mcpwm_cap_channel_handle_t cap_channel);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
91
components/driver/include/driver/mcpwm_cmpr.h
Normal file
91
components/driver/include/driver/mcpwm_cmpr.h
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM comparator configuration
|
||||
*/
|
||||
typedef struct {
|
||||
struct {
|
||||
uint32_t update_cmp_on_tez: 1; /*!< Whether to update compare value when timer count equals to zero (tez) */
|
||||
uint32_t update_cmp_on_tep: 1; /*!< Whether to update compare value when timer count equals to peak (tep) */
|
||||
uint32_t update_cmp_on_sync: 1; /*!< Whether to update compare value on sync event */
|
||||
} flags; /*!< Extra configuration flags for comparator */
|
||||
} mcpwm_comparator_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM comparator
|
||||
*
|
||||
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`, the new comparator will be allocated from this operator
|
||||
* @param[in] config MCPWM comparator configuration
|
||||
* @param[out] ret_cmpr Returned MCPWM comparator
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM comparator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM comparator failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM comparator failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM comparator failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM comparator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_config_t *config, mcpwm_cmpr_handle_t *ret_cmpr);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM comparator
|
||||
*
|
||||
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM comparator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM comparator failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM comparator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr);
|
||||
|
||||
/**
|
||||
* @brief Group of supported MCPWM compare event callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_compare_event_cb_t on_reach; /*!< ISR callback function which would be invoked when counter reaches compare value */
|
||||
} mcpwm_comparator_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for MCPWM comparator
|
||||
*
|
||||
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_comparator_register_event_callbacks(mcpwm_cmpr_handle_t cmpr, const mcpwm_comparator_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Set MCPWM comparator's compare value
|
||||
*
|
||||
* @param[in] cmpr MCPWM comparator handle, allocated by `mcpwm_new_comparator()`
|
||||
* @param[in] cmp_ticks The new compare value
|
||||
* @return
|
||||
* - ESP_OK: Set MCPWM compare value successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set MCPWM compare value failed because of invalid argument (e.g. the cmp_ticks is out of range)
|
||||
* - ESP_ERR_INVALID_STATE: Set MCPWM compare value failed because the operator doesn't have a timer connected
|
||||
* - ESP_FAIL: Set MCPWM compare value failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_comparator_set_compare_value(mcpwm_cmpr_handle_t cmpr, uint32_t cmp_ticks);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
112
components/driver/include/driver/mcpwm_fault.h
Normal file
112
components/driver/include/driver/mcpwm_fault.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM GPIO fault configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< In which MCPWM group that the GPIO fault belongs to */
|
||||
int gpio_num; /*!< GPIO used by the fault signal */
|
||||
struct {
|
||||
uint32_t active_level: 1; /*!< On which level the fault signal is treated as active */
|
||||
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
|
||||
uint32_t pull_up: 1; /*!< Whether to pull up internally */
|
||||
uint32_t pull_down: 1; /*!< Whether to pull down internally */
|
||||
} flags; /*!< Extra configuration flags for GPIO fault */
|
||||
} mcpwm_gpio_fault_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM GPIO fault
|
||||
*
|
||||
* @param[in] config MCPWM GPIO fault configuration
|
||||
* @param[out] ret_fault Returned GPIO fault handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM GPIO fault successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM GPIO fault failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM GPIO fault failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM GPIO fault failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM GPIO fault failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
|
||||
|
||||
/**
|
||||
* @brief MCPWM software fault configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
} mcpwm_soft_fault_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM software fault
|
||||
*
|
||||
* @param[in] config MCPWM software fault configuration
|
||||
* @param[out] ret_fault Returned software fault handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM software fault successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM software fault failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM software fault failed because out of memory
|
||||
* - ESP_FAIL: Create MCPWM software fault failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_soft_fault(const mcpwm_soft_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM fault
|
||||
*
|
||||
* @param[in] fault MCPWM fault handle allocated by `mcpwm_new_gpio_fault()` or `mcpwm_new_soft_fault()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM fault successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM fault failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM fault failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_fault(mcpwm_fault_handle_t fault);
|
||||
|
||||
/**
|
||||
* @brief Activate the software fault, trigger the fault event for once
|
||||
*
|
||||
* @param[in] fault MCPWM soft fault, allocated by `mcpwm_new_soft_fault()`
|
||||
* @return
|
||||
* - ESP_OK: Trigger MCPWM software fault event successfully
|
||||
* - ESP_ERR_INVALID_ARG: Trigger MCPWM software fault event failed because of invalid argument
|
||||
* - ESP_FAIL: Trigger MCPWM software fault event failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_soft_fault_activate(mcpwm_fault_handle_t fault);
|
||||
|
||||
/**
|
||||
* @brief Group of supported MCPWM fault event callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_fault_event_cb_t on_fault_enter; /*!< ISR callback function that would be invoked when fault signal becomes active */
|
||||
mcpwm_fault_event_cb_t on_fault_exit; /*!< ISR callback function that would be invoked when fault signal becomes inactive */
|
||||
} mcpwm_fault_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for MCPWM fault
|
||||
*
|
||||
* @param[in] fault MCPWM GPIO fault handle, allocated by `mcpwm_new_gpio_fault()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_fault_register_event_callbacks(mcpwm_fault_handle_t fault, const mcpwm_fault_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
187
components/driver/include/driver/mcpwm_gen.h
Normal file
187
components/driver/include/driver/mcpwm_gen.h
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM generator configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int gen_gpio_num; /*!< The GPIO number used to output the PWM signal */
|
||||
struct {
|
||||
uint32_t invert_pwm: 1; /*!< Whether to invert the PWM signal (done by GPIO matrix) */
|
||||
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
|
||||
} flags; /*!< Extra configuration flags for generator */
|
||||
} mcpwm_generator_config_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate MCPWM generator from given operator
|
||||
*
|
||||
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] config MCPWM generator configuration
|
||||
* @param[out] ret_gen Returned MCPWM generator
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM generator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM generator failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM generator failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM generator failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM generator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_config_t *config, mcpwm_gen_handle_t *ret_gen);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM generator
|
||||
*
|
||||
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM generator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM generator failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM generator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen);
|
||||
|
||||
/**
|
||||
* @brief Set force level for MCPWM generator
|
||||
*
|
||||
* @note The force level will be applied to the generator immediately, regardless any other events that would change the generator's behaviour.
|
||||
* @note If the `hold_on` is true, the force level will retain forever, until user removes the force level by setting the force level to `-1`.
|
||||
* @note If the `hold_on` is false, the force level can be overridden by the next event action.
|
||||
*
|
||||
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
|
||||
* @param[in] level GPIO level to be applied to MCPWM generator, specially, -1 means to remove the force level
|
||||
* @param[in] hold_on Whether the forced PWM level should retain (i.e. will remain unchanged until manually remove the force level)
|
||||
* @return
|
||||
* - ESP_OK: Set force level for MCPWM generator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set force level for MCPWM generator failed because of invalid argument
|
||||
* - ESP_FAIL: Set force level for MCPWM generator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_generator_set_force_level(mcpwm_gen_handle_t gen, int level, bool hold_on);
|
||||
|
||||
/**
|
||||
* @brief Generator action on specific timer event
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_timer_direction_t direction; /*!< Timer direction */
|
||||
mcpwm_timer_event_t event; /*!< Timer event */
|
||||
mcpwm_generator_action_t action; /*!< Generator action should perform */
|
||||
} mcpwm_gen_timer_event_action_t;
|
||||
|
||||
/**
|
||||
* @brief Help macros to construct a mcpwm_gen_timer_event_action_t entry
|
||||
*/
|
||||
#define MCPWM_GEN_TIMER_EVENT_ACTION(dir, ev, act) \
|
||||
(mcpwm_gen_timer_event_action_t) { .direction = dir, .event = ev, .action = act }
|
||||
#define MCPWM_GEN_TIMER_EVENT_ACTION_END() \
|
||||
(mcpwm_gen_timer_event_action_t) { .event = MCPWM_TIMER_EVENT_INVALID }
|
||||
|
||||
/**
|
||||
* @brief Set generator actions on different MCPWM timer events
|
||||
*
|
||||
* @param[in] gen MCPWM generator handle, allocated by `mcpwm_new_generator()`
|
||||
* @param[in] ev_act MCPWM timer event action list, must be terminated by `MCPWM_GEN_TIMER_EVENT_ACTION_END()`
|
||||
* @return
|
||||
* - ESP_OK: Set generator actions successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Set generator actions failed because of timer is not connected to operator
|
||||
* - ESP_FAIL: Set generator actions failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_generator_set_actions_on_timer_event(mcpwm_gen_handle_t gen, mcpwm_gen_timer_event_action_t ev_act, ...);
|
||||
|
||||
/**
|
||||
* @brief Generator action on specific comparator event
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_timer_direction_t direction; /*!< Timer direction */
|
||||
mcpwm_cmpr_handle_t comparator; /*!< Comparator handle */
|
||||
mcpwm_generator_action_t action; /*!< Generator action should perform */
|
||||
} mcpwm_gen_compare_event_action_t;
|
||||
|
||||
/**
|
||||
* @brief Help macros to construct a mcpwm_gen_compare_event_action_t entry
|
||||
*/
|
||||
#define MCPWM_GEN_COMPARE_EVENT_ACTION(dir, cmp, act) \
|
||||
(mcpwm_gen_compare_event_action_t) { .direction = dir, .comparator = cmp, .action = act }
|
||||
#define MCPWM_GEN_COMPARE_EVENT_ACTION_END() \
|
||||
(mcpwm_gen_compare_event_action_t) { .comparator = NULL }
|
||||
|
||||
/**
|
||||
* @brief Set generator actions on different MCPWM compare events
|
||||
*
|
||||
* @param[in] generator MCPWM generator handle, allocated by `mcpwm_new_generator()`
|
||||
* @param[in] ev_act MCPWM compare event action list, must be terminated by `MCPWM_GEN_COMPARE_EVENT_ACTION_END()`
|
||||
* @return
|
||||
* - ESP_OK: Set generator actions successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
|
||||
* - ESP_FAIL: Set generator actions failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_generator_set_actions_on_compare_event(mcpwm_gen_handle_t generator, mcpwm_gen_compare_event_action_t ev_act, ...);
|
||||
|
||||
/**
|
||||
* @brief Generator action on specific brake event
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_timer_direction_t direction; /*!< Timer direction */
|
||||
mcpwm_operator_brake_mode_t brake_mode; /*!< Brake mode */
|
||||
mcpwm_generator_action_t action; /*!< Generator action should perform */
|
||||
} mcpwm_gen_brake_event_action_t;
|
||||
|
||||
/**
|
||||
* @brief Help macros to construct a mcpwm_gen_brake_event_action_t entry
|
||||
*/
|
||||
#define MCPWM_GEN_BRAKE_EVENT_ACTION(dir, mode, act) \
|
||||
(mcpwm_gen_brake_event_action_t) { .direction = dir, .brake_mode = mode, .action = act }
|
||||
#define MCPWM_GEN_BRAKE_EVENT_ACTION_END() \
|
||||
(mcpwm_gen_brake_event_action_t) { .brake_mode = MCPWM_OPER_BRAKE_MODE_INVALID }
|
||||
|
||||
/**
|
||||
* @brief Set generator actions on different MCPWM brake events
|
||||
*
|
||||
* @param[in] generator MCPWM generator handle, allocated by `mcpwm_new_generator()`
|
||||
* @param[in] ev_act MCPWM brake event action list, must be terminated by `MCPWM_GEN_BRAKE_EVENT_ACTION_END()`
|
||||
* @return
|
||||
* - ESP_OK: Set generator actions successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set generator actions failed because of invalid argument
|
||||
* - ESP_FAIL: Set generator actions failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_generator_set_actions_on_brake_event(mcpwm_gen_handle_t generator, mcpwm_gen_brake_event_action_t ev_act, ...);
|
||||
|
||||
/**
|
||||
* @brief MCPWM dead time configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t posedge_delay_ticks; /*!< delay time applied to rising edge, 0 means no rising delay time */
|
||||
uint32_t negedge_delay_ticks; /*!< delay time applied to falling edge, 0 means no falling delay time */
|
||||
struct {
|
||||
uint32_t invert_output: 1; /*!< Invert the signal after applied the dead time */
|
||||
} flags; /*!< Extra flags for dead time configuration */
|
||||
} mcpwm_dead_time_config_t;
|
||||
|
||||
/**
|
||||
* @brief Set dead time for MCPWM generator
|
||||
*
|
||||
* @param[in] in_generator MCPWM generator, before adding the dead time
|
||||
* @param[in] out_generator MCPWM generator, after adding the dead time
|
||||
* @param[in] config MCPWM dead time configuration
|
||||
* @return
|
||||
* - ESP_OK: Set dead time for MCPWM generator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set dead time for MCPWM generator failed because of invalid argument
|
||||
* - ESP_FAIL: Set dead time for MCPWM generator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t in_generator, mcpwm_gen_handle_t out_generator, const mcpwm_dead_time_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
159
components/driver/include/driver/mcpwm_oper.h
Normal file
159
components/driver/include/driver/mcpwm_oper.h
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM operator configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< Specify from which group to allocate the MCPWM operator */
|
||||
struct {
|
||||
uint32_t update_gen_action_on_tez: 1; /*!< Whether to update generator action when timer counts to zero */
|
||||
uint32_t update_gen_action_on_tep: 1; /*!< Whether to update generator action when timer counts to peak */
|
||||
uint32_t update_gen_action_on_sync: 1; /*!< Whether to update generator action on sync event */
|
||||
uint32_t update_dead_time_on_tez: 1; /*!< Whether to update dead time when timer counts to zero */
|
||||
uint32_t update_dead_time_on_tep: 1; /*!< Whether to update dead time when timer counts to peak */
|
||||
uint32_t update_dead_time_on_sync: 1; /*!< Whether to update dead time on sync event */
|
||||
} flags; /*!< Extra configuration flags for operator */
|
||||
} mcpwm_operator_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM operator
|
||||
*
|
||||
* @param[in] config MCPWM operator configuration
|
||||
* @param[out] ret_oper Returned MCPWM operator handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM operator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM operator failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM operator failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM operator failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM operator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_handle_t *ret_oper);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM operator
|
||||
*
|
||||
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM operator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM operator failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM operator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper);
|
||||
|
||||
/**
|
||||
* @brief Connect MCPWM operator and timer, so that the operator can be driven by the timer
|
||||
*
|
||||
* @param[in] oper MCPWM operator handle, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Connect MCPWM operator and timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Connect MCPWM operator and timer failed because of invalid argument
|
||||
* - ESP_FAIL: Connect MCPWM operator and timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_operator_connect_timer(mcpwm_oper_handle_t oper, mcpwm_timer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief MCPWM brake configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_fault_handle_t fault; /*!< Which fault causes the operator to brake */
|
||||
mcpwm_operator_brake_mode_t brake_mode; /*!< Brake mode */
|
||||
struct {
|
||||
uint32_t cbc_recover_on_tez: 1; /*!< Recovery CBC brake state on tez event */
|
||||
uint32_t cbc_recover_on_tep: 1; /*!< Recovery CBC brake state on tep event */
|
||||
} flags; /*!< Extra flags for brake configuration */
|
||||
} mcpwm_brake_config_t;
|
||||
|
||||
/**
|
||||
* @brief Set brake method for MCPWM operator
|
||||
*
|
||||
* @param[in] operator MCPWM operator, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] config MCPWM brake configuration
|
||||
* @return
|
||||
* - ESP_OK: Set trip for operator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set trip for operator failed because of invalid argument
|
||||
* - ESP_FAIL: Set trip for operator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_operator_set_brake_on_fault(mcpwm_oper_handle_t operator, const mcpwm_brake_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Try to make the operator recover from fault
|
||||
*
|
||||
* @note To recover from fault or escape from trip, you make sure the fault signal has dissappeared already.
|
||||
* Otherwise the recovery can't succeed.
|
||||
*
|
||||
* @param[in] operator MCPWM operator, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] fault MCPWM fault handle
|
||||
* @return
|
||||
* - ESP_OK: Recover from fault successfully
|
||||
* - ESP_ERR_INVALID_ARG: Recover from fault failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Recover from fault failed because the fault source is still active
|
||||
* - ESP_FAIL: Recover from fault failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_operator_recover_from_fault(mcpwm_oper_handle_t operator, mcpwm_fault_handle_t fault);
|
||||
|
||||
/**
|
||||
* @brief Group of supported MCPWM operator event callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_brake_event_cb_t on_brake_cbc; /*!< callback function when mcpwm operator brakes in CBC */
|
||||
mcpwm_brake_event_cb_t on_brake_ost; /*!< callback function when mcpwm operator brakes in OST */
|
||||
} mcpwm_operator_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for MCPWM operator
|
||||
*
|
||||
* @param[in] oper MCPWM operator handle, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_operator_register_event_callbacks(mcpwm_oper_handle_t oper, const mcpwm_operator_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief MCPWM carrier configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t frequency_hz; /*!< Carrier frequency in Hz */
|
||||
uint32_t first_pulse_duration_us; /*!< The duration of the first PWM pulse, in us */
|
||||
float duty_cycle; /*!< Carrier duty cycle */
|
||||
struct {
|
||||
uint32_t invert_before_modulate: 1; /*!< Invert the raw signal */
|
||||
uint32_t invert_after_modulate: 1; /*!< Invert the modulated signal */
|
||||
} flags; /*!< Extra flags for carrier configuration */
|
||||
} mcpwm_carrier_config_t;
|
||||
|
||||
/**
|
||||
* @brief Apply carrier feature for MCPWM operator
|
||||
*
|
||||
* @param[in] oper MCPWM operator, allocated by `mcpwm_new_operator()`
|
||||
* @param[in] config MCPWM carrier specific configuration
|
||||
* @return
|
||||
* - ESP_OK: Set carrier for operator successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set carrier for operator failed because of invalid argument
|
||||
* - ESP_FAIL: Set carrier for operator failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_operator_apply_carrier(mcpwm_oper_handle_t oper, const mcpwm_carrier_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
20
components/driver/include/driver/mcpwm_prelude.h
Normal file
20
components/driver/include/driver/mcpwm_prelude.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief MCPWM peripheral contains many submodules, whose drivers are scattered in different header files.
|
||||
* This header file serves as a prelude, contains every thing that is needed to work with the MCPWM peripheral.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "driver/mcpwm_cmpr.h"
|
||||
#include "driver/mcpwm_gen.h"
|
||||
#include "driver/mcpwm_fault.h"
|
||||
#include "driver/mcpwm_sync.h"
|
||||
#include "driver/mcpwm_cap.h"
|
114
components/driver/include/driver/mcpwm_sync.h
Normal file
114
components/driver/include/driver/mcpwm_sync.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer sync source configuration
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_timer_event_t timer_event; /*!< Timer event, upon which MCPWM timer will generate the sync signal */
|
||||
struct {
|
||||
uint32_t propagate_input_sync: 1; /*!< The input sync signal would be routed to its sync output */
|
||||
} flags; /*!< Extra configuration flags for timer sync source */
|
||||
} mcpwm_timer_sync_src_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM timer sync source
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @param[in] config MCPWM timer sync source configuration
|
||||
* @param[out] ret_sync Returned MCPWM sync handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM timer sync source successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM timer sync source failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM timer sync source failed because out of memory
|
||||
* - ESP_ERR_INVALID_STATE: Create MCPWM timer sync source failed because the timer has created a sync source before
|
||||
* - ESP_FAIL: Create MCPWM timer sync source failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_timer_sync_src(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync);
|
||||
|
||||
/**
|
||||
* @brief MCPWM GPIO sync source configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< MCPWM group ID */
|
||||
int gpio_num; /*!< GPIO used by sync source */
|
||||
struct {
|
||||
uint32_t active_neg: 1; /*!< Whether the sync signal is active on negedge, by default, the sync signal's posedge is treated as active */
|
||||
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
|
||||
uint32_t pull_up: 1; /*!< Whether to pull up internally */
|
||||
uint32_t pull_down: 1; /*!< Whether to pull down internally */
|
||||
} flags; /*!< Extra configuration flags for GPIO sync source */
|
||||
} mcpwm_gpio_sync_src_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM GPIO sync source
|
||||
*
|
||||
* @param[in] config MCPWM GPIO sync source configuration
|
||||
* @param[out] ret_sync Returned MCPWM GPIO sync handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM GPIO sync source successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM GPIO sync source failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM GPIO sync source failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM GPIO sync source failed because can't find free resource
|
||||
* - ESP_FAIL: Create MCPWM GPIO sync source failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_gpio_sync_src(const mcpwm_gpio_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync);
|
||||
|
||||
/**
|
||||
* @brief MCPWM software sync configuration structure
|
||||
*/
|
||||
typedef struct {
|
||||
} mcpwm_soft_sync_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM software sync source
|
||||
*
|
||||
* @param[in] config MCPWM software sync source configuration
|
||||
* @param[out] ret_sync Returned software sync handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM software sync successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM software sync failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM software sync failed because out of memory
|
||||
* - ESP_FAIL: Create MCPWM software sync failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_soft_sync_src(const mcpwm_soft_sync_config_t *config, mcpwm_sync_handle_t *ret_sync);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM sync source
|
||||
*
|
||||
* @param[in] sync MCPWM sync handle, allocated by `mcpwm_new_timer_sync_src()` or `mcpwm_new_gpio_sync_src()` or `mcpwm_new_soft_sync_src()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM sync source successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM sync source failed because of invalid argument
|
||||
* - ESP_FAIL: Delete MCPWM sync source failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_sync_src(mcpwm_sync_handle_t sync);
|
||||
|
||||
/**
|
||||
* @brief Activate the software sync, trigger the sync event for once
|
||||
*
|
||||
* @param[in] sync MCPWM soft sync handle, allocated by `mcpwm_new_soft_sync_src()`
|
||||
* @return
|
||||
* - ESP_OK: Trigger MCPWM software sync event successfully
|
||||
* - ESP_ERR_INVALID_ARG: Trigger MCPWM software sync event failed because of invalid argument
|
||||
* - ESP_FAIL: Trigger MCPWM software sync event failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_soft_sync_activate(mcpwm_sync_handle_t sync);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
144
components/driver/include/driver/mcpwm_timer.h
Normal file
144
components/driver/include/driver/mcpwm_timer.h
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Group of supported MCPWM timer event callbacks
|
||||
* @note The callbacks are all running under ISR environment
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_timer_event_cb_t on_full; /*!< callback function when MCPWM timer counts to peak value */
|
||||
mcpwm_timer_event_cb_t on_empty; /*!< callback function when MCPWM timer counts to zero */
|
||||
mcpwm_timer_event_cb_t on_stop; /*!< callback function when MCPWM timer stops */
|
||||
} mcpwm_timer_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< Specify from which group to allocate the MCPWM timer */
|
||||
mcpwm_timer_clock_source_t clk_src; /*!< MCPWM timer clock source */
|
||||
uint32_t resolution_hz; /*!< Counter resolution in Hz, ranges from around 300KHz to 80MHz.
|
||||
The step size of each count tick equals to (1 / resolution_hz) seconds */
|
||||
mcpwm_timer_count_mode_t count_mode; /*!< Count mode */
|
||||
uint32_t period_ticks; /*!< Number of count ticks within a period */
|
||||
struct {
|
||||
uint32_t update_period_on_empty: 1; /*!< Whether to update period when timer counts to zero */
|
||||
uint32_t update_period_on_sync: 1; /*!< Whether to update period on sync event */
|
||||
} flags; /*!< Extra configuration flags for timer */
|
||||
} mcpwm_timer_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create MCPWM timer
|
||||
*
|
||||
* @param[in] config MCPWM timer configuration
|
||||
* @param[out] ret_timer Returned MCPWM timer handle
|
||||
* @return
|
||||
* - ESP_OK: Create MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create MCPWM timer failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create MCPWM timer failed because out of memory
|
||||
* - ESP_ERR_NOT_FOUND: Create MCPWM timer failed because all hardware timers are used up and no more free one
|
||||
* - ESP_FAIL: Create MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle_t *ret_timer);
|
||||
|
||||
/**
|
||||
* @brief Delete MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Delete MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete MCPWM timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Delete MCPWM timer failed because timer is not in init state
|
||||
* - ESP_FAIL: Delete MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Enable MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Enable MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Enable MCPWM timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Enable MCPWM timer failed because timer is enabled already
|
||||
* - ESP_FAIL: Enable MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_enable(mcpwm_timer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Disable MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @return
|
||||
* - ESP_OK: Disable MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Disable MCPWM timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Disable MCPWM timer failed because timer is disabled already
|
||||
* - ESP_FAIL: Disable MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_disable(mcpwm_timer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Send specific start/stop commands to MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @param[in] command Supported command list for MCPWM timer
|
||||
* @return
|
||||
* - ESP_OK: Start or stop MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Start or stop MCPWM timer failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Start or stop MCPWM timer failed because timer is not enabled
|
||||
* - ESP_FAIL: Start or stop MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_start_stop(mcpwm_timer_handle_t timer, mcpwm_timer_start_stop_cmd_t command);
|
||||
|
||||
/**
|
||||
* @brief Set event callbacks for MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set event callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
|
||||
* - ESP_ERR_INVALID_STATE: Set event callbacks failed because timer is not in init state
|
||||
* - ESP_FAIL: Set event callbacks failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_register_event_callbacks(mcpwm_timer_handle_t timer, const mcpwm_timer_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief MCPWM Timer sync phase configuration
|
||||
*/
|
||||
typedef struct {
|
||||
mcpwm_sync_handle_t sync_src; /*!< The sync event source. Set to NULL will disable the timer being synced by others */
|
||||
uint32_t count_value; /*!< The count value that should lock to upon sync event */
|
||||
mcpwm_timer_direction_t direction; /*!< The count direction that should lock to upon sync event */
|
||||
} mcpwm_timer_sync_phase_config_t;
|
||||
|
||||
/**
|
||||
* @brief Set sync phase for MCPWM timer
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @param[in] config MCPWM timer sync phase configuration
|
||||
* @return
|
||||
* - ESP_OK: Set sync phase for MCPWM timer successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set sync phase for MCPWM timer failed because of invalid argument
|
||||
* - ESP_FAIL: Set sync phase for MCPWM timer failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_set_phase_on_sync(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_phase_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
145
components/driver/include/driver/mcpwm_types.h
Normal file
145
components/driver/include/driver/mcpwm_types.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hal/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM timer handle
|
||||
*/
|
||||
typedef struct mcpwm_timer_t *mcpwm_timer_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM operator handle
|
||||
*/
|
||||
typedef struct mcpwm_oper_t *mcpwm_oper_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM comparator handle
|
||||
*/
|
||||
typedef struct mcpwm_cmpr_t *mcpwm_cmpr_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM generator handle
|
||||
*/
|
||||
typedef struct mcpwm_gen_t *mcpwm_gen_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM fault handle
|
||||
*/
|
||||
typedef struct mcpwm_fault_t *mcpwm_fault_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM sync handle
|
||||
*/
|
||||
typedef struct mcpwm_sync_t *mcpwm_sync_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM capture timer handle
|
||||
*/
|
||||
typedef struct mcpwm_cap_timer_t *mcpwm_cap_timer_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of MCPWM capture channel handle
|
||||
*/
|
||||
typedef struct mcpwm_cap_channel_t *mcpwm_cap_channel_handle_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer event data
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t count_value; /*!< MCPWM timer count value */
|
||||
mcpwm_timer_direction_t direction; /*!< MCPWM timer count direction */
|
||||
} mcpwm_timer_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM timer event callback function
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle
|
||||
* @param[in] edata MCPWM timer event data, fed by driver
|
||||
* @param[in] user_ctx User data, set in `mcpwm_timer_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*mcpwm_timer_event_cb_t)(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief MCPWM brake event data
|
||||
*/
|
||||
typedef struct {
|
||||
} mcpwm_brake_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM operator brake event callback function
|
||||
*
|
||||
* @param[in] operator MCPWM operator handle
|
||||
* @param[in] edata MCPWM brake event data, fed by driver
|
||||
* @param[in] user_ctx User data, set in `mcpwm_operator_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*mcpwm_brake_event_cb_t)(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief MCPWM fault event data
|
||||
*/
|
||||
typedef struct {
|
||||
} mcpwm_fault_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM fault event callback function
|
||||
*
|
||||
* @param fault MCPWM fault handle
|
||||
* @param ev_data MCPWM fault event data, fed by driver
|
||||
* @param user_ctx User data, set in `mcpwm_fault_register_event_callbacks()`
|
||||
* @return whether a task switch is needed after the callback returns
|
||||
*/
|
||||
typedef bool (*mcpwm_fault_event_cb_t)(mcpwm_fault_handle_t fault, const mcpwm_fault_event_data_t *ev_data, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief MCPWM compare event data
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t compare_ticks; /*!< Compare value */
|
||||
mcpwm_timer_direction_t direction; /*!< Count direction */
|
||||
} mcpwm_compare_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM comparator event callback function
|
||||
*
|
||||
* @param comparator MCPWM comparator handle
|
||||
* @param edata MCPWM comparator event data, fed by driver
|
||||
* @param user_ctx User data, set in `mcpwm_comparator_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*mcpwm_compare_event_cb_t)(mcpwm_cmpr_handle_t comparator, const mcpwm_compare_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture event data
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t cap_value; /*!< Captured value */
|
||||
mcpwm_capture_edge_t cap_edge; /*!< Capture edge */
|
||||
} mcpwm_capture_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief MCPWM capture event callback function
|
||||
*
|
||||
* @param cap_channel MCPWM capture channel handle
|
||||
* @param ev_data MCPWM capture event data, fed by driver
|
||||
* @param user_ctx User data, set in `mcpwm_capture_channel_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*mcpwm_capture_event_cb_t)(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
36
components/driver/include/esp_private/mcpwm.h
Normal file
36
components/driver/include/esp_private/mcpwm.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// DO NOT USE THESE APIS IN YOUR APPLICATIONS
|
||||
// The following APIs are for internal use, public to other IDF components, but not for users' applications.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get MCPWM timer phase
|
||||
*
|
||||
* @param[in] timer MCPWM timer handle, allocated by `mcpwm_new_timer()`
|
||||
* @param[out] count_value Returned MCPWM timer phase
|
||||
* @param[out] direction Returned MCPWM timer counting direction
|
||||
* @return
|
||||
* - ESP_OK: Get MCPWM timer status successfully
|
||||
* - ESP_ERR_INVALID_ARG: Get MCPWM timer status failed because of invalid argument
|
||||
* - ESP_FAIL: Get MCPWM timer status failed because of other error
|
||||
*/
|
||||
esp_err_t mcpwm_timer_get_phase(mcpwm_timer_handle_t timer, uint32_t *count_value, mcpwm_timer_direction_t *direction);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
420
components/driver/mcpwm/mcpwm_cap.c
Normal file
420
components/driver/mcpwm/mcpwm_cap.c
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_cap.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static void mcpwm_capture_default_isr(void *args);
|
||||
|
||||
static esp_err_t mcpwm_cap_timer_register_to_group(mcpwm_cap_timer_t *cap_timer, int group_id)
|
||||
{
|
||||
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
||||
|
||||
bool new_timer = false;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
if (!group->cap_timer) {
|
||||
group->cap_timer = cap_timer;
|
||||
new_timer = true;
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
if (!new_timer) {
|
||||
mcpwm_release_group_handle(group);
|
||||
group = NULL;
|
||||
} else {
|
||||
cap_timer->group = group;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(new_timer, ESP_ERR_NOT_FOUND, TAG, "no free cap timer in group (%d)", group_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_cap_timer_unregister_from_group(mcpwm_cap_timer_t *cap_timer)
|
||||
{
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->cap_timer = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// capture timer has a reference on group, release it now
|
||||
mcpwm_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_cap_timer_destory(mcpwm_cap_timer_t *cap_timer)
|
||||
{
|
||||
if (cap_timer->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_delete(cap_timer->pm_lock), TAG, "delete pm_lock failed");
|
||||
}
|
||||
if (cap_timer->group) {
|
||||
mcpwm_cap_timer_unregister_from_group(cap_timer);
|
||||
}
|
||||
free(cap_timer);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_capture_timer(const mcpwm_capture_timer_config_t *config, mcpwm_cap_timer_handle_t *ret_cap_timer)
|
||||
{
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_cap_timer_t *cap_timer = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_cap_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
||||
err, TAG, "invalid group ID:%d", config->group_id);
|
||||
|
||||
cap_timer = heap_caps_calloc(1, sizeof(mcpwm_cap_timer_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(cap_timer, ESP_ERR_NO_MEM, err, TAG, "no mem for capture timer");
|
||||
|
||||
switch (config->clk_src) {
|
||||
case MCPWM_CAPTURE_CLK_SRC_APB:
|
||||
cap_timer->resolution_hz = esp_clk_apb_freq();
|
||||
#if CONFIG_PM_ENABLE
|
||||
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "mcpwm_cap_timer", &cap_timer->pm_lock);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
|
||||
#endif // CONFIG_PM_ENABLE
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid clock source:%d", config->clk_src);
|
||||
}
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_cap_timer_register_to_group(cap_timer, config->group_id), err, TAG, "register timer failed");
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
int group_id = group->group_id;
|
||||
|
||||
// fill in other capture timer specific members
|
||||
cap_timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_INIT;
|
||||
*ret_cap_timer = cap_timer;
|
||||
ESP_LOGD(TAG, "new capture timer at %p, in group (%d)", cap_timer, group_id);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (cap_timer) {
|
||||
mcpwm_cap_timer_destory(cap_timer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_capture_timer(mcpwm_cap_timer_handle_t cap_timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; i++) {
|
||||
ESP_RETURN_ON_FALSE(!cap_timer->cap_channels[i], ESP_ERR_INVALID_STATE, TAG, "cap channel still in working");
|
||||
}
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
|
||||
ESP_LOGD(TAG, "del capture timer in group %d", group->group_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_cap_timer_destory(cap_timer), TAG, "destory capture timer failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_timer_enable(mcpwm_cap_timer_handle_t cap_timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
if (cap_timer->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(cap_timer->pm_lock), TAG, "acquire pm_lock failed");
|
||||
}
|
||||
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_ENABLE;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_timer_disable(mcpwm_cap_timer_handle_t cap_timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
|
||||
if (cap_timer->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_release(cap_timer->pm_lock), TAG, "release pm_lock failed");
|
||||
}
|
||||
cap_timer->fsm = MCPWM_CAP_TIMER_FSM_INIT;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_timer_start(mcpwm_cap_timer_handle_t cap_timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
|
||||
portENTER_CRITICAL_SAFE(&cap_timer->spinlock);
|
||||
mcpwm_ll_capture_enable_timer(group->hal.dev, true);
|
||||
portEXIT_CRITICAL_SAFE(&cap_timer->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_timer_stop(mcpwm_cap_timer_handle_t cap_timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(cap_timer->fsm == MCPWM_CAP_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not enabled yet");
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
|
||||
portENTER_CRITICAL_SAFE(&cap_timer->spinlock);
|
||||
mcpwm_ll_capture_enable_timer(group->hal.dev, false);
|
||||
portEXIT_CRITICAL_SAFE(&cap_timer->spinlock);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_capture_channel_register_to_timer(mcpwm_cap_channel_t *cap_channel, mcpwm_cap_timer_t *cap_timer)
|
||||
{
|
||||
int cap_chan_id = -1;
|
||||
portENTER_CRITICAL(&cap_timer->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; i++) {
|
||||
if (!cap_timer->cap_channels[i]) {
|
||||
cap_timer->cap_channels[i] = cap_channel;
|
||||
cap_chan_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&cap_timer->spinlock);
|
||||
ESP_RETURN_ON_FALSE(cap_chan_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free channel in the timer (%d)", cap_timer->group->group_id);
|
||||
|
||||
cap_channel->cap_chan_id = cap_chan_id;
|
||||
cap_channel->cap_timer = cap_timer;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_capture_channel_unregister_from_timer(mcpwm_cap_channel_t *cap_chan)
|
||||
{
|
||||
mcpwm_cap_timer_t *cap_timer = cap_chan->cap_timer;
|
||||
int cap_chan_id = cap_chan->cap_chan_id;
|
||||
|
||||
portENTER_CRITICAL(&cap_timer->spinlock);
|
||||
cap_timer->cap_channels[cap_chan_id] = NULL;
|
||||
portEXIT_CRITICAL(&cap_timer->spinlock);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_capture_channel_destory(mcpwm_cap_channel_t *cap_chan)
|
||||
{
|
||||
if (cap_chan->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(cap_chan->intr), TAG, "delete interrupt service failed");
|
||||
}
|
||||
if (cap_chan->cap_timer) {
|
||||
mcpwm_capture_channel_unregister_from_timer(cap_chan);
|
||||
}
|
||||
free(cap_chan);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_capture_channel(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_channel_config_t *config, mcpwm_cap_channel_handle_t *ret_cap_channel)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_cap_channel_t *cap_chan = NULL;
|
||||
ESP_GOTO_ON_FALSE(cap_timer && config && ret_cap_channel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->prescale && config->prescale <= MCPWM_LL_MAX_CAPTURE_PRESCALE, ESP_ERR_INVALID_ARG, err, TAG, "invalid prescale");
|
||||
|
||||
// create instance firstly, then install onto platform
|
||||
cap_chan = calloc(1, sizeof(mcpwm_cap_channel_t));
|
||||
ESP_GOTO_ON_FALSE(cap_chan, ESP_ERR_NO_MEM, err, TAG, "no mem for capture channel");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_capture_channel_register_to_timer(cap_chan, cap_timer), err, TAG, "register channel failed");
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int cap_chan_id = cap_chan->cap_chan_id;
|
||||
|
||||
mcpwm_ll_capture_enable_channel(hal->dev, cap_chan_id, true); // enable channel
|
||||
mcpwm_ll_capture_enable_negedge(hal->dev, cap_chan_id, config->flags.neg_edge);
|
||||
mcpwm_ll_capture_enable_posedge(hal->dev, cap_chan_id, config->flags.pos_edge);
|
||||
mcpwm_ll_invert_input(hal->dev, cap_chan_id, config->flags.invert_cap_signal);
|
||||
mcpwm_ll_capture_set_prescale(hal->dev, cap_chan_id, config->prescale);
|
||||
|
||||
if (config->gpio_num >= 0) {
|
||||
// GPIO configuration
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
|
||||
.pin_bit_mask = (1ULL << config->gpio_num),
|
||||
.pull_down_en = config->flags.pull_down,
|
||||
.pull_up_en = config->flags.pull_up,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config capture GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group->group_id].captures[cap_chan_id].cap_sig, 0);
|
||||
}
|
||||
|
||||
cap_chan->gpio_num = config->gpio_num;
|
||||
*ret_cap_channel = cap_chan;
|
||||
ESP_LOGD(TAG, "new capture channel (%d,%d) at %p", group->group_id, cap_chan_id, cap_chan);
|
||||
return ESP_OK;
|
||||
err:
|
||||
if (cap_chan) {
|
||||
mcpwm_capture_channel_destory(cap_chan);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_capture_channel(mcpwm_cap_channel_handle_t cap_channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_cap_timer_t *cap_timer = cap_channel->cap_timer;
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int cap_chan_id = cap_channel->cap_chan_id;
|
||||
|
||||
ESP_LOGD(TAG, "del capture channel (%d,%d)", group->group_id, cap_channel->cap_chan_id);
|
||||
if (cap_channel->gpio_num >= 0) {
|
||||
gpio_reset_pin(cap_channel->gpio_num);
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id), false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// disable capture channel
|
||||
mcpwm_ll_capture_enable_channel(group->hal.dev, cap_channel->cap_chan_id, false);
|
||||
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_capture_channel_destory(cap_channel), TAG, "destory capture channel failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_channel_register_event_callbacks(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = cap_channel->cap_timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int group_id = group->group_id;
|
||||
int cap_chan_id = cap_channel->cap_chan_id;
|
||||
|
||||
#if CONFIG_MCWPM_ISR_IRAM_SAFE
|
||||
if (cbs->on_cap) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_cap), ESP_ERR_INVALID_ARG, TAG, "on_cap callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
if (!cap_channel->intr) {
|
||||
// we want the interrupt servie to be enabled after allocation successfully
|
||||
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
||||
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_CAPTURE(cap_chan_id),
|
||||
mcpwm_capture_default_isr, cap_channel, &cap_channel->intr), TAG, "install interrupt service for cap channel failed");
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CAPTURE(cap_chan_id), cbs->on_cap != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
cap_channel->on_cap = cbs->on_cap;
|
||||
cap_channel->user_data = user_data;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_channel_trigger_soft_catch(mcpwm_cap_channel_handle_t cap_channel)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_cap_timer_t *cap_timer = cap_channel->cap_timer;
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
|
||||
// note: soft capture can also triggers the interrupt routine
|
||||
mcpwm_ll_trigger_soft_capture(group->hal.dev, cap_channel->cap_chan_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_capture_timer_set_phase_on_sync(mcpwm_cap_timer_handle_t cap_timer, const mcpwm_capture_timer_sync_phase_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cap_timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
// capture timer only support count up
|
||||
ESP_RETURN_ON_FALSE(config->direction == MCPWM_TIMER_DIRECTION_UP, ESP_ERR_INVALID_ARG, TAG, "invalid sync direction");
|
||||
mcpwm_group_t *group = cap_timer->group;
|
||||
mcpwm_sync_t *sync_source = config->sync_src;
|
||||
|
||||
// a non-NULL sync_src means to enable sync feature
|
||||
if (sync_source) {
|
||||
switch (sync_source->type) {
|
||||
case MCPWM_SYNC_TYPE_GPIO: {
|
||||
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "capture timer and sync source are not in the same group");
|
||||
mcpwm_gpio_sync_src_t *gpio_sync_src = __containerof(sync_source, mcpwm_gpio_sync_src_t, base);
|
||||
mcpwm_ll_capture_set_gpio_sync(group->hal.dev, gpio_sync_src->sync_id);
|
||||
ESP_LOGD(TAG, "enable sync to GPIO (%d,%d) for cap timer (%d)",
|
||||
group->group_id, gpio_sync_src->sync_id, group->group_id);
|
||||
break;
|
||||
}
|
||||
case MCPWM_SYNC_TYPE_TIMER: {
|
||||
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "capture timer and sync source are not in the same group");
|
||||
mcpwm_timer_sync_src_t *timer_sync_src = __containerof(sync_source, mcpwm_timer_sync_src_t, base);
|
||||
mcpwm_ll_capture_set_timer_sync(group->hal.dev, timer_sync_src->timer->timer_id);
|
||||
ESP_LOGD(TAG, "enable sync to pwm timer (%d,%d) for cap timer (%d)",
|
||||
group->group_id, timer_sync_src->timer->timer_id, group->group_id);
|
||||
break;
|
||||
}
|
||||
case MCPWM_SYNC_TYPE_SOFT: {
|
||||
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_source, mcpwm_soft_sync_src_t, base);
|
||||
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_CAP;
|
||||
soft_sync->cap_timer = cap_timer;
|
||||
soft_sync->base.group = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mcpwm_ll_capture_enable_timer_sync(group->hal.dev, true);
|
||||
mcpwm_ll_capture_set_sync_phase_value(group->hal.dev, config->count_value);
|
||||
} else { // disable sync feature
|
||||
mcpwm_ll_capture_enable_timer_sync(group->hal.dev, false);
|
||||
ESP_LOGD(TAG, "disable sync for cap timer (%d)", group->group_id);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
IRAM_ATTR static void mcpwm_capture_default_isr(void *args)
|
||||
{
|
||||
mcpwm_cap_channel_t *cap_chan = (mcpwm_cap_channel_t *)args;
|
||||
mcpwm_group_t *group = cap_chan->cap_timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int cap_id = cap_chan->cap_chan_id;
|
||||
bool need_yield = false;
|
||||
|
||||
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_CAPTURE(cap_id));
|
||||
|
||||
// read capture value and pass to user
|
||||
mcpwm_capture_event_data_t data = {
|
||||
.cap_value = mcpwm_ll_capture_get_value(hal->dev, cap_id),
|
||||
.cap_edge = mcpwm_ll_capture_get_edge(hal->dev, cap_id),
|
||||
};
|
||||
if (status & MCPWM_LL_EVENT_CAPTURE(cap_id)) {
|
||||
mcpwm_capture_event_cb_t cb = cap_chan->on_cap;
|
||||
if (cb) {
|
||||
if (cb(cap_chan, &data, cap_chan->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
210
components/driver/mcpwm/mcpwm_cmpr.c
Normal file
210
components/driver/mcpwm/mcpwm_cmpr.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_cmpr.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static void mcpwm_comparator_default_isr(void *args);
|
||||
|
||||
static esp_err_t mcpwm_comparator_register_to_operator(mcpwm_cmpr_t *cmpr, mcpwm_oper_t *oper)
|
||||
{
|
||||
int cmpr_id = -1;
|
||||
portENTER_CRITICAL(&oper->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
|
||||
if (!oper->comparators[i]) {
|
||||
oper->comparators[i] = cmpr;
|
||||
cmpr_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&oper->spinlock);
|
||||
ESP_RETURN_ON_FALSE(cmpr_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free comparator in operator (%d,%d)", oper->group->group_id, oper->oper_id);
|
||||
|
||||
cmpr->cmpr_id = cmpr_id;
|
||||
cmpr->operator = oper;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_comparator_unregister_from_operator(mcpwm_cmpr_t *cmpr)
|
||||
{
|
||||
mcpwm_oper_t *oper = cmpr->operator;
|
||||
int cmpr_id = cmpr->cmpr_id;
|
||||
|
||||
portENTER_CRITICAL(&oper->spinlock);
|
||||
oper->comparators[cmpr_id] = NULL;
|
||||
portEXIT_CRITICAL(&oper->spinlock);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_comparator_destory(mcpwm_cmpr_t *cmpr)
|
||||
{
|
||||
if (cmpr->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(cmpr->intr), TAG, "uninstall interrupt service failed");
|
||||
}
|
||||
if (cmpr->operator) {
|
||||
mcpwm_comparator_unregister_from_operator(cmpr);
|
||||
}
|
||||
free(cmpr);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_config_t *config, mcpwm_cmpr_handle_t *ret_cmpr)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_cmpr_t *cmpr = NULL;
|
||||
ESP_GOTO_ON_FALSE(oper && config && ret_cmpr, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
|
||||
cmpr = heap_caps_calloc(1, sizeof(mcpwm_cmpr_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(cmpr, ESP_ERR_NO_MEM, err, TAG, "no mem for comparator");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_comparator_register_to_operator(cmpr, oper), err, TAG, "register comparator failed");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
int cmpr_id = cmpr->cmpr_id;
|
||||
|
||||
mcpwm_ll_operator_enable_update_compare_on_tez(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_tez);
|
||||
mcpwm_ll_operator_enable_update_compare_on_tep(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_tep);
|
||||
mcpwm_ll_operator_enable_update_compare_on_sync(hal->dev, oper_id, cmpr_id, config->flags.update_cmp_on_sync);
|
||||
|
||||
// fill in other comparator members
|
||||
cmpr->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
*ret_cmpr = cmpr;
|
||||
ESP_LOGD(TAG, "new comparator (%d,%d,%d) at %p", group->group_id, oper_id, cmpr_id, cmpr);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (cmpr) {
|
||||
mcpwm_comparator_destory(cmpr);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cmpr, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *operator= cmpr->operator;
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = operator->oper_id;
|
||||
int cmpr_id = cmpr->cmpr_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id), false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
ESP_LOGD(TAG, "del comparator (%d,%d,%d)", group->group_id, oper_id, cmpr_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_comparator_destory(cmpr), TAG, "destory comparator failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_comparator_set_compare_value(mcpwm_cmpr_handle_t cmpr, uint32_t cmp_ticks)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cmpr, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *oper = cmpr->operator;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_timer_t *timer = oper->timer;
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_STATE, TAG, "timer and operator are not connected");
|
||||
ESP_RETURN_ON_FALSE(cmp_ticks < timer->peak_ticks, ESP_ERR_INVALID_ARG, TAG, "compare value out of range");
|
||||
|
||||
portENTER_CRITICAL_SAFE(&cmpr->spinlock);
|
||||
mcpwm_ll_operator_set_compare_value(group->hal.dev, oper->oper_id, cmpr->cmpr_id, cmp_ticks);
|
||||
portEXIT_CRITICAL_SAFE(&cmpr->spinlock);
|
||||
|
||||
cmpr->compare_ticks = cmp_ticks;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_comparator_register_event_callbacks(mcpwm_cmpr_handle_t cmpr, const mcpwm_comparator_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(cmpr && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *oper = cmpr->operator;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int group_id = group->group_id;
|
||||
int oper_id = oper->oper_id;
|
||||
int cmpr_id = cmpr->cmpr_id;
|
||||
|
||||
#if CONFIG_MCWPM_ISR_IRAM_SAFE
|
||||
if (cbs->on_reach) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_reach), ESP_ERR_INVALID_ARG, TAG, "on_reach callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
if (!cmpr->intr) {
|
||||
// we want the interrupt servie to be enabled after allocation successfully
|
||||
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
||||
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id),
|
||||
mcpwm_comparator_default_isr, cmpr, &cmpr->intr), TAG, "install interrupt service for comparator failed");
|
||||
}
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id), cbs->on_reach != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
cmpr->on_reach = cbs->on_reach;
|
||||
cmpr->user_data = user_data;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR mcpwm_comparator_default_isr(void *args)
|
||||
{
|
||||
mcpwm_cmpr_t *cmpr = (mcpwm_cmpr_t *)args;
|
||||
mcpwm_oper_t *oper = cmpr->operator;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
int cmpr_id = cmpr->cmpr_id;
|
||||
bool need_yield = false;
|
||||
|
||||
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id));
|
||||
|
||||
mcpwm_compare_event_data_t edata = {
|
||||
.compare_ticks = cmpr->compare_ticks,
|
||||
// .direction = TODO
|
||||
};
|
||||
|
||||
if (status & MCPWM_LL_EVENT_CMP_EQUAL(oper_id, cmpr_id)) {
|
||||
mcpwm_compare_event_cb_t cb = cmpr->on_reach;
|
||||
if (cb) {
|
||||
if (cb(cmpr, &edata, cmpr->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
135
components/driver/mcpwm/mcpwm_com.c
Normal file
135
components/driver/mcpwm/mcpwm_com.c
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/lock.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
typedef struct {
|
||||
_lock_t mutex; // platform level mutex lock
|
||||
mcpwm_group_t *groups[SOC_MCPWM_GROUPS]; // array of MCPWM group instances
|
||||
int group_ref_counts[SOC_MCPWM_GROUPS]; // reference count used to protect group install/uninstall
|
||||
} mcpwm_platform_t;
|
||||
|
||||
static mcpwm_platform_t s_platform; // singleton platform
|
||||
|
||||
mcpwm_group_t *mcpwm_acquire_group_handle(int group_id)
|
||||
{
|
||||
bool new_group = false;
|
||||
mcpwm_group_t *group = NULL;
|
||||
|
||||
// prevent install mcpwm group concurrently
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
if (!s_platform.groups[group_id]) {
|
||||
group = heap_caps_calloc(1, sizeof(mcpwm_group_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
if (group) {
|
||||
new_group = true;
|
||||
s_platform.groups[group_id] = group;
|
||||
group->group_id = group_id;
|
||||
group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
// enable APB to access MCPWM registers
|
||||
periph_module_enable(mcpwm_periph_signals.groups[group_id].module);
|
||||
periph_module_reset(mcpwm_periph_signals.groups[group_id].module);
|
||||
// initialize HAL context
|
||||
mcpwm_hal_init_config_t hal_config = {
|
||||
.group_id = group_id
|
||||
};
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
mcpwm_hal_init(hal, &hal_config);
|
||||
// disable all interrupts and clear pending status
|
||||
mcpwm_ll_intr_enable(hal->dev, UINT32_MAX, false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, UINT32_MAX);
|
||||
}
|
||||
} else { // group already install
|
||||
group = s_platform.groups[group_id];
|
||||
}
|
||||
if (group) {
|
||||
// someone acquired the group handle means we have a new object that refer to this group
|
||||
s_platform.group_ref_counts[group_id]++;
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (new_group) {
|
||||
ESP_LOGD(TAG, "new group(%d) at %p", group_id, group);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
|
||||
void mcpwm_release_group_handle(mcpwm_group_t *group)
|
||||
{
|
||||
int group_id = group->group_id;
|
||||
bool do_deinitialize = false;
|
||||
|
||||
_lock_acquire(&s_platform.mutex);
|
||||
s_platform.group_ref_counts[group_id]--;
|
||||
if (s_platform.group_ref_counts[group_id] == 0) {
|
||||
do_deinitialize = true;
|
||||
s_platform.groups[group_id] = NULL; // deregister from platfrom
|
||||
// hal layer deinitialize
|
||||
mcpwm_hal_deinit(&group->hal);
|
||||
periph_module_disable(mcpwm_periph_signals.groups[group_id].module);
|
||||
free(group);
|
||||
}
|
||||
_lock_release(&s_platform.mutex);
|
||||
|
||||
if (do_deinitialize) {
|
||||
ESP_LOGD(TAG, "del group(%d)", group_id);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_select_periph_clock(mcpwm_group_t *group, mcpwm_timer_clock_source_t clk_src)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t periph_src_clk_hz = 0;
|
||||
bool clock_selection_conflict = false;
|
||||
bool do_clock_init = false;
|
||||
// check if we need to update the group clock source, group clock source is shared by all mcpwm objects
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
if (group->clk_src == 0) {
|
||||
group->clk_src = clk_src;
|
||||
do_clock_init = true;
|
||||
} else {
|
||||
clock_selection_conflict = (group->clk_src != clk_src);
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
|
||||
"group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src);
|
||||
|
||||
if (do_clock_init) {
|
||||
// [clk_tree] ToDo: replace the following switch-case table by clock_tree APIs
|
||||
switch (clk_src) {
|
||||
case MCPWM_TIMER_CLK_SRC_DEFAULT:
|
||||
periph_src_clk_hz = 160000000;
|
||||
#if CONFIG_PM_ENABLE
|
||||
sprintf(group->pm_lock_name, "mcpwm_%d", group->group_id); // e.g. mcpwm_0
|
||||
ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, group->pm_lock_name, &group->pm_lock);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "create ESP_PM_APB_FREQ_MAX lock failed");
|
||||
ESP_LOGD(TAG, "install ESP_PM_APB_FREQ_MAX lock for MCPWM group(%d)", group->group_id);
|
||||
#endif // CONFIG_PM_ENABLE
|
||||
break;
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not supported", clk_src);
|
||||
break;
|
||||
}
|
||||
mcpwm_ll_group_set_clock_prescale(group->hal.dev, MCPWM_PERIPH_CLOCK_PRE_SCALE);
|
||||
group->resolution_hz = periph_src_clk_hz / MCPWM_PERIPH_CLOCK_PRE_SCALE;
|
||||
ESP_LOGD(TAG, "group (%d) clock resolution:%uHz", group->group_id, group->resolution_hz);
|
||||
}
|
||||
return ret;
|
||||
}
|
302
components/driver/mcpwm/mcpwm_fault.c
Normal file
302
components/driver/mcpwm/mcpwm_fault.c
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_fault.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static void mcpwm_gpio_fault_default_isr(void *args);
|
||||
static esp_err_t mcpwm_del_gpio_fault(mcpwm_fault_handle_t fault);
|
||||
static esp_err_t mcpwm_del_soft_fault(mcpwm_fault_handle_t fault);
|
||||
|
||||
static esp_err_t mcpwm_gpio_fault_register_to_group(mcpwm_gpio_fault_t *fault, int group_id)
|
||||
{
|
||||
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
||||
|
||||
int fault_id = -1;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_GPIO_FAULTS_PER_GROUP; i++) {
|
||||
if (!group->gpio_faults[i]) {
|
||||
fault_id = i;
|
||||
group->gpio_faults[i] = fault;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (fault_id < 0) {
|
||||
mcpwm_release_group_handle(group);
|
||||
group = NULL;
|
||||
} else {
|
||||
fault->base.group = group;
|
||||
fault->fault_id = fault_id;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(fault_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free gpio fault in group (%d)", group_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_gpio_fault_unregister_from_group(mcpwm_gpio_fault_t *fault)
|
||||
{
|
||||
mcpwm_group_t *group = fault->base.group;
|
||||
int fault_id = fault->fault_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->gpio_faults[fault_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// fault has a reference on group, release it now
|
||||
mcpwm_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_gpio_fault_destory(mcpwm_gpio_fault_t *fault)
|
||||
{
|
||||
if (fault->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(fault->intr), TAG, "uninstall interrupt service failed");
|
||||
}
|
||||
if (fault->base.group) {
|
||||
mcpwm_gpio_fault_unregister_from_group(fault);
|
||||
}
|
||||
free(fault);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fault_handle_t *ret_fault)
|
||||
{
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_gpio_fault_t *fault = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_fault, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
||||
err, TAG, "invalid group ID:%d", config->group_id);
|
||||
|
||||
fault = heap_caps_calloc(1, sizeof(mcpwm_gpio_fault_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(fault, ESP_ERR_NO_MEM, err, TAG, "no mem for gpio fault");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_gpio_fault_register_to_group(fault, config->group_id), err, TAG, "register gpio fault failed");
|
||||
mcpwm_group_t *group = fault->base.group;
|
||||
int group_id = group->group_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int fault_id = fault->fault_id;
|
||||
|
||||
// GPIO configuration
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
|
||||
.pin_bit_mask = (1ULL << config->gpio_num),
|
||||
.pull_down_en = config->flags.pull_down,
|
||||
.pull_up_en = config->flags.pull_up,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config fault GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group_id].gpio_faults[fault_id].fault_sig, 0);
|
||||
|
||||
// set fault detection polarity
|
||||
// different gpio faults share the same config register, using a group level spin lock
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_fault_set_active_level(hal->dev, fault_id, config->flags.active_level);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// enable fault detection
|
||||
mcpwm_ll_fault_enable_detection(hal->dev, fault_id, true);
|
||||
|
||||
// fill in other operator members
|
||||
fault->base.type = MCPWM_FAULT_TYPE_GPIO;
|
||||
fault->gpio_num = config->gpio_num;
|
||||
fault->base.del = mcpwm_del_gpio_fault;
|
||||
*ret_fault = &fault->base;
|
||||
ESP_LOGD(TAG, "new gpio fault (%d,%d) at %p, GPIO: %d", group_id, fault_id, fault, config->gpio_num);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (fault) {
|
||||
mcpwm_gpio_fault_destory(fault);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_del_gpio_fault(mcpwm_fault_handle_t fault)
|
||||
{
|
||||
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
|
||||
mcpwm_group_t *group = fault->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int fault_id = gpio_fault->fault_id;
|
||||
|
||||
ESP_LOGD(TAG, "del GPIO fault (%d,%d)", group->group_id, fault_id);
|
||||
gpio_reset_pin(gpio_fault->gpio_num);
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_MASK(fault_id), false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_FAULT_MASK(fault_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// disable fault detection
|
||||
mcpwm_ll_fault_enable_detection(hal->dev, fault_id, false);
|
||||
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_gpio_fault_destory(gpio_fault), TAG, "destory GPIO fault failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_soft_fault(const mcpwm_soft_fault_config_t *config, mcpwm_fault_handle_t *ret_fault)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_soft_fault_t *soft_fault = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_fault, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
soft_fault = heap_caps_calloc(1, sizeof(mcpwm_soft_fault_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(soft_fault, ESP_ERR_NO_MEM, err, TAG, "no mem for soft fault");
|
||||
|
||||
// fill in other fault members
|
||||
soft_fault->base.type = MCPWM_FAULT_TYPE_SOFT;
|
||||
soft_fault->base.del = mcpwm_del_soft_fault;
|
||||
*ret_fault = &soft_fault->base;
|
||||
ESP_LOGD(TAG, "new soft fault at %p", soft_fault);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (soft_fault) {
|
||||
free(soft_fault);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_del_soft_fault(mcpwm_fault_handle_t fault)
|
||||
{
|
||||
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
|
||||
ESP_LOGD(TAG, "del soft fault %p", soft_fault);
|
||||
free(soft_fault);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_soft_fault_activate(mcpwm_fault_handle_t fault)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(fault->type == MCPWM_FAULT_TYPE_SOFT, ESP_ERR_INVALID_ARG, TAG, "not a valid soft fault");
|
||||
mcpwm_group_t *group = fault->group;
|
||||
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
|
||||
mcpwm_oper_t *operator = soft_fault->operator;
|
||||
ESP_RETURN_ON_FALSE(operator, ESP_ERR_INVALID_STATE, TAG, "no operator is assigned to the fault");
|
||||
|
||||
switch (operator->brake_mode_on_soft_fault) {
|
||||
case MCPWM_OPER_BRAKE_MODE_CBC:
|
||||
mcpwm_ll_brake_trigger_soft_cbc(group->hal.dev, operator->oper_id);
|
||||
break;
|
||||
case MCPWM_OPER_BRAKE_MODE_OST:
|
||||
mcpwm_ll_brake_trigger_soft_ost(group->hal.dev, operator->oper_id);
|
||||
break;
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "unknown brake mode:%d", operator->brake_mode_on_soft_fault);
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_fault(mcpwm_fault_handle_t fault)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return fault->del(fault);
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_fault_register_event_callbacks(mcpwm_fault_handle_t fault, const mcpwm_fault_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(fault && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(fault->type == MCPWM_FAULT_TYPE_GPIO, ESP_ERR_INVALID_ARG, TAG, "only gpio fault can register event callback");
|
||||
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
|
||||
mcpwm_group_t *group = fault->group;
|
||||
int group_id = group->group_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int fault_id = gpio_fault->fault_id;
|
||||
|
||||
#if CONFIG_MCWPM_ISR_IRAM_SAFE
|
||||
if (cbs->on_fault_enter) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_fault_enter), ESP_ERR_INVALID_ARG, TAG, "on_fault_enter callback not in IRAM");
|
||||
}
|
||||
if (cbs->on_fault_exit) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_fault_exit), ESP_ERR_INVALID_ARG, TAG, "on_fault_exit callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
if (!gpio_fault->intr) {
|
||||
// we want the interrupt servie to be enabled after allocation successfully
|
||||
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ESP_INTR_FLAG_INTRDISABLED;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
||||
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_FAULT_MASK(fault_id),
|
||||
mcpwm_gpio_fault_default_isr, gpio_fault, &gpio_fault->intr), TAG, "install interrupt service for gpio fault failed");
|
||||
}
|
||||
|
||||
// different mcpwm events share the same interrupt control register
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_ENTER(fault_id), cbs->on_fault_enter != NULL);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_FAULT_EXIT(fault_id), cbs->on_fault_exit != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
gpio_fault->on_fault_enter = cbs->on_fault_enter;
|
||||
gpio_fault->on_fault_exit = cbs->on_fault_exit;
|
||||
gpio_fault->user_data = user_data;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR mcpwm_gpio_fault_default_isr(void *args)
|
||||
{
|
||||
mcpwm_gpio_fault_t *fault = (mcpwm_gpio_fault_t *)args;
|
||||
mcpwm_group_t *group = fault->base.group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int fault_id = fault->fault_id;
|
||||
bool need_yield = false;
|
||||
|
||||
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_FAULT_MASK(fault_id));
|
||||
|
||||
mcpwm_fault_event_data_t edata = {
|
||||
// TODO
|
||||
};
|
||||
|
||||
if (status & MCPWM_LL_EVENT_FAULT_ENTER(fault_id)) {
|
||||
mcpwm_fault_event_cb_t cb = fault->on_fault_enter;
|
||||
if (cb) {
|
||||
if (cb(&fault->base, &edata, fault->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status & MCPWM_LL_EVENT_FAULT_EXIT(fault_id)) {
|
||||
mcpwm_fault_event_cb_t cb = fault->on_fault_exit;
|
||||
if (cb) {
|
||||
if (cb(&fault->base, &edata, fault->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
264
components/driver/mcpwm/mcpwm_gen.c
Normal file
264
components/driver/mcpwm/mcpwm_gen.c
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/mcpwm_gen.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static esp_err_t mcpwm_generator_register_to_operator(mcpwm_gen_t *gen, mcpwm_oper_t *oper)
|
||||
{
|
||||
int gen_id = -1;
|
||||
portENTER_CRITICAL(&oper->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
|
||||
if (!oper->generators[i]) {
|
||||
oper->generators[i] = gen;
|
||||
gen_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&oper->spinlock);
|
||||
ESP_RETURN_ON_FALSE(gen_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free generator in operator (%d,%d)", oper->group->group_id, oper->oper_id);
|
||||
|
||||
gen->gen_id = gen_id;
|
||||
gen->operator = oper;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_generator_unregister_from_operator(mcpwm_gen_t *gen)
|
||||
{
|
||||
mcpwm_oper_t *oper = gen->operator;
|
||||
int gen_id = gen->gen_id;
|
||||
|
||||
portENTER_CRITICAL(&oper->spinlock);
|
||||
oper->generators[gen_id] = NULL;
|
||||
portEXIT_CRITICAL(&oper->spinlock);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_generator_destory(mcpwm_gen_t *gen)
|
||||
{
|
||||
if (gen->operator) {
|
||||
mcpwm_generator_unregister_from_operator(gen);
|
||||
}
|
||||
free(gen);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_config_t *config, mcpwm_gen_handle_t *ret_gen)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_gen_t *gen = NULL;
|
||||
ESP_GOTO_ON_FALSE(oper && config && ret_gen, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
|
||||
gen = heap_caps_calloc(1, sizeof(mcpwm_gen_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(gen, ESP_ERR_NO_MEM, err, TAG, "no mem for generator");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_generator_register_to_operator(gen, oper), err, TAG, "register generator failed");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
int gen_id = gen->gen_id;
|
||||
|
||||
// reset generator
|
||||
mcpwm_hal_generator_reset(hal, oper_id, gen_id);
|
||||
|
||||
// GPIO configuration
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0), // also enable the input path if `io_loop_back` is enabled
|
||||
.pin_bit_mask = (1ULL << config->gen_gpio_num),
|
||||
.pull_down_en = false,
|
||||
.pull_up_en = true,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config gen GPIO failed");
|
||||
esp_rom_gpio_connect_out_signal(config->gen_gpio_num,
|
||||
mcpwm_periph_signals.groups[group->group_id].operators[oper_id].generators[gen_id].pwm_sig,
|
||||
config->flags.invert_pwm, 0);
|
||||
|
||||
// fill in other generator members
|
||||
gen->gen_gpio_num = config->gen_gpio_num;
|
||||
gen->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
*ret_gen = gen;
|
||||
ESP_LOGD(TAG, "new generator (%d,%d,%d) at %p, GPIO %d", group->group_id, oper_id, gen_id, gen, gen->gen_gpio_num);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (gen) {
|
||||
mcpwm_generator_destory(gen);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *oper = gen->operator;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
|
||||
ESP_LOGD(TAG, "del generator (%d,%d,%d)", group->group_id, oper->oper_id, gen->gen_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_destory(gen), TAG, "destory generator failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_generator_set_force_level(mcpwm_gen_handle_t gen, int level, bool hold_on)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gen && level <= 1, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *oper = gen->operator;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
int gen_id = gen->gen_id;
|
||||
|
||||
if (level < 0) { // to remove the force level
|
||||
if (hold_on) {
|
||||
mcpwm_ll_gen_disable_continue_force_action(hal->dev, oper_id, gen_id);
|
||||
} else {
|
||||
mcpwm_ll_gen_disable_noncontinue_force_action(hal->dev, oper_id, gen_id);
|
||||
}
|
||||
} else { // to enable the force output level
|
||||
if (hold_on) {
|
||||
mcpwm_ll_gen_set_continue_force_level(hal->dev, oper_id, gen_id, level);
|
||||
} else {
|
||||
mcpwm_ll_gen_set_noncontinue_force_level(hal->dev, oper_id, gen_id, level);
|
||||
mcpwm_ll_gen_trigger_noncontinue_force_action(hal->dev, oper_id, gen_id);
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_generator_set_actions_on_timer_event(mcpwm_gen_handle_t gen, mcpwm_gen_timer_event_action_t ev_act, ...)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *operator= gen->operator;
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_timer_t *timer = operator->timer;
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_STATE, TAG, "no timer is connected to the operator");
|
||||
mcpwm_gen_timer_event_action_t ev_act_itor = ev_act;
|
||||
bool invalid_utep = false;
|
||||
bool invalid_dtez = false;
|
||||
va_list it;
|
||||
va_start(it, ev_act);
|
||||
while (ev_act_itor.event != MCPWM_TIMER_EVENT_INVALID) {
|
||||
invalid_utep = (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) &&
|
||||
(ev_act_itor.direction == MCPWM_TIMER_DIRECTION_UP) &&
|
||||
(ev_act_itor.event == MCPWM_TIMER_EVENT_FULL);
|
||||
invalid_dtez = (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) &&
|
||||
(ev_act_itor.direction == MCPWM_TIMER_DIRECTION_DOWN) &&
|
||||
(ev_act_itor.event == MCPWM_TIMER_EVENT_EMPTY);
|
||||
if (invalid_utep || invalid_dtez) {
|
||||
va_end(it);
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "UTEP and DTEZ can't be reached under MCPWM_TIMER_COUNT_MODE_UP_DOWN mode");
|
||||
}
|
||||
mcpwm_ll_generator_set_action_on_timer_event(group->hal.dev, operator->oper_id, gen->gen_id,
|
||||
ev_act_itor.direction, ev_act_itor.event, ev_act_itor.action);
|
||||
ev_act_itor = va_arg(it, mcpwm_gen_timer_event_action_t);
|
||||
}
|
||||
va_end(it);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_generator_set_actions_on_compare_event(mcpwm_gen_handle_t gen, mcpwm_gen_compare_event_action_t ev_act, ...)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *operator= gen->operator;
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_gen_compare_event_action_t ev_act_itor = ev_act;
|
||||
va_list it;
|
||||
va_start(it, ev_act);
|
||||
while (ev_act_itor.comparator) {
|
||||
mcpwm_ll_generator_set_action_on_compare_event(group->hal.dev, operator->oper_id, gen->gen_id,
|
||||
ev_act_itor.direction, ev_act_itor.comparator->cmpr_id, ev_act_itor.action);
|
||||
ev_act_itor = va_arg(it, mcpwm_gen_compare_event_action_t);
|
||||
}
|
||||
va_end(it);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_generator_set_actions_on_brake_event(mcpwm_gen_handle_t gen, mcpwm_gen_brake_event_action_t ev_act, ...)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(gen, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_oper_t *operator= gen->operator;
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_gen_brake_event_action_t ev_act_itor = ev_act;
|
||||
va_list it;
|
||||
va_start(it, ev_act);
|
||||
while (ev_act_itor.brake_mode != MCPWM_OPER_BRAKE_MODE_INVALID) {
|
||||
mcpwm_ll_generator_set_action_on_brake_event(group->hal.dev, operator->oper_id, gen->gen_id,
|
||||
ev_act_itor.direction, ev_act_itor.brake_mode, ev_act_itor.action);
|
||||
ev_act_itor = va_arg(it, mcpwm_gen_brake_event_action_t);
|
||||
}
|
||||
va_end(it);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t in_generator, mcpwm_gen_handle_t out_generator, const mcpwm_dead_time_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(in_generator && out_generator && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(in_generator->operator == out_generator->operator, ESP_ERR_INVALID_ARG, TAG, "in/out generator are not derived from the same operator");
|
||||
ESP_RETURN_ON_FALSE(config->negedge_delay_ticks < MCPWM_LL_MAX_DEAD_DELAY && config->posedge_delay_ticks < MCPWM_LL_MAX_DEAD_DELAY,
|
||||
ESP_ERR_INVALID_ARG, TAG, "delay time out of range");
|
||||
mcpwm_oper_t *operator= in_generator->operator;
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = operator->oper_id;
|
||||
|
||||
// Note: to better understand the following code, you should read the deadtime module topology diagram in the TRM
|
||||
// check if we want to bypass the deadtime module
|
||||
bool bypass = (config->negedge_delay_ticks == 0) && (config->posedge_delay_ticks == 0);
|
||||
// check is we want to delay on the both edge
|
||||
bool delay_on_both_edge = config->posedge_delay_ticks && config->negedge_delay_ticks;
|
||||
int out_path_id = -1;
|
||||
if (bypass) {
|
||||
// out path is same to the input path of generator
|
||||
out_path_id = in_generator->gen_id;
|
||||
} else if (config->negedge_delay_ticks) {
|
||||
out_path_id = 1; // FED path
|
||||
} else {
|
||||
out_path_id = 0; // RED path
|
||||
}
|
||||
bool swap_path = out_path_id != out_generator->gen_id;
|
||||
mcpwm_ll_deadtime_bypass_path(hal->dev, oper_id, out_path_id, bypass); // S0/1
|
||||
if (!bypass) {
|
||||
if (config->posedge_delay_ticks) {
|
||||
mcpwm_ll_deadtime_red_select_generator(hal->dev, oper_id, in_generator->gen_id); // S4
|
||||
} else {
|
||||
mcpwm_ll_deadtime_fed_select_generator(hal->dev, oper_id, in_generator->gen_id); // S5
|
||||
}
|
||||
mcpwm_ll_deadtime_enable_deb(hal->dev, oper_id, delay_on_both_edge); // S8
|
||||
mcpwm_ll_deadtime_invert_outpath(hal->dev, oper_id, out_path_id, config->flags.invert_output); // S2/3
|
||||
mcpwm_ll_deadtime_swap_out_path(hal->dev, oper_id, out_generator->gen_id, swap_path); // S6/S7
|
||||
}
|
||||
// set delay time
|
||||
if (config->posedge_delay_ticks) {
|
||||
mcpwm_ll_deadtime_set_rising_delay(hal->dev, oper_id, config->posedge_delay_ticks);
|
||||
}
|
||||
if (config->negedge_delay_ticks) {
|
||||
mcpwm_ll_deadtime_set_falling_delay(hal->dev, oper_id, config->negedge_delay_ticks);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "operator (%d,%d) dead time (R:%u,F:%u), topology code:%x", group->group_id, oper_id,
|
||||
config->posedge_delay_ticks, config->negedge_delay_ticks, mcpwm_ll_deadtime_get_switch_topology(hal->dev, oper_id));
|
||||
return ESP_OK;
|
||||
}
|
363
components/driver/mcpwm/mcpwm_oper.c
Normal file
363
components/driver/mcpwm/mcpwm_oper.c
Normal file
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static void mcpwm_operator_default_isr(void *args);
|
||||
|
||||
static esp_err_t mcpwm_operator_register_to_group(mcpwm_oper_t *oper, int group_id)
|
||||
{
|
||||
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
||||
|
||||
int oper_id = -1;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_OPERATORS_PER_GROUP; i++) {
|
||||
if (!group->operators[i]) {
|
||||
oper_id = i;
|
||||
group->operators[i] = oper;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (oper_id < 0) {
|
||||
mcpwm_release_group_handle(group);
|
||||
group = NULL;
|
||||
} else {
|
||||
oper->group = group;
|
||||
oper->oper_id = oper_id;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(oper_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free operators in group (%d)", group_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_operator_unregister_from_group(mcpwm_oper_t *oper)
|
||||
{
|
||||
mcpwm_group_t *group = oper->group;
|
||||
int oper_id = oper->oper_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->operators[oper_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// operator has a reference on group, release it now
|
||||
mcpwm_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_operator_destory(mcpwm_oper_t *oper)
|
||||
{
|
||||
if (oper->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(oper->intr), TAG, "uninstall interrupt service failed");
|
||||
}
|
||||
if (oper->group) {
|
||||
mcpwm_operator_unregister_from_group(oper);
|
||||
}
|
||||
free(oper);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_handle_t *ret_oper)
|
||||
{
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_oper_t *operator= NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_oper, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
||||
err, TAG, "invalid group ID:%d", config->group_id);
|
||||
|
||||
operator= heap_caps_calloc(1, sizeof(mcpwm_oper_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(operator, ESP_ERR_NO_MEM, err, TAG, "no mem for operator");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_operator_register_to_group(operator, config->group_id), err, TAG, "register operator failed");
|
||||
mcpwm_group_t *group = operator->group;
|
||||
int group_id = group->group_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = operator->oper_id;
|
||||
|
||||
// reset MCPWM operator
|
||||
mcpwm_hal_operator_reset(hal, oper_id);
|
||||
|
||||
// set the time point that the generator can update the action
|
||||
mcpwm_ll_operator_enable_update_action_on_tez(hal->dev, oper_id, config->flags.update_gen_action_on_tez);
|
||||
mcpwm_ll_operator_enable_update_action_on_tep(hal->dev, oper_id, config->flags.update_gen_action_on_tep);
|
||||
mcpwm_ll_operator_enable_update_action_on_sync(hal->dev, oper_id, config->flags.update_gen_action_on_sync);
|
||||
// set the time point that the deadtime can update the delay parameter
|
||||
mcpwm_ll_deadtime_enable_update_delay_on_tez(hal->dev, oper_id, config->flags.update_dead_time_on_tez);
|
||||
mcpwm_ll_deadtime_enable_update_delay_on_tep(hal->dev, oper_id, config->flags.update_dead_time_on_tep);
|
||||
mcpwm_ll_deadtime_enable_update_delay_on_sync(hal->dev, oper_id, config->flags.update_dead_time_on_sync);
|
||||
// set the clock source for dead time submodule, the resolution is the same to the MCPWM group
|
||||
mcpwm_ll_operator_set_deadtime_clock_src(hal->dev, oper_id, MCPWM_LL_DEADTIME_CLK_SRC_GROUP);
|
||||
operator->deadtime_resolution_hz = group->resolution_hz;
|
||||
|
||||
// fill in other operator members
|
||||
operator->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
*ret_oper = operator;
|
||||
ESP_LOGD(TAG, "new operator (%d,%d) at %p", group_id, oper_id, operator);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (operator) {
|
||||
mcpwm_operator_destory(operator);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(oper, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
|
||||
ESP_RETURN_ON_FALSE(!oper->comparators[i], ESP_ERR_INVALID_STATE, TAG, "comparator still in working");
|
||||
}
|
||||
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
|
||||
ESP_RETURN_ON_FALSE(!oper->generators[i], ESP_ERR_INVALID_STATE, TAG, "generator still in working");
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(!oper->soft_fault, ESP_ERR_INVALID_STATE, TAG, "soft fault still in working");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
int oper_id = oper->oper_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_MASK(oper_id), false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_OPER_MASK(oper_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
ESP_LOGD(TAG, "del operator (%d,%d)", group->group_id, oper_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_operator_destory(oper), TAG, "destory operator failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_operator_connect_timer(mcpwm_oper_handle_t oper, mcpwm_timer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(oper && timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(oper->group == timer->group, ESP_ERR_INVALID_ARG, TAG, "operator and timer should reside in the same group");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
|
||||
// connect operator and timer
|
||||
mcpwm_ll_operator_connect_timer(hal->dev, oper->oper_id, timer->timer_id);
|
||||
// change the the clock source of deadtime submodule to use MCPWM timer
|
||||
mcpwm_ll_operator_set_deadtime_clock_src(hal->dev, oper->oper_id, MCPWM_LL_DEADTIME_CLK_SRC_TIMER);
|
||||
oper->deadtime_resolution_hz = timer->resolution_hz;
|
||||
|
||||
oper->timer = timer;
|
||||
ESP_LOGD(TAG, "connect operator (%d) and timer (%d) in group (%d)", oper->oper_id, timer->timer_id, group->group_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_operator_apply_carrier(mcpwm_oper_handle_t oper, const mcpwm_carrier_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(oper, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
uint32_t real_frequency = 0;
|
||||
uint32_t real_fpd = 0;
|
||||
float real_duty = 0.0;
|
||||
|
||||
if (config && config->frequency_hz) {
|
||||
uint8_t pre_scale = group->resolution_hz / 8 / config->frequency_hz;
|
||||
mcpwm_ll_carrier_set_prescale(hal->dev, oper_id, pre_scale);
|
||||
real_frequency = group->resolution_hz / 8 / pre_scale;
|
||||
|
||||
uint8_t duty = (uint8_t)(config->duty_cycle * 8);
|
||||
mcpwm_ll_carrier_set_duty(hal->dev, oper_id, duty);
|
||||
real_duty = (float) duty / 8.0F;
|
||||
|
||||
uint8_t first_pulse_ticks = (uint8_t)(config->first_pulse_duration_us * real_frequency / 1000000UL);
|
||||
ESP_RETURN_ON_FALSE(first_pulse_ticks > 0 && first_pulse_ticks <= MCPWM_LL_MAX_CARRIER_ONESHOT,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid first pulse duration");
|
||||
mcpwm_ll_carrier_set_first_pulse_width(hal->dev, oper_id, first_pulse_ticks);
|
||||
real_fpd = first_pulse_ticks * 1000000UL / real_frequency;
|
||||
|
||||
mcpwm_ll_carrier_in_invert(hal->dev, oper_id, config->flags.invert_before_modulate);
|
||||
mcpwm_ll_carrier_out_invert(hal->dev, oper_id, config->flags.invert_after_modulate);
|
||||
}
|
||||
|
||||
mcpwm_ll_carrier_enable(hal->dev, oper_id, real_frequency > 0);
|
||||
|
||||
if (real_frequency > 0) {
|
||||
ESP_LOGD(TAG, "enable carrier modulation for operator(%d,%d), freq=%uHz, duty=%.2f, FPD=%dus",
|
||||
group->group_id, oper_id, real_frequency, real_duty, real_fpd);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "disable carrier for operator (%d,%d)", group->group_id, oper_id);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_operator_register_event_callbacks(mcpwm_oper_handle_t oper, const mcpwm_operator_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(oper && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int group_id = group->group_id;
|
||||
int oper_id = oper->oper_id;
|
||||
|
||||
#if CONFIG_MCWPM_ISR_IRAM_SAFE
|
||||
if (cbs->on_brake_cbc) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_brake_cbc), ESP_ERR_INVALID_ARG, TAG, "on_brake_cbc callback not in IRAM");
|
||||
}
|
||||
if (cbs->on_brake_ost) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_brake_ost), ESP_ERR_INVALID_ARG, TAG, "on_brake_ost callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
if (!oper->intr) {
|
||||
// we want the interrupt servie to be enabled after allocation successfully
|
||||
int isr_flags = MCPWM_INTR_ALLOC_FLAG & ~ ESP_INTR_FLAG_INTRDISABLED;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
||||
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_OPER_MASK(oper_id),
|
||||
mcpwm_operator_default_isr, oper, &oper->intr), TAG, "install interrupt service for operator failed");
|
||||
}
|
||||
|
||||
// enable/disable interrupt events
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_BRAKE_CBC(oper_id), cbs->on_brake_cbc != NULL);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_OPER_BRAKE_OST(oper_id), cbs->on_brake_ost != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
oper->on_brake_cbc = cbs->on_brake_cbc;
|
||||
oper->on_brake_ost = cbs->on_brake_ost;
|
||||
oper->user_data = user_data;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_operator_set_brake_on_fault(mcpwm_oper_handle_t operator, const mcpwm_brake_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(operator && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_fault_t *fault = config->fault;
|
||||
|
||||
int oper_id = operator->oper_id;
|
||||
mcpwm_ll_brake_enable_cbc_refresh_on_tez(group->hal.dev, oper_id, config->flags.cbc_recover_on_tez);
|
||||
mcpwm_ll_fault_enable_cbc_refresh_on_tep(group->hal.dev, oper_id, config->flags.cbc_recover_on_tep);
|
||||
|
||||
switch (fault->type) {
|
||||
case MCPWM_FAULT_TYPE_GPIO: {
|
||||
ESP_RETURN_ON_FALSE(group == fault->group, ESP_ERR_INVALID_ARG, TAG, "fault and operator not in the same group");
|
||||
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
|
||||
mcpwm_ll_brake_enable_cbc_mode(group->hal.dev, oper_id, gpio_fault->fault_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_CBC);
|
||||
mcpwm_ll_brake_enable_oneshot_mode(group->hal.dev, oper_id, gpio_fault->fault_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_OST);
|
||||
operator->brake_mode_on_gpio_fault[gpio_fault->fault_id] = config->brake_mode;
|
||||
break;
|
||||
}
|
||||
case MCPWM_FAULT_TYPE_SOFT: {
|
||||
mcpwm_soft_fault_t *soft_fault = __containerof(fault, mcpwm_soft_fault_t, base);
|
||||
ESP_RETURN_ON_FALSE(!soft_fault->operator || soft_fault->operator == operator, ESP_ERR_INVALID_STATE, TAG, "soft fault already used by another operator");
|
||||
soft_fault->operator = operator;
|
||||
soft_fault->base.group = operator->group;
|
||||
mcpwm_ll_brake_enable_soft_cbc(group->hal.dev, oper_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_CBC);
|
||||
mcpwm_ll_brake_enable_soft_ost(group->hal.dev, oper_id, config->brake_mode == MCPWM_OPER_BRAKE_MODE_OST);
|
||||
operator->brake_mode_on_soft_fault = config->brake_mode;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "unknown fault type:%d", fault->type);
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_operator_recover_from_fault(mcpwm_oper_handle_t operator, mcpwm_fault_handle_t fault)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(operator && fault, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = operator->group;
|
||||
mcpwm_operator_brake_mode_t brake_mode;
|
||||
|
||||
// check the brake mode on the fault event
|
||||
switch (fault->type) {
|
||||
case MCPWM_FAULT_TYPE_GPIO: {
|
||||
mcpwm_gpio_fault_t *gpio_fault = __containerof(fault, mcpwm_gpio_fault_t, base);
|
||||
brake_mode = operator->brake_mode_on_gpio_fault[gpio_fault->fault_id];
|
||||
break;
|
||||
}
|
||||
case MCPWM_FAULT_TYPE_SOFT:
|
||||
brake_mode = operator->brake_mode_on_soft_fault;
|
||||
break;
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "unknown fault type:%d", fault->type);
|
||||
break;
|
||||
}
|
||||
|
||||
bool fault_signal_is_active = false;
|
||||
if (brake_mode == MCPWM_OPER_BRAKE_MODE_OST) {
|
||||
fault_signal_is_active = mcpwm_ll_ost_brake_active(group->hal.dev, operator->oper_id);
|
||||
// OST brake can't recover automatically, need to manually recovery the operator
|
||||
if (!fault_signal_is_active) {
|
||||
mcpwm_ll_brake_clear_ost(group->hal.dev, operator->oper_id);
|
||||
}
|
||||
} else {
|
||||
fault_signal_is_active = mcpwm_ll_cbc_brake_active(group->hal.dev, operator->oper_id);
|
||||
// CBC brake can recover automatically after deactivating the fault signal
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_FALSE(!fault_signal_is_active, ESP_ERR_INVALID_STATE, TAG, "recover fail, fault signal still active");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR mcpwm_operator_default_isr(void *args)
|
||||
{
|
||||
mcpwm_oper_t *oper = (mcpwm_oper_t *)args;
|
||||
mcpwm_group_t *group = oper->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int oper_id = oper->oper_id;
|
||||
bool need_yield = false;
|
||||
|
||||
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_OPER_MASK(oper_id));
|
||||
|
||||
mcpwm_brake_event_data_t edata = {};
|
||||
|
||||
if (status & MCPWM_LL_EVENT_OPER_BRAKE_CBC(oper_id)) {
|
||||
mcpwm_brake_event_cb_t cb = oper->on_brake_cbc;
|
||||
if (cb) {
|
||||
if (cb(oper, &edata, oper->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status & MCPWM_LL_EVENT_OPER_BRAKE_OST(oper_id)) {
|
||||
mcpwm_brake_event_cb_t cb = oper->on_brake_ost;
|
||||
if (cb) {
|
||||
if (cb(oper, &edata, oper->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
221
components/driver/mcpwm/mcpwm_private.h
Normal file
221
components/driver/mcpwm/mcpwm_private.h
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_pm.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/mcpwm_hal.h"
|
||||
#include "hal/mcpwm_types.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_MCPWM_ISR_IRAM_SAFE
|
||||
#define MCPWM_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define MCPWM_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
#if CONFIG_MCPWM_ISR_IRAM_SAFE
|
||||
#define MCPWM_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_IRAM)
|
||||
#else
|
||||
#define MCPWM_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_INTRDISABLED)
|
||||
#endif
|
||||
|
||||
#define MCPWM_PERIPH_CLOCK_PRE_SCALE (2)
|
||||
#define MCPWM_PM_LOCK_NAME_LEN_MAX 16
|
||||
|
||||
typedef struct mcpwm_group_t mcpwm_group_t;
|
||||
typedef struct mcpwm_timer_t mcpwm_timer_t;
|
||||
typedef struct mcpwm_cap_timer_t mcpwm_cap_timer_t;
|
||||
typedef struct mcpwm_oper_t mcpwm_oper_t;
|
||||
typedef struct mcpwm_cmpr_t mcpwm_cmpr_t;
|
||||
typedef struct mcpwm_gen_t mcpwm_gen_t;
|
||||
typedef struct mcpwm_fault_t mcpwm_fault_t;
|
||||
typedef struct mcpwm_gpio_fault_t mcpwm_gpio_fault_t;
|
||||
typedef struct mcpwm_soft_fault_t mcpwm_soft_fault_t;
|
||||
typedef struct mcpwm_sync_t mcpwm_sync_t;
|
||||
typedef struct mcpwm_gpio_sync_src_t mcpwm_gpio_sync_src_t;
|
||||
typedef struct mcpwm_timer_sync_src_t mcpwm_timer_sync_src_t;
|
||||
typedef struct mcpwm_soft_sync_src_t mcpwm_soft_sync_src_t;
|
||||
typedef struct mcpwm_cap_channel_t mcpwm_cap_channel_t;
|
||||
|
||||
struct mcpwm_group_t {
|
||||
int group_id; // group ID, index from 0
|
||||
mcpwm_hal_context_t hal; // HAL instance is at group level
|
||||
portMUX_TYPE spinlock; // group level spinlock
|
||||
uint32_t resolution_hz; // MCPWM group clock resolution
|
||||
esp_pm_lock_handle_t pm_lock; // power management lock
|
||||
mcpwm_timer_clock_source_t clk_src; // source clock
|
||||
mcpwm_cap_timer_t *cap_timer; // mcpwm capture timers
|
||||
mcpwm_timer_t *timers[SOC_MCPWM_TIMERS_PER_GROUP]; // mcpwm timer array
|
||||
mcpwm_oper_t *operators[SOC_MCPWM_OPERATORS_PER_GROUP]; // mcpwm operator array
|
||||
mcpwm_gpio_fault_t *gpio_faults[SOC_MCPWM_GPIO_FAULTS_PER_GROUP]; // mcpwm fault detectors array
|
||||
mcpwm_gpio_sync_src_t *gpio_sync_srcs[SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP]; // mcpwm gpio sync array
|
||||
#if CONFIG_PM_ENABLE
|
||||
char pm_lock_name[MCPWM_PM_LOCK_NAME_LEN_MAX]; // pm lock name
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MCPWM_TIMER_FSM_INIT,
|
||||
MCPWM_TIMER_FSM_ENABLE,
|
||||
} mcpwm_timer_fsm_t;
|
||||
|
||||
struct mcpwm_timer_t {
|
||||
int timer_id; // timer ID, index from 0
|
||||
mcpwm_group_t *group; // which group the timer belongs to
|
||||
mcpwm_timer_fsm_t fsm; // driver FSM
|
||||
portMUX_TYPE spinlock; // spin lock
|
||||
intr_handle_t intr; // interrupt handle
|
||||
uint32_t resolution_hz; // resolution of the timer
|
||||
uint32_t peak_ticks; // peak ticks that the timer could reach to
|
||||
mcpwm_timer_sync_src_t *sync_src; // timer sync_src
|
||||
mcpwm_timer_count_mode_t count_mode; // count mode
|
||||
mcpwm_timer_event_cb_t on_full; // callback function when MCPWM timer counts to peak value
|
||||
mcpwm_timer_event_cb_t on_empty; // callback function when MCPWM timer counts to zero
|
||||
mcpwm_timer_event_cb_t on_stop; // callback function when MCPWM timer stops
|
||||
void *user_data; // user data which would be passed to the timer callbacks
|
||||
};
|
||||
|
||||
struct mcpwm_oper_t {
|
||||
int oper_id; // operator ID, index from 0
|
||||
mcpwm_group_t *group; // which group the timer belongs to
|
||||
mcpwm_timer_t *timer; // which timer is connected to this operator
|
||||
portMUX_TYPE spinlock; // spin lock
|
||||
intr_handle_t intr; // interrupt handle
|
||||
mcpwm_gen_t *generators[SOC_MCPWM_GENERATORS_PER_OPERATOR]; // mcpwm generator array
|
||||
mcpwm_cmpr_t *comparators[SOC_MCPWM_COMPARATORS_PER_OPERATOR]; // mcpwm comparator array
|
||||
mcpwm_soft_fault_t *soft_fault; // mcpwm software fault
|
||||
mcpwm_operator_brake_mode_t brake_mode_on_soft_fault; // brake mode on software triggered fault
|
||||
mcpwm_operator_brake_mode_t brake_mode_on_gpio_fault[SOC_MCPWM_GPIO_FAULTS_PER_GROUP]; // brake mode on GPIO triggered faults
|
||||
uint32_t deadtime_resolution_hz; // resolution of deadtime submodule
|
||||
mcpwm_brake_event_cb_t on_brake_cbc; // callback function which would be invoked when mcpwm operator goes into trip zone
|
||||
mcpwm_brake_event_cb_t on_brake_ost; // callback function which would be invoked when mcpwm operator goes into trip zone
|
||||
void *user_data; // user data which would be passed to the trip zone callback
|
||||
};
|
||||
|
||||
struct mcpwm_cmpr_t {
|
||||
int cmpr_id; // comparator ID, index from 0
|
||||
mcpwm_oper_t *operator; // which operator that the comparator resides in
|
||||
intr_handle_t intr; // interrupt handle
|
||||
portMUX_TYPE spinlock; // spin lock
|
||||
uint32_t compare_ticks; // compare value of this comparator
|
||||
mcpwm_compare_event_cb_t on_reach; // ISR callback function which would be invoked on timer counter reaches compare value
|
||||
void *user_data; // user data which would be passed to the comparator callbacks
|
||||
};
|
||||
|
||||
struct mcpwm_gen_t {
|
||||
int gen_id; // generator ID, index from 0
|
||||
mcpwm_oper_t *operator; // which operator that the generator resides in
|
||||
int gen_gpio_num; // GPIO number used by the generator
|
||||
portMUX_TYPE spinlock; // spin lock
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MCPWM_FAULT_TYPE_GPIO, // external GPIO fault
|
||||
MCPWM_FAULT_TYPE_SOFT, // software fault
|
||||
} mcpwm_fault_type_t;
|
||||
|
||||
struct mcpwm_fault_t {
|
||||
mcpwm_group_t *group; // which group the fault belongs to
|
||||
mcpwm_fault_type_t type; // fault type
|
||||
esp_err_t (*del)(mcpwm_fault_t *fault);
|
||||
};
|
||||
|
||||
struct mcpwm_gpio_fault_t {
|
||||
mcpwm_fault_t base; // base class
|
||||
int fault_id; // fault detector ID, index from 0
|
||||
int gpio_num; // GPIO number of fault detector
|
||||
intr_handle_t intr; // interrupt handle
|
||||
mcpwm_fault_event_cb_t on_fault_enter; // ISR callback function that would be invoked when fault signal got triggered
|
||||
mcpwm_fault_event_cb_t on_fault_exit; // ISR callback function that would be invoked when fault signal got clear
|
||||
void *user_data; // user data which would be passed to the isr_cb
|
||||
};
|
||||
|
||||
struct mcpwm_soft_fault_t {
|
||||
mcpwm_fault_t base; // base class
|
||||
mcpwm_oper_t *operator; // the operator where the soft fault allocated from
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MCPWM_SYNC_TYPE_TIMER, // sync event generated by MCPWM timer count event
|
||||
MCPWM_SYNC_TYPE_GPIO, // sync event generated by GPIO
|
||||
MCPWM_SYNC_TYPE_SOFT, // sync event generated by software
|
||||
} mcpwm_sync_src_type_t;
|
||||
|
||||
struct mcpwm_sync_t {
|
||||
mcpwm_group_t *group; // which group the sync_src belongs to
|
||||
mcpwm_sync_src_type_t type; // sync_src type
|
||||
esp_err_t (*del)(mcpwm_sync_t *sync_src);
|
||||
};
|
||||
|
||||
struct mcpwm_gpio_sync_src_t {
|
||||
mcpwm_sync_t base; // base class
|
||||
int sync_id; // sync signal ID
|
||||
int gpio_num; // GPIO number
|
||||
};
|
||||
|
||||
struct mcpwm_timer_sync_src_t {
|
||||
mcpwm_sync_t base; // base class
|
||||
mcpwm_timer_t *timer; // timer handle, where this sync_src allocated from
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MCPWM_SOFT_SYNC_FROM_NONE, // the software sync event generator has not been assigned
|
||||
MCPWM_SOFT_SYNC_FROM_TIMER, // the software sync event is generated by MCPWM timer
|
||||
MCPWM_SOFT_SYNC_FROM_CAP, // the software sync event is generated by MCPWM capture timer
|
||||
} mcpwm_soft_sync_source_t;
|
||||
|
||||
struct mcpwm_soft_sync_src_t {
|
||||
mcpwm_sync_t base; // base class
|
||||
mcpwm_soft_sync_source_t soft_sync_from; // where the software sync event is generated by
|
||||
union {
|
||||
mcpwm_timer_t *timer; // soft sync is generated by which MCPWM timer
|
||||
mcpwm_cap_timer_t *cap_timer; // soft sync is generated by which MCPWM capture timer
|
||||
};
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MCPWM_CAP_TIMER_FSM_INIT,
|
||||
MCPWM_CAP_TIMER_FSM_ENABLE,
|
||||
} mcpwm_cap_timer_fsm_t;
|
||||
|
||||
struct mcpwm_cap_timer_t {
|
||||
mcpwm_group_t *group; // which group the capture timer belongs to
|
||||
portMUX_TYPE spinlock; // spin lock, to prevent concurrently accessing capture timer level resources, including registers
|
||||
uint32_t resolution_hz; // resolution of capture timer
|
||||
mcpwm_cap_timer_fsm_t fsm; // driver FSM
|
||||
esp_pm_lock_handle_t pm_lock; // power management lock
|
||||
mcpwm_cap_channel_t *cap_channels[SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER]; // capture channel array
|
||||
};
|
||||
|
||||
struct mcpwm_cap_channel_t {
|
||||
int cap_chan_id; // capture channel ID, index from 0
|
||||
mcpwm_cap_timer_t *cap_timer; // which capture timer that the channel resides in
|
||||
uint32_t prescale; // prescale of capture signal
|
||||
int gpio_num; // GPIO number used by the channel
|
||||
intr_handle_t intr; // Interrupt handle
|
||||
mcpwm_capture_event_cb_t on_cap; // Callback function which would be invoked in capture interrupt routine
|
||||
void *user_data; // user data which would be passed to the capture callback
|
||||
};
|
||||
|
||||
mcpwm_group_t *mcpwm_acquire_group_handle(int group_id);
|
||||
void mcpwm_release_group_handle(mcpwm_group_t *group);
|
||||
esp_err_t mcpwm_select_periph_clock(mcpwm_group_t *group, mcpwm_timer_clock_source_t clk_src);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
297
components/driver/mcpwm/mcpwm_sync.c
Normal file
297
components/driver/mcpwm/mcpwm_sync.c
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_sync.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static esp_err_t mcpwm_del_timer_sync_src(mcpwm_sync_t *sync_src);
|
||||
static esp_err_t mcpwm_del_gpio_sync_src(mcpwm_sync_t *sync_src);
|
||||
static esp_err_t mcpwm_del_soft_sync_src(mcpwm_sync_t *sync_src);
|
||||
|
||||
static esp_err_t mcpwm_timer_sync_src_register_to_timer(mcpwm_timer_sync_src_t *timer_sync_src, mcpwm_timer_t *timer)
|
||||
{
|
||||
bool new_sync = false;
|
||||
portENTER_CRITICAL(&timer->spinlock);
|
||||
if (!timer->sync_src) {
|
||||
new_sync = true;
|
||||
timer->sync_src = timer_sync_src;
|
||||
}
|
||||
portEXIT_CRITICAL(&timer->spinlock);
|
||||
ESP_RETURN_ON_FALSE(new_sync, ESP_ERR_INVALID_STATE, TAG, "timer sync_src already installed for timer (%d,%d)",
|
||||
timer->group->group_id, timer->timer_id);
|
||||
|
||||
timer_sync_src->timer = timer;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_timer_sync_src_unregister_from_timer(mcpwm_timer_sync_src_t *timer_sync_src)
|
||||
{
|
||||
mcpwm_timer_t *timer = timer_sync_src->timer;
|
||||
|
||||
portENTER_CRITICAL(&timer->spinlock);
|
||||
timer->sync_src = NULL;
|
||||
portEXIT_CRITICAL(&timer->spinlock);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_timer_sync_src_destory(mcpwm_timer_sync_src_t *timer_sync_src)
|
||||
{
|
||||
if (timer_sync_src->timer) {
|
||||
mcpwm_timer_sync_src_unregister_from_timer(timer_sync_src);
|
||||
}
|
||||
free(timer_sync_src);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_timer_sync_src(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_timer_sync_src_t *timer_sync_src = NULL;
|
||||
ESP_GOTO_ON_FALSE(timer && config && ret_sync, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
timer_sync_src = heap_caps_calloc(1, sizeof(mcpwm_timer_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(timer_sync_src, ESP_ERR_NO_MEM, err, TAG, "no mem for timer sync_src");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_timer_sync_src_register_to_timer(timer_sync_src, timer), err, TAG, "register timer sync_src failed");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int timer_id = timer->timer_id;
|
||||
|
||||
if (config->flags.propagate_input_sync) {
|
||||
mcpwm_ll_timer_propagate_input_sync(hal->dev, timer_id);
|
||||
} else {
|
||||
switch (config->timer_event) {
|
||||
case MCPWM_TIMER_EVENT_EMPTY:
|
||||
mcpwm_ll_timer_sync_out_on_timer_event(hal->dev, timer_id, MCPWM_TIMER_EVENT_EMPTY);
|
||||
break;
|
||||
case MCPWM_TIMER_EVENT_FULL:
|
||||
mcpwm_ll_timer_sync_out_on_timer_event(hal->dev, timer_id, MCPWM_TIMER_EVENT_FULL);
|
||||
break;
|
||||
default:
|
||||
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown timer sync event:%d", config->timer_event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
timer_sync_src->base.group = group;
|
||||
timer_sync_src->base.type = MCPWM_SYNC_TYPE_TIMER;
|
||||
timer_sync_src->base.del = mcpwm_del_timer_sync_src;
|
||||
*ret_sync = &timer_sync_src->base;
|
||||
ESP_LOGD(TAG, "new timer sync_src at %p in timer (%d,%d), event:%c", timer_sync_src, group->group_id, timer_id, "EP?"[config->timer_event]);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (timer_sync_src) {
|
||||
mcpwm_timer_sync_src_destory(timer_sync_src);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_del_timer_sync_src(mcpwm_sync_t *sync_src)
|
||||
{
|
||||
mcpwm_timer_sync_src_t *timer_sync_src = __containerof(sync_src, mcpwm_timer_sync_src_t, base);
|
||||
mcpwm_timer_t *timer = timer_sync_src->timer;
|
||||
int timer_id = timer->timer_id;
|
||||
mcpwm_group_t *group = sync_src->group;
|
||||
|
||||
mcpwm_ll_timer_disable_sync_out(group->hal.dev, timer_id);
|
||||
ESP_LOGD(TAG, "del timer sync_src in timer (%d,%d)", group->group_id, timer_id);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_sync_src_destory(timer_sync_src), TAG, "destory timer sync_src failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_gpio_sync_src_register_to_group(mcpwm_gpio_sync_src_t *gpio_sync_src, int group_id)
|
||||
{
|
||||
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
||||
|
||||
int sync_id = -1;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP; i++) {
|
||||
if (!group->gpio_sync_srcs[i]) {
|
||||
sync_id = i;
|
||||
group->gpio_sync_srcs[i] = gpio_sync_src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
if (sync_id < 0) {
|
||||
mcpwm_release_group_handle(group);
|
||||
group = NULL;
|
||||
} else {
|
||||
gpio_sync_src->base.group = group;
|
||||
gpio_sync_src->sync_id = sync_id;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(sync_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free gpio sync_src in group (%d)", group_id);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_gpio_sync_src_unregister_from_group(mcpwm_gpio_sync_src_t *gpio_sync_src)
|
||||
{
|
||||
mcpwm_group_t *group = gpio_sync_src->base.group;
|
||||
int sync_id = gpio_sync_src->sync_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->gpio_sync_srcs[sync_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// sync_src has a reference on group, release it now
|
||||
mcpwm_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_gpio_sync_src_destory(mcpwm_gpio_sync_src_t *gpio_sync_src)
|
||||
{
|
||||
if (gpio_sync_src->base.group) {
|
||||
mcpwm_gpio_sync_src_unregister_from_group(gpio_sync_src);
|
||||
}
|
||||
free(gpio_sync_src);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_gpio_sync_src(const mcpwm_gpio_sync_src_config_t *config, mcpwm_sync_handle_t *ret_sync)
|
||||
{
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_gpio_sync_src_t *gpio_sync_src = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_sync, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
||||
err, TAG, "invalid group ID:%d", config->group_id);
|
||||
|
||||
gpio_sync_src = heap_caps_calloc(1, sizeof(mcpwm_gpio_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(gpio_sync_src, ESP_ERR_NO_MEM, err, TAG, "no mem for gpio sync_src");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_gpio_sync_src_register_to_group(gpio_sync_src, config->group_id), err, TAG, "register gpio sync_src failed");
|
||||
mcpwm_group_t *group = gpio_sync_src->base.group;
|
||||
int group_id = group->group_id;
|
||||
int sync_id = gpio_sync_src->sync_id;
|
||||
|
||||
// GPIO configuration
|
||||
gpio_config_t gpio_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), // also enable the output path if `io_loop_back` is enabled
|
||||
.pin_bit_mask = (1ULL << config->gpio_num),
|
||||
.pull_down_en = config->flags.pull_down,
|
||||
.pull_up_en = config->flags.pull_up,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config sync GPIO failed");
|
||||
esp_rom_gpio_connect_in_signal(config->gpio_num, mcpwm_periph_signals.groups[group_id].gpio_synchros[sync_id].sync_sig, 0);
|
||||
|
||||
// different ext sync share the same config register, using a group level spin lock
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_invert_gpio_sync_input(group->hal.dev, sync_id, config->flags.active_neg);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// fill in other operator members
|
||||
gpio_sync_src->base.type = MCPWM_SYNC_TYPE_GPIO;
|
||||
gpio_sync_src->gpio_num = config->gpio_num;
|
||||
gpio_sync_src->base.del = mcpwm_del_gpio_sync_src;
|
||||
*ret_sync = &gpio_sync_src->base;
|
||||
ESP_LOGD(TAG, "new gpio sync_src (%d,%d) at %p, GPIO:%d", group_id, sync_id, gpio_sync_src, config->gpio_num);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (gpio_sync_src) {
|
||||
mcpwm_gpio_sync_src_destory(gpio_sync_src);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_del_gpio_sync_src(mcpwm_sync_t *sync_src)
|
||||
{
|
||||
mcpwm_gpio_sync_src_t *gpio_sync_src = __containerof(sync_src, mcpwm_gpio_sync_src_t, base);
|
||||
mcpwm_group_t *group = sync_src->group;
|
||||
|
||||
ESP_LOGD(TAG, "del gpio sync_src (%d,%d)", group->group_id, gpio_sync_src->sync_id);
|
||||
gpio_reset_pin(gpio_sync_src->gpio_num);
|
||||
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_gpio_sync_src_destory(gpio_sync_src), TAG, "destory GPIO sync_src failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_soft_sync_src(const mcpwm_soft_sync_config_t *config, mcpwm_sync_handle_t *ret_sync)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_soft_sync_src_t *soft_sync = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_sync, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
soft_sync = heap_caps_calloc(1, sizeof(mcpwm_soft_sync_src_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(soft_sync, ESP_ERR_NO_MEM, err, TAG, "no mem for soft sync");
|
||||
|
||||
// fill in other sync member
|
||||
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_NONE;
|
||||
soft_sync->base.type = MCPWM_SYNC_TYPE_SOFT;
|
||||
soft_sync->base.del = mcpwm_del_soft_sync_src;
|
||||
*ret_sync = &soft_sync->base;
|
||||
ESP_LOGD(TAG, "new soft sync at %p", soft_sync);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (soft_sync) {
|
||||
free(soft_sync);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_del_soft_sync_src(mcpwm_sync_t *sync_src)
|
||||
{
|
||||
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_src, mcpwm_soft_sync_src_t, base);
|
||||
ESP_LOGD(TAG, "del soft sync %p", soft_sync);
|
||||
free(soft_sync);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_sync_src(mcpwm_sync_handle_t sync_src)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sync_src, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return sync_src->del(sync_src);
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_soft_sync_activate(mcpwm_sync_handle_t sync_src)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(sync_src, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(sync_src->type == MCPWM_SYNC_TYPE_SOFT, ESP_ERR_INVALID_ARG, TAG, "not a valid soft sync");
|
||||
mcpwm_group_t *group = sync_src->group;
|
||||
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_src, mcpwm_soft_sync_src_t, base);
|
||||
|
||||
switch (soft_sync->soft_sync_from) {
|
||||
case MCPWM_SOFT_SYNC_FROM_TIMER: {
|
||||
mcpwm_timer_t *timer = soft_sync->timer;
|
||||
mcpwm_ll_timer_trigger_soft_sync(group->hal.dev, timer->timer_id);
|
||||
break;
|
||||
}
|
||||
case MCPWM_SOFT_SYNC_FROM_CAP: {
|
||||
mcpwm_ll_capture_trigger_sw_sync(group->hal.dev);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "no soft sync generator is assigned");
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
364
components/driver/mcpwm/mcpwm_timer.c
Normal file
364
components/driver/mcpwm/mcpwm_timer.c
Normal file
@@ -0,0 +1,364 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "hal/mcpwm_ll.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "esp_private/mcpwm.h"
|
||||
#include "mcpwm_private.h"
|
||||
|
||||
static const char *TAG = "mcpwm";
|
||||
|
||||
static void mcpwm_timer_default_isr(void *args);
|
||||
|
||||
static esp_err_t mcpwm_timer_register_to_group(mcpwm_timer_t *timer, int group_id)
|
||||
{
|
||||
mcpwm_group_t *group = mcpwm_acquire_group_handle(group_id);
|
||||
ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", group_id);
|
||||
|
||||
int timer_id = -1;
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
if (!group->timers[i]) {
|
||||
timer_id = i;
|
||||
group->timers[i] = timer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
if (timer_id < 0) {
|
||||
mcpwm_release_group_handle(group);
|
||||
group = NULL;
|
||||
} else {
|
||||
timer->group = group;
|
||||
timer->timer_id = timer_id;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(timer_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free timer in group (%d)", group_id);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void mcpwm_timer_unregister_from_group(mcpwm_timer_t *timer)
|
||||
{
|
||||
mcpwm_group_t *group = timer->group;
|
||||
int timer_id = timer->timer_id;
|
||||
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
group->timers[timer_id] = NULL;
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
// timer has a reference on group, release it now
|
||||
mcpwm_release_group_handle(group);
|
||||
}
|
||||
|
||||
static esp_err_t mcpwm_timer_destory(mcpwm_timer_t *timer)
|
||||
{
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_free(timer->intr), TAG, "uninstall interrupt service failed");
|
||||
}
|
||||
if (timer->group) {
|
||||
mcpwm_timer_unregister_from_group(timer);
|
||||
}
|
||||
free(timer);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle_t *ret_timer)
|
||||
{
|
||||
#if CONFIG_MCPWM_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
mcpwm_timer_t *timer = NULL;
|
||||
ESP_GOTO_ON_FALSE(config && ret_timer, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
ESP_GOTO_ON_FALSE(config->group_id < SOC_MCPWM_GROUPS && config->group_id >= 0, ESP_ERR_INVALID_ARG,
|
||||
err, TAG, "invalid group ID:%d", config->group_id);
|
||||
|
||||
timer = heap_caps_calloc(1, sizeof(mcpwm_timer_t), MCPWM_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(timer, ESP_ERR_NO_MEM, err, TAG, "no mem for timer");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_timer_register_to_group(timer, config->group_id), err, TAG, "register timer failed");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int timer_id = timer->timer_id;
|
||||
// select the clock source
|
||||
ESP_GOTO_ON_ERROR(mcpwm_select_periph_clock(group, config->clk_src), err, TAG, "set group clock failed");
|
||||
// reset the timer to a determined state
|
||||
mcpwm_hal_timer_reset(hal, timer_id);
|
||||
// set timer resolution
|
||||
uint32_t prescale = group->resolution_hz / config->resolution_hz;
|
||||
mcpwm_ll_timer_set_clock_prescale(hal->dev, timer_id, prescale);
|
||||
timer->resolution_hz = group->resolution_hz / prescale;
|
||||
if (timer->resolution_hz != config->resolution_hz) {
|
||||
ESP_LOGW(TAG, "adjust timer resolution to %uHz", timer->resolution_hz);
|
||||
}
|
||||
|
||||
// set the peak tickes that the timer can reach to
|
||||
timer->count_mode = config->count_mode;
|
||||
uint32_t peak_ticks = config->period_ticks;
|
||||
if (timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN) {
|
||||
peak_ticks /= 2; // in symmetric mode, peak_ticks = period_ticks / 2
|
||||
}
|
||||
timer->peak_ticks = peak_ticks;
|
||||
mcpwm_ll_timer_set_peak(hal->dev, timer_id, peak_ticks, timer->count_mode == MCPWM_TIMER_COUNT_MODE_UP_DOWN);
|
||||
// set count direction
|
||||
mcpwm_ll_timer_set_count_mode(hal->dev, timer_id, timer->count_mode);
|
||||
// what time is allowed to update the period
|
||||
mcpwm_ll_timer_enable_update_period_on_sync(hal->dev, timer_id, config->flags.update_period_on_sync);
|
||||
mcpwm_ll_timer_enable_update_period_on_tez(hal->dev, timer_id, config->flags.update_period_on_empty);
|
||||
|
||||
// fill in other timer specific members
|
||||
timer->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
timer->fsm = MCPWM_TIMER_FSM_INIT;
|
||||
*ret_timer = timer;
|
||||
ESP_LOGD(TAG, "new timer(%d,%d) at %p, resolution:%uHz, peak:%u, count_mod:%c",
|
||||
group_id, timer_id, timer, timer->resolution_hz, timer->peak_ticks, "SUDB"[timer->count_mode]);
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (timer) {
|
||||
mcpwm_timer_destory(timer);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
// check child resources are in free state
|
||||
ESP_RETURN_ON_FALSE(!timer->sync_src, ESP_ERR_INVALID_STATE, TAG, "timer sync_src still in working");
|
||||
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
int timer_id = timer->timer_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
|
||||
// disable and clear the pending interrupt
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_MASK(timer_id), false);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, MCPWM_LL_EVENT_TIMER_MASK(timer_id));
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
ESP_LOGD(TAG, "del timer (%d,%d)", group->group_id, timer_id);
|
||||
// recycle memory resource
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_destory(timer), TAG, "destory timer failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_register_event_callbacks(mcpwm_timer_handle_t timer, const mcpwm_timer_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
|
||||
#if CONFIG_MCWPM_ISR_IRAM_SAFE
|
||||
if (cbs->on_empty) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_empty), ESP_ERR_INVALID_ARG, TAG, "on_empty callback not in IRAM");
|
||||
}
|
||||
if (cbs->on_full) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_full), ESP_ERR_INVALID_ARG, TAG, "on_full callback not in IRAM");
|
||||
}
|
||||
if (cbs->on_stop) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_stop), ESP_ERR_INVALID_ARG, TAG, "on_stop callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
// lazy install interrupt service
|
||||
if (!timer->intr) {
|
||||
int isr_flags = MCPWM_INTR_ALLOC_FLAG;
|
||||
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(mcpwm_periph_signals.groups[group_id].irq_id, isr_flags,
|
||||
(uint32_t)mcpwm_ll_intr_get_status_reg(hal->dev), MCPWM_LL_EVENT_TIMER_MASK(timer_id),
|
||||
mcpwm_timer_default_isr, timer, &timer->intr), TAG, "install interrupt service for timer failed");
|
||||
}
|
||||
|
||||
// enable/disable interrupt events
|
||||
portENTER_CRITICAL(&group->spinlock);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_FULL(timer_id), cbs->on_full != NULL);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_EMPTY(timer_id), cbs->on_empty != NULL);
|
||||
mcpwm_ll_intr_enable(hal->dev, MCPWM_LL_EVENT_TIMER_STOP(timer_id), cbs->on_stop != NULL);
|
||||
portEXIT_CRITICAL(&group->spinlock);
|
||||
|
||||
timer->on_stop = cbs->on_stop;
|
||||
timer->on_full = cbs->on_full;
|
||||
timer->on_empty = cbs->on_empty;
|
||||
timer->user_data = user_data;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_get_phase(mcpwm_timer_handle_t timer, uint32_t *count_value, mcpwm_timer_direction_t *direction)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && count_value && direction, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
int timer_id = timer->timer_id;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
|
||||
portENTER_CRITICAL(&timer->spinlock);
|
||||
*count_value = mcpwm_ll_timer_get_count_value(hal->dev, timer_id);
|
||||
*direction = mcpwm_ll_timer_get_count_direction(hal->dev, timer_id);
|
||||
portEXIT_CRITICAL(&timer->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_enable(mcpwm_timer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "timer not in init state");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_enable(timer->intr), TAG, "enable interrupt failed");
|
||||
}
|
||||
if (group->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(group->pm_lock), TAG, "acquire pm lock failed");
|
||||
}
|
||||
timer->fsm = MCPWM_TIMER_FSM_ENABLE;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_disable(mcpwm_timer_handle_t timer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
if (timer->intr) {
|
||||
ESP_RETURN_ON_ERROR(esp_intr_disable(timer->intr), TAG, "disable interrupt failed");
|
||||
}
|
||||
if (group->pm_lock) {
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_release(group->pm_lock), TAG, "acquire pm lock failed");
|
||||
}
|
||||
timer->fsm = MCPWM_TIMER_FSM_INIT;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_start_stop(mcpwm_timer_handle_t timer, mcpwm_timer_start_stop_cmd_t command)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(timer->fsm == MCPWM_TIMER_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "timer not in enable state");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
|
||||
portENTER_CRITICAL_SAFE(&timer->spinlock);
|
||||
mcpwm_ll_timer_set_start_stop_command(group->hal.dev, timer->timer_id, command);
|
||||
portEXIT_CRITICAL_SAFE(&timer->spinlock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t mcpwm_timer_set_phase_on_sync(mcpwm_timer_handle_t timer, const mcpwm_timer_sync_phase_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(timer && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
mcpwm_group_t *group = timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int group_id = group->group_id;
|
||||
int timer_id = timer->timer_id;
|
||||
mcpwm_sync_handle_t sync_source = config->sync_src;
|
||||
|
||||
// enable sync feature and set sync phase
|
||||
if (sync_source) {
|
||||
ESP_RETURN_ON_FALSE(config->count_value < MCPWM_LL_MAX_COUNT_VALUE, ESP_ERR_INVALID_ARG, TAG, "invalid sync count value");
|
||||
switch (sync_source->type) {
|
||||
case MCPWM_SYNC_TYPE_TIMER: {
|
||||
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "timer and sync source are not in the same group");
|
||||
mcpwm_timer_sync_src_t *timer_sync_src = __containerof(sync_source, mcpwm_timer_sync_src_t, base);
|
||||
mcpwm_ll_timer_set_timer_sync_input(hal->dev, timer_id, timer_sync_src->timer->timer_id);
|
||||
ESP_LOGD(TAG, "enable sync to timer (%d,%d) for timer (%d,%d)",
|
||||
group_id, timer_sync_src->timer->timer_id, group_id, timer_id);
|
||||
break;
|
||||
}
|
||||
case MCPWM_SYNC_TYPE_GPIO: {
|
||||
ESP_RETURN_ON_FALSE(group == sync_source->group, ESP_ERR_INVALID_ARG, TAG, "timer and sync source are not in the same group");
|
||||
mcpwm_gpio_sync_src_t *gpio_sync_src = __containerof(sync_source, mcpwm_gpio_sync_src_t, base);
|
||||
mcpwm_ll_timer_set_gpio_sync_input(hal->dev, timer_id, gpio_sync_src->sync_id);
|
||||
ESP_LOGD(TAG, "enable sync to gpio (%d) for timer (%d,%d)",
|
||||
gpio_sync_src->gpio_num, group_id, timer_id);
|
||||
break;
|
||||
}
|
||||
case MCPWM_SYNC_TYPE_SOFT: {
|
||||
mcpwm_soft_sync_src_t *soft_sync = __containerof(sync_source, mcpwm_soft_sync_src_t, base);
|
||||
if (soft_sync->soft_sync_from == MCPWM_SOFT_SYNC_FROM_TIMER && soft_sync->timer != timer) {
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "soft sync already used by another timer");
|
||||
}
|
||||
soft_sync->soft_sync_from = MCPWM_SOFT_SYNC_FROM_TIMER;
|
||||
soft_sync->timer = timer;
|
||||
soft_sync->base.group = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mcpwm_ll_timer_set_sync_phase_direction(hal->dev, timer_id, config->direction);
|
||||
mcpwm_ll_timer_set_sync_phase_value(hal->dev, timer_id, config->count_value);
|
||||
mcpwm_ll_timer_enable_sync_input(hal->dev, timer_id, true);
|
||||
} else { // disable sync feature
|
||||
mcpwm_ll_timer_enable_sync_input(hal->dev, timer_id, false);
|
||||
ESP_LOGD(TAG, "disable sync for timer (%d,%d)", group_id, timer_id);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR mcpwm_timer_default_isr(void *args)
|
||||
{
|
||||
mcpwm_timer_t *timer = (mcpwm_timer_t *)args;
|
||||
mcpwm_group_t *group = timer->group;
|
||||
mcpwm_hal_context_t *hal = &group->hal;
|
||||
int timer_id = timer->timer_id;
|
||||
bool need_yield = false;
|
||||
uint32_t status = mcpwm_ll_intr_get_status(hal->dev);
|
||||
mcpwm_ll_intr_clear_status(hal->dev, status & MCPWM_LL_EVENT_TIMER_MASK(timer_id));
|
||||
|
||||
mcpwm_timer_event_data_t edata = {
|
||||
.direction = mcpwm_ll_timer_get_count_direction(hal->dev, timer_id),
|
||||
.count_value = mcpwm_ll_timer_get_count_value(hal->dev, timer_id),
|
||||
};
|
||||
|
||||
if (status & MCPWM_LL_EVENT_TIMER_STOP(timer_id)) {
|
||||
mcpwm_timer_event_cb_t cb = timer->on_stop;
|
||||
if (cb) {
|
||||
if (cb(timer, &edata, timer->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status & MCPWM_LL_EVENT_TIMER_FULL(timer_id)) {
|
||||
mcpwm_timer_event_cb_t cb = timer->on_full;
|
||||
if (cb) {
|
||||
if (cb(timer, &edata, timer->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (status & MCPWM_LL_EVENT_TIMER_EMPTY(timer_id)) {
|
||||
mcpwm_timer_event_cb_t cb = timer->on_empty;
|
||||
if (cb) {
|
||||
if (cb(timer, &edata, timer->user_data)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
# 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(legacy_mcpwm_driver_test)
|
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
@@ -0,0 +1,7 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_legacy_mcpwm.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)
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#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)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
@@ -12,7 +12,6 @@
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#if SOC_MCPWM_SUPPORTED
|
||||
#include "soc/mcpwm_periph.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "driver/mcpwm.h"
|
||||
@@ -568,5 +567,3 @@ TEST_CASE("MCPWM capture test", "[mcpwm]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SOC_MCPWM_SUPPORTED
|
@@ -0,0 +1,21 @@
|
||||
# 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',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_legacy_mcpwm(dut: Dut) -> None:
|
||||
dut.expect('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output()
|
@@ -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
|
@@ -0,0 +1,3 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN=y
|
18
components/driver/test_apps/mcpwm/CMakeLists.txt
Normal file
18
components/driver/test_apps/mcpwm/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mcpwm_test)
|
||||
|
||||
if(CONFIG_COMPILER_DUMP_RTL_FILES)
|
||||
add_custom_target(check_test_app_sections ALL
|
||||
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
|
||||
--rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/
|
||||
--elf-file ${CMAKE_BINARY_DIR}/mcpwm_test.elf
|
||||
find-refs
|
||||
--from-sections=.iram0.text
|
||||
--to-sections=.flash.text,.flash.rodata
|
||||
--exit-code
|
||||
DEPENDS ${elf}
|
||||
)
|
||||
endif()
|
2
components/driver/test_apps/mcpwm/README.md
Normal file
2
components/driver/test_apps/mcpwm/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
14
components/driver/test_apps/mcpwm/main/CMakeLists.txt
Normal file
14
components/driver/test_apps/mcpwm/main/CMakeLists.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_mcpwm_cap.c"
|
||||
"test_mcpwm_cmpr.c"
|
||||
"test_mcpwm_fault.c"
|
||||
"test_mcpwm_gen.c"
|
||||
"test_mcpwm_oper.c"
|
||||
"test_mcpwm_sync.c"
|
||||
"test_mcpwm_timer.c"
|
||||
"test_mcpwm_utils.c")
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(SRCS ${srcs}
|
||||
WHOLE_ARCHIVE)
|
51
components/driver/test_apps/mcpwm/main/test_app_main.c
Normal file
51
components/driver/test_apps/mcpwm/main/test_app_main.c
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
// Some resources are lazy allocated in GPTimer driver, the threshold is left for that case
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-300)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
// __ __ ____ ______ ____ __ _____ _
|
||||
// | \/ |/ ___| _ \ \ / / \/ | |_ _|__ ___| |_
|
||||
// | |\/| | | | |_) \ \ /\ / /| |\/| | | |/ _ \/ __| __|
|
||||
// | | | | |___| __/ \ V V / | | | | | | __/\__ \ |_
|
||||
// |_| |_|\____|_| \_/\_/ |_| |_| |_|\___||___/\__|
|
||||
printf(" __ __ ____ ______ ____ __ _____ _\r\n");
|
||||
printf("| \\/ |/ ___| _ \\ \\ / / \\/ | |_ _|__ ___| |_\r\n");
|
||||
printf("| |\\/| | | | |_) \\ \\ /\\ / /| |\\/| | | |/ _ \\/ __| __|\r\n");
|
||||
printf("| | | | |___| __/ \\ V V / | | | | | | __/\\__ \\ |_\r\n");
|
||||
printf("|_| |_|\\____|_| \\_/\\_/ |_| |_| |_|\\___||___/\\__|\r\n");
|
||||
unity_run_menu();
|
||||
}
|
238
components/driver/test_apps/mcpwm/main/test_mcpwm_cap.c
Normal file
238
components/driver/test_apps/mcpwm/main/test_mcpwm_cap.c
Normal file
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "driver/mcpwm_cap.h"
|
||||
#include "driver/mcpwm_sync.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "test_mcpwm_utils.h"
|
||||
|
||||
TEST_CASE("mcpwm_capture_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
printf("install mcpwm capture timers\r\n");
|
||||
mcpwm_capture_timer_config_t cap_timer_config = {
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
};
|
||||
int total_cap_timers = SOC_MCPWM_GROUPS * SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP;
|
||||
mcpwm_cap_timer_handle_t cap_timers[total_cap_timers];
|
||||
int k = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
cap_timer_config.group_id = i;
|
||||
for (int j = 0; j < SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[k++]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_timer(&cap_timer_config, &cap_timers[0]));
|
||||
}
|
||||
|
||||
printf("install mcpwm capture channels\r\n");
|
||||
mcpwm_capture_channel_config_t cap_chan_config = {
|
||||
.gpio_num = 0,
|
||||
.prescale = 2,
|
||||
.flags.pos_edge = true,
|
||||
.flags.pull_up = true,
|
||||
};
|
||||
mcpwm_cap_channel_handle_t cap_channels[total_cap_timers][SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER];
|
||||
for (int i = 0; i < total_cap_timers; i++) {
|
||||
for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][j]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_capture_channel(cap_timers[i], &cap_chan_config, &cap_channels[i][0]));
|
||||
}
|
||||
|
||||
printf("uninstall mcpwm capture channels and timers\r\n");
|
||||
for (int i = 0; i < total_cap_timers; i++) {
|
||||
for (int j = 0; j < SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER; j++) {
|
||||
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channels[i][j]));
|
||||
}
|
||||
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_MCPWM_CALLBACK_ATTR
|
||||
static bool test_capture_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_data)
|
||||
{
|
||||
uint32_t *cap_value = (uint32_t *)user_data;
|
||||
if (edata->cap_edge == MCPWM_CAP_EDGE_NEG) {
|
||||
cap_value[1] = edata->cap_value;
|
||||
} else {
|
||||
cap_value[0] = edata->cap_value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_capture_ext_gpio", "[mcpwm]")
|
||||
{
|
||||
printf("install mcpwm capture timer\r\n");
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
mcpwm_capture_timer_config_t cap_timer_config = {
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_APB,
|
||||
.group_id = 0,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
|
||||
|
||||
const int cap_gpio = 0;
|
||||
// put the GPIO into a preset state
|
||||
gpio_set_level(cap_gpio, 0);
|
||||
|
||||
printf("install mcpwm capture channel\r\n");
|
||||
mcpwm_cap_channel_handle_t pps_channel;
|
||||
mcpwm_capture_channel_config_t cap_chan_config = {
|
||||
.gpio_num = cap_gpio,
|
||||
.prescale = 1,
|
||||
.flags.pos_edge = true,
|
||||
.flags.neg_edge = true,
|
||||
.flags.io_loop_back = true, // so we can use GPIO functions to simulate the external capture signal
|
||||
.flags.pull_up = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &pps_channel));
|
||||
|
||||
printf("install callback for capture channel\r\n");
|
||||
mcpwm_capture_event_callbacks_t cbs = {
|
||||
.on_cap = test_capture_callback,
|
||||
};
|
||||
uint32_t cap_value[2] = {0};
|
||||
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(pps_channel, &cbs, cap_value));
|
||||
|
||||
printf("enable and start capture timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
|
||||
TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));
|
||||
|
||||
printf("simulate GPIO capture signal\r\n");
|
||||
gpio_set_level(cap_gpio, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
gpio_set_level(cap_gpio, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
printf("capture value: Pos=%u, Neg=%u\r\n", cap_value[0], cap_value[1]);
|
||||
// Capture timer is clocked from APB by default
|
||||
uint32_t clk_src_res = esp_clk_apb_freq();
|
||||
TEST_ASSERT_UINT_WITHIN(100000, clk_src_res / 10, cap_value[1] - cap_value[0]);
|
||||
|
||||
printf("uninstall capture channel and timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_del_capture_channel(pps_channel));
|
||||
TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
|
||||
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint32_t cap_data[2];
|
||||
int cap_data_index;
|
||||
} test_soft_catch_user_data_t;
|
||||
|
||||
TEST_MCPWM_CALLBACK_ATTR
|
||||
static bool soft_cap_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
|
||||
{
|
||||
test_soft_catch_user_data_t *cbdata = (test_soft_catch_user_data_t *)user_data;
|
||||
cbdata->cap_data[cbdata->cap_data_index++] = data->cap_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_capture_software_catch", "[mcpwm]")
|
||||
{
|
||||
printf("install mcpwm capture timer\r\n");
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
mcpwm_capture_timer_config_t cap_timer_config = {
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
|
||||
|
||||
printf("install mcpwm capture channel\r\n");
|
||||
mcpwm_cap_channel_handle_t cap_channel = NULL;
|
||||
mcpwm_capture_channel_config_t cap_chan_config = {
|
||||
.gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
|
||||
.prescale = 2,
|
||||
};
|
||||
test_soft_catch_user_data_t test_callback_data = {};
|
||||
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));
|
||||
|
||||
printf("register event callback for capture channel\r\n");
|
||||
mcpwm_capture_event_callbacks_t cbs = {
|
||||
.on_cap = soft_cap_callback,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &test_callback_data));
|
||||
|
||||
printf("enable and start capture timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_capture_timer_enable(cap_timer));
|
||||
TEST_ESP_OK(mcpwm_capture_timer_start(cap_timer));
|
||||
|
||||
printf("trigger software catch\r\n");
|
||||
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
// check user data
|
||||
TEST_ASSERT_EQUAL(2, test_callback_data.cap_data_index);
|
||||
uint32_t delta = test_callback_data.cap_data[1] - test_callback_data.cap_data[0];
|
||||
esp_rom_printf("duration=%u ticks\r\n", delta);
|
||||
// Capture timer is clocked from APB by default
|
||||
uint32_t clk_src_res = esp_clk_apb_freq();
|
||||
TEST_ASSERT_UINT_WITHIN(80000, clk_src_res / 100, delta);
|
||||
|
||||
printf("uninstall capture channel and timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_capture_timer_disable(cap_timer));
|
||||
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
|
||||
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
|
||||
}
|
||||
|
||||
TEST_MCPWM_CALLBACK_ATTR
|
||||
static bool test_capture_after_sync_callback(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *data, void *user_data)
|
||||
{
|
||||
uint32_t *cap_data = (uint32_t *)user_data;
|
||||
*cap_data = data->cap_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_capture_timer_sync_phase_lock", "[mcpwm]")
|
||||
{
|
||||
mcpwm_capture_timer_config_t cap_timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
};
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
|
||||
|
||||
mcpwm_sync_handle_t soft_sync = NULL;
|
||||
mcpwm_soft_sync_config_t soft_sync_config = {};
|
||||
TEST_ESP_OK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync));
|
||||
|
||||
mcpwm_capture_timer_sync_phase_config_t sync_config = {
|
||||
.count_value = 1000,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
.sync_src = soft_sync,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_capture_timer_set_phase_on_sync(cap_timer, &sync_config));
|
||||
mcpwm_cap_channel_handle_t cap_channel = NULL;
|
||||
mcpwm_capture_channel_config_t cap_chan_config = {
|
||||
.gpio_num = -1, // don't need any GPIO, we use software to trigger a catch
|
||||
.prescale = 1,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_capture_channel(cap_timer, &cap_chan_config, &cap_channel));
|
||||
|
||||
mcpwm_capture_event_callbacks_t cbs = {
|
||||
.on_cap = test_capture_after_sync_callback,
|
||||
};
|
||||
uint32_t cap_data;
|
||||
TEST_ESP_OK(mcpwm_capture_channel_register_event_callbacks(cap_channel, &cbs, &cap_data));
|
||||
|
||||
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
printf("capture data before sync: %u\r\n", cap_data);
|
||||
|
||||
TEST_ESP_OK(mcpwm_soft_sync_activate(soft_sync));
|
||||
TEST_ESP_OK(mcpwm_capture_channel_trigger_soft_catch(cap_channel));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
printf("capture data after sync: %u\r\n", cap_data);
|
||||
TEST_ASSERT_EQUAL(1000, cap_data);
|
||||
TEST_ESP_OK(mcpwm_del_capture_channel(cap_channel));
|
||||
TEST_ESP_OK(mcpwm_del_capture_timer(cap_timer));
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(soft_sync));
|
||||
}
|
113
components/driver/test_apps/mcpwm/main/test_mcpwm_cmpr.c
Normal file
113
components/driver/test_apps/mcpwm/main/test_mcpwm_cmpr.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "driver/mcpwm_cmpr.h"
|
||||
|
||||
TEST_CASE("mcpwm_comparator_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
mcpwm_timer_handle_t timer;
|
||||
mcpwm_oper_handle_t operator;
|
||||
mcpwm_cmpr_handle_t comparators[SOC_MCPWM_COMPARATORS_PER_OPERATOR];
|
||||
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.period_ticks = 10 * 1000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
printf("install timer and operator");
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
|
||||
printf("install comparator\r\n");
|
||||
mcpwm_comparator_config_t comparator_config = {};
|
||||
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparators[i]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_comparator(operator, &comparator_config, &comparators[0]));
|
||||
|
||||
printf("connect MCPWM timer and operators\r\n");
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
printf("uninstall timer, operator and comparators\r\n");
|
||||
// can't delete operator if the comparators are still in working
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_operator(operator));
|
||||
for (int i = 0; i < SOC_MCPWM_COMPARATORS_PER_OPERATOR; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparators[i]));
|
||||
}
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
static bool test_compare_on_reach(mcpwm_cmpr_handle_t cmpr, const mcpwm_compare_event_data_t *ev_data, void *user_data)
|
||||
{
|
||||
uint32_t *counts = (uint32_t *)user_data;
|
||||
(*counts)++;
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_comparator_event_callback", "[mcpwm]")
|
||||
{
|
||||
mcpwm_timer_handle_t timer;
|
||||
mcpwm_oper_handle_t operator;
|
||||
mcpwm_cmpr_handle_t comparator;
|
||||
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.period_ticks = 10 * 1000, // 10ms
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_comparator_config_t comparator_config = {};
|
||||
printf("install timer, operator and comparator");
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator));
|
||||
|
||||
// set compare value before connecting timer and operator will fail
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_comparator_set_compare_value(comparator, 5000));
|
||||
printf("connect MCPWM timer and operators\r\n");
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
// compare ticks can't exceed the timer's period ticks
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mcpwm_comparator_set_compare_value(comparator, 20 * 1000));
|
||||
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator, 5 * 1000));
|
||||
|
||||
printf("register compare event callback\r\n");
|
||||
uint32_t compare_counts = 0;
|
||||
mcpwm_comparator_event_callbacks_t cbs = {
|
||||
.on_reach = test_compare_on_reach,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_comparator_register_event_callbacks(comparator, &cbs, &compare_counts));
|
||||
|
||||
printf("start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
printf("compare_counts=%u\r\n", compare_counts);
|
||||
// the timer period is 10ms, the expected compare_counts = 1s/10ms = 100
|
||||
TEST_ASSERT_INT_WITHIN(1, 100, compare_counts);
|
||||
|
||||
printf("uninstall timer, operator and comparator\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparator));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
93
components/driver/test_apps/mcpwm/main/test_mcpwm_fault.c
Normal file
93
components/driver/test_apps/mcpwm/main/test_mcpwm_fault.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "driver/mcpwm_fault.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
TEST_CASE("mcpwm_fault_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
printf("install and uninstall gpio faults\r\n");
|
||||
mcpwm_gpio_fault_config_t gpio_fault_config = {
|
||||
.gpio_num = 0,
|
||||
};
|
||||
int total_gpio_faults = SOC_MCPWM_GPIO_FAULTS_PER_GROUP * SOC_MCPWM_GROUPS;
|
||||
mcpwm_fault_handle_t gpio_faults[total_gpio_faults];
|
||||
int fault_itor = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
gpio_fault_config.group_id = i;
|
||||
for (int j = 0; j < SOC_MCPWM_GPIO_FAULTS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_faults[fault_itor++]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_faults[0]));
|
||||
}
|
||||
for (int i = 0; i < total_gpio_faults; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_fault(gpio_faults[i]));
|
||||
}
|
||||
|
||||
printf("install and uninstall software fault\r\n");
|
||||
mcpwm_soft_fault_config_t soft_fault_config = {};
|
||||
mcpwm_fault_handle_t soft_fault = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_soft_fault(&soft_fault_config, &soft_fault));
|
||||
TEST_ESP_OK(mcpwm_del_fault(soft_fault));
|
||||
}
|
||||
|
||||
static bool test_fault_enter_callback(mcpwm_fault_handle_t detector, const mcpwm_fault_event_data_t *status, void *user_data)
|
||||
{
|
||||
TaskHandle_t task_handle = (TaskHandle_t)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
esp_rom_printf("fault found\r\n");
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static bool test_fault_exit_callback(mcpwm_fault_handle_t detector, const mcpwm_fault_event_data_t *status, void *user_data)
|
||||
{
|
||||
TaskHandle_t task_handle = (TaskHandle_t)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
esp_rom_printf("fault relieved\r\n");
|
||||
vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_gpio_fault_event_callbacks", "[mcpwm]")
|
||||
{
|
||||
printf("create gpio fault\r\n");
|
||||
const int fault_gpio = 0;
|
||||
mcpwm_fault_handle_t fault = NULL;
|
||||
mcpwm_gpio_fault_config_t gpio_fault_config = {
|
||||
.group_id = 0,
|
||||
.gpio_num = fault_gpio,
|
||||
.flags.active_level = true, // active on high level
|
||||
.flags.pull_down = true,
|
||||
.flags.io_loop_back = true, // for debug, so that we can use gpio_set_level to mimic a fault source
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &fault));
|
||||
|
||||
// put fault GPIO into a safe state
|
||||
gpio_set_level(fault_gpio, 0);
|
||||
|
||||
printf("register callback for the gpio fault\r\n");
|
||||
mcpwm_fault_event_callbacks_t cbs = {
|
||||
.on_fault_enter = test_fault_enter_callback,
|
||||
.on_fault_exit = test_fault_exit_callback,
|
||||
};
|
||||
TaskHandle_t task_to_notify = xTaskGetCurrentTaskHandle();
|
||||
TEST_ESP_OK(mcpwm_fault_register_event_callbacks(fault, &cbs, task_to_notify));
|
||||
TEST_ASSERT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
|
||||
|
||||
printf("trigget a fault event\r\n");
|
||||
gpio_set_level(fault_gpio, 1);
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)));
|
||||
|
||||
printf("remove the fault source\r\n");
|
||||
gpio_set_level(fault_gpio, 0);
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(10)));
|
||||
|
||||
TEST_ESP_OK(mcpwm_del_fault(fault));
|
||||
}
|
646
components/driver/test_apps/mcpwm/main/test_mcpwm_gen.c
Normal file
646
components/driver/test_apps/mcpwm/main/test_mcpwm_gen.c
Normal file
@@ -0,0 +1,646 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "driver/mcpwm_cmpr.h"
|
||||
#include "driver/mcpwm_gen.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
TEST_CASE("mcpwm_generator_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
mcpwm_operator_config_t oper_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t oper = NULL;
|
||||
printf("create a MCPWM operator\r\n");
|
||||
TEST_ESP_OK(mcpwm_new_operator(&oper_config, &oper));
|
||||
|
||||
printf("create MCPWM generators from that operator\r\n");
|
||||
mcpwm_gen_handle_t gens[SOC_MCPWM_GENERATORS_PER_OPERATOR];
|
||||
mcpwm_generator_config_t gen_config = {
|
||||
.gen_gpio_num = 0,
|
||||
};
|
||||
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
|
||||
TEST_ESP_OK(mcpwm_new_generator(oper, &gen_config, &gens[i]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_generator(oper, &gen_config, &gens[0]));
|
||||
|
||||
printf("delete generators and operator\r\n");
|
||||
// can't delete operator if the generator is till in working
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_operator(oper));
|
||||
for (int i = 0; i < SOC_MCPWM_GENERATORS_PER_OPERATOR; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_generator(gens[i]));
|
||||
}
|
||||
TEST_ESP_OK(mcpwm_del_operator(oper));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_generator_force_level_hold_on", "[mcpwm]")
|
||||
{
|
||||
// The operator can even work without the timer
|
||||
printf("create operator and generator\r\n");
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
const int gen_gpio = 0;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = gen_gpio,
|
||||
.flags.io_loop_back = true, // loop back for test
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
|
||||
|
||||
printf("add force level to the generator, hold on");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, true));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, true));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
|
||||
}
|
||||
|
||||
printf("remove the force level\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, -1, true));
|
||||
|
||||
printf("delete generator and operator\r\n");
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_generator_force_level_recovery", "[mcpwm]")
|
||||
{
|
||||
printf("create mcpwm timer\r\n");
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
.resolution_hz = 1000000,
|
||||
.period_ticks = 50000,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
printf("create operator\r\n");
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
.flags.update_gen_action_on_tez = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
printf("create generator\r\n");
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
const int gen_gpio = 0;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = gen_gpio,
|
||||
.flags.io_loop_back = true, // loop back for test
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
|
||||
|
||||
printf("add force level to the generator, and recovery by events");
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, false));
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, false));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
|
||||
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 0, false));
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
// generator should output high level on tez event, the previous force level should disappear
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
TEST_ESP_OK(mcpwm_generator_set_force_level(generator, 1, false));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
// generator should output low level on tez event, the previous force level should disappear
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_gpio));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
printf("delete generator, operator and timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_generator_action_on_timer_event", "[mcpwm]")
|
||||
{
|
||||
const int generator_gpio = 0;
|
||||
printf("create timer and operator\r\n");
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
.period_ticks = 1000,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
mcpwm_operator_config_t oper_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t oper = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&oper_config, &oper));
|
||||
|
||||
printf("connect timer and operator\r\n");
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(oper, timer));
|
||||
|
||||
printf("create generator\r\n");
|
||||
mcpwm_generator_config_t gen_config = {
|
||||
.gen_gpio_num = generator_gpio,
|
||||
.flags.io_loop_back = 1, // so that we can read the GPIO value by GPIO driver
|
||||
};
|
||||
mcpwm_gen_handle_t gen = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_generator(oper, &gen_config, &gen));
|
||||
|
||||
printf("set generator to output high on timer full\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_KEEP),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
printf("start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
printf("stop timer on full\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_FULL));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(generator_gpio));
|
||||
|
||||
printf("set generator to output low on timer full\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_KEEP),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
printf("start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
printf("stop timer on full\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_FULL));
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(generator_gpio));
|
||||
|
||||
printf("delete timer, operator, generator\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_generator(gen));
|
||||
TEST_ESP_OK(mcpwm_del_operator(oper));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
typedef void (*set_gen_actions_cb_t)(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb);
|
||||
|
||||
static void mcpwm_gen_action_test_template(uint32_t timer_resolution, uint32_t period, mcpwm_timer_count_mode_t count_mode,
|
||||
uint32_t cmpa, uint32_t cmpb, int gpioa, int gpiob, set_gen_actions_cb_t set_generator_actions)
|
||||
{
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.count_mode = count_mode,
|
||||
.resolution_hz = timer_resolution,
|
||||
.period_ticks = period,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
mcpwm_cmpr_handle_t comparator_a = NULL;
|
||||
mcpwm_cmpr_handle_t comparator_b = NULL;
|
||||
mcpwm_comparator_config_t comparator_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_a));
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_b));
|
||||
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_a, cmpa));
|
||||
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_b, cmpb));
|
||||
|
||||
mcpwm_gen_handle_t generator_a = NULL;
|
||||
mcpwm_gen_handle_t generator_b = NULL;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = gpioa,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_a));
|
||||
generator_config.gen_gpio_num = gpiob;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_b));
|
||||
|
||||
set_generator_actions(generator_a, generator_b, comparator_a, comparator_b);
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator_a));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator_b));
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparator_a));
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparator_b));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
static void single_edge_active_high(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void single_edge_active_low(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void pulse_placement(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void dual_edge_active_low_asym(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void dual_edge_active_low_sym(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void dual_edge_complementary(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_generator_action_on_compare_event", "[mcpwm]")
|
||||
{
|
||||
printf("[Asymmetric, SingleEdge, ActiveHigh]\r\n");
|
||||
// PWMA: high = [1->350], low = [351->499,0]
|
||||
// PWMB: high = [1->200], low = [201->499,0]
|
||||
mcpwm_gen_action_test_template(1000000, 500, MCPWM_TIMER_COUNT_MODE_UP, 350, 200, 0, 2, single_edge_active_high);
|
||||
|
||||
printf("[Asymmetric, SingleEdge, ActiveLow]\r\n");
|
||||
// PWMA: low = [0->300], high = [301->499]
|
||||
// PWMB: low = [0->150], high = [151->499]
|
||||
mcpwm_gen_action_test_template(1000000, 500, MCPWM_TIMER_COUNT_MODE_UP, 300, 150, 0, 2, single_edge_active_low);
|
||||
|
||||
printf("[Asymmetric, PulsePlacement]\r\n");
|
||||
// PWMA: low = [0->200], high = [201->400], low = [401->599]
|
||||
// PWMB: high = [0->599], low = [0->599]
|
||||
mcpwm_gen_action_test_template(1000000, 600, MCPWM_TIMER_COUNT_MODE_UP, 200, 400, 0, 2, pulse_placement);
|
||||
|
||||
printf("[Asymmetric, DualEdge, ActiveLow]\r\n");
|
||||
// PWMA: low = [0->250], high = [251->599, 600->450], low = [451->1]
|
||||
// PWMB: low = [0->599], low = [600->1]
|
||||
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 250, 450, 0, 2, dual_edge_active_low_asym);
|
||||
|
||||
printf("[Symmetric, DualEdge, ActiveLow]\r\n");
|
||||
// PWMA: low = [0->400], high = [401->599, 600->400], low = [399->1]
|
||||
// PWMB: low = [0->500], high = [501->599, 600->500], low = [499->1]
|
||||
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 400, 500, 0, 2, dual_edge_active_low_sym);
|
||||
|
||||
printf("[Symmetric, DualEdge, Complementary]\r\n");
|
||||
// PWMA: low = [0->350], high = [351->599, 600->350], low = [349->1]
|
||||
// PWMB: low = [0->400], high = [401->599, 600->400], low = [399->1]
|
||||
mcpwm_gen_action_test_template(1000000, 1200, MCPWM_TIMER_COUNT_MODE_UP_DOWN, 350, 400, 0, 2, dual_edge_complementary);
|
||||
}
|
||||
|
||||
typedef void (*set_dead_time_cb_t)(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb);
|
||||
|
||||
static void mcpwm_deadtime_test_template(uint32_t timer_resolution, uint32_t period, uint32_t cmpa, uint32_t cmpb, int gpioa, int gpiob,
|
||||
set_gen_actions_cb_t set_generator_actions, set_dead_time_cb_t set_dead_time)
|
||||
{
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = timer_resolution,
|
||||
.period_ticks = period,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
mcpwm_cmpr_handle_t comparator_a = NULL;
|
||||
mcpwm_cmpr_handle_t comparator_b = NULL;
|
||||
mcpwm_comparator_config_t comparator_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_a));
|
||||
TEST_ESP_OK(mcpwm_new_comparator(operator, &comparator_config, &comparator_b));
|
||||
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_a, cmpa));
|
||||
TEST_ESP_OK(mcpwm_comparator_set_compare_value(comparator_b, cmpb));
|
||||
|
||||
mcpwm_gen_handle_t generator_a = NULL;
|
||||
mcpwm_gen_handle_t generator_b = NULL;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = gpioa,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_a));
|
||||
generator_config.gen_gpio_num = gpiob;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator_b));
|
||||
|
||||
set_generator_actions(generator_a, generator_b, comparator_a, comparator_b);
|
||||
set_dead_time(generator_a, generator_b);
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator_a));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator_b));
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparator_a));
|
||||
TEST_ESP_OK(mcpwm_del_comparator(comparator_b));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
static void ahc_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void ahc_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 50,
|
||||
.negedge_delay_ticks = 0
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
dead_time_config.posedge_delay_ticks = 0;
|
||||
dead_time_config.negedge_delay_ticks = 100;
|
||||
dead_time_config.flags.invert_output = true;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
static void alc_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void alc_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 50,
|
||||
.negedge_delay_ticks = 0,
|
||||
.flags.invert_output = true
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
dead_time_config.posedge_delay_ticks = 0;
|
||||
dead_time_config.negedge_delay_ticks = 100;
|
||||
dead_time_config.flags.invert_output = false;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
static void ah_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void ah_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 50,
|
||||
.negedge_delay_ticks = 0,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
dead_time_config.posedge_delay_ticks = 0;
|
||||
dead_time_config.negedge_delay_ticks = 100;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
static void al_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void al_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 50,
|
||||
.negedge_delay_ticks = 0,
|
||||
.flags.invert_output = true
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
dead_time_config.posedge_delay_ticks = 0;
|
||||
dead_time_config.negedge_delay_ticks = 100;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
static void reda_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void reda_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 50,
|
||||
.negedge_delay_ticks = 0,
|
||||
};
|
||||
// apply deadtime to generator_a
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
// bypass deadtime module for generator_b
|
||||
dead_time_config.posedge_delay_ticks = 0;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
static void fedb_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void fedb_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 0,
|
||||
.negedge_delay_ticks = 0,
|
||||
};
|
||||
// generator_a bypass the deadtime module (no delay)
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
// apply dead time to generator_b
|
||||
dead_time_config.negedge_delay_ticks = 50;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
|
||||
|
||||
}
|
||||
|
||||
static void redfedb_only_set_generator_actions(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
|
||||
{
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_compare_event(genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
static void redfedb_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
|
||||
{
|
||||
mcpwm_dead_time_config_t dead_time_config = {
|
||||
.posedge_delay_ticks = 0,
|
||||
.negedge_delay_ticks = 0,
|
||||
};
|
||||
// generator_a bypass the deadtime module (no delay)
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
|
||||
// apply dead time on both edge for generator_b
|
||||
dead_time_config.negedge_delay_ticks = 50;
|
||||
dead_time_config.posedge_delay_ticks = 50;
|
||||
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_generator_deadtime_classical_configuration", "[mcpwm]")
|
||||
{
|
||||
printf("Active High Complementary\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, ahc_set_generator_actions, ahc_set_dead_time);
|
||||
|
||||
printf("Active Low Complementary\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, alc_set_generator_actions, alc_set_dead_time);
|
||||
|
||||
printf("Active High\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, ah_set_generator_actions, ah_set_dead_time);
|
||||
|
||||
printf("Active Low\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 600, 200, 400, 0, 2, al_set_generator_actions, al_set_dead_time);
|
||||
|
||||
printf("RED on A, Bypass B\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, reda_only_set_generator_actions, reda_only_set_dead_time);
|
||||
|
||||
printf("Bypass A, FED on B\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, fedb_only_set_generator_actions, fedb_only_set_dead_time);
|
||||
|
||||
printf("Bypass A, RED + FED on B\r\n");
|
||||
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, redfedb_only_set_generator_actions, redfedb_only_set_dead_time);
|
||||
}
|
380
components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c
Normal file
380
components/driver/test_apps/mcpwm/main/test_mcpwm_oper.c
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/mcpwm_oper.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "driver/mcpwm_gen.h"
|
||||
#include "driver/mcpwm_fault.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
TEST_CASE("mcpwm_operator_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
const int total_operators = SOC_MCPWM_OPERATORS_PER_GROUP * SOC_MCPWM_GROUPS;
|
||||
mcpwm_timer_handle_t timers[SOC_MCPWM_GROUPS];
|
||||
mcpwm_oper_handle_t operators[total_operators];
|
||||
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1 * 1000 * 1000,
|
||||
.period_ticks = 10 * 1000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
};
|
||||
printf("install one MCPWM timer for each group\r\n");
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
timer_config.group_id = i;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
printf("install MCPWM operators for each group\r\n");
|
||||
int k = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
operator_config.group_id = i;
|
||||
for (int j = 0; j < SOC_MCPWM_OPERATORS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operators[k++]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_operator(&operator_config, &operators[0]));
|
||||
}
|
||||
printf("connect MCPWM timer and operators\r\n");
|
||||
k = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
for (int j = 0; j < SOC_MCPWM_OPERATORS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operators[k++], timers[i]));
|
||||
}
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_ARG, mcpwm_operator_connect_timer(operators[0], timers[1]));
|
||||
printf("uninstall operators and timers\r\n");
|
||||
for (int i = 0; i < total_operators; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_operator(operators[i]));
|
||||
}
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_operator_carrier", "[mcpwm]")
|
||||
{
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000, // 1MHz, 1us per tick
|
||||
.period_ticks = 20000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = 0,
|
||||
};
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &generator));
|
||||
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(generator,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
|
||||
printf("add carrier to PWM wave\r\n");
|
||||
mcpwm_carrier_config_t carrier_config = {
|
||||
.frequency_hz = 1000000, // 1MHz carrier
|
||||
.duty_cycle = 0.5,
|
||||
.first_pulse_duration_us = 10,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_operator_apply_carrier(operator, &carrier_config));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
printf("remove carrier from PWM wave\r\n");
|
||||
carrier_config.frequency_hz = 0;
|
||||
TEST_ESP_OK(mcpwm_operator_apply_carrier(operator, &carrier_config));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(200));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_generator(generator));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
static bool test_cbc_brake_on_gpio_fault_callback(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_data)
|
||||
{
|
||||
esp_rom_printf("cbc brake\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool test_ost_brake_on_gpio_fault_callback(mcpwm_oper_handle_t operator, const mcpwm_brake_event_data_t *edata, void *user_data)
|
||||
{
|
||||
esp_rom_printf("ost brake\r\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_operator_brake_on_gpio_fault", "[mcpwm]")
|
||||
{
|
||||
printf("install timer\r\n");
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = 1000000, // 1MHz, 1us per tick
|
||||
.period_ticks = 20000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
printf("install operator\r\n");
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
printf("set brake event callbacks for operator\r\n");
|
||||
mcpwm_operator_event_callbacks_t cbs = {
|
||||
.on_brake_cbc = test_cbc_brake_on_gpio_fault_callback,
|
||||
.on_brake_ost = test_ost_brake_on_gpio_fault_callback,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_operator_register_event_callbacks(operator, &cbs, NULL));
|
||||
|
||||
printf("install gpio fault\r\n");
|
||||
mcpwm_gpio_fault_config_t gpio_fault_config = {
|
||||
.group_id = 0,
|
||||
.flags.active_level = 1,
|
||||
.flags.io_loop_back = true,
|
||||
.flags.pull_down = true,
|
||||
};
|
||||
mcpwm_fault_handle_t gpio_cbc_fault = NULL;
|
||||
mcpwm_fault_handle_t gpio_ost_fault = NULL;
|
||||
const int cbc_fault_gpio = 4;
|
||||
const int ost_fault_gpio = 5;
|
||||
|
||||
gpio_fault_config.gpio_num = cbc_fault_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_cbc_fault));
|
||||
gpio_fault_config.gpio_num = ost_fault_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_gpio_fault(&gpio_fault_config, &gpio_ost_fault));
|
||||
|
||||
// put fault GPIO into a safe state
|
||||
gpio_set_level(cbc_fault_gpio, 0);
|
||||
gpio_set_level(ost_fault_gpio, 0);
|
||||
|
||||
printf("set brake mode on fault\r\n");
|
||||
mcpwm_brake_config_t brake_config = {
|
||||
.fault = gpio_cbc_fault,
|
||||
.brake_mode = MCPWM_OPER_BRAKE_MODE_CBC,
|
||||
.flags.cbc_recover_on_tez = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
|
||||
brake_config.fault = gpio_ost_fault;
|
||||
brake_config.brake_mode = MCPWM_OPER_BRAKE_MODE_OST;
|
||||
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
|
||||
|
||||
printf("create generators\r\n");
|
||||
const int gen_a_gpio = 0;
|
||||
const int gen_b_gpio = 2;
|
||||
mcpwm_gen_handle_t gen_a = NULL;
|
||||
mcpwm_gen_handle_t gen_b = NULL;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
generator_config.gen_gpio_num = gen_a_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_a));
|
||||
generator_config.gen_gpio_num = gen_b_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_b));
|
||||
|
||||
printf("set generator actions on timer event\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_a,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_b,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
|
||||
printf("set generator actions on brake event\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_a,
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_b,
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
|
||||
printf("enable and start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
printf("trigger GPIO fault signal, brake in CBC mode\r\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
gpio_set_level(cbc_fault_gpio, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_a_gpio));
|
||||
// remove the fault signal
|
||||
gpio_set_level(cbc_fault_gpio, 0);
|
||||
// recovery
|
||||
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, gpio_cbc_fault));
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// should recovery automatically
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
|
||||
}
|
||||
|
||||
printf("trigger GPIO fault signal, brake in OST mode\r\n");
|
||||
for (int i = 0; i < 10; i++) {
|
||||
gpio_set_level(ost_fault_gpio, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
|
||||
// can't recover because fault signal is still active
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_operator_recover_from_fault(operator, gpio_ost_fault));
|
||||
// remove the fault signal
|
||||
gpio_set_level(ost_fault_gpio, 0);
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// for ost brake, the generator can't recover before we manually recover it
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
|
||||
// now it's safe to recover the operator
|
||||
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, gpio_ost_fault));
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// should recovery now
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
|
||||
}
|
||||
|
||||
printf("delete all mcpwm objects\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_fault(gpio_cbc_fault));
|
||||
TEST_ESP_OK(mcpwm_del_fault(gpio_ost_fault));
|
||||
TEST_ESP_OK(mcpwm_del_generator(gen_a));
|
||||
TEST_ESP_OK(mcpwm_del_generator(gen_b));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_operator_brake_on_soft_fault", "[mcpwm]")
|
||||
{
|
||||
printf("install timer\r\n");
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = 1000000, // 1MHz, 1us per tick
|
||||
.period_ticks = 20000,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
printf("install operator\r\n");
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_operator(&operator_config, &operator));
|
||||
TEST_ESP_OK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
printf("install soft fault\r\n");
|
||||
mcpwm_soft_fault_config_t soft_fault_config = {};
|
||||
mcpwm_fault_handle_t soft_fault = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_soft_fault(&soft_fault_config, &soft_fault));
|
||||
|
||||
printf("set brake mode on fault\r\n");
|
||||
mcpwm_brake_config_t brake_config = {
|
||||
.fault = soft_fault,
|
||||
.brake_mode = MCPWM_OPER_BRAKE_MODE_CBC,
|
||||
.flags.cbc_recover_on_tez = true,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
|
||||
|
||||
printf("create generators\r\n");
|
||||
const int gen_a_gpio = 0;
|
||||
const int gen_b_gpio = 2;
|
||||
mcpwm_gen_handle_t gen_a = NULL;
|
||||
mcpwm_gen_handle_t gen_b = NULL;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.flags.io_loop_back = true,
|
||||
};
|
||||
generator_config.gen_gpio_num = gen_a_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_a));
|
||||
generator_config.gen_gpio_num = gen_b_gpio;
|
||||
TEST_ESP_OK(mcpwm_new_generator(operator, &generator_config, &gen_b));
|
||||
|
||||
printf("set generator actions on timer event\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_a,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_timer_event(gen_b,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
|
||||
printf("set generator actions on brake event\r\n");
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_a,
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
TEST_ESP_OK(mcpwm_generator_set_actions_on_brake_event(gen_b,
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
|
||||
printf("enable and start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
printf("trigger soft fault signal, brake in CBC mode\r\n");
|
||||
for (int i = 0; i < 1; i++) {
|
||||
// stop the timer, so the operator can't recover from fault automatically
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// check initial generator output
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
|
||||
TEST_ESP_OK(mcpwm_soft_fault_activate(soft_fault));
|
||||
// check generate output on fault event
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_a_gpio));
|
||||
// start the timer, so that operator can recover at a specific event (e.g. tez)
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
// recover on tez
|
||||
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, soft_fault));
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// the generator output should be recoverd automatically
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_a_gpio));
|
||||
}
|
||||
|
||||
printf("change the brake mode to ost\r\n");
|
||||
brake_config.brake_mode = MCPWM_OPER_BRAKE_MODE_OST;
|
||||
TEST_ESP_OK(mcpwm_operator_set_brake_on_fault(operator, &brake_config));
|
||||
|
||||
printf("trigger soft fault signal, brake in OST mode\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// check initial generator output
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
|
||||
TEST_ESP_OK(mcpwm_soft_fault_activate(soft_fault));
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
|
||||
vTaskDelay(pdMS_TO_TICKS(40));
|
||||
// don't recover without a manual recover
|
||||
TEST_ASSERT_EQUAL(1, gpio_get_level(gen_b_gpio));
|
||||
TEST_ESP_OK(mcpwm_operator_recover_from_fault(operator, soft_fault));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
// should recovery now
|
||||
TEST_ASSERT_EQUAL(0, gpio_get_level(gen_b_gpio));
|
||||
}
|
||||
|
||||
printf("delete all mcpwm objects\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_fault(soft_fault));
|
||||
TEST_ESP_OK(mcpwm_del_generator(gen_a));
|
||||
TEST_ESP_OK(mcpwm_del_generator(gen_b));
|
||||
TEST_ESP_OK(mcpwm_del_operator(operator));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
}
|
204
components/driver/test_apps/mcpwm/main/test_mcpwm_sync.c
Normal file
204
components/driver/test_apps/mcpwm/main/test_mcpwm_sync.c
Normal file
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "driver/mcpwm_sync.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_private/mcpwm.h"
|
||||
#include "test_mcpwm_utils.h"
|
||||
|
||||
TEST_CASE("mcpwm_sync_source_install_uninstall", "[mcpwm]")
|
||||
{
|
||||
printf("install timer sync_src\r\n");
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000, // 1MHz
|
||||
.period_ticks = 200,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
const int total_timers = SOC_MCPWM_TIMERS_PER_GROUP * SOC_MCPWM_GROUPS;
|
||||
mcpwm_timer_handle_t timers[total_timers];
|
||||
int k = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
timer_config.group_id = i;
|
||||
for (int j = 0; j < SOC_MCPWM_TIMERS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[k++]));
|
||||
}
|
||||
}
|
||||
mcpwm_timer_sync_src_config_t timer_sync_src_config = {
|
||||
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
|
||||
};
|
||||
mcpwm_sync_handle_t timer_syncs[total_timers];
|
||||
for (int i = 0; i < total_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[i], &timer_sync_src_config, &timer_syncs[i]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_new_timer_sync_src(timers[0], &timer_sync_src_config, &timer_syncs[0]));
|
||||
|
||||
printf("install gpio sync_src\r\n");
|
||||
mcpwm_gpio_sync_src_config_t gpio_sync_config = {
|
||||
.gpio_num = 0,
|
||||
};
|
||||
const int total_gpio_sync_srcs = SOC_MCPWM_GROUPS * SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP;
|
||||
mcpwm_sync_handle_t gpio_sync_srcs[total_gpio_sync_srcs];
|
||||
k = 0;
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
gpio_sync_config.group_id = i;
|
||||
for (int j = 0; j < SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP; j++) {
|
||||
TEST_ESP_OK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_srcs[k++]));
|
||||
}
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_srcs[0]));
|
||||
|
||||
printf("delete synchors\r\n");
|
||||
for (int i = 0; i < total_gpio_sync_srcs; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(gpio_sync_srcs[i]));
|
||||
}
|
||||
for (int i = 0; i < total_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(timer_syncs[i]));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_soft_sync_timer_phase_lock", "[mcpwm]")
|
||||
{
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = 1000000, // 1MHz
|
||||
.period_ticks = 200,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_STOP_FULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
check_mcpwm_timer_phase(&timer, 1, timer_config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
|
||||
|
||||
printf("install soft sync source\r\n");
|
||||
mcpwm_sync_handle_t soft_sync = NULL;
|
||||
mcpwm_soft_sync_config_t soft_sync_config = {};
|
||||
TEST_ESP_OK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync));
|
||||
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 77,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
.sync_src = soft_sync,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timer, &sync_phase_config));
|
||||
TEST_ESP_OK(mcpwm_soft_sync_activate(soft_sync));
|
||||
check_mcpwm_timer_phase(&timer, 1, 77, MCPWM_TIMER_DIRECTION_UP);
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(soft_sync));
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_gpio_sync_timer_phase_lock", "[mcpwm]")
|
||||
{
|
||||
// GPIO
|
||||
// |
|
||||
// v
|
||||
// timer0-->timer1-->timer2
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = 1000000, // 1MHz, 1us per tick
|
||||
.period_ticks = 500,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_sync_src_config_t sync_config = {
|
||||
.flags.propagate_input_sync = 1, // reuse the input sync source as the output sync trigger
|
||||
};
|
||||
mcpwm_timer_handle_t timers[SOC_MCPWM_TIMERS_PER_GROUP];
|
||||
mcpwm_sync_handle_t sync_srcs[SOC_MCPWM_TIMERS_PER_GROUP];
|
||||
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
|
||||
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[i], &sync_config, &sync_srcs[i]));
|
||||
}
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 100,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
};
|
||||
mcpwm_sync_handle_t gpio_sync_src;
|
||||
const int gpio_num = 0;
|
||||
mcpwm_gpio_sync_src_config_t gpio_sync_config = {
|
||||
.group_id = 0,
|
||||
.gpio_num = gpio_num,
|
||||
.flags.io_loop_back = true, // so that we can use gpio driver to simulate the sync signal
|
||||
.flags.pull_down = true, // internally pull down
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_src));
|
||||
// put the GPIO into initial state
|
||||
gpio_set_level(gpio_num, 0);
|
||||
for (int i = 1; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
sync_phase_config.sync_src = sync_srcs[i - 1];
|
||||
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
|
||||
}
|
||||
sync_phase_config.sync_src = gpio_sync_src;
|
||||
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[0], &sync_phase_config));
|
||||
|
||||
// simulate an GPIO sync singal
|
||||
gpio_set_level(gpio_num, 1);
|
||||
gpio_set_level(gpio_num, 0);
|
||||
check_mcpwm_timer_phase(timers, SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP, 100, MCPWM_TIMER_DIRECTION_UP);
|
||||
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(gpio_sync_src));
|
||||
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(sync_srcs[i]));
|
||||
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_timer_sync_timer_phase_lock", "[mcpwm]")
|
||||
{
|
||||
// +->timer1
|
||||
// |
|
||||
// timer0---+
|
||||
// |
|
||||
// +->timer2
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = 1000000, // 1MHz, 1us per tick
|
||||
.period_ticks = 500,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
|
||||
};
|
||||
mcpwm_timer_handle_t timers[SOC_MCPWM_TIMERS_PER_GROUP];
|
||||
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
mcpwm_timer_sync_src_config_t sync_config = {
|
||||
.timer_event = MCPWM_TIMER_EVENT_FULL,
|
||||
};
|
||||
mcpwm_sync_handle_t sync_src;
|
||||
TEST_ESP_OK(mcpwm_new_timer_sync_src(timers[0], &sync_config, &sync_src));
|
||||
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 50,
|
||||
.direction = MCPWM_TIMER_DIRECTION_DOWN,
|
||||
.sync_src = sync_src,
|
||||
};
|
||||
for (int i = 1; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
|
||||
}
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timers[0]));
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[0], MCPWM_TIMER_START_STOP_FULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
check_mcpwm_timer_phase(&timers[1], 2, 50, MCPWM_TIMER_DIRECTION_DOWN);
|
||||
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timers[0]));
|
||||
TEST_ESP_OK(mcpwm_del_sync_src(sync_src));
|
||||
for (int i = 0; i < SOC_MCPWM_TIMERS_PER_GROUP; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
|
||||
}
|
||||
}
|
186
components/driver/test_apps/mcpwm/main/test_mcpwm_timer.c
Normal file
186
components/driver/test_apps/mcpwm/main/test_mcpwm_timer.c
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "driver/mcpwm_timer.h"
|
||||
#include "esp_private/mcpwm.h"
|
||||
#include "test_mcpwm_utils.h"
|
||||
|
||||
TEST_CASE("mcpwm_timer_start_stop", "[mcpwm]")
|
||||
{
|
||||
mcpwm_timer_config_t config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000, // 1MHz
|
||||
.period_ticks = 400,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP_DOWN,
|
||||
};
|
||||
const int num_timers = SOC_MCPWM_TIMERS_PER_GROUP * SOC_MCPWM_GROUPS;
|
||||
|
||||
printf("create mcpwm timer instances\r\n");
|
||||
mcpwm_timer_handle_t timers[num_timers];
|
||||
for (int i = 0; i < SOC_MCPWM_GROUPS; i++) {
|
||||
for (int j = 0; j < SOC_MCPWM_TIMERS_PER_GROUP; j++) {
|
||||
config.group_id = i;
|
||||
TEST_ESP_OK(mcpwm_new_timer(&config, &timers[i * SOC_MCPWM_TIMERS_PER_GROUP + j]));
|
||||
}
|
||||
TEST_ESP_ERR(ESP_ERR_NOT_FOUND, mcpwm_new_timer(&config, &timers[0]));
|
||||
}
|
||||
|
||||
// can't do start/stop control before enable
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_timer_start_stop(timers[0], MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
printf("enable timers\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timers[i]));
|
||||
}
|
||||
|
||||
printf("start timer and then stop when empty\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_STOP_EMPTY));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
check_mcpwm_timer_phase(timers, num_timers, 0, MCPWM_TIMER_DIRECTION_UP);
|
||||
|
||||
printf("start timer and then stop when full\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_STOP_FULL));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
check_mcpwm_timer_phase(timers, num_timers, config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
|
||||
|
||||
printf("start freely and stop manually when full\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
// stop at next counter full
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_STOP_FULL));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
check_mcpwm_timer_phase(timers, num_timers, config.period_ticks / 2, MCPWM_TIMER_DIRECTION_DOWN);
|
||||
|
||||
printf("start freely and stop manually when empty\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
// stop at next counter empty
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_STOP_EMPTY));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
check_mcpwm_timer_phase(timers, num_timers, 0, MCPWM_TIMER_DIRECTION_UP);
|
||||
|
||||
// can't delete timer before disable
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_del_timer(timers[0]));
|
||||
|
||||
printf("disable timers\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timers[i]));
|
||||
}
|
||||
|
||||
printf("delete timers\r\n");
|
||||
for (int i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_del_timer(timers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
#define TEST_MCPWM_TIMER_EVENT_BIT_FULL (1 << 0)
|
||||
#define TEST_MCPWM_TIMER_EVENT_BIT_EMPTY (1 << 1)
|
||||
#define TEST_MCPWM_TIMER_EVENT_BIT_STOP (1 << 2)
|
||||
|
||||
typedef struct {
|
||||
EventGroupHandle_t event_group;
|
||||
uint32_t expected_full_counts;
|
||||
uint32_t expected_empty_counts;
|
||||
uint32_t accumulate_full_counts;
|
||||
uint32_t accumulate_empty_counts;
|
||||
} test_mcpwm_timer_user_data_t;
|
||||
|
||||
static bool test_on_stop(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
|
||||
{
|
||||
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
esp_rom_printf("timer stopped at %u\r\n", edata->count_value);
|
||||
TEST_ASSERT_EQUAL(0, edata->count_value);
|
||||
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_STOP, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static bool test_on_full(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
|
||||
{
|
||||
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
udata->accumulate_full_counts++;
|
||||
if (udata->accumulate_full_counts >= udata->expected_full_counts) {
|
||||
udata->accumulate_full_counts = 0;
|
||||
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_FULL, &high_task_wakeup);
|
||||
}
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static bool test_on_empty(mcpwm_timer_handle_t timer, const mcpwm_timer_event_data_t *edata, void *user_data)
|
||||
{
|
||||
test_mcpwm_timer_user_data_t *udata = (test_mcpwm_timer_user_data_t *)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
udata->accumulate_empty_counts++;
|
||||
if (udata->accumulate_empty_counts >= udata->expected_empty_counts) {
|
||||
udata->accumulate_empty_counts = 0;
|
||||
xEventGroupSetBitsFromISR(udata->event_group, TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, &high_task_wakeup);
|
||||
}
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("mcpwm_timer_event_callbacks", "[mcpwm]")
|
||||
{
|
||||
EventGroupHandle_t event_group = xEventGroupCreate();
|
||||
EventBits_t bits = 0;
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1 * 1000 * 1000, // 1MHz, 1us per tick
|
||||
.period_ticks = 20 * 1000, // 20ms, 50Hz
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
printf("create mcpwm timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
printf("register event callbacks\r\n");
|
||||
mcpwm_timer_event_callbacks_t cbs = {
|
||||
.on_stop = test_on_stop,
|
||||
.on_full = test_on_full,
|
||||
.on_empty = test_on_empty,
|
||||
};
|
||||
test_mcpwm_timer_user_data_t udata = {
|
||||
.event_group = event_group,
|
||||
.expected_empty_counts = 50,
|
||||
.expected_full_counts = 50,
|
||||
};
|
||||
TEST_ESP_OK(mcpwm_timer_register_event_callbacks(timer, &cbs, &udata));
|
||||
|
||||
printf("enable timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_enable(timer));
|
||||
|
||||
printf("start timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
printf("wait for full and empty events\r\n");
|
||||
bits = xEventGroupWaitBits(event_group, TEST_MCPWM_TIMER_EVENT_BIT_FULL | TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, pdTRUE, pdTRUE, pdMS_TO_TICKS(1050));
|
||||
TEST_ASSERT_EQUAL(TEST_MCPWM_TIMER_EVENT_BIT_FULL | TEST_MCPWM_TIMER_EVENT_BIT_EMPTY, bits);
|
||||
|
||||
printf("stop timer and wait for event\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_STOP_EMPTY));
|
||||
bits = xEventGroupWaitBits(event_group, TEST_MCPWM_TIMER_EVENT_BIT_STOP, pdTRUE, pdTRUE, pdMS_TO_TICKS(50));
|
||||
TEST_ASSERT_EQUAL(TEST_MCPWM_TIMER_EVENT_BIT_STOP, bits);
|
||||
|
||||
printf("disable timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_timer_disable(timer));
|
||||
|
||||
printf("delete timer\r\n");
|
||||
TEST_ESP_OK(mcpwm_del_timer(timer));
|
||||
vEventGroupDelete(event_group);
|
||||
}
|
20
components/driver/test_apps/mcpwm/main/test_mcpwm_utils.c
Normal file
20
components/driver/test_apps/mcpwm/main/test_mcpwm_utils.c
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "unity.h"
|
||||
#include "esp_private/mcpwm.h"
|
||||
#include "test_mcpwm_utils.h"
|
||||
|
||||
void check_mcpwm_timer_phase(mcpwm_timer_handle_t *timers, size_t num_timers,
|
||||
uint32_t expected_count, mcpwm_timer_direction_t expected_direction)
|
||||
{
|
||||
uint32_t count_value;
|
||||
mcpwm_timer_direction_t direction;
|
||||
for (size_t i = 0; i < num_timers; i++) {
|
||||
TEST_ESP_OK(mcpwm_timer_get_phase(timers[i], &count_value, &direction));
|
||||
TEST_ASSERT_INT_WITHIN(1, expected_count, count_value);
|
||||
TEST_ASSERT_EQUAL(expected_direction, direction);
|
||||
}
|
||||
}
|
17
components/driver/test_apps/mcpwm/main/test_mcpwm_utils.h
Normal file
17
components/driver/test_apps/mcpwm/main/test_mcpwm_utils.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_attr.h"
|
||||
#include "driver/mcpwm_types.h"
|
||||
|
||||
#if CONFIG_MCPWM_ISR_IRAM_SAFE
|
||||
#define TEST_MCPWM_CALLBACK_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define TEST_MCPWM_CALLBACK_ATTR
|
||||
#endif // CONFIG_MCPWM_ISR_IRAM_SAFE
|
||||
|
||||
void check_mcpwm_timer_phase(mcpwm_timer_handle_t *timers, size_t num_timers,
|
||||
uint32_t expected_count, mcpwm_timer_direction_t expected_direction);
|
22
components/driver/test_apps/mcpwm/pytest_mcpwm.py
Normal file
22
components/driver/test_apps/mcpwm/pytest_mcpwm.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'release',
|
||||
'iram_safe',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_mcpwm(dut: Dut) -> None:
|
||||
dut.expect('Press ENTER to see the list of tests')
|
||||
dut.write('*')
|
||||
dut.expect_unity_test_output()
|
5
components/driver/test_apps/mcpwm/sdkconfig.ci.iram_safe
Normal file
5
components/driver/test_apps/mcpwm/sdkconfig.ci.iram_safe
Normal file
@@ -0,0 +1,5 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_MCPWM_ISR_IRAM_SAFE=y
|
||||
|
||||
# silent the error check, as the error string are stored in rodata, causing RTL check failure
|
||||
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
|
5
components/driver/test_apps/mcpwm/sdkconfig.ci.release
Normal file
5
components/driver/test_apps/mcpwm/sdkconfig.ci.release
Normal file
@@ -0,0 +1,5 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
2
components/driver/test_apps/mcpwm/sdkconfig.defaults
Normal file
2
components/driver/test_apps/mcpwm/sdkconfig.defaults
Normal file
@@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
26
docs/_static/diagrams/mcpwm/deadtime_active_high.json
vendored
Normal file
26
docs/_static/diagrams/mcpwm/deadtime_active_high.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a.....b..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0....1....0...",
|
||||
"node": ".....c....."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0...1......0..",
|
||||
"node": "...........d.."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->c RED",
|
||||
"b|->d FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "Active High"
|
||||
}
|
||||
}
|
26
docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json
vendored
Normal file
26
docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a.....b..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0....1....0...",
|
||||
"node": ".....c....."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "1...0......1..",
|
||||
"node": "...........d.."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->c RED",
|
||||
"b|->d FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "Active High, Complementary"
|
||||
}
|
||||
}
|
26
docs/_static/diagrams/mcpwm/deadtime_active_low.json
vendored
Normal file
26
docs/_static/diagrams/mcpwm/deadtime_active_low.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a.....b..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "1....0....1...",
|
||||
"node": ".....c....."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "1...0......1..",
|
||||
"node": "...........d.."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->c RED",
|
||||
"b|->d FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "Active Low"
|
||||
}
|
||||
}
|
26
docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json
vendored
Normal file
26
docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a.....b..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "1....0....1...",
|
||||
"node": ".....c....."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0...1......0..",
|
||||
"node": "...........d.."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->c RED",
|
||||
"b|->d FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "Active Low, Complementary"
|
||||
}
|
||||
}
|
28
docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json
vendored
Normal file
28
docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin_A",
|
||||
"wave": "0...1.....0..."
|
||||
},
|
||||
{
|
||||
"name": "origin_B",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "..........a..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0...1.....0..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0...1......0..",
|
||||
"node": "...........b..."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->b FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "FED on B, Bypass A"
|
||||
}
|
||||
}
|
28
docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json
vendored
Normal file
28
docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin_A",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a........."
|
||||
},
|
||||
{
|
||||
"name": "origin_B",
|
||||
"wave": "0...1.....0..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0....1....0...",
|
||||
"node": ".....b....."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0...1.....0..."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->b RED"
|
||||
],
|
||||
"head": {
|
||||
"text": "RED on A, Bypass B"
|
||||
}
|
||||
}
|
29
docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json
vendored
Normal file
29
docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "origin_A",
|
||||
"wave": "0...1.....0..."
|
||||
},
|
||||
{
|
||||
"name": "origin_B",
|
||||
"wave": "0...1.....0...",
|
||||
"node": "....a.....b..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0...1.....0..."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0....1.....0..",
|
||||
"node": ".....c.....d..."
|
||||
}
|
||||
],
|
||||
"edge": [
|
||||
"a|->c RED",
|
||||
"b|->d FED"
|
||||
],
|
||||
"head": {
|
||||
"text": "Bypass A, RED + FED on B"
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "01..0..1..0."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0..1..0..1.."
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Dual Edge Asymmetric Waveform, Active Low"
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0.1..0...1..0.."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "0..10.....10..."
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Dual Edge Symmetric Waveform, Active Low"
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "01..0...1..0"
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "1.01.....01."
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Dual Edge Symmetric Waveform, Complementary"
|
||||
}
|
||||
}
|
59
docs/_static/diagrams/mcpwm/mcpwm_overview.diag
vendored
Normal file
59
docs/_static/diagrams/mcpwm/mcpwm_overview.diag
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
blockdiag mcpwm_overview {
|
||||
default_fontsize = 18;
|
||||
node_width = 130;
|
||||
node_height = 100;
|
||||
default_group_color = lightgrey;
|
||||
|
||||
mcpwm_timers [label = "MCPWM\nTimer", stacked];
|
||||
timer_sync [label = "Timer Sync", stacked];
|
||||
timer_sync <-> mcpwm_timers;
|
||||
|
||||
mcpwm_capture_timer [label = "MCPWM\nCapture Timer"];
|
||||
mcpwm_capture_channels [label = "MCPWM\nCapture Chan", stacked];
|
||||
mcpwm_capture_gpio [label = "Cap\nGPIO", shape = minidiamond];
|
||||
mcpwm_capture_timer -> mcpwm_capture_channels;
|
||||
mcpwm_capture_channels <- mcpwm_capture_gpio;
|
||||
|
||||
timer_sync -> mcpwm_capture_timer;
|
||||
gpio_sync [label = "Sync\nGPIO", shape = minidiamond];
|
||||
gpio_sync -> mcpwm_timers;
|
||||
gpio_sync -> mcpwm_capture_timer;
|
||||
|
||||
mcpwm_compares [label = "MCPWM\nComparators", stacked];
|
||||
mcpwm_generators [label = "MCPWM\nGenerators", stacked];
|
||||
mcpwm_dead_time [label = "Dead Time"];
|
||||
mcpwm_carrier [label = "Carrier\nModulation"];
|
||||
mcpwm_brake [label = "Brake"];
|
||||
pwma [label = "PWM_A", shape = minidiamond];
|
||||
pwmb [label = "PWM_B", shape = minidiamond];
|
||||
stub [shape = none];
|
||||
mcpwm_timers -> mcpwm_generators;
|
||||
mcpwm_timers -> mcpwm_compares;
|
||||
mcpwm_compares -> mcpwm_generators [folded];
|
||||
mcpwm_generators -> mcpwm_dead_time;
|
||||
mcpwm_dead_time -> mcpwm_carrier;
|
||||
mcpwm_carrier -> mcpwm_brake;
|
||||
mcpwm_brake -> stub;
|
||||
stub -> pwma, pwmb;
|
||||
mcpwm_generators -> mcpwm_carrier;
|
||||
mcpwm_generators -> mcpwm_brake;
|
||||
mcpwm_generators -> stub;
|
||||
|
||||
gpio_faults [label = "Fault\nGPIO", shape = minidiamond];
|
||||
mcpwm_brake <- gpio_faults [folded];
|
||||
|
||||
group {
|
||||
label = "MCPWM Operators";
|
||||
mcpwm_compares, mcpwm_generators, mcpwm_dead_time, mcpwm_carrier, mcpwm_brake;
|
||||
}
|
||||
|
||||
group {
|
||||
label = "MCPWM Capture";
|
||||
mcpwm_capture_timer, mcpwm_capture_channels, mcpwm_capture_gpio;
|
||||
}
|
||||
|
||||
group {
|
||||
label = "MCPWM Sync";
|
||||
gpio_sync, timer_sync;
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/pulse_placement_asym.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/pulse_placement_asym.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "0.1..0..1..0..1..0..1..0.."
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "01.....0.....1.....0.....1"
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Pulse Placement Asymmetric Waveform"
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "01....0..1....0..1"
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "01..0....1..0....1"
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Single Edge Asymmetric Waveform, Active High"
|
||||
}
|
||||
}
|
15
docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json
vendored
Normal file
15
docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"signal": [
|
||||
{
|
||||
"name": "pwm_A",
|
||||
"wave": "10....1..0....1..0"
|
||||
},
|
||||
{
|
||||
"name": "pwm_B",
|
||||
"wave": "10..1....0..1....0"
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"text": "Single Edge Asymmetric Waveform, Active Low"
|
||||
}
|
||||
}
|
BIN
docs/_static/mcpwm-block-diagram.png
vendored
BIN
docs/_static/mcpwm-block-diagram.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
BIN
docs/_static/mcpwm-brushed-dc-control.png
vendored
BIN
docs/_static/mcpwm-brushed-dc-control.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 20 KiB |
BIN
docs/_static/mcpwm-overview.png
vendored
BIN
docs/_static/mcpwm-overview.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
@@ -5,7 +5,14 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_pdm.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_std.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_cap.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_cmpr.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_fault.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_gen.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_oper.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_sync.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_timer.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
|
||||
|
@@ -5,7 +5,14 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_std.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_tdm.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/i2s_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_cap.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_cmpr.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_fault.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_gen.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_oper.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_sync.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_timer.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/mcpwm_types.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \
|
||||
$(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -213,6 +213,7 @@ LEDC
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Channel installation has been separated for TX and RX channels into :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`.
|
||||
- ``rmt_set_clk_div`` and ``rmt_get_clk_div`` are removed. Channel clock configuration can only be done during channel installation.
|
||||
- ``rmt_set_rx_idle_thresh`` and ``rmt_get_rx_idle_thresh`` are removed. In the new driver, the RX channel IDLE threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_max_ns`.
|
||||
@@ -259,11 +260,63 @@ LCD
|
||||
MCPWM
|
||||
-----
|
||||
|
||||
- ``mcpwm_capture_enable`` is removed. To enable capture channel, please use :cpp:func:`mcpwm_capture_enable_channel`.
|
||||
- ``mcpwm_capture_disable`` is remove. To disable capture channel, please use :cpp:func:`mcpwm_capture_capture_disable_channel`.
|
||||
- ``mcpwm_sync_enable`` is removed. To configure synchronization, please use :cpp:func:`mcpwm_sync_configure`.
|
||||
- ``mcpwm_isr_register`` is removed. You can register event callbacks, for capture channels. e.g. :cpp:member:`mcpwm_capture_config_t::capture_cb`.
|
||||
- ``mcpwm_carrier_oneshot_mode_disable`` is removed. Disable the first pulse (a.k.a the one-shot pulse) in the carrier is not supported by hardware.
|
||||
MCPWM driver was redesigned (see :doc:`MCPWM <../../api-reference/peripherals/mcpwm>`), meanwhile, the legacy driver is deprecated. The new driver's aim is to make each MCPWM submodule independent to each other, and give the freedom of resource connection back to users. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/mcpwm.h``. However, by default, using legacy driver will bring compile warnings like ``legacy MCPWM driver is deprecated, please migrate to the new driver (include driver/mcpwm_prelude.h)``. This warning can be suppressed by the Kconfig option :ref:`CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN`.
|
||||
|
||||
The major breaking changes in concept and usage are listed as follows:
|
||||
|
||||
Breaking Changes in Concepts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The new MCPWM driver is object-oriented, where most of the MCPWM submodule has a driver object associated with it. The driver object is created by factory function like :cpp:func:`mcpwm_new_timer`. IO control function always needs an object handle, in the first place.
|
||||
|
||||
The legacy driver has an inappropriate assumption, that is the MCPWM operator should be connected to different MCPWM timer. In fact, the hardware doesn't have such limitation. In the new driver, a MCPWM timer can be connected to multiple operators, so that the operators can achieve the best synchronization performance.
|
||||
|
||||
The legacy driver preset the way to generate a PWM waveform into a so called ``mcpwm_duty_type_t``, however, the duty cycle modes listed there are far from sufficient. Likewise, legacy driver has several preset ``mcpwm_deadtime_type_t``, which also doesn't cover all the use cases. What's more, user usually gets confused by the name of the duty cycle mode and dead-time mode. In the new driver, there're no such limitation, but user has to construct the generator behavior from scratch.
|
||||
|
||||
In the legacy driver, the ways to synchronize the MCPWM timer by GPIO, software and other timer module are not unified. It increased learning costs for users. In the new driver, the synchronization APIs are unified.
|
||||
|
||||
The legacy driver has mixed the concepts of "Fault detector" and "Fault handler". Which make the APIs very confusing to users. In the new driver, the fault object just represents a failure source, and we introduced a new concept -- **brake** to express the concept of "Fault handler". What's more, the new driver supports software fault.
|
||||
|
||||
The legacy drive only provides callback functions for the capture submodule. The new driver provides more useful callbacks for various MCPWM submodules, like timer stop, compare match, fault enter, brake, etc.
|
||||
|
||||
- ``mcpwm_io_signals_t`` and ``mcpwm_pin_config_t`` are not used. GPIO configuration has been moved into submodule's configuration structure.
|
||||
- ``mcpwm_timer_t``, ``mcpwm_generator_t`` are not used. Timer and generator are represented by :cpp:type:`mcpwm_timer_handle_t` and :cpp:type:`mcpwm_gen_handle_t`.
|
||||
- ``mcpwm_fault_signal_t`` and ``mcpwm_sync_signal_t`` are not used. Fault and sync source are represented by :cpp:type:`mcpwm_fault_handle_t` and :cpp:type:`mcpwm_sync_handle_t`.
|
||||
- ``mcpwm_capture_signal_t`` is not used. A capture channel is represented by :cpp:type:`mcpwm_cap_channel_handle_t`.
|
||||
|
||||
Breaking Changes in Usage
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``mcpwm_gpio_init`` and ``mcpwm_set_pin``: GPIO configurations are moved to submodule's own configuration. e.g. set the PWM GPIO in :cpp:member:`mcpwm_generator_config_t::gen_gpio_num`.
|
||||
- ``mcpwm_init``: To get an expected PWM waveform, you need to allocated at least one MCPWM timer and MCPWM operator, then connect them by calling :cpp:func:`mcpwm_operator_connect_timer`. After that, you should set the generator's actions on various events by calling e.g. :cpp:func:`mcpwm_generator_set_actions_on_timer_event`, :cpp:func:`mcpwm_generator_set_actions_on_compare_event`.
|
||||
- ``mcpwm_group_set_resolution``: in the new driver, the group resolution is fixed to the maximum, usually it's 80MHz.
|
||||
- ``mcpwm_timer_set_resolution``: MCPWM Timer resolution is set in :cpp:member:`mcpwm_timer_config_t::resolution_hz`.
|
||||
- ``mcpwm_set_frequency``: PWM frequency is determined by :cpp:member:`mcpwm_timer_config_t::resolution_hz`, :cpp:member:`mcpwm_timer_config_t::count_mode` and :cpp:member:`mcpwm_timer_config_t::period_ticks`.
|
||||
- ``mcpwm_set_duty``: To set the PWM duty cycle, you should call :cpp:func:`mcpwm_comparator_set_compare_value` to change comparator's threshold.
|
||||
- ``mcpwm_set_duty_type``: There won't be any preset duty types, the duty type is configured by setting different generator actions. e.g. :cpp:func:`mcpwm_generator_set_actions_on_timer_event`.
|
||||
- ``mcpwm_set_signal_high`` and ``mcpwm_set_signal_low`` are replaced by :cpp:func:`mcpwm_generator_set_force_level`. In the new driver, it's implemented by setting force action for the generator, instead of changing the duty cycle to 0% or 100% at the background.
|
||||
- ``mcpwm_start`` and ``mcpwm_stop`` are replaced by :cpp:func:`mcpwm_timer_start_stop`. You have more modes to start and stop the MCPWM timer, see :cpp:type:`mcpwm_timer_start_stop_cmd_t`.
|
||||
- ``mcpwm_carrier_init``: It's replaced by :cpp:func:`mcpwm_operator_apply_carrier`.
|
||||
- ``mcpwm_carrier_enable`` and ``mcpwm_carrier_disable``: Enabling and disabling carrier submodule is done automatically by checking whether the carrier configuration structure :cpp:type:`mcpwm_carrier_config_t` is NULL.
|
||||
- ``mcpwm_carrier_set_period`` is replaced by :cpp:member:`mcpwm_carrier_config_t::frequency_hz`.
|
||||
- ``mcpwm_carrier_set_duty_cycle`` is replaced by :cpp:member:`mcpwm_carrier_config_t::duty_cycle`.
|
||||
- ``mcpwm_carrier_oneshot_mode_enable`` is replaced by :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us`.
|
||||
- ``mcpwm_carrier_oneshot_mode_disable`` is removed. Disabling the first pulse (a.k.a the one-shot pulse) in the carrier is never supported by the hardware.
|
||||
- ``mcpwm_carrier_output_invert`` is replaced by :cpp:member:`mcpwm_carrier_config_t::invert_before_modulate` and :cpp:member:`mcpwm_carrier_config_t::invert_after_modulate`.
|
||||
- ``mcpwm_deadtime_enable`` and ``mcpwm_deadtime_disable`` are replaced by :cpp:func:`mcpwm_generator_set_dead_time`.
|
||||
- ``mcpwm_fault_init`` is replaced by :cpp:func:`mcpwm_new_gpio_fault`.
|
||||
- ``mcpwm_fault_set_oneshot_mode``, ``mcpwm_fault_set_cyc_mode`` are replaced by :cpp:func:`mcpwm_operator_set_brake_on_fault` and :cpp:func:`mcpwm_generator_set_actions_on_brake_event`.
|
||||
- ``mcpwm_capture_enable`` is removed. It's duplicated to :cpp:func:`mcpwm_capture_enable_channel`.
|
||||
- ``mcpwm_capture_disable`` is removed. It's duplicated to :cpp:func:`mcpwm_capture_capture_disable_channel`.
|
||||
- ``mcpwm_capture_enable_channel`` and ``mcpwm_capture_disable_channel`` are replaced by :cpp:func:`mcpwm_new_capture_channel` and :cpp:func:`mcpwm_del_capture_channel`.
|
||||
- ``mcpwm_capture_signal_get_value`` and ``mcpwm_capture_signal_get_edge``: Capture timer count value and capture edge are provided in the capture event callback, via :cpp:type:`mcpwm_capture_event_data_t`. Capture data are only valuable when capture event happens. Providing single API to fetch capture data is meaningless.
|
||||
- ``mcpwm_sync_enable`` is removed. It's duplicated to :cpp:func:`mcpwm_sync_configure`.
|
||||
- ``mcpwm_sync_configure`` is replaced by :cpp:func:`mcpwm_timer_set_phase_on_sync`.
|
||||
- ``mcpwm_sync_disable`` is equivalent to setting :cpp:member:`mcpwm_timer_sync_phase_config_t::sync_src` to ``NULL``.
|
||||
- ``mcpwm_set_timer_sync_output`` is replaced by :cpp:func:`mcpwm_new_timer_sync_src`.
|
||||
- ``mcpwm_timer_trigger_soft_sync`` is replaced by :cpp:func:`mcpwm_soft_sync_activate`.
|
||||
- ``mcpwm_sync_invert_gpio_synchro`` is equivalent to setting :cpp:member:`mcpwm_gpio_sync_src_config_t::active_neg`.
|
||||
- ``mcpwm_isr_register`` is removed. You can register various event callbacks instead. For example, to register capture event callback, you can use :cpp:func:`mcpwm_capture_channel_register_event_callbacks`.
|
||||
|
||||
.. only:: SOC_DEDICATED_GPIO_SUPPORTED
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
.. include:: ../../../en/api-reference/peripherals/mcpwm.rst
|
||||
.. include:: ../../../en/api-reference/peripherals/mcpwm.rst
|
||||
|
@@ -70,6 +70,22 @@ examples/peripherals/mcpwm:
|
||||
disable:
|
||||
- if: SOC_MCPWM_SUPPORTED != 1
|
||||
|
||||
examples/peripherals/mcpwm/mcpwm_bdc_speed_control:
|
||||
disable:
|
||||
- if: SOC_MCPWM_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32s3"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
|
||||
examples/peripherals/mcpwm/mcpwm_bldc_hall_control:
|
||||
disable:
|
||||
- if: SOC_MCPWM_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET != "esp32s3"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
|
||||
examples/peripherals/pcnt:
|
||||
disable:
|
||||
- if: SOC_PCNT_SUPPORTED != 1
|
||||
|
@@ -4,7 +4,11 @@
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. However the PWM signals from ESP32 can't drive motors directly as the motor usually consumes high current. So an H-bridge like [DRV8848](https://www.ti.com/product/DRV8848) should be used to provide the needed voltage and current for brushed DC motor. To measure the speed of motor, a photoelectric encoder is used to generate the "speed feedback" signals (e.g. a pair of quadrature signal). The example uses a simple PID control approach to keep the motor speed in a constant speed. The example provides a console command line interface for user to update the PID parameters according to actual situation.
|
||||
This example mainly illustrates how to drive a brushed DC motor by generating two specific PWM signals. However the PWM signals from ESP chip can't drive motors directly as the motor usually consumes high current. So an H-bridge like [DRV8848](https://www.ti.com/product/DRV8848) should be used to provide the needed voltage and current for brushed DC motor. To simplify the DC motor control of MCPWM peripheral driver, there's a component called [bdc_motor](components/bdc_motor/README.md) which abstracts the common operations into a generic interface. The most useful operations are: `forward`, `reverse`, `coast` and `brake`.
|
||||
|
||||
To measure the speed of motor, a photoelectric encoder is used to generate the "speed feedback" signals (e.g. a pair of quadrature signal). In the example, we use the PCNT peripheral to decode that quadrature signals. For more information, please refer to [rotary encoder example](../../pcnt/rotary_encoder/README.md) as well.
|
||||
|
||||
The example uses a simple PID algorithm to keep the motor spin in a stable speed. The PID component is fetched from the [IDF Component Registry](https://components.espressif.com/component/espressif/pid_ctrl).
|
||||
|
||||
## How to Use Example
|
||||
|
||||
@@ -12,7 +16,7 @@ Before project configuration and build, be sure to set the correct chip target u
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A development board with any Espressif SoC which features MCPWM and PCNT peripheral (e.g., ESP32-DevKitC, ESP32-S3-Motor-Devkit, etc.)
|
||||
* A USB cable for Power supply and programming
|
||||
* A separate 12V power supply for brushed DC motor and H-bridge (the voltage depends on the motor model used in the example)
|
||||
* A motor driving board to transfer pwm signal into driving signal
|
||||
@@ -23,28 +27,28 @@ Connection :
|
||||
```
|
||||
Power(12V)
|
||||
|
|
||||
v
|
||||
+----------------+ +--------------------+
|
||||
| | | H-Bridge |
|
||||
| GND +<----------->| GND | +--------------+
|
||||
| | | | | |
|
||||
| GENA_GPIO_NUM +----PWM0A--->| IN_A OUT_A +----->| Brushed |
|
||||
| | | | | DC |
|
||||
| GENB_GPIO_NUM +----PWM0B--->| IN_B OUT_B +----->| Motor |
|
||||
| | | | | |
|
||||
| ESP | +--------------------+ | |
|
||||
| | +------+-------+
|
||||
| | |
|
||||
| | +--------------------+ |
|
||||
| VCC3.3 +------------>| VCC Encoder | |
|
||||
| | | | |
|
||||
| GND +<----------->| |<------------+
|
||||
| | | |
|
||||
|PHASEA_GPIO_NUM |<---PhaseA---+ C1 |
|
||||
| | | |
|
||||
|PHASEB_GPIO_NUM |<---PhaseB---+ C2 |
|
||||
| | | |
|
||||
+----------------+ +--------------------+
|
||||
ESP v
|
||||
+-------------------+ +--------------------+
|
||||
| | | H-Bridge |
|
||||
| GND +<----------->| GND | +--------------+
|
||||
| | | | | |
|
||||
| BDC_MCPWM_GPIO_A +----PWM0A--->| IN_A OUT_A +----->| Brushed |
|
||||
| | | | | DC |
|
||||
| BDC_MCPWM_GPIO_B +----PWM0B--->| IN_B OUT_B +----->| Motor |
|
||||
| | | | | |
|
||||
| | +--------------------+ | |
|
||||
| | +------+-------+
|
||||
| | |
|
||||
| | +--------------------+ |
|
||||
| VCC3.3 +------------>| VCC Encoder | |
|
||||
| | | | |
|
||||
| GND +<----------->| |<------------+
|
||||
| | | |
|
||||
|BDC_ENCODER_GPIO_A |<---PhaseA---+ C1 |
|
||||
| | | |
|
||||
|BDC_ENCODER_GPIO_B |<---PhaseB---+ C2 |
|
||||
| | | |
|
||||
+-------------------+ +--------------------+
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
@@ -62,51 +66,30 @@ Run the example, you will see the following output log:
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
configure mcpwm gpio
|
||||
init mcpwm driver
|
||||
init and start rotary encoder
|
||||
init PID control block
|
||||
init motor control timer
|
||||
D (561) gptimer: new group (0) @0x3fce0a24
|
||||
D (561) gptimer: new gptimer (0,0) at 0x3fce0964, resolution=1000000Hz
|
||||
create motor control task
|
||||
start motor control timer
|
||||
D (571) gptimer: install interrupt service for timer (0,0)
|
||||
install console command line
|
||||
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
dc-motor>
|
||||
dc-motor> help
|
||||
help
|
||||
Print the list of registered commands
|
||||
|
||||
pid [-p <kp>] [-i <ki>] [-d <kd>]
|
||||
Set PID parameters
|
||||
-p <kp> Set Kp value of PID
|
||||
-i <ki> Set Ki value of PID
|
||||
-d <kd> Set Kd value of PID
|
||||
I (308) example: Create DC motor
|
||||
I (308) gpio: GPIO[7]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (318) gpio: GPIO[15]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (328) example: Init pcnt driver to decode rotary signal
|
||||
I (328) gpio: GPIO[36]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (338) gpio: GPIO[35]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (348) gpio: GPIO[35]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (358) gpio: GPIO[36]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (368) example: Create PID control block
|
||||
I (378) example: Create a timer to do PID calculation periodically
|
||||
I (378) example: Enable motor
|
||||
I (388) example: Forward motor
|
||||
I (388) example: Start motor speed loop
|
||||
```
|
||||
|
||||
### Set PID parameters
|
||||
### View velocity curve in [Serial Studio](https://github.com/Serial-Studio/Serial-Studio)
|
||||
|
||||
* Command: `pid -p <double> -i <double> -d <double> -t <loc/inc>`
|
||||
* 'p' - proportion value
|
||||
* 'i' - integral value
|
||||
* 'd' - differential value
|
||||
* 't' - PID calculation type (locational or incremental).
|
||||
To help tune the PID parameters (i.e. `Kp`, `Ki` and `Kd` in the example), this example supports to log a short string frame of runtime motor speed. The string frame can be parsed by [Serial Studio](https://github.com/Serial-Studio/Serial-Studio). This example also provides the [communication description file](serial-studio-dashboard.json) out of the box, which can be loaded by Serial Studio and then plot the curves as follows:
|
||||
|
||||
```bash
|
||||
mcpwm-motor> pid -p 0.8 -i 0.02 -d 0.1 -t inc
|
||||
pid: kp = 0.800
|
||||
pid: ki = 0.020
|
||||
pid: kd = 0.100
|
||||
pid: type = increment
|
||||
```
|
||||

|
||||
|
||||
## Troubleshooting
|
||||
|
||||
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
|
||||
* The PID parameter set in ths example might not work well in all kinds of motors, because it's not adaptive. You need to fine tune the parameters again by yourself.
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
@@ -0,0 +1,9 @@
|
||||
set(srcs "src/bdc_motor.c")
|
||||
|
||||
if(CONFIG_SOC_MCPWM_SUPPORTED)
|
||||
list(APPEND srcs "src/bdc_motor_mcpwm_impl.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include" "interface"
|
||||
PRIV_REQUIRES "driver")
|
@@ -0,0 +1,7 @@
|
||||
# Brushed DC Motor Component
|
||||
|
||||
This directory contains an implementation for Brushed DC Motor by different peripherals. Currently only MCPWM is supported as the BDC motor backend.
|
||||
|
||||
To learn more about how to use this component, please check API Documentation from header file [bdc_motor.h](./include/bdc_motor.h).
|
||||
|
||||
Please note that this component is not considered to be a part of ESP-IDF stable API. It may change and it may be removed in the future releases.
|
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Brushed DC Motor handle
|
||||
*/
|
||||
typedef struct bdc_motor_t *bdc_motor_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Enable BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Enable motor successfully
|
||||
* - ESP_ERR_INVALID_ARG: Enable motor failed because of invalid parameters
|
||||
* - ESP_FAIL: Enable motor failed because other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_enable(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Disable BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Disable motor successfully
|
||||
* - ESP_ERR_INVALID_ARG: Disable motor failed because of invalid parameters
|
||||
* - ESP_FAIL: Disable motor failed because other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_disable(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Set speed for bdc motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
* @param speed: BDC speed
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Set motor speed successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set motor speed failed because of invalid parameters
|
||||
* - ESP_FAIL: Set motor speed failed because other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_set_speed(bdc_motor_handle_t motor, uint32_t speed);
|
||||
|
||||
/**
|
||||
* @brief Forward BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Forward motor successfully
|
||||
* - ESP_FAIL: Forward motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_forward(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Reverse BDC Motor
|
||||
*
|
||||
* @param strip: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Reverse motor successfully
|
||||
* - ESP_FAIL: Reverse motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_reverse(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Stop motor in a coast way (a.k.a Fast Decay)
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Stop motor successfully
|
||||
* - ESP_FAIL: Stop motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_coast(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Stop motor in a brake way (a.k.a Slow Decay)
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Stop motor successfully
|
||||
* - ESP_FAIL: Stop motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_brake(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief Free BDC Motor resources
|
||||
*
|
||||
* @param strip: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Free resources successfully
|
||||
* - ESP_FAIL: Free resources failed because error occurred
|
||||
*/
|
||||
esp_err_t bdc_motor_del(bdc_motor_handle_t motor);
|
||||
|
||||
/**
|
||||
* @brief BDC Motor Configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t pwma_gpio_num; /*!< BDC Motor PWM A gpio number */
|
||||
uint32_t pwmb_gpio_num; /*!< BDC Motor PWM B gpio number */
|
||||
uint32_t pwm_freq_hz; /*!< PWM frequency, in Hz */
|
||||
} bdc_motor_config_t;
|
||||
|
||||
/**
|
||||
* @brief BDC Motor MCPWM specific configuration
|
||||
*/
|
||||
typedef struct {
|
||||
int group_id; /*!< MCPWM group number */
|
||||
uint32_t resolution_hz; /*!< MCPWM timer resolution */
|
||||
} bdc_motor_mcpwm_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create BDC Motor based on MCPWM peripheral
|
||||
*
|
||||
* @param motor_config: BDC Motor configuration
|
||||
* @param mcpwm_config: MCPWM specific configuration
|
||||
* @param ret_motor Returned BDC Motor handle
|
||||
* @return
|
||||
* - ESP_OK: Create BDC Motor handle successfully
|
||||
* - ESP_ERR_INVALID_ARG: Create BDC Motor handle failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Create BDC Motor handle failed because of out of memory
|
||||
* - ESP_FAIL: Create BDC Motor handle failed because some other error
|
||||
*/
|
||||
esp_err_t bdc_motor_new_mcpwm_device(const bdc_motor_config_t *motor_config, const bdc_motor_mcpwm_config_t *mcpwm_config, bdc_motor_handle_t *ret_motor);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct bdc_motor_t bdc_motor_t; /*!< Type of BDC motor */
|
||||
|
||||
/**
|
||||
* @brief BDC motor interface definition
|
||||
*/
|
||||
struct bdc_motor_t {
|
||||
/**
|
||||
* @brief Enable BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Enable motor successfully
|
||||
* - ESP_ERR_INVALID_ARG: Enable motor failed because of invalid parameters
|
||||
* - ESP_FAIL: Enable motor failed because other error occurred
|
||||
*/
|
||||
esp_err_t (*enable)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Disable BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Disable motor successfully
|
||||
* - ESP_ERR_INVALID_ARG: Disable motor failed because of invalid parameters
|
||||
* - ESP_FAIL: Disable motor failed because other error occurred
|
||||
*/
|
||||
esp_err_t (*disable)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Set speed for bdc motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
* @param speed: BDC speed
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Set motor speed successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set motor speed failed because of invalid parameters
|
||||
* - ESP_FAIL: Set motor speed failed because other error occurred
|
||||
*/
|
||||
esp_err_t (*set_speed)(bdc_motor_t *motor, uint32_t speed);
|
||||
|
||||
/**
|
||||
* @brief Forward BDC motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Forward motor successfully
|
||||
* - ESP_FAIL: Forward motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t (*forward)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Reverse BDC Motor
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Reverse motor successfully
|
||||
* - ESP_FAIL: Reverse motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t (*reverse)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Stop motor in a coast way (a.k.a Fast Decay)
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Stop motor successfully
|
||||
* - ESP_FAIL: Stop motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t (*coast)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Stop motor in a brake way (a.k.a Slow Decay)
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Stop motor successfully
|
||||
* - ESP_FAIL: Stop motor failed because some other error occurred
|
||||
*/
|
||||
esp_err_t (*brake)(bdc_motor_t *motor);
|
||||
|
||||
/**
|
||||
* @brief Free BDC Motor handle resources
|
||||
*
|
||||
* @param motor: BDC Motor handle
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK: Free resources successfully
|
||||
* - ESP_FAIL: Free resources failed because error occurred
|
||||
*/
|
||||
esp_err_t (*del)(bdc_motor_t *motor);
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "bdc_motor.h"
|
||||
#include "bdc_motor_interface.h"
|
||||
|
||||
static const char *TAG = "bdc_motor";
|
||||
|
||||
esp_err_t bdc_motor_enable(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->enable(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_disable(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->disable(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_set_speed(bdc_motor_handle_t motor, uint32_t speed)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->set_speed(motor, speed);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_forward(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->forward(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_reverse(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->reverse(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_coast(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->coast(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_brake(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->brake(motor);
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_del(bdc_motor_handle_t motor)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(motor, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
return motor->del(motor);
|
||||
}
|
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "driver/mcpwm_prelude.h"
|
||||
#include "bdc_motor.h"
|
||||
#include "bdc_motor_interface.h"
|
||||
|
||||
static const char *TAG = "bdc_motor_mcpwm";
|
||||
|
||||
typedef struct {
|
||||
bdc_motor_t base;
|
||||
mcpwm_timer_handle_t timer;
|
||||
mcpwm_oper_handle_t operator;
|
||||
mcpwm_cmpr_handle_t cmpa;
|
||||
mcpwm_cmpr_handle_t cmpb;
|
||||
mcpwm_gen_handle_t gena;
|
||||
mcpwm_gen_handle_t genb;
|
||||
} bdc_motor_mcpwm_obj;
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_set_speed(bdc_motor_t *motor, uint32_t speed)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(mcpwm_motor->cmpa, speed), TAG, "set compare value failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_comparator_set_compare_value(mcpwm_motor->cmpb, speed), TAG, "set compare value failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_enable(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_enable(mcpwm_motor->timer), TAG, "enable timer failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_start_stop(mcpwm_motor->timer, MCPWM_TIMER_START_NO_STOP), TAG, "start timer failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_disable(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_start_stop(mcpwm_motor->timer, MCPWM_TIMER_STOP_EMPTY), TAG, "stop timer failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_timer_disable(mcpwm_motor->timer), TAG, "disable timer failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_forward(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, -1, true), TAG, "disable force level for gena failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 0, true), TAG, "set force level for genb failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_reverse(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, -1, true), TAG, "disable force level for genb failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 0, true), TAG, "set force level for gena failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_coast(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 0, true), TAG, "set force level for gena failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 0, true), TAG, "set force level for genb failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_brake(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->gena, 1, true), TAG, "set force level for gena failed");
|
||||
ESP_RETURN_ON_ERROR(mcpwm_generator_set_force_level(mcpwm_motor->genb, 1, true), TAG, "set force level for genb failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t bdc_motor_mcpwm_del(bdc_motor_t *motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = __containerof(motor, bdc_motor_mcpwm_obj, base);
|
||||
mcpwm_del_generator(mcpwm_motor->gena);
|
||||
mcpwm_del_generator(mcpwm_motor->genb);
|
||||
mcpwm_del_comparator(mcpwm_motor->cmpa);
|
||||
mcpwm_del_comparator(mcpwm_motor->cmpb);
|
||||
mcpwm_del_operator(mcpwm_motor->operator);
|
||||
mcpwm_del_timer(mcpwm_motor->timer);
|
||||
free(mcpwm_motor);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t bdc_motor_new_mcpwm_device(const bdc_motor_config_t *motor_config, const bdc_motor_mcpwm_config_t *mcpwm_config, bdc_motor_handle_t *ret_motor)
|
||||
{
|
||||
bdc_motor_mcpwm_obj *mcpwm_motor = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_GOTO_ON_FALSE(motor_config && mcpwm_config && ret_motor, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
mcpwm_motor = calloc(1, sizeof(bdc_motor_mcpwm_obj));
|
||||
ESP_GOTO_ON_FALSE(mcpwm_motor, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt motor");
|
||||
|
||||
// mcpwm timer
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = mcpwm_config->group_id,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = mcpwm_config->resolution_hz,
|
||||
.period_ticks = mcpwm_config->resolution_hz / motor_config->pwm_freq_hz,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_timer(&timer_config, &mcpwm_motor->timer), err, TAG, "create MCPWM timer failed");
|
||||
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = mcpwm_config->group_id,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_operator(&operator_config, &mcpwm_motor->operator), err, TAG, "create MCPWM operator failed");
|
||||
|
||||
ESP_GOTO_ON_ERROR(mcpwm_operator_connect_timer(mcpwm_motor->operator, mcpwm_motor->timer), err, TAG, "connect timer and operator failed");
|
||||
|
||||
mcpwm_comparator_config_t comparator_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_comparator(mcpwm_motor->operator, &comparator_config, &mcpwm_motor->cmpa), err, TAG, "create comparator failed");
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_comparator(mcpwm_motor->operator, &comparator_config, &mcpwm_motor->cmpb), err, TAG, "create comparator failed");
|
||||
|
||||
// set the initial compare value for both comparators
|
||||
mcpwm_comparator_set_compare_value(mcpwm_motor->cmpa, 0);
|
||||
mcpwm_comparator_set_compare_value(mcpwm_motor->cmpb, 0);
|
||||
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = motor_config->pwma_gpio_num,
|
||||
};
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_generator(mcpwm_motor->operator, &generator_config, &mcpwm_motor->gena), err, TAG, "create generator failed");
|
||||
generator_config.gen_gpio_num = motor_config->pwmb_gpio_num;
|
||||
ESP_GOTO_ON_ERROR(mcpwm_new_generator(mcpwm_motor->operator, &generator_config, &mcpwm_motor->genb), err, TAG, "create generator failed");
|
||||
|
||||
mcpwm_generator_set_actions_on_timer_event(mcpwm_motor->gena,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END());
|
||||
mcpwm_generator_set_actions_on_compare_event(mcpwm_motor->gena,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, mcpwm_motor->cmpa, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END());
|
||||
mcpwm_generator_set_actions_on_timer_event(mcpwm_motor->genb,
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END());
|
||||
mcpwm_generator_set_actions_on_compare_event(mcpwm_motor->genb,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, mcpwm_motor->cmpb, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END());
|
||||
|
||||
mcpwm_motor->base.enable = bdc_motor_mcpwm_enable;
|
||||
mcpwm_motor->base.disable = bdc_motor_mcpwm_disable;
|
||||
mcpwm_motor->base.forward = bdc_motor_mcpwm_forward;
|
||||
mcpwm_motor->base.reverse = bdc_motor_mcpwm_reverse;
|
||||
mcpwm_motor->base.coast = bdc_motor_mcpwm_coast;
|
||||
mcpwm_motor->base.brake = bdc_motor_mcpwm_brake;
|
||||
mcpwm_motor->base.set_speed = bdc_motor_mcpwm_set_speed;
|
||||
mcpwm_motor->base.del = bdc_motor_mcpwm_del;
|
||||
*ret_motor = &mcpwm_motor->base;
|
||||
return ESP_OK;
|
||||
|
||||
err:
|
||||
if (mcpwm_motor) {
|
||||
if (mcpwm_motor->gena) {
|
||||
mcpwm_del_generator(mcpwm_motor->gena);
|
||||
}
|
||||
if (mcpwm_motor->genb) {
|
||||
mcpwm_del_generator(mcpwm_motor->genb);
|
||||
}
|
||||
if (mcpwm_motor->cmpa) {
|
||||
mcpwm_del_comparator(mcpwm_motor->cmpa);
|
||||
}
|
||||
if (mcpwm_motor->cmpb) {
|
||||
mcpwm_del_comparator(mcpwm_motor->cmpb);
|
||||
}
|
||||
if (mcpwm_motor->operator) {
|
||||
mcpwm_del_operator(mcpwm_motor->operator);
|
||||
}
|
||||
if (mcpwm_motor->timer) {
|
||||
mcpwm_del_timer(mcpwm_motor->timer);
|
||||
}
|
||||
free(mcpwm_motor);
|
||||
}
|
||||
return ret;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config SERIAL_STUDIO_DEBUG
|
||||
bool "Enable log that can be parsed by Serial Studio"
|
||||
default "n"
|
||||
help
|
||||
Enable this option, the example will print a string at runtime with a specific format,
|
||||
which can be parsed by the Serial Studio tool.
|
||||
With the "serial-studio-dashboard.json" template file provided in this example,
|
||||
user can observe the speed in a curve window in the Serial Studio.
|
||||
|
||||
endmenu
|
@@ -5,176 +5,97 @@
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/pulse_cnt.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "bdc_motor.h"
|
||||
#include "pid_ctrl.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
// Enable this config, we will print debug formated string, which in return can be captured and parsed by Serial-Studio
|
||||
#define SERIAL_STUDIO_DEBUG 0
|
||||
#define SERIAL_STUDIO_DEBUG CONFIG_SERIAL_STUDIO_DEBUG
|
||||
|
||||
#define BDC_MCPWM_UNIT 0
|
||||
#define BDC_MCPWM_TIMER 0
|
||||
#define BDC_MCPWM_GENA_GPIO_NUM 7
|
||||
#define BDC_MCPWM_GENB_GPIO_NUM 15
|
||||
#define BDC_MCPWM_FREQ_HZ 1500
|
||||
#define BDC_MCPWM_TIMER_RESOLUTION_HZ 10000000 // 10MHz, 1 tick = 0.1us
|
||||
#define BDC_MCPWM_FREQ_HZ 25000 // 25KHz PWM
|
||||
#define BDC_MCPWM_DUTY_TICK_MAX (BDC_MCPWM_TIMER_RESOLUTION_HZ / BDC_MCPWM_FREQ_HZ) // maximum value we can set for the duty cycle, in ticks
|
||||
#define BDC_MCPWM_GPIO_A 7
|
||||
#define BDC_MCPWM_GPIO_B 15
|
||||
|
||||
#define BDC_ENCODER_PCNT_HIGH_LIMIT 100
|
||||
#define BDC_ENCODER_PCNT_LOW_LIMIT -100
|
||||
#define BDC_ENCODER_PHASEA_GPIO_NUM 36
|
||||
#define BDC_ENCODER_PHASEB_GPIO_NUM 35
|
||||
#define BDC_ENCODER_GPIO_A 36
|
||||
#define BDC_ENCODER_GPIO_B 35
|
||||
#define BDC_ENCODER_PCNT_HIGH_LIMIT 1000
|
||||
#define BDC_ENCODER_PCNT_LOW_LIMIT -1000
|
||||
|
||||
#define BDC_PID_CALCULATION_PERIOD_US 10000
|
||||
#define BDC_PID_FEEDBACK_QUEUE_LEN 10
|
||||
|
||||
static pid_ctrl_parameter_t pid_runtime_param = {
|
||||
.kp = 0.6,
|
||||
.ki = 0.3,
|
||||
.kd = 0.12,
|
||||
.cal_type = PID_CAL_TYPE_INCREMENTAL,
|
||||
.max_output = 100,
|
||||
.min_output = -100,
|
||||
.max_integral = 1000,
|
||||
.min_integral = -1000,
|
||||
};
|
||||
static bool pid_need_update = false;
|
||||
static int expect_pulses = 300;
|
||||
static int real_pulses;
|
||||
#define BDC_PID_LOOP_PERIOD_MS 10 // calculate the motor speed every 10ms
|
||||
#define BDC_PID_EXPECT_SPEED 400 // expected motor speed, in the pulses counted by the rotary encoder
|
||||
|
||||
typedef struct {
|
||||
pcnt_unit_handle_t hall_pcnt_encoder;
|
||||
int accumu_count;
|
||||
QueueHandle_t pid_feedback_queue;
|
||||
} motor_control_timer_context_t;
|
||||
|
||||
typedef struct {
|
||||
QueueHandle_t pid_feedback_queue;
|
||||
bdc_motor_handle_t motor;
|
||||
pcnt_unit_handle_t pcnt_encoder;
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
} motor_control_task_context_t;
|
||||
int accumu_count;
|
||||
int report_pulses;
|
||||
} motor_control_context_t;
|
||||
|
||||
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
motor_control_timer_context_t *ctx = (motor_control_timer_context_t *)user_ctx;
|
||||
ctx->accumu_count += edata->watch_point_value;
|
||||
int *accumu_count = (int *)user_ctx;
|
||||
*accumu_count += edata->watch_point_value;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void brushed_motor_set_duty(float duty_cycle)
|
||||
{
|
||||
/* motor moves in forward direction, with duty cycle = duty % */
|
||||
if (duty_cycle > 0) {
|
||||
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A);
|
||||
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, duty_cycle);
|
||||
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B, MCPWM_DUTY_MODE_0);
|
||||
}
|
||||
/* motor moves in backward direction, with duty cycle = -duty % */
|
||||
else {
|
||||
mcpwm_set_signal_low(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_B);
|
||||
mcpwm_set_duty(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, -duty_cycle);
|
||||
mcpwm_set_duty_type(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, MCPWM_OPR_A, MCPWM_DUTY_MODE_0);
|
||||
}
|
||||
}
|
||||
|
||||
static bool motor_ctrl_timer_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *arg)
|
||||
static void pid_loop_cb(void *args)
|
||||
{
|
||||
static int last_pulse_count = 0;
|
||||
BaseType_t high_task_awoken = pdFALSE;
|
||||
motor_control_timer_context_t *user_ctx = (motor_control_timer_context_t *)arg;
|
||||
pcnt_unit_handle_t pcnt_unit = user_ctx->hall_pcnt_encoder;
|
||||
motor_control_context_t *ctx = (motor_control_context_t *)args;
|
||||
pcnt_unit_handle_t pcnt_unit = ctx->pcnt_encoder;
|
||||
pid_ctrl_block_handle_t pid_ctrl = ctx->pid_ctrl;
|
||||
bdc_motor_handle_t motor = ctx->motor;
|
||||
|
||||
// get the result from rotary encoder
|
||||
int cur_pulse_count = 0;
|
||||
pcnt_unit_get_count(pcnt_unit, &cur_pulse_count);
|
||||
cur_pulse_count += user_ctx->accumu_count;
|
||||
|
||||
int delta = cur_pulse_count - last_pulse_count;
|
||||
cur_pulse_count += ctx->accumu_count;
|
||||
int real_pulses = cur_pulse_count - last_pulse_count;
|
||||
last_pulse_count = cur_pulse_count;
|
||||
xQueueSendFromISR(user_ctx->pid_feedback_queue, &delta, &high_task_awoken);
|
||||
ctx->report_pulses = real_pulses;
|
||||
|
||||
return high_task_awoken == pdTRUE;
|
||||
}
|
||||
// calculate the speed error
|
||||
float error = BDC_PID_EXPECT_SPEED - real_pulses;
|
||||
float new_speed = 0;
|
||||
|
||||
static void bdc_ctrl_task(void *arg)
|
||||
{
|
||||
float duty_cycle = 0;
|
||||
motor_control_task_context_t *user_ctx = (motor_control_task_context_t *)arg;
|
||||
while (1) {
|
||||
xQueueReceive(user_ctx->pid_feedback_queue, &real_pulses, portMAX_DELAY);
|
||||
float error = expect_pulses - real_pulses;
|
||||
pid_compute(user_ctx->pid_ctrl, error, &duty_cycle);
|
||||
brushed_motor_set_duty(duty_cycle);
|
||||
}
|
||||
}
|
||||
|
||||
static struct {
|
||||
struct arg_dbl *kp;
|
||||
struct arg_dbl *ki;
|
||||
struct arg_dbl *kd;
|
||||
struct arg_end *end;
|
||||
} pid_ctrl_args;
|
||||
|
||||
static int do_pid_ctrl_cmd(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **)&pid_ctrl_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, pid_ctrl_args.end, argv[0]);
|
||||
return 0;
|
||||
}
|
||||
if (pid_ctrl_args.kp->count) {
|
||||
pid_runtime_param.kp = pid_ctrl_args.kp->dval[0];
|
||||
}
|
||||
if (pid_ctrl_args.ki->count) {
|
||||
pid_runtime_param.ki = pid_ctrl_args.ki->dval[0];
|
||||
}
|
||||
if (pid_ctrl_args.kd->count) {
|
||||
pid_runtime_param.kd = pid_ctrl_args.kd->dval[0];
|
||||
}
|
||||
|
||||
pid_need_update = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_pid_console_command(void)
|
||||
{
|
||||
pid_ctrl_args.kp = arg_dbl0("p", NULL, "<kp>", "Set Kp value of PID");
|
||||
pid_ctrl_args.ki = arg_dbl0("i", NULL, "<ki>", "Set Ki value of PID");
|
||||
pid_ctrl_args.kd = arg_dbl0("d", NULL, "<kd>", "Set Kd value of PID");
|
||||
pid_ctrl_args.end = arg_end(2);
|
||||
const esp_console_cmd_t pid_ctrl_cmd = {
|
||||
.command = "pid",
|
||||
.help = "Set PID parameters",
|
||||
.hint = NULL,
|
||||
.func = &do_pid_ctrl_cmd,
|
||||
.argtable = &pid_ctrl_args
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&pid_ctrl_cmd));
|
||||
// set the new speed
|
||||
pid_compute(pid_ctrl, error, &new_speed);
|
||||
bdc_motor_set_speed(motor, (uint32_t)new_speed);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static motor_control_timer_context_t my_timer_ctx = {};
|
||||
|
||||
QueueHandle_t pid_fb_queue = xQueueCreate(BDC_PID_FEEDBACK_QUEUE_LEN, sizeof(int));
|
||||
assert(pid_fb_queue);
|
||||
|
||||
printf("configure mcpwm gpio\r\n");
|
||||
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0A, BDC_MCPWM_GENA_GPIO_NUM));
|
||||
ESP_ERROR_CHECK(mcpwm_gpio_init(BDC_MCPWM_UNIT, MCPWM0B, BDC_MCPWM_GENB_GPIO_NUM));
|
||||
printf("init mcpwm driver\n");
|
||||
mcpwm_config_t pwm_config = {
|
||||
.frequency = BDC_MCPWM_FREQ_HZ,
|
||||
.cmpr_a = 0,
|
||||
.cmpr_b = 0,
|
||||
.counter_mode = MCPWM_UP_COUNTER,
|
||||
.duty_mode = MCPWM_DUTY_MODE_0,
|
||||
static motor_control_context_t motor_ctrl_ctx = {
|
||||
.accumu_count = 0,
|
||||
.pcnt_encoder = NULL,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_init(BDC_MCPWM_UNIT, BDC_MCPWM_TIMER, &pwm_config));
|
||||
|
||||
printf("init and start rotary encoder\r\n");
|
||||
ESP_LOGI(TAG, "Create DC motor");
|
||||
bdc_motor_config_t motor_config = {
|
||||
.pwm_freq_hz = BDC_MCPWM_FREQ_HZ,
|
||||
.pwma_gpio_num = BDC_MCPWM_GPIO_A,
|
||||
.pwmb_gpio_num = BDC_MCPWM_GPIO_B,
|
||||
};
|
||||
bdc_motor_mcpwm_config_t mcpwm_config = {
|
||||
.group_id = 0,
|
||||
.resolution_hz = BDC_MCPWM_TIMER_RESOLUTION_HZ,
|
||||
};
|
||||
bdc_motor_handle_t motor = NULL;
|
||||
ESP_ERROR_CHECK(bdc_motor_new_mcpwm_device(&motor_config, &mcpwm_config, &motor));
|
||||
motor_ctrl_ctx.motor = motor;
|
||||
|
||||
ESP_LOGI(TAG, "Init pcnt driver to decode rotary signal");
|
||||
pcnt_unit_config_t unit_config = {
|
||||
.high_limit = BDC_ENCODER_PCNT_HIGH_LIMIT,
|
||||
.low_limit = BDC_ENCODER_PCNT_LOW_LIMIT,
|
||||
@@ -186,14 +107,14 @@ void app_main(void)
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
|
||||
pcnt_chan_config_t chan_a_config = {
|
||||
.edge_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
|
||||
.level_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
|
||||
.edge_gpio_num = BDC_ENCODER_GPIO_A,
|
||||
.level_gpio_num = BDC_ENCODER_GPIO_B,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_a = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_a_config, &pcnt_chan_a));
|
||||
pcnt_chan_config_t chan_b_config = {
|
||||
.edge_gpio_num = BDC_ENCODER_PHASEB_GPIO_NUM,
|
||||
.level_gpio_num = BDC_ENCODER_PHASEA_GPIO_NUM,
|
||||
.edge_gpio_num = BDC_ENCODER_GPIO_B,
|
||||
.level_gpio_num = BDC_ENCODER_GPIO_A,
|
||||
};
|
||||
pcnt_channel_handle_t pcnt_chan_b = NULL;
|
||||
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_b_config, &pcnt_chan_b));
|
||||
@@ -204,70 +125,55 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_HIGH_LIMIT));
|
||||
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, BDC_ENCODER_PCNT_LOW_LIMIT));
|
||||
pcnt_event_callbacks_t pcnt_cbs = {
|
||||
.on_reach = example_pcnt_on_reach,
|
||||
.on_reach = example_pcnt_on_reach, // accumulate the overflow in the callback
|
||||
};
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &pcnt_cbs, &my_timer_ctx));
|
||||
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &pcnt_cbs, &motor_ctrl_ctx.accumu_count));
|
||||
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
|
||||
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
|
||||
motor_ctrl_ctx.pcnt_encoder = pcnt_unit;
|
||||
|
||||
printf("init PID control block\r\n");
|
||||
pid_ctrl_block_handle_t pid_ctrl;
|
||||
ESP_LOGI(TAG, "Create PID control block");
|
||||
pid_ctrl_parameter_t pid_runtime_param = {
|
||||
.kp = 0.6,
|
||||
.ki = 0.4,
|
||||
.kd = 0.2,
|
||||
.cal_type = PID_CAL_TYPE_INCREMENTAL,
|
||||
.max_output = BDC_MCPWM_DUTY_TICK_MAX - 1,
|
||||
.min_output = 0,
|
||||
.max_integral = 1000,
|
||||
.min_integral = -1000,
|
||||
};
|
||||
pid_ctrl_block_handle_t pid_ctrl = NULL;
|
||||
pid_ctrl_config_t pid_config = {
|
||||
.init_param = pid_runtime_param,
|
||||
};
|
||||
ESP_ERROR_CHECK(pid_new_control_block(&pid_config, &pid_ctrl));
|
||||
motor_ctrl_ctx.pid_ctrl = pid_ctrl;
|
||||
|
||||
printf("init motor control timer\r\n");
|
||||
gptimer_handle_t gptimer;
|
||||
gptimer_config_t timer_config = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = 1000000, // 1MHz, 1 tick = 1us
|
||||
ESP_LOGI(TAG, "Create a timer to do PID calculation periodically");
|
||||
const esp_timer_create_args_t periodic_timer_args = {
|
||||
.callback = pid_loop_cb,
|
||||
.arg = &motor_ctrl_ctx,
|
||||
.name = "pid_loop"
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
|
||||
esp_timer_handle_t pid_loop_timer = NULL;
|
||||
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &pid_loop_timer));
|
||||
|
||||
printf("create motor control task\r\n");
|
||||
static motor_control_task_context_t my_ctrl_task_ctx = {};
|
||||
my_ctrl_task_ctx.pid_feedback_queue = pid_fb_queue;
|
||||
my_ctrl_task_ctx.pid_ctrl = pid_ctrl;
|
||||
xTaskCreate(bdc_ctrl_task, "bdc_ctrl_task", 4096, &my_ctrl_task_ctx, 5, NULL);
|
||||
ESP_LOGI(TAG, "Enable motor");
|
||||
ESP_ERROR_CHECK(bdc_motor_enable(motor));
|
||||
ESP_LOGI(TAG, "Forward motor");
|
||||
ESP_ERROR_CHECK(bdc_motor_forward(motor));
|
||||
|
||||
printf("start motor control timer\r\n");
|
||||
my_timer_ctx.pid_feedback_queue = pid_fb_queue;
|
||||
my_timer_ctx.hall_pcnt_encoder = pcnt_unit;
|
||||
gptimer_event_callbacks_t gptimer_cbs = {
|
||||
.on_alarm = motor_ctrl_timer_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &gptimer_cbs, &my_timer_ctx));
|
||||
gptimer_alarm_config_t alarm_config = {
|
||||
.reload_count = 0,
|
||||
.alarm_count = BDC_PID_CALCULATION_PERIOD_US,
|
||||
.flags.auto_reload_on_alarm = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
|
||||
ESP_ERROR_CHECK(gptimer_enable(gptimer));
|
||||
ESP_ERROR_CHECK(gptimer_start(gptimer));
|
||||
|
||||
printf("install console command line\r\n");
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.prompt = "dc-motor>";
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
register_pid_console_command();
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
ESP_LOGI(TAG, "Start motor speed loop");
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(pid_loop_timer, BDC_PID_LOOP_PERIOD_MS * 1000));
|
||||
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
// the following logging format is according to the requirement of serial-studio
|
||||
// also see the parser mapping file `serial-studio-proto-map.json` in the project folder
|
||||
// the following logging format is according to the requirement of serial-studio frame format
|
||||
// also see the dashboard config file `serial-studio-dashboard.json` for more information
|
||||
#if SERIAL_STUDIO_DEBUG
|
||||
printf("/*%d*/\r\n", real_pulses);
|
||||
printf("/*%d*/\r\n", motor_ctrl_ctx.report_pulses);
|
||||
#endif
|
||||
if (pid_need_update) {
|
||||
pid_update_parameters(pid_ctrl, &pid_runtime_param);
|
||||
pid_need_update = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.generic
|
||||
def test_bdc_speed_control_example(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Create DC motor')
|
||||
dut.expect_exact('example: Init pcnt driver to decode rotary signal')
|
||||
dut.expect_exact('example: Create PID control block')
|
||||
dut.expect_exact('example: Create a timer to do PID calculation periodically')
|
||||
dut.expect_exact('example: Enable motor')
|
||||
dut.expect_exact('example: Forward motor')
|
||||
dut.expect_exact('example: Start motor speed loop')
|
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"frameEnd": "*/",
|
||||
"frameStart": "/*",
|
||||
"groups": [
|
||||
{
|
||||
"datasets": [
|
||||
{
|
||||
"alarm": 1000,
|
||||
"fft": false,
|
||||
"fftSamples": 1024,
|
||||
"graph": true,
|
||||
"led": false,
|
||||
"log": false,
|
||||
"max": 500,
|
||||
"min": 0,
|
||||
"title": "current speed",
|
||||
"units": "",
|
||||
"value": "%1",
|
||||
"widget": "gauge"
|
||||
}
|
||||
],
|
||||
"title": "speed",
|
||||
"widget": ""
|
||||
}
|
||||
],
|
||||
"separator": ",",
|
||||
"title": "Brushed DC Motor Speed"
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"fe": "*/",
|
||||
"fs": "/*",
|
||||
"g": [
|
||||
{
|
||||
"d": [
|
||||
{
|
||||
"g": true,
|
||||
"max": 100,
|
||||
"min": 0,
|
||||
"t": "pulses within 10ms",
|
||||
"u": "",
|
||||
"v": "%1",
|
||||
"w": ""
|
||||
}
|
||||
],
|
||||
"t": "Encoder Feedback",
|
||||
}
|
||||
],
|
||||
"s": ",",
|
||||
"t": "Brushed DC Motor Speed"
|
||||
}
|
@@ -1,51 +1,54 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# MCPWM BLDC Hall motor control Example
|
||||
# MCPWM BLDC Motor Control with HALL Sensor Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example will illustrate how to use MCPWM driver to control BLDC motor with hall sensor feedback. In the example, a timer is running at the background to update the motor speed periodically.
|
||||
|
||||
With the hardware fault detection feature of MCPWM, the example will shut down the MOSFETs when over current happens.
|
||||
The MCPWM peripheral can generate three pairs of complementary PWMs by the internal dead time submodule, which is suitable for a BLDC motor application. This example demonstrates how to use the MCPWM peripheral to control a BLDC motor in a six-step commutation scheme.
|
||||
We will change the on/off state of the six MOSFETs in a predefined order when the Hall sensor detects a change of the motor phase, so that the motor can spin in a predefined direction.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
1. The BLDC motor used in this example has a hall sensor capture sequence of `6-->4-->5-->1-->3-->2-->6-->4-->` and so on.
|
||||
2. A three-phase gate driver, this example uses [IR2136](https://www.infineon.com/cms/en/product/power/gate-driver-ics/ir2136s/).
|
||||
3. Six N-MOSFETs, this example uses [IRF540NS](https://www.infineon.com/cms/en/product/power/mosfet/12v-300v-n-channel-power-mosfet/irf540ns/).
|
||||
4. A development board with any Espressif SoC which features MCPWM peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
5. A USB cable for Power supply and programming.
|
||||
1. A ESP board with MCPWM peripheral supported (e.g. ESP32-S3-Motor-DevKit)
|
||||
2. A BLDC motor whose commutation table is `6-->4-->5-->1-->3-->2-->6`
|
||||
3. A three-phase gate driver, for example, the [DRV8302](https://www.ti.com.cn/product/zh-cn/DRV8302)
|
||||
4. Six N-MOSFETs, for example, the [IRF540NS](https://www.infineon.com/cms/en/product/power/mosfet/12v-300v-n-channel-power-mosfet/irf540ns/)
|
||||
5. A USB cable for Power supply and programming
|
||||
|
||||
Connection :
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌───────────────────────────┐ │
|
||||
│ │ │ │
|
||||
┌─────────┴─────────┴───────────┐ ┌────────┴───────┴──────────┐
|
||||
│ GPIO19 GPIO18 │ │ EN FAULT │
|
||||
│ GPIO21├──────┤PWM_UH │ ┌────────────┐
|
||||
│ GPIO22├──────┤PWM_UL U├────────┤ │
|
||||
│ │ │ │ │ │
|
||||
│ GPIO23├──────┤PWM_VH V├────────┤ BLDC │
|
||||
│ ESP Board GPIO25├──────┤PWM_VL 3-Phase Bridge │ │ │
|
||||
│ │ │ + W├────────┤ │
|
||||
│ GPIO26├──────┤PWM_WH MOSFET │ └─┬───┬───┬──┘
|
||||
│ GPIO27├──────┤PWM_WL │ │ │ │
|
||||
│ GPIO5 GPIO4 GPIO2 │ │ │ │ │ │
|
||||
└─────┬──────┬──────┬───────────┘ └───────────────────────────┘ │ │ │
|
||||
│ │ │ Hall U │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
||||
│ │ Hall V │ │
|
||||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||||
│ Hall W │
|
||||
└───────────────────────────────────────────────────────────────────────────────┘
|
||||
+---------------------------------------------------------------------------------+
|
||||
| |
|
||||
| +---------------------------------------------+ | VM
|
||||
| | | | ^
|
||||
| | +---------------------------+ | | |
|
||||
| | | | | | |
|
||||
+-------------+-----------------------------+---------+-----------+ +--------+-------+-----+---++
|
||||
| GND BLDC_DRV_FAULT_GPIO BLDC_DRV_EN_GPIO | | EN FAULT GND |
|
||||
| BLDC_PWM_UH_GPIO +------+PWM_UH | +------------+
|
||||
| BLDC_PWM_UL_GPIO +------+PWM_UL U+--------+ |
|
||||
| | | | | |
|
||||
| ESP Board BLDC_PWM_VH_GPIO +------+PWM_VH V+--------+ BLDC |
|
||||
| BLDC_PWM_VL_GPIO +------+PWM_VL 3-Phase Bridge | | |
|
||||
| | | + W+--------+ |
|
||||
| BLDC_PWM_WH_GPIO +------+PWM_WH MOSFET | +-+---+---+--+
|
||||
| BLDC_PWM_WL_GPIO +------+PWM_WL | | | |
|
||||
| HALL_CAP_W_GPIO HALL_CAP_V_GPIO HALL_CAP_U_GPIO | | | | | |
|
||||
+-----------+------------------+------------------+---------------+ +---------------------------+ | | |
|
||||
| | | Hall U | | |
|
||||
| | +-------------------------------------------------------------+ | |
|
||||
| | Hall V | |
|
||||
| +------------------------------------------------------------------------------------+ |
|
||||
| Hall W |
|
||||
+-----------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
You can change the GPIO number in the [example code](main/mcpwm_bldc_hall_control_example_main.c) according to your board. You can define the spin direction in the code as well by the `BLDC_SPIN_DIRECTION_CCW` macro.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
@@ -62,20 +65,42 @@ Run the example, you will see the following output log:
|
||||
```
|
||||
...
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (327) example: Disable gate driver
|
||||
I (327) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (337) example: Setup PWM and Hall GPIO (pull up internally)
|
||||
I (347) example: Initialize PWM (default to turn off all MOSFET)
|
||||
I (357) example: Initialize over current fault action
|
||||
I (357) example: Initialize Hall sensor capture
|
||||
I (367) example: Please turn on the motor power
|
||||
I (5367) example: Enable gate driver
|
||||
I (5367) example: Changing speed at background
|
||||
I (307) example: Disable MOSFET gate
|
||||
I (307) gpio: GPIO[46]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (317) example: Create MCPWM timer
|
||||
I (317) example: Create MCPWM operator
|
||||
I (327) example: Connect operators to the same timer
|
||||
I (327) example: Create comparators
|
||||
I (337) example: Create over current fault detector
|
||||
I (337) gpio: GPIO[10]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (347) example: Set brake mode on the fault event
|
||||
I (357) example: Create PWM generators
|
||||
I (357) gpio: GPIO[47]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (367) gpio: GPIO[21]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (377) gpio: GPIO[14]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (387) gpio: GPIO[13]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (397) gpio: GPIO[12]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (407) gpio: GPIO[11]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (417) example: Set generator actions
|
||||
I (417) example: Setup deadtime
|
||||
I (427) example: Turn off all the gates by default
|
||||
I (427) example: Create Hall sensor capture channels
|
||||
I (437) gpio: GPIO[4]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (447) gpio: GPIO[5]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (457) gpio: GPIO[6]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (457) example: Register event callback for capture channels
|
||||
I (467) example: Start a timer to adjust motor speed periodically
|
||||
I (477) example: Enable MOSFET gate
|
||||
I (477) example: Start the MCPWM timer
|
||||
...
|
||||
```
|
||||
|
||||
## Dive into the example
|
||||
The BLDC motor will update the speed periodically.
|
||||
|
||||
1. How to change the rotation direction?
|
||||
## Troubleshooting
|
||||
|
||||
The rotation direction is controlled by how the hall sensor value is parsed. If you pass `false` to `bldc_get_hall_sensor_value`, the BLDC should rotate in clock wise. Likewise, passing `true` to that function will make tha BLDC rotate in counter clock wise.
|
||||
* Make sure your ESP board and H-bridge module have been connected to the same GND panel.
|
||||
* Check the fault signal polarity, otherwise the motor will not spin if the MCPWM detector treats the normal level as a fault one.
|
||||
* Don't use the PC USB as the power source of the BLDC motor (see the `VM` in the above connection diagram), it might damage your UAB port.
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
||||
|
@@ -8,47 +8,46 @@
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "driver/mcpwm_prelude.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define PWM_DEFAULT_FREQ 14400
|
||||
#define PWM_MIN_DUTY 40.0
|
||||
#define PWM_MAX_DUTY 80.0
|
||||
#define PWM_DUTY_STEP 5.0
|
||||
#define BLDC_MCPWM_GROUP 0
|
||||
#define BLDC_MCPWM_TIMER_U 0
|
||||
#define BLDC_MCPWM_TIMER_V 1
|
||||
#define BLDC_MCPWM_TIMER_W 2
|
||||
#define BLDC_MCPWM_GEN_HIGH MCPWM_GEN_A
|
||||
#define BLDC_MCPWM_GEN_LOW MCPWM_GEN_B
|
||||
#define BLDC_MCPWM_TIMER_RESOLUTION_HZ 10000000 // 10MHz, 1 tick = 0.1us
|
||||
#define BLDC_MCPWM_PERIOD 500 // 50us, 20KHz
|
||||
#define BLDC_SPIN_DIRECTION_CCW false // define the spin direction
|
||||
#define BLDC_SPEED_UPDATE_PERIOD_US 200000 // 200ms
|
||||
|
||||
#define BLDC_DRV_EN_GPIO 46
|
||||
#define BLDC_DRV_FAULT_GPIO 10
|
||||
#define BLDC_PWM_UH_GPIO 47
|
||||
#define BLDC_PWM_UL_GPIO 21
|
||||
#define BLDC_PWM_VH_GPIO 14
|
||||
#define BLDC_PWM_VL_GPIO 13
|
||||
#define BLDC_PWM_WH_GPIO 12
|
||||
#define BLDC_PWM_WL_GPIO 11
|
||||
#define HALL_CAP_U_GPIO 4
|
||||
#define HALL_CAP_V_GPIO 5
|
||||
#define HALL_CAP_W_GPIO 6
|
||||
|
||||
#define BLDC_DRV_EN_GPIO 18
|
||||
#define BLDC_DRV_FAULT_GPIO 19
|
||||
#define BLDC_DRV_OVER_CURRENT_FAULT MCPWM_SELECT_F0
|
||||
|
||||
#define BLDC_PWM_UH_GPIO 21
|
||||
#define BLDC_PWM_UL_GPIO 22
|
||||
#define BLDC_PWM_VH_GPIO 23
|
||||
#define BLDC_PWM_VL_GPIO 25
|
||||
#define BLDC_PWM_WH_GPIO 26
|
||||
#define BLDC_PWM_WL_GPIO 27
|
||||
#define HALL_CAP_U_GPIO 2
|
||||
#define HALL_CAP_V_GPIO 4
|
||||
#define HALL_CAP_W_GPIO 5
|
||||
#define BLDC_MCPWM_OP_INDEX_U 0
|
||||
#define BLDC_MCPWM_OP_INDEX_V 1
|
||||
#define BLDC_MCPWM_OP_INDEX_W 2
|
||||
#define BLDC_MCPWM_GEN_INDEX_HIGH 0
|
||||
#define BLDC_MCPWM_GEN_INDEX_LOW 1
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
typedef void (*bldc_hall_phase_action_t)(mcpwm_gen_handle_t (*gens)[2]);
|
||||
|
||||
static inline uint32_t bldc_get_hall_sensor_value(bool ccw)
|
||||
{
|
||||
uint32_t hall_val = gpio_get_level(HALL_CAP_U_GPIO) * 4 + gpio_get_level(HALL_CAP_V_GPIO) * 2 + gpio_get_level(HALL_CAP_W_GPIO) * 1;
|
||||
return ccw ? hall_val ^ (0x07) : hall_val;
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR bldc_hall_updated(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_channel, const cap_event_data_t *edata, void *user_data)
|
||||
static bool IRAM_ATTR bldc_hall_updated(mcpwm_cap_channel_handle_t cap_channel, const mcpwm_capture_event_data_t *edata, void *user_data)
|
||||
{
|
||||
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
@@ -56,101 +55,99 @@ static bool IRAM_ATTR bldc_hall_updated(mcpwm_unit_t mcpwm, mcpwm_capture_channe
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
static void update_bldc_speed(void *arg)
|
||||
// U+V-
|
||||
static void bldc_set_phase_up_vm(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
static float duty = PWM_MIN_DUTY;
|
||||
static float duty_step = PWM_DUTY_STEP;
|
||||
duty += duty_step;
|
||||
if (duty > PWM_MAX_DUTY || duty < PWM_MIN_DUTY) {
|
||||
duty_step *= -1;
|
||||
}
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH, duty);
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_LOW, duty);
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH, duty);
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_LOW, duty);
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH, duty);
|
||||
mcpwm_set_duty(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_LOW, duty);
|
||||
// U+ = PWM, U- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
// V+ = 0, V- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
// W+ = 0, W- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
}
|
||||
|
||||
// U+V- / A+B-
|
||||
static void bldc_set_phase_up_vm(void)
|
||||
// W+U-
|
||||
static void bldc_set_phase_wp_um(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // U+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // U- = _PWM_
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH); // V+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_LOW); // V- = 1
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH); // W+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_LOW); // W- = 0
|
||||
// U+ = 0, U- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
|
||||
// V+ = 0, V- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
|
||||
// W+ = PWM, W- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
}
|
||||
|
||||
// W+U- / C+A-
|
||||
static void bldc_set_phase_wp_um(void)
|
||||
// W+V-
|
||||
static void bldc_set_phase_wp_vm(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH); // U+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_LOW); // U- = 1
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH); // V+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_LOW); // V- = 0
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // W+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // W- = _PWM_
|
||||
// U+ = 0, U- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
|
||||
// V+ = 0, V- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
|
||||
// W+ = PWM, W- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
}
|
||||
|
||||
// W+V- / C+B-
|
||||
static void bldc_set_phase_wp_vm(void)
|
||||
// V+U-
|
||||
static void bldc_set_phase_vp_um(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH); // U+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_LOW); // U- = 0
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH); // V+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_LOW); // V- = 1
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // W+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // W- = _PWM_
|
||||
// U+ = 0, U- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
|
||||
// V+ = PWM, V- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
|
||||
// W+ = 0, W- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
}
|
||||
|
||||
// V+U- / B+A-
|
||||
static void bldc_set_phase_vp_um(void)
|
||||
// V+W-
|
||||
static void bldc_set_phase_vp_wm(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH); // U+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_LOW); // U- = 1
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // V+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // V- = _PWM_
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH); // W+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_LOW); // W- = 0
|
||||
}
|
||||
// U+ = 0, U- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
|
||||
// V+W- / B+C-
|
||||
static void bldc_set_phase_vp_wm(void)
|
||||
{
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH); // U+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_LOW); // U- = 0
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // V+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // V- = _PWM_
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH); // W+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_LOW); // W- = 1
|
||||
// V+ = PWM, V- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
|
||||
// W+ = 0, W- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
}
|
||||
|
||||
// U+W- / A+C-
|
||||
static void bldc_set_phase_up_wm(void)
|
||||
static void bldc_set_phase_up_wm(mcpwm_gen_handle_t (*gens)[2])
|
||||
{
|
||||
mcpwm_set_duty_type(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_MCPWM_GEN_HIGH, MCPWM_DUTY_MODE_0); // U+ = PWM
|
||||
mcpwm_deadtime_enable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, MCPWM_ACTIVE_HIGH_COMPLIMENT_MODE, 3, 3); // U- = _PWM_
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_HIGH); // V+ = 0
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_MCPWM_GEN_LOW); // V- = 0
|
||||
mcpwm_deadtime_disable(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W);
|
||||
mcpwm_set_signal_low(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_HIGH); // W+ = 0
|
||||
mcpwm_set_signal_high(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_MCPWM_GEN_LOW); // W- = 1
|
||||
}
|
||||
// U+ = PWM, U- = _PWM_
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_HIGH], -1, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_U][BLDC_MCPWM_GEN_INDEX_LOW], -1, true);
|
||||
|
||||
typedef void (*bldc_hall_phase_action_t)(void);
|
||||
// V+ = 0, V- = 0
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_V][BLDC_MCPWM_GEN_INDEX_LOW], 0, true);
|
||||
|
||||
// W+ = 0, W- = 1
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_HIGH], 0, true);
|
||||
mcpwm_generator_set_force_level(gens[BLDC_MCPWM_OP_INDEX_W][BLDC_MCPWM_GEN_INDEX_LOW], 1, true);
|
||||
}
|
||||
|
||||
static const bldc_hall_phase_action_t s_hall_actions[] = {
|
||||
[2] = bldc_set_phase_up_vm,
|
||||
@@ -161,90 +158,191 @@ static const bldc_hall_phase_action_t s_hall_actions[] = {
|
||||
[3] = bldc_set_phase_up_wm,
|
||||
};
|
||||
|
||||
static void update_motor_speed_callback(void *arg)
|
||||
{
|
||||
static int step = 20;
|
||||
static int cur_speed = 0;
|
||||
if ((cur_speed + step) > 300 || (cur_speed + step) < 0) {
|
||||
step *= -1;
|
||||
}
|
||||
cur_speed += step;
|
||||
|
||||
mcpwm_cmpr_handle_t *cmprs = (mcpwm_cmpr_handle_t *)arg;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(cmprs[i], cur_speed));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uint32_t hall_sensor_value = 0;
|
||||
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
|
||||
|
||||
ESP_LOGI(TAG, "Disable gate driver");
|
||||
ESP_LOGI(TAG, "Disable MOSFET gate");
|
||||
gpio_config_t drv_en_config = {
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pin_bit_mask = 1 << BLDC_DRV_EN_GPIO,
|
||||
.pin_bit_mask = 1ULL << BLDC_DRV_EN_GPIO,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&drv_en_config));
|
||||
gpio_set_level(BLDC_DRV_EN_GPIO, 0);
|
||||
|
||||
ESP_LOGI(TAG, "Setup PWM and Hall GPIO (pull up internally)");
|
||||
mcpwm_pin_config_t mcpwm_gpio_config = {
|
||||
.mcpwm0a_out_num = BLDC_PWM_UH_GPIO,
|
||||
.mcpwm0b_out_num = BLDC_PWM_UL_GPIO,
|
||||
.mcpwm1a_out_num = BLDC_PWM_VH_GPIO,
|
||||
.mcpwm1b_out_num = BLDC_PWM_VL_GPIO,
|
||||
.mcpwm2a_out_num = BLDC_PWM_WH_GPIO,
|
||||
.mcpwm2b_out_num = BLDC_PWM_WL_GPIO,
|
||||
.mcpwm_cap0_in_num = HALL_CAP_U_GPIO,
|
||||
.mcpwm_cap1_in_num = HALL_CAP_V_GPIO,
|
||||
.mcpwm_cap2_in_num = HALL_CAP_W_GPIO,
|
||||
.mcpwm_sync0_in_num = -1, //Not used
|
||||
.mcpwm_sync1_in_num = -1, //Not used
|
||||
.mcpwm_sync2_in_num = -1, //Not used
|
||||
.mcpwm_fault0_in_num = BLDC_DRV_FAULT_GPIO,
|
||||
.mcpwm_fault1_in_num = -1, //Not used
|
||||
.mcpwm_fault2_in_num = -1 //Not used
|
||||
ESP_LOGI(TAG, "Create MCPWM timer");
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = BLDC_MCPWM_TIMER_RESOLUTION_HZ,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
.period_ticks = BLDC_MCPWM_PERIOD,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_set_pin(BLDC_MCPWM_GROUP, &mcpwm_gpio_config));
|
||||
// In case there's no pull-up resister for hall sensor on board
|
||||
gpio_pullup_en(HALL_CAP_U_GPIO);
|
||||
gpio_pullup_en(HALL_CAP_V_GPIO);
|
||||
gpio_pullup_en(HALL_CAP_W_GPIO);
|
||||
gpio_pullup_en(BLDC_DRV_FAULT_GPIO);
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
ESP_LOGI(TAG, "Initialize PWM (default to turn off all MOSFET)");
|
||||
mcpwm_config_t pwm_config = {
|
||||
.frequency = PWM_DEFAULT_FREQ,
|
||||
.cmpr_a = PWM_MIN_DUTY,
|
||||
.cmpr_b = PWM_MIN_DUTY,
|
||||
.counter_mode = MCPWM_UP_COUNTER,
|
||||
.duty_mode = MCPWM_HAL_GENERATOR_MODE_FORCE_LOW,
|
||||
ESP_LOGI(TAG, "Create MCPWM operator");
|
||||
mcpwm_oper_handle_t operators[3];
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_init(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, &pwm_config));
|
||||
ESP_ERROR_CHECK(mcpwm_init(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, &pwm_config));
|
||||
ESP_ERROR_CHECK(mcpwm_init(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, &pwm_config));
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &operators[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialize over current fault action");
|
||||
ESP_ERROR_CHECK(mcpwm_fault_init(BLDC_MCPWM_GROUP, MCPWM_LOW_LEVEL_TGR, BLDC_DRV_OVER_CURRENT_FAULT));
|
||||
ESP_ERROR_CHECK(mcpwm_fault_set_cyc_mode(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_U, BLDC_DRV_OVER_CURRENT_FAULT, MCPWM_ACTION_FORCE_LOW, MCPWM_ACTION_FORCE_LOW));
|
||||
ESP_ERROR_CHECK(mcpwm_fault_set_cyc_mode(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_V, BLDC_DRV_OVER_CURRENT_FAULT, MCPWM_ACTION_FORCE_LOW, MCPWM_ACTION_FORCE_LOW));
|
||||
ESP_ERROR_CHECK(mcpwm_fault_set_cyc_mode(BLDC_MCPWM_GROUP, BLDC_MCPWM_TIMER_W, BLDC_DRV_OVER_CURRENT_FAULT, MCPWM_ACTION_FORCE_LOW, MCPWM_ACTION_FORCE_LOW));
|
||||
ESP_LOGI(TAG, "Connect operators to the same timer");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators[i], timer));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initialize Hall sensor capture");
|
||||
mcpwm_capture_config_t cap_config = {
|
||||
.cap_edge = MCPWM_BOTH_EDGE,
|
||||
.cap_prescale = 1,
|
||||
.capture_cb = bldc_hall_updated,
|
||||
.user_data = cur_task,
|
||||
ESP_LOGI(TAG, "Create comparators");
|
||||
mcpwm_cmpr_handle_t comparators[3];
|
||||
mcpwm_comparator_config_t compare_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(BLDC_MCPWM_GROUP, 0, &cap_config));
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(BLDC_MCPWM_GROUP, 1, &cap_config));
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(BLDC_MCPWM_GROUP, 2, &cap_config));
|
||||
ESP_LOGI(TAG, "Please turn on the motor power");
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
ESP_LOGI(TAG, "Enable gate driver");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_new_comparator(operators[i], &compare_config, &comparators[i]));
|
||||
// set compare value to 0, we will adjust the speed in a period timer callback
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators[i], 0));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create over current fault detector");
|
||||
mcpwm_fault_handle_t over_cur_fault = NULL;
|
||||
mcpwm_gpio_fault_config_t gpio_fault_config = {
|
||||
.gpio_num = BLDC_DRV_FAULT_GPIO,
|
||||
.group_id = 0,
|
||||
.flags.active_level = 0, // low level means fault, refer to DRV8302 datasheet
|
||||
.flags.pull_up = true, // internally pull up
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_gpio_fault(&gpio_fault_config, &over_cur_fault));
|
||||
|
||||
ESP_LOGI(TAG, "Set brake mode on the fault event");
|
||||
mcpwm_brake_config_t brake_config = {
|
||||
.brake_mode = MCPWM_OPER_BRAKE_MODE_CBC,
|
||||
.fault = over_cur_fault,
|
||||
.flags.cbc_recover_on_tez = true,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_operator_set_brake_on_fault(operators[i], &brake_config));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create PWM generators");
|
||||
mcpwm_gen_handle_t generators[3][2] = {};
|
||||
mcpwm_generator_config_t gen_config = {};
|
||||
const int gen_gpios[3][2] = {
|
||||
{BLDC_PWM_UH_GPIO, BLDC_PWM_UL_GPIO},
|
||||
{BLDC_PWM_VH_GPIO, BLDC_PWM_VL_GPIO},
|
||||
{BLDC_PWM_WH_GPIO, BLDC_PWM_WL_GPIO},
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
gen_config.gen_gpio_num = gen_gpios[i][j];
|
||||
ESP_ERROR_CHECK(mcpwm_new_generator(operators[i], &gen_config, &generators[i][j]));
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Set generator actions");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH],
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH],
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators[i], MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_brake_event(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH],
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_brake_event(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH],
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_CBC, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_BRAKE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Setup deadtime");
|
||||
mcpwm_dead_time_config_t dt_config = {
|
||||
.posedge_delay_ticks = 5,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH], generators[i][BLDC_MCPWM_GEN_INDEX_HIGH], &dt_config));
|
||||
}
|
||||
dt_config = (mcpwm_dead_time_config_t) {
|
||||
.negedge_delay_ticks = 5,
|
||||
.flags.invert_output = true,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(generators[i][BLDC_MCPWM_GEN_INDEX_HIGH], generators[i][BLDC_MCPWM_GEN_INDEX_LOW], &dt_config));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Turn off all the gates");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 2; j++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators[i][j], 0, true));
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create Hall sensor capture channels");
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
mcpwm_capture_timer_config_t cap_timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_timer(&cap_timer_config, &cap_timer));
|
||||
mcpwm_cap_channel_handle_t cap_channels[3];
|
||||
mcpwm_capture_channel_config_t cap_channel_config = {
|
||||
.prescale = 1,
|
||||
.flags.pull_up = true,
|
||||
.flags.neg_edge = true,
|
||||
.flags.pos_edge = true,
|
||||
};
|
||||
const int cap_chan_gpios[3] = {HALL_CAP_U_GPIO, HALL_CAP_V_GPIO, HALL_CAP_W_GPIO};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
cap_channel_config.gpio_num = cap_chan_gpios[i];
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, &cap_channel_config, &cap_channels[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Register event callback for capture channels");
|
||||
TaskHandle_t task_to_notify = xTaskGetCurrentTaskHandle();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
mcpwm_capture_event_callbacks_t cbs = {
|
||||
.on_cap = bldc_hall_updated,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_channels[i], &cbs, task_to_notify));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Start a timer to adjust motor speed periodically");
|
||||
esp_timer_handle_t periodic_timer = NULL;
|
||||
const esp_timer_create_args_t periodic_timer_args = {
|
||||
.callback = update_motor_speed_callback,
|
||||
.arg = comparators,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, BLDC_SPEED_UPDATE_PERIOD_US));
|
||||
|
||||
ESP_LOGI(TAG, "Enable MOSFET gate");
|
||||
gpio_set_level(BLDC_DRV_EN_GPIO, 1);
|
||||
ESP_LOGI(TAG, "Changing speed at background");
|
||||
const esp_timer_create_args_t bldc_timer_args = {
|
||||
.callback = update_bldc_speed,
|
||||
.name = "bldc_speed"
|
||||
};
|
||||
esp_timer_handle_t bldc_speed_timer;
|
||||
ESP_ERROR_CHECK(esp_timer_create(&bldc_timer_args, &bldc_speed_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(bldc_speed_timer, 2000000));
|
||||
|
||||
ESP_LOGI(TAG, "Start the MCPWM timer");
|
||||
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
uint32_t hall_sensor_value = 0;
|
||||
while (1) {
|
||||
// The rotation direction is controlled by inverting the hall sensor value
|
||||
hall_sensor_value = bldc_get_hall_sensor_value(false);
|
||||
if (hall_sensor_value >= 1 && hall_sensor_value < sizeof(s_hall_actions) / sizeof(s_hall_actions[0])) {
|
||||
s_hall_actions[hall_sensor_value]();
|
||||
hall_sensor_value = bldc_get_hall_sensor_value(BLDC_SPIN_DIRECTION_CCW);
|
||||
if (hall_sensor_value >= 1 && hall_sensor_value <= 6) {
|
||||
s_hall_actions[hall_sensor_value](generators);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "invalid bldc phase, wrong hall sensor value:%d", hall_sensor_value);
|
||||
}
|
||||
|
@@ -0,0 +1,25 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32s3
|
||||
@pytest.mark.generic
|
||||
def test_bldc_hall_control_example(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Disable MOSFET gate')
|
||||
dut.expect_exact('example: Create MCPWM timer')
|
||||
dut.expect_exact('example: Create MCPWM operator')
|
||||
dut.expect_exact('example: Connect operators to the same timer')
|
||||
dut.expect_exact('example: Create comparators')
|
||||
dut.expect_exact('example: Create over current fault detector')
|
||||
dut.expect_exact('example: Set brake mode on the fault event')
|
||||
dut.expect_exact('example: Create PWM generators')
|
||||
dut.expect_exact('example: Set generator actions')
|
||||
dut.expect_exact('example: Setup deadtime')
|
||||
dut.expect_exact('example: Turn off all the gates')
|
||||
dut.expect_exact('example: Create Hall sensor capture channels')
|
||||
dut.expect_exact('example: Start a timer to adjust motor speed periodically')
|
||||
dut.expect_exact('example: Enable MOSFET gate')
|
||||
dut.expect_exact('example: Start the MCPWM timer')
|
@@ -1,14 +1,15 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# HC-SR04 Example
|
||||
# HC-SR04 Example based on MCPWM Capture
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The capture module in MCPWM peripheral is designed to accurately log the time stamp on the hardware side when an event happens (compared to GPIO ISR which requires a software-based logging method). Each capture unit has three channels, which can be used together to capture IO events parallelly.
|
||||
This example shows how to make use of the HW features to decode the pulse width signals generated from a common HC-SR04 sonar range finder -- [HC-SR04](https://www.sparkfun.com/products/15569).
|
||||
The capture module in MCPWM peripheral is designed to accurately log the time stamp on the hardware side when an event happens (compared to GPIO ISR which requires a software-based logging method). Each capture unit has three channels, which can be used together to capture IO events in parallel.
|
||||
|
||||
The signal that HC-SR04 produces (and what can be handled by this example) is a simple pulse whose width indicates the measured distance. A pulse is required to send to HC-SR04 on `Trig` pin to begin a new measurement. Then the pulse described above will be sent back on `Echo` pin for decoding.
|
||||
This example shows how to make use of the hardware features to decode the pulse width signals generated from a common HC-SR04 sonar sensor -- [HC-SR04](https://www.sparkfun.com/products/15569).
|
||||
|
||||
The signal that HC-SR04 produces (and what can be handled by this example) is a simple pulse whose width indicates the measured distance. An excitation pulse is required to send to HC-SR04 on `Trig` pin to begin a new measurement. Then the pulse described above will appear on the `Echo` pin after a while.
|
||||
|
||||
Typical signals:
|
||||
|
||||
@@ -30,8 +31,8 @@ Echo +-----+
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* An ESP development board
|
||||
* HC-SR04 module
|
||||
* An ESP development board that features the MCPWM peripheral
|
||||
* An HC-SR04 sensor module
|
||||
|
||||
Connection :
|
||||
|
||||
@@ -60,21 +61,24 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (314) hc-sr04: HC-SR04 example based on capture function from MCPWM
|
||||
I (324) hc-sr04: Echo pin configured
|
||||
I (324) gpio: GPIO[19]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (334) hc-sr04: Trig pin configured
|
||||
I (344) hc-sr04: trig task started
|
||||
I (444) hc-sr04: Pulse width: 419us, Measured distance: 7.22cm
|
||||
I (544) hc-sr04: Pulse width: 419us, Measured distance: 7.22cm
|
||||
I (644) hc-sr04: Pulse width: 416us, Measured distance: 7.17cm
|
||||
I (744) hc-sr04: Pulse width: 415us, Measured distance: 7.16cm
|
||||
I (844) hc-sr04: Pulse width: 415us, Measured distance: 7.16cm
|
||||
I (944) hc-sr04: Pulse width: 416us, Measured distance: 7.17cm
|
||||
I (1044) hc-sr04: Pulse width: 391us, Measured distance: 6.74cm
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (304) example: Create capture queue
|
||||
I (304) example: Install capture timer
|
||||
I (304) example: Install capture channel
|
||||
I (314) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (324) example: Register capture callback
|
||||
I (324) example: Create a timer to trig HC_SR04 periodically
|
||||
I (334) example: Configure Trig pin
|
||||
I (334) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (344) example: Enable and start capture timer
|
||||
I (434) example: Pulse width: 189.02us, Measured distance: 3.26cm
|
||||
I (534) example: Pulse width: 189.02us, Measured distance: 3.26cm
|
||||
I (634) example: Pulse width: 189.01us, Measured distance: 3.26cm
|
||||
I (734) example: Pulse width: 188.98us, Measured distance: 3.26cm
|
||||
I (834) example: Pulse width: 188.99us, Measured distance: 3.26cm
|
||||
```
|
||||
|
||||
This example runs at 10Hz sampling rate. out of range data is dropped and only valid measurement is printed.
|
||||
This example runs at 10Hz sampling rate. Measure data that out of the range is dropped and only valid measurement is printed out.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
@@ -6,119 +6,111 @@
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "driver/mcpwm_cap.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
const static char *TAG = "hc-sr04";
|
||||
const static char *TAG = "example";
|
||||
|
||||
#define HC_SR04_SAMPLE_PERIOD_MS 100
|
||||
_Static_assert(HC_SR04_SAMPLE_PERIOD_MS > 50, "Sample period too short!");
|
||||
#define HC_SR04_PIN_ECHO GPIO_NUM_18
|
||||
#define HC_SR04_PIN_TRIG GPIO_NUM_19
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//////////////////// Please update the following configuration according to your board spec ////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#define HC_SR04_TRIG_GPIO 0
|
||||
#define HC_SR04_ECHO_GPIO 2
|
||||
|
||||
#define TRIGGER_THREAD_STACK_SIZE 512
|
||||
#define TRIGGER_THREAD_PRIORITY 5
|
||||
|
||||
typedef struct {
|
||||
uint32_t capture_signal;
|
||||
mcpwm_capture_signal_t sel_cap_signal;
|
||||
} capture;
|
||||
|
||||
static uint32_t cap_val_begin_of_sample = 0;
|
||||
static uint32_t cap_val_end_of_sample = 0;
|
||||
|
||||
static QueueHandle_t cap_queue;
|
||||
|
||||
/**
|
||||
* @brief generate single pulse on Trig pin to activate a new sample
|
||||
*/
|
||||
static void gen_trig_output(void *arg) {
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
while (true) {
|
||||
vTaskDelayUntil(&xLastWakeTime, HC_SR04_SAMPLE_PERIOD_MS / portTICK_PERIOD_MS);
|
||||
ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 1)); // set high
|
||||
esp_rom_delay_us(10);
|
||||
ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 0)); // set low
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief this is an ISR callback, we take action according to the captured edge
|
||||
*/
|
||||
static bool sr04_echo_isr_handler(mcpwm_unit_t mcpwm, mcpwm_capture_channel_id_t cap_sig, const cap_event_data_t *edata,
|
||||
void *arg) {
|
||||
//calculate the interval in the ISR,
|
||||
//so that the interval will be always correct even when cap_queue is not handled in time and overflow.
|
||||
static bool hc_sr04_echo_callback(mcpwm_cap_channel_handle_t cap_chan, const mcpwm_capture_event_data_t *edata, void *user_data)
|
||||
{
|
||||
static uint32_t cap_val_begin_of_sample = 0;
|
||||
static uint32_t cap_val_end_of_sample = 0;
|
||||
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
if (edata->cap_edge == MCPWM_POS_EDGE) {
|
||||
|
||||
//calculate the interval in the ISR,
|
||||
//so that the interval will be always correct even when capture_queue is not handled in time and overflow.
|
||||
if (edata->cap_edge == MCPWM_CAP_EDGE_POS) {
|
||||
// store the timestamp when pos edge is detected
|
||||
cap_val_begin_of_sample = edata->cap_value;
|
||||
cap_val_end_of_sample = cap_val_begin_of_sample;
|
||||
} else {
|
||||
cap_val_end_of_sample = edata->cap_value;
|
||||
// following formula refers to: https://www.elecrow.com/download/HC_SR04%20Datasheet.pdf
|
||||
uint32_t pulse_count = cap_val_end_of_sample - cap_val_begin_of_sample;
|
||||
// send measurement back though queue
|
||||
xQueueSendFromISR(cap_queue, &pulse_count, &high_task_wakeup);
|
||||
uint32_t tof_ticks = cap_val_end_of_sample - cap_val_begin_of_sample;
|
||||
|
||||
// notify the task to calculate the distance
|
||||
xTaskNotifyFromISR(task_to_notify, tof_ticks, eSetValueWithOverwrite, &high_task_wakeup);
|
||||
}
|
||||
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
ESP_LOGI(TAG, "HC-SR04 example based on capture function from MCPWM");
|
||||
/**
|
||||
* @brief generate single pulse on Trig pin to start a new sample
|
||||
*/
|
||||
static void gen_trig_output(void)
|
||||
{
|
||||
gpio_set_level(HC_SR04_TRIG_GPIO, 1); // set high
|
||||
esp_rom_delay_us(10);
|
||||
gpio_set_level(HC_SR04_TRIG_GPIO, 0); // set low
|
||||
}
|
||||
|
||||
// the queue where we read data
|
||||
cap_queue = xQueueCreate(1, sizeof(uint32_t));
|
||||
if (cap_queue == NULL) {
|
||||
ESP_LOGE(TAG, "failed to alloc cap_queue");
|
||||
return;
|
||||
}
|
||||
|
||||
/* configure Echo pin */
|
||||
// set CAP_0 on GPIO
|
||||
ESP_ERROR_CHECK(mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM_CAP_0, HC_SR04_PIN_ECHO));
|
||||
// enable pull down CAP0, to reduce noise
|
||||
ESP_ERROR_CHECK(gpio_pulldown_en(HC_SR04_PIN_ECHO));
|
||||
// enable both edge capture on CAP0
|
||||
mcpwm_capture_config_t conf = {
|
||||
.cap_edge = MCPWM_BOTH_EDGE,
|
||||
.cap_prescale = 1,
|
||||
.capture_cb = sr04_echo_isr_handler,
|
||||
.user_data = NULL
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Install capture timer");
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
mcpwm_capture_timer_config_t cap_conf = {
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_capture_enable_channel(MCPWM_UNIT_0, MCPWM_SELECT_CAP0, &conf));
|
||||
ESP_LOGI(TAG, "Echo pin configured");
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_timer(&cap_conf, &cap_timer));
|
||||
|
||||
/* configure Trig pin */
|
||||
ESP_LOGI(TAG, "Install capture channel");
|
||||
mcpwm_cap_channel_handle_t cap_chan = NULL;
|
||||
mcpwm_capture_channel_config_t cap_ch_conf = {
|
||||
.gpio_num = HC_SR04_ECHO_GPIO,
|
||||
.prescale = 1,
|
||||
// capture on both edge
|
||||
.flags.neg_edge = true,
|
||||
.flags.pos_edge = true,
|
||||
// pull up internally
|
||||
.flags.pull_up = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, &cap_ch_conf, &cap_chan));
|
||||
|
||||
ESP_LOGI(TAG, "Register capture callback");
|
||||
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
|
||||
mcpwm_capture_event_callbacks_t cbs = {
|
||||
.on_cap = hc_sr04_echo_callback,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_chan, &cbs, cur_task));
|
||||
|
||||
ESP_LOGI(TAG, "Configure Trig pin");
|
||||
gpio_config_t io_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pin_bit_mask = BIT64(HC_SR04_PIN_TRIG),
|
||||
.pin_bit_mask = 1ULL << HC_SR04_TRIG_GPIO,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
ESP_ERROR_CHECK(gpio_set_level(HC_SR04_PIN_TRIG, 0)); // drive low by default
|
||||
ESP_LOGI(TAG, "Trig pin configured");
|
||||
// drive low by default
|
||||
ESP_ERROR_CHECK(gpio_set_level(HC_SR04_TRIG_GPIO, 0));
|
||||
|
||||
// start generating trig signal
|
||||
xTaskCreate(gen_trig_output, "gen_trig_output", TRIGGER_THREAD_STACK_SIZE, NULL, TRIGGER_THREAD_PRIORITY, NULL);
|
||||
ESP_LOGI(TAG, "trig task started");
|
||||
// forever loop
|
||||
while (true) {
|
||||
uint32_t pulse_count;
|
||||
// block and wait for new measurement
|
||||
xQueueReceive(cap_queue, &pulse_count, portMAX_DELAY);
|
||||
uint32_t pulse_width_us = pulse_count * (1000000.0 / esp_clk_apb_freq());
|
||||
// following formula is based on: https://www.elecrow.com/download/HC_SR04%20Datasheet.pdf
|
||||
if (pulse_width_us > 35000) {
|
||||
// out of range
|
||||
continue;
|
||||
ESP_LOGI(TAG, "Enable and start capture timer");
|
||||
ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer));
|
||||
ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer));
|
||||
|
||||
uint32_t tof_ticks;
|
||||
while (1) {
|
||||
// trigger the sensor to start a new sample
|
||||
gen_trig_output();
|
||||
// wait for echo done signal
|
||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &tof_ticks, pdMS_TO_TICKS(1000)) == pdTRUE) {
|
||||
float pulse_width_us = tof_ticks * (1000000.0 / esp_clk_apb_freq());
|
||||
if (pulse_width_us > 35000) {
|
||||
// out of range
|
||||
continue;
|
||||
}
|
||||
// convert the pulse width into measure distance
|
||||
float distance = (float) pulse_width_us / 58;
|
||||
ESP_LOGI(TAG, "Measured distance: %.2fcm", distance);
|
||||
}
|
||||
float distance = (float) pulse_width_us / 58;
|
||||
ESP_LOGI(TAG, "Pulse width: %uus, Measured distance: %.2fcm", pulse_width_us, distance);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,16 @@
|
||||
# SPDX-FileCopyrightText: 2021-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
|
||||
def test_hc_sr04_example(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Install capture timer')
|
||||
dut.expect_exact('example: Install capture channel')
|
||||
dut.expect_exact('example: Register capture callback')
|
||||
dut.expect_exact('example: Configure Trig pin')
|
||||
dut.expect_exact('example: Enable and start capture timer')
|
@@ -4,7 +4,7 @@
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example illustrates how to drive a typical [RC Servo](https://en.wikipedia.org/wiki/Servo_%28radio_control%29) by sending a PWM signal using the MCPWM driver. The PWM pulse has a frequency of 50Hz (period of 20ms), and the active-high time (which controls the rotation) ranges from 1ms to 2ms with 1.5ms always being center of range.
|
||||
This example illustrates how to drive a typical [RC Servo](https://en.wikipedia.org/wiki/Servo_%28radio_control%29) by sending a PWM signal using the MCPWM driver. The PWM pulse has a frequency of 50Hz (period of 20ms), and the active-high time (which controls the rotation) ranges from 0.5s to 2.5ms with 1.5ms always being center of range.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
@@ -17,15 +17,16 @@ This example illustrates how to drive a typical [RC Servo](https://en.wikipedia.
|
||||
Connection :
|
||||
|
||||
```
|
||||
+-------+ +-----------------+
|
||||
| | | |
|
||||
| +-+ GPIO18++ PWM(Orange) +----------+ |
|
||||
| ESP |---5V------+ Vcc(Red) +--------------| Servo Motor |
|
||||
| +---------+ GND(Brown) +----------+ |
|
||||
| | | |
|
||||
+-------+ +-----------------+
|
||||
ESP Board Servo Motor 5V
|
||||
+-------------------+ +---------------+ ^
|
||||
| SERVO_PULSE_GPIO +-----+PWM VCC +----+
|
||||
| | | |
|
||||
| GND +-----+GND |
|
||||
+-------------------+ +---------------+
|
||||
```
|
||||
|
||||
Note that, some kind of servo might need a higher current supply than the development board usually can provide. It's recommended to power the servo separately.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
@@ -42,11 +43,22 @@ Run the example, you will see the following output log:
|
||||
```
|
||||
...
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (349) example: Angle of rotation: -90
|
||||
I (449) example: Angle of rotation: -89
|
||||
I (549) example: Angle of rotation: -88
|
||||
I (649) example: Angle of rotation: -87
|
||||
I (749) example: Angle of rotation: -86
|
||||
I (305) example: Create timer and operator
|
||||
I (305) example: Connect timer and operator
|
||||
I (305) example: Create comparator and generator from the operator
|
||||
I (315) gpio: GPIO[44]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (325) example: Set generator action on timer and compare event
|
||||
I (335) example: Enable and start timer
|
||||
I (335) example: Angle of rotation: 0
|
||||
I (1345) example: Angle of rotation: 2
|
||||
I (2345) example: Angle of rotation: 4
|
||||
I (3345) example: Angle of rotation: 6
|
||||
I (4345) example: Angle of rotation: 8
|
||||
I (5345) example: Angle of rotation: 10
|
||||
I (6345) example: Angle of rotation: 12
|
||||
I (7345) example: Angle of rotation: 14
|
||||
I (8345) example: Angle of rotation: 16
|
||||
I (9345) example: Angle of rotation: 18
|
||||
...
|
||||
```
|
||||
|
||||
@@ -54,6 +66,4 @@ The servo will rotate from -90 degree to 90 degree, and then turn back again.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Note that, some kind of servo might need a higher current supply than the development board usually can provide. It's recommended to power the servo separately.
|
||||
|
||||
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
||||
|
@@ -1,47 +1,93 @@
|
||||
/* Servo Motor control example
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/mcpwm.h"
|
||||
#include "driver/mcpwm_prelude.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
// You can get these value from the datasheet of servo you use, in general pulse width varies between 1000 to 2000 mocrosecond
|
||||
#define SERVO_MIN_PULSEWIDTH_US (1000) // Minimum pulse width in microsecond
|
||||
#define SERVO_MAX_PULSEWIDTH_US (2000) // Maximum pulse width in microsecond
|
||||
#define SERVO_MAX_DEGREE (90) // Maximum angle in degree upto which servo can rotate
|
||||
// Please consult the datasheet of your servo before changing the following parameters
|
||||
#define SERVO_MIN_PULSEWIDTH_US 500 // Minimum pulse width in microsecond
|
||||
#define SERVO_MAX_PULSEWIDTH_US 2500 // Maximum pulse width in microsecond
|
||||
#define SERVO_MIN_DEGREE -90 // Minimum angle
|
||||
#define SERVO_MAX_DEGREE 90 // Maximum angle
|
||||
|
||||
#define SERVO_PULSE_GPIO (18) // GPIO connects to the PWM signal line
|
||||
#define SERVO_PULSE_GPIO 0 // GPIO connects to the PWM signal line
|
||||
#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
|
||||
#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms
|
||||
|
||||
static inline uint32_t example_convert_servo_angle_to_duty_us(int angle)
|
||||
static inline uint32_t example_angle_to_compare(int angle)
|
||||
{
|
||||
return (angle + SERVO_MAX_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / (2 * SERVO_MAX_DEGREE) + SERVO_MIN_PULSEWIDTH_US;
|
||||
return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) / (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) + SERVO_MIN_PULSEWIDTH_US;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, SERVO_PULSE_GPIO); // To drive a RC servo, one MCPWM generator is enough
|
||||
|
||||
mcpwm_config_t pwm_config = {
|
||||
.frequency = 50, // frequency = 50Hz, i.e. for every servo motor time period should be 20ms
|
||||
.cmpr_a = 0, // duty cycle of PWMxA = 0
|
||||
.counter_mode = MCPWM_UP_COUNTER,
|
||||
.duty_mode = MCPWM_DUTY_MODE_0,
|
||||
ESP_LOGI(TAG, "Create timer and operator");
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = SERVO_TIMEBASE_RESOLUTION_HZ,
|
||||
.period_ticks = SERVO_TIMEBASE_PERIOD,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
mcpwm_oper_handle_t operator = NULL;
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0, // operator must be in the same group to the timer
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &operator));
|
||||
|
||||
ESP_LOGI(TAG, "Connect timer and operator");
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operator, timer));
|
||||
|
||||
ESP_LOGI(TAG, "Create comparator and generator from the operator");
|
||||
mcpwm_cmpr_handle_t comparator = NULL;
|
||||
mcpwm_comparator_config_t comparator_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_comparator(operator, &comparator_config, &comparator));
|
||||
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
mcpwm_generator_config_t generator_config = {
|
||||
.gen_gpio_num = SERVO_PULSE_GPIO,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_generator(operator, &generator_config, &generator));
|
||||
|
||||
// set the initial compare value, so that the servo will spin to the center position
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(0)));
|
||||
|
||||
ESP_LOGI(TAG, "Set generator action on timer and compare event");
|
||||
// go high on counter empty
|
||||
ESP_ERROR_CHECK(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()));
|
||||
// go low on compare threshold
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(generator,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
|
||||
ESP_LOGI(TAG, "Enable and start timer");
|
||||
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
int angle = 0;
|
||||
int step = 2;
|
||||
while (1) {
|
||||
for (int angle = -SERVO_MAX_DEGREE; angle < SERVO_MAX_DEGREE; angle++) {
|
||||
ESP_LOGI(TAG, "Angle of rotation: %d", angle);
|
||||
ESP_ERROR_CHECK(mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, example_convert_servo_angle_to_duty_us(angle)));
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); //Add delay, since it takes time for servo to rotate, generally 100ms/60degree rotation under 5V power supply
|
||||
ESP_LOGI(TAG, "Angle of rotation: %d", angle);
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, example_angle_to_compare(angle)));
|
||||
//Add delay, since it takes time for servo to rotate, usually 200ms/60degree rotation under 5V power supply
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
if ((angle + step) > 60 || (angle + step) < -60) {
|
||||
step *= -1;
|
||||
}
|
||||
angle += step;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,17 @@
|
||||
# SPDX-FileCopyrightText: 2021-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
|
||||
def test_servo_mg996r_example(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Create timer and operator')
|
||||
dut.expect_exact('example: Connect timer and operator')
|
||||
dut.expect_exact('example: Create comparator and generator from the operator')
|
||||
dut.expect_exact('example: Set generator action on timer and compare event')
|
||||
dut.expect_exact('example: Enable and start timer')
|
||||
dut.expect_exact('example: Angle of rotation: 0')
|
@@ -3,4 +3,4 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(mcpwm_sync_example)
|
||||
project(mcpwm_sync)
|
88
examples/peripherals/mcpwm/mcpwm_sync/README.md
Normal file
88
examples/peripherals/mcpwm/mcpwm_sync/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
| Supported Targets | ESP32 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
# MCPWM Sync Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
MCPWM timers can't be started together because you have to call **mcpwm_timer_start_stop** function timer by timer, so the generators driven by them are not in sync with each other. But there're several ways to force these timers jump to the same point by setting sync phase for timers and then wait for a proper sync event to happen.
|
||||
|
||||
This example illustrates how to generate three PWMs that are in perfect synchronization.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with any Espressif SoC which features MCPWM peripheral (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
It is recommended to have an oscilloscope or logic analyzer to view the generated PWM waveforms.
|
||||
|
||||
Connection :
|
||||
|
||||
```
|
||||
ESP Board oscilloscope / logic analyzer
|
||||
+--------------------------+ +----------------------------+
|
||||
| | | |
|
||||
| EXAMPLE_GEN_GPIO0 +-----------------+ channelA |
|
||||
| | | |
|
||||
| EXAMPLE_GEN_GPIO1 +-----------------+ channelB |
|
||||
| | | |
|
||||
| EXAMPLE_GEN_GPIO2 +-----------------+ channelC |
|
||||
| | | |
|
||||
| GND +-----------------+ GND |
|
||||
| | | |
|
||||
+--------------------------+ +----------------------------+
|
||||
```
|
||||
|
||||
Above used GPIO numbers (e.g. `EXAMPLE_GEN_GPIO0`) can be changed in [the source file](main/mcpwm_sync_example_main.c).
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
You can select the way to synchronize the MCPWM timers in the menu: `Example Configuration` -> `Where the sync event is generated from`.
|
||||
|
||||
* GPIO
|
||||
* This approach will consume a GPIO, where a configurable pulse on the GPIO is treated as the sync event. And the sync event is routed to each MCPWM timers.
|
||||
* Timer TEZ
|
||||
* This approach won't consume any GPIO, the sync even is generated by a main timer, and then routed to other MCPWM timers. The drawback of this approach is, the main timer will have a tiny phase shift to other two timers.
|
||||
* Software (optional)
|
||||
* This approach won't consume any GPIO as well and also doesn't have the drawback in the `Timer TEZ` approach. The main timer is sync by software, and it will propagate the sync event to other timers.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (305) example: Create timers
|
||||
I (305) example: Create operators
|
||||
I (305) example: Connect timers and operators with each other
|
||||
I (315) example: Create comparators
|
||||
I (315) example: Create generators
|
||||
I (325) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (335) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (345) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (355) example: Set generator actions on timer and compare event
|
||||
I (355) example: Start timers one by one, so they are not synced
|
||||
I (495) example: Force the output level to low, timer still running
|
||||
I (495) example: Setup sync strategy
|
||||
I (495) example: Create GPIO sync source
|
||||
I (495) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 1| Intr:0
|
||||
I (505) example: Set timers to sync on the GPIO
|
||||
I (505) example: Trigger a pulse on the GPIO as a sync event
|
||||
I (515) example: Now the output PWMs should in sync
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "mcpwm_sync_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
20
examples/peripherals/mcpwm/mcpwm_sync/main/Kconfig.projbuild
Normal file
20
examples/peripherals/mcpwm/mcpwm_sync/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,20 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_SYNC_FROM
|
||||
prompt "Where the sync event is generated from"
|
||||
default EXAMPLE_SYNC_FROM_GPIO
|
||||
help
|
||||
Select MCPWM sync source.
|
||||
|
||||
config EXAMPLE_SYNC_FROM_GPIO
|
||||
bool "GPIO"
|
||||
|
||||
config EXAMPLE_SYNC_FROM_TEZ
|
||||
bool "Timer TEZ"
|
||||
|
||||
config EXAMPLE_SYNC_FROM_SOFT
|
||||
bool "Software"
|
||||
depends on SOC_MCPWM_SWSYNC_CAN_PROPAGATE
|
||||
endchoice
|
||||
|
||||
endmenu
|
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/mcpwm_prelude.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
const static char *TAG = "example";
|
||||
|
||||
#define EXAMPLE_TIMER_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
|
||||
#define EXAMPLE_TIMER_PERIOD 1000 // 1000 ticks, 1ms
|
||||
#define EXAMPLE_GEN_GPIO0 0
|
||||
#define EXAMPLE_GEN_GPIO1 2
|
||||
#define EXAMPLE_GEN_GPIO2 4
|
||||
#define EXAMPLE_SYNC_GPIO 5
|
||||
|
||||
#if CONFIG_EXAMPLE_SYNC_FROM_GPIO
|
||||
static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
|
||||
{
|
||||
// +----GPIO----+
|
||||
// | | |
|
||||
// | | |
|
||||
// v v v
|
||||
// timer0 timer1 timer2
|
||||
ESP_LOGI(TAG, "Create GPIO sync source");
|
||||
mcpwm_sync_handle_t gpio_sync_source = NULL;
|
||||
mcpwm_gpio_sync_src_config_t gpio_sync_config = {
|
||||
.group_id = 0, // GPIO fault should be in the same group of the above timers
|
||||
.gpio_num = EXAMPLE_SYNC_GPIO,
|
||||
.flags.pull_down = true,
|
||||
.flags.active_neg = false, // by default, a posedge pulse can trigger a sync event
|
||||
.flags.io_loop_back = true, // then we can trigger a sync event using `gpio_set_level` on the same GPIO
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_source));
|
||||
|
||||
ESP_LOGI(TAG, "Set timers to sync on the GPIO");
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 0,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
.sync_src = gpio_sync_source,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Trigger a pulse on the GPIO as a sync event");
|
||||
gpio_set_level(EXAMPLE_SYNC_GPIO, 0);
|
||||
gpio_set_level(EXAMPLE_SYNC_GPIO, 1);
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_SYNC_FROM_GPIO
|
||||
|
||||
#if CONFIG_EXAMPLE_SYNC_FROM_TEZ
|
||||
static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
|
||||
{
|
||||
// +->timer1
|
||||
// (TEZ) |
|
||||
// timer0---+
|
||||
// |
|
||||
// +->timer2
|
||||
ESP_LOGI(TAG, "Create TEZ sync source from timer0");
|
||||
mcpwm_sync_handle_t timer_sync_source = NULL;
|
||||
mcpwm_timer_sync_src_config_t timer_sync_config = {
|
||||
.timer_event = MCPWM_TIMER_EVENT_EMPTY, // generate sync event on timer empty
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(timers[0], &timer_sync_config, &timer_sync_source));
|
||||
|
||||
ESP_LOGI(TAG, "Set other timers sync to the first timer");
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 0,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
.sync_src = timer_sync_source,
|
||||
};
|
||||
for (int i = 1; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Wait some time for the timer TEZ event");
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_SYNC_FROM_TEZ
|
||||
|
||||
#if CONFIG_EXAMPLE_SYNC_FROM_SOFT
|
||||
static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
|
||||
{
|
||||
// soft
|
||||
// |
|
||||
// v
|
||||
// +-timer0--+
|
||||
// | |
|
||||
// v v
|
||||
// timer1 timer2
|
||||
ESP_LOGI(TAG, "Create software sync source");
|
||||
mcpwm_sync_handle_t soft_sync_source = NULL;
|
||||
mcpwm_soft_sync_config_t soft_sync_config = {};
|
||||
ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync_source));
|
||||
|
||||
ESP_LOGI(TAG, "Create timer sync source to propagate the sync event");
|
||||
mcpwm_sync_handle_t timer_sync_source;
|
||||
mcpwm_timer_sync_src_config_t timer_sync_config = {
|
||||
.flags.propagate_input_sync = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(timers[0], &timer_sync_config, &timer_sync_source));
|
||||
|
||||
ESP_LOGI(TAG, "Set sync phase for timers");
|
||||
mcpwm_timer_sync_phase_config_t sync_phase_config = {
|
||||
.count_value = 0,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
.sync_src = soft_sync_source,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[0], &sync_phase_config));
|
||||
sync_phase_config.sync_src = timer_sync_source;
|
||||
for (int i = 1; i < 3; ++i) {
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Trigger the software sync event");
|
||||
ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync_source));
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_SYNC_FROM_SOFT
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Create timers");
|
||||
mcpwm_timer_handle_t timers[3];
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.group_id = 0,
|
||||
.resolution_hz = EXAMPLE_TIMER_RESOLUTION_HZ,
|
||||
.period_ticks = EXAMPLE_TIMER_PERIOD,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timers[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create operators");
|
||||
mcpwm_oper_handle_t operators[3];
|
||||
mcpwm_operator_config_t operator_config = {
|
||||
.group_id = 0, // operator should be in the same group of the above timers
|
||||
};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &operators[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Connect timers and operators with each other");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators[i], timers[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create comparators");
|
||||
mcpwm_cmpr_handle_t comparators[3];
|
||||
mcpwm_comparator_config_t compare_config = {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_new_comparator(operators[i], &compare_config, &comparators[i]));
|
||||
// init compare for each comparator
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators[i], 200));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Create generators");
|
||||
mcpwm_gen_handle_t generators[3];
|
||||
const int gen_gpios[3] = {EXAMPLE_GEN_GPIO0, EXAMPLE_GEN_GPIO1, EXAMPLE_GEN_GPIO2};
|
||||
mcpwm_generator_config_t gen_config = {};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
gen_config.gen_gpio_num = gen_gpios[i];
|
||||
ESP_ERROR_CHECK(mcpwm_new_generator(operators[i], &gen_config, &generators[i]));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Set generator actions on timer and compare event");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(generators[i],
|
||||
// when the timer value is zero, and is counting up, set output to high
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
|
||||
MCPWM_GEN_TIMER_EVENT_ACTION_END()));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(generators[i],
|
||||
// when compare event happens, and timer is counting up, set output to low
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators[i], MCPWM_GEN_ACTION_LOW),
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION_END()));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Start timers one by one, so they are not synced");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_timer_enable(timers[i]));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timers[i], MCPWM_TIMER_START_NO_STOP));
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
// Manually added this "IDLE" phase, which can help us distinguish the wave forms before and after sync
|
||||
ESP_LOGI(TAG, "Force the output level to low, timer still running");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators[i], 0, true));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Setup sync strategy");
|
||||
example_setup_sync_strategy(timers);
|
||||
|
||||
ESP_LOGI(TAG, "Now the output PWMs should in sync");
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
// remove the force level on the generator, so that we can see the PWM again
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators[i], -1, true));
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user