mcpwm: can't apply the same delay module to multiple generators

This is a hardware limitation, one delay module can only be used by one generator at one time.

Closes https://github.com/espressif/esp-idf/issues/11327
This commit is contained in:
morris 2023-05-05 16:29:20 +08:00
parent f00c78b020
commit 49c83f112d
8 changed files with 88 additions and 11 deletions

View File

@ -215,12 +215,17 @@ typedef struct {
/**
* @brief Set dead time for MCPWM generator
*
* @note Due to a hardware limitation, you can't set rising edge delay for both MCPWM generator 0 and 1 at the same time,
* otherwise, there will be a conflict inside the dead time module. The same goes for the falling edge setting.
* But you can set both the rising edge and falling edge delay for the same 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_ERR_INVALID_STATE: Set dead time for MCPWM generator failed because of invalid state (e.g. delay module is already in use by other generator)
* - 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);

View File

@ -268,6 +268,36 @@ esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t in_generator, mcpwm_g
mcpwm_hal_context_t *hal = &group->hal;
int oper_id = oper->oper_id;
// one delay module can only be used by one generator at a time
bool delay_module_conflict = false;
portENTER_CRITICAL(&oper->spinlock);
if (config->posedge_delay_ticks) {
if (oper->posedge_delay_owner && oper->posedge_delay_owner != in_generator) {
delay_module_conflict = true;
}
}
if (config->negedge_delay_ticks) {
if (oper->negedge_delay_owner && oper->negedge_delay_owner != in_generator) {
delay_module_conflict = true;
}
}
if (!delay_module_conflict) {
if (config->posedge_delay_ticks) {
// set owner if delay module is used
oper->posedge_delay_owner = in_generator;
} else if (oper->posedge_delay_owner == in_generator) {
// clear owner if delay module is previously used by in_generator, but now it is not used
oper->posedge_delay_owner = NULL;
}
if (config->negedge_delay_ticks) {
oper->negedge_delay_owner = in_generator;
} else if (oper->negedge_delay_owner == in_generator) {
oper->negedge_delay_owner = NULL;
}
}
portEXIT_CRITICAL(&oper->spinlock);
ESP_RETURN_ON_FALSE(!delay_module_conflict, ESP_ERR_INVALID_STATE, TAG, "delay module is in use by other generator");
// 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);

View File

@ -102,6 +102,8 @@ struct mcpwm_oper_t {
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_gen_t *posedge_delay_owner; // which generator owns the positive edge delay
mcpwm_gen_t *negedge_delay_owner; // which generator owns the negative edge delay
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

View File

@ -591,6 +591,23 @@ static void redfedb_only_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle
TEST_ESP_OK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
}
static void invalid_reda_redb_set_dead_time(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb)
{
mcpwm_dead_time_config_t dead_time_config = {
.posedge_delay_ticks = 10,
};
// generator_a adds delay on the posedge
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
// generator_b adds delay on the posedge as well, which is not allowed
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, mcpwm_generator_set_dead_time(genb, genb, &dead_time_config));
// bypass the delay module for generator_a
dead_time_config.posedge_delay_ticks = 0;
TEST_ESP_OK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config));
// now generator_b can add delay on the posedge
dead_time_config.posedge_delay_ticks = 10;
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");
@ -613,6 +630,9 @@ TEST_CASE("mcpwm_generator_deadtime_classical_configuration", "[mcpwm]")
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);
printf("Can't apply one delay module to multiple generators\r\n");
mcpwm_deadtime_test_template(1000000, 500, 350, 350, 0, 2, redfedb_only_set_generator_actions, invalid_reda_redb_set_dead_time);
}
TEST_CASE("mcpwm_duty_empty_full", "[mcpwm]")

View File

@ -3,7 +3,7 @@
{
"name": "origin",
"wave": "0...1.....0...",
"node": "....a.....b..."
"node": "....a.e...b..."
},
{
"name": "pwm_A",
@ -13,12 +13,13 @@
{
"name": "pwm_B",
"wave": "1...0......1..",
"node": "...........d.."
"node": "......f....d.."
}
],
"edge": [
"a|->c RED",
"b|->d FED"
"b|->d FED",
"e<->f Invert"
],
"head": {
"text": "Active High, Complementary"

View File

@ -3,22 +3,24 @@
{
"name": "origin",
"wave": "0...1.....0...",
"node": "....a.....b..."
"node": "....a..ef.b..."
},
{
"name": "pwm_A",
"wave": "1....0....1...",
"node": ".....c....."
"node": ".....c.g..."
},
{
"name": "pwm_B",
"wave": "1...0......1..",
"node": "...........d.."
"node": "........h..d.."
}
],
"edge": [
"a|->c RED",
"b|->d FED"
"b|->d FED",
"e<->g Invert",
"f<->h Invert"
],
"head": {
"text": "Active Low"

View File

@ -3,12 +3,12 @@
{
"name": "origin",
"wave": "0...1.....0...",
"node": "....a.....b..."
"node": "....a..e..b..."
},
{
"name": "pwm_A",
"wave": "1....0....1...",
"node": ".....c....."
"node": ".....c.f..."
},
{
"name": "pwm_B",
@ -18,7 +18,8 @@
],
"edge": [
"a|->c RED",
"b|->d FED"
"b|->d FED",
"e<->f Invert"
],
"head": {
"text": "Active Low, Complementary"

View File

@ -407,13 +407,29 @@ Dead Time
In power electronics, the rectifier and inverter are commonly used. This requires the use of rectifier bridge and inverter bridge. Each bridge arm has two power electronic devices, such as MOSFET, IGBT, etc. The two MOSFETs on the same arm can't conduct at the same time, otherwise there will be a short circuit. The fact is that, although the PWM wave shows it is turning off the switch, but the MOSFET still needs a small time window to make that happen. This requires an extra delay to be added to the existing PWM wave that generated by setting `Generator Actions on Events <#generator-actions-on-events>`__.
The dead-time driver works like a *decorator*, which is also reflected in the function parameters of :cpp:func:`mcpwm_generator_set_dead_time`, where it takes the primary generator handle (``in_generator``), and returns a generator (``out_generator``) after applying the dead-time. Please note, if the ``out_generator`` and ``in_generator`` are the same, it means we're adding the time delay to the PWM waveform in a "in-place" fashion. In turn, if the ``out_generator`` and ``in_generator`` are different, it means we're deriving a new PWM waveform from the existing ``in_generator``.
The dead time driver works like a *decorator*. This is also reflected in the function parameters of :cpp:func:`mcpwm_generator_set_dead_time`, where it takes the primary generator handle (``in_generator``), and returns a new generator (``out_generator``) after applying the dead time. Please note, if the ``out_generator`` and ``in_generator`` are the same, it means we are adding the time delay to the PWM waveform in an "in-place" fashion. In turn, if the ``out_generator`` and ``in_generator`` are different, it means we're deriving a new PWM waveform from the existing ``in_generator``.
Dead-time specific configuration is listed in the :cpp:type:`mcpwm_dead_time_config_t` structure:
- :cpp:member:`mcpwm_dead_time_config_t::posedge_delay_ticks` and :cpp:member:`mcpwm_dead_time_config_t::negedge_delay_ticks` set the number of ticks to delay the PWM waveform on the rising and falling edge. Specifically, setting both of them to zero means to bypass the dead-time module. The resolution of the dead-time tick is the same to the timer that is connected with the operator by :cpp:func:`mcpwm_operator_connect_timer`.
- :cpp:member:`mcpwm_dead_time_config_t::invert_output`: Whether to invert the signal after applying the dead-time, which can be used to control the delay edge polarity.
.. warning::
Due to the hardware limitation, one delay module (either `posedge delay` or `negedge delay`) can't be applied to multiple MCPWM generators at the same time. e.g. the following configuration is **invalid**:
.. code:: c
mcpwm_dead_time_config_t dt_config = {
.posedge_delay_ticks = 10,
};
// Set posedge delay to generator A
mcpwm_generator_set_dead_time(mcpwm_gen_a, mcpwm_gen_a, &dt_config);
// NOTE: This is invalid, you can't apply the posedge delay to another generator
mcpwm_generator_set_dead_time(mcpwm_gen_b, mcpwm_gen_b, &dt_config);
However, you can apply `posedge delay` to generator A and `negedge delay` to generator B. You can also set both `posedge delay` and `negedge delay` for generator A, while letting generator B bypass the dead time module.
.. note::
It is also possible to generate the required dead time by setting `Generator Actions on Events <#generator-actions-on-events>`__, especially by controlling edge placement using different comparators. However, if the more classical edge delay-based dead time with polarity control is required, then the dead-time submodule should be used.