ESP32-S3 MCPWM Crash (LoadProhibited) In Mcpwm_generator_set_actions_on_timer_event (IDF V5.4.1)
ESP32-S3 MCPWM Crash (LoadProhibited) in mcpwm_generator_set_actions_on_timer_event (IDF v5.4.1)
Introduction
The ESP32-S3 is a popular microcontroller used in various applications, including IoT devices, robotics, and more. However, users have reported a crash issue with the MCPWM (Motor Control PWM) peripheral on the ESP32-S3 when using the ESP-IDF (Espressif IoT Development Framework) version 5.4.1. In this article, we will investigate the cause of this crash and provide a solution.
Background
The MCPWM peripheral is a critical component of the ESP32-S3, responsible for generating PWM signals for motor control applications. The crash occurs when setting the timer event action for the first generator (mcpwm_generator_0) shortly after creating the timer, operator, and generator objects.
Symptoms
The crash is characterized by a LoadProhibited exception happening inside the low-level function mcpwm_ll_generator_set_action_on_timer_event, which is called by mcpwm_generator_set_actions_on_timer_event. The crash dump shows that all MCPWM object handles (timer, operator, generator) are valid (non-NULL) just before the crashing call. The invalid memory address (EXCVADDR) reported in the crash dump changes on different runs, suggesting memory corruption or an invalid internal pointer within the driver structure (group->hal.dev).
Steps to Reproduce
To reproduce the crash, follow these steps:
- Compile and flash the minimal reproducible code below onto an ESP32-S3 board using PlatformIO with the latest stable espressif32 platform (which uses ESP-IDF v5.4.1).
- Open the Serial Monitor.
- Trigger the touch2Pin (GPIO 5).
- Observe the crash and backtrace in the serial output.
Minimal Reproducible Code
#include <Arduino.h>
#include <driver/gpio.h>
#include <driver/mcpwm_prelude.h>
#include <hal/gpio_ll.h> // Needed for gpio_ll_set_level in ISR (though ISR is removed in this version)
// --- Debug Macro ---
#define DEBUG_HAAS true
#if DEBUG_HAAS
#define DEBUG_SERIAL_PRINT(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_SERIAL_PRINT(...) do {} while (0)
#endif
// --- System State ---
typedef enum { STATE_IDLE, STATE_OUTPUT_BURSTING } SystemState;
volatile SystemState currentState = STATE_IDLE;
// --- Pins ---
const gpio_num_t led1Pin = GPIO_NUM_17;
const gpio_num_t touch1Pin = GPIO_NUM_1; // Unused
const gpio_num_t touch2Pin = GPIO_NUM_5; // Trigger
const gpio_num_t DIO1_0 = GPIO_NUM_9; // MCPWM Out 0
const gpio_num_t DIO1_1 = GPIO_NUM_11; // MCPWM Out 1 (Unused in minimal test)
const gpio_num_t EN1 = GPIO_NUM_10; // Output Enable
// --- MCPWM Config ---
uint32_t target_frequency = 1000000; // Original target freq (overridden in init)
uint16_t target_count = 100; // Original target count
float target_duty_cycle = 0.5f; // Original target duty
// --- Touch ---
volatile bool touch1detected = false;
volatile bool touch2detected = false;
const uint16_t threshold = 60000;
// --- MCPWM Handles ---
mcpwm_timer_handle_t mcpwm_timer = NULL;
mcpwm_oper_handle_t mcpwm_operator = NULL;
// mcpwm_cmpr_handle_t mcpwm_comparator_a = NULL; // Removed
mcpwm_gen_handle_t mcpwm_generator_0 = NULL;
// mcpwm_gen_handle_t mcpwm_generator_1 = NULL; // Removed
const int MCPWM_TIMER_RESOLUTION_HZ = 80 * 1000 * 1000; // 80MHz
// const int MCPWM_DEAD_TIME_RESOLUTION_HZ = 80 * 1000 * 1000; // Removed
// const uint32_t MCPWM_DEAD_TIME_TICKS = 1; // Removed
// volatile bool pwm_burst_active = false; // Removed
// volatile uint16_t mcpwm_cycle_counter = 0; // Removed
// volatile uint16_t mcpwm_target_cycles = 0; // Removed
// --- Function Declarations ---
esp_err_t init_pwm_mcpwm(uint32_t frequency, float duty_cycle);
void deinit_pwm_mcpwm();
esp_err_t generate_mcpwm_burst(uint16_t cycles);
// --- ISRs ---
void IRAM_ATTR touch1_isr () { touch1detected = true; }
void IRAM_ATTR touch2_isr () { touch2detected = true; }
// static bool IRAM_ATTR mcpwm_timer_isr_handler(...) // Removed
// --- MCPWM Init (Minimal Failing Case) ---
esp_err_t init_pwm_mcpwm(uint32_t frequency, float duty_cycle) {
// --- Values Simplified ---
frequency = 20000; // 20 kHz
// ---------------------------
DEBUG_SERIAL_PRINT("--- init_pwm_mcpwm Start (EXTREME SIMPLE TEST) ---\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
DEBUG_SERIAL_PRINT("Input Params (Overridden): frequency=%lu\r\n", frequency); vTaskDelay(pdMS_TO_TICKS(10));
esp_err_t ret = ESP_OK;
// --- Timer Config (Simple) ---
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = MCPWM_TIMER_RESOLUTION_HZ,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.period_ticks = (uint32_t)MCPWM_TIMER_RESOLUTION_HZ / frequency, // 4000
.intr_priority = 0,
.flags = { .update_period_on_empty = false, .update_period_on_sync = false }
};
DEBUG_SERIAL_PRINT("Timer Config: period_ticks=%lu\r\n", timer_config.period_ticks); vTaskDelay(pdMS_TO_TICKS(10));
// --- Operator Config (Simple) ---
mcpwm_operator_config_t oper_config = {
.group_id = 0,
.intr_priority = 0,
// Default flags (all false)
};
DEBUG_SERIAL_PRINT("Operator Config: Default flags\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Generator Config (Simple) ---
mcpwm_generator_config_t gen_config = {};
DEBUG_SERIAL_PRINT("Generator Config: Default flags\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Callbacks (None) ---
mcpwm_timer_event_callbacks_t cbs = {
.on_full = NULL,
.on_empty = NULL,
.on_stop = NULL
};
DEBUG_SERIAL_PRINT("Callbacks Config: All NULL\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Variables locales ---
mcpwm_gen_timer_event_action_t timer_action = {};
DEBUG_SERIAL_PRINT("--- Starting MCPWM Object Creation ---\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 1. Create Timer ---
DEBUG_SERIAL_PRINT("Calling mcpwm_new_timer...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_new_timer(&timer_config, &mcpwm_timer);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to create MCPWM timer: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_new_timer OK. Handle: %p\r\n", mcpwm_timer); vTaskDelay(pdMS_TO_TICKS(10));
// --- 2. Create Operator ---
DEBUG_SERIAL_PRINT("Calling mcpwm_new_operator...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_new_operator(&oper_config, &mcpwm_operator);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to create MCPWM operator: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_new_operator OK. Handle: %p\r\n", mcpwm_operator); vTaskDelay(pdMS_TO_TICKS(10));
// --- 3. Connect Timer and Operator ---
DEBUG_SERIAL_PRINT("Calling mcpwm_operator_connect_timer...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_operator_connect_timer(mcpwm_operator, mcpwm_timer);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to connect MCPWM timer and operator: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_operator_connect_timer OK.\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 4. Comparators SKIPPED ---
DEBUG_SERIAL_PRINT("[DEBUG] Comparator creation/setting SKIPPED.\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 5. Create Generators ---
DEBUG_SERIAL_PRINT("Creating Generator 0 (GPIO: %d)...\r\n", DIO1_0); v<br/>
**Q&A: ESP32-S3 MCPWM Crash (LoadProhibited) in mcpwm_generator_set_actions_on_timer_event (IDF v5.4.1)**
**Q: What is the cause of the crash?**
A: The crash is caused by a LoadProhibited exception happening inside the low-level function mcpwm_ll_generator_set_action_on_timer_event, which is called by mcpwm_generator_set_actions_on_timer_event. This function is responsible for setting the timer event action for the first generator (mcpwm_generator_0).
**Q: What is the LoadProhibited exception?**
A: The LoadProhibited exception is a type of exception that occurs when the CPU tries to access a memory location that is not allowed to be accessed. In this case, the exception is happening because the function mcpwm_ll_generator_set_action_on_timer_event is trying to access a memory location that is not valid.
**Q: Why is the memory location not valid?**
A: The memory location is not valid because the function mcpwm_ll_generator_set_action_on_timer_event is trying to access a pointer that has been corrupted. This corruption is likely caused by a bug in the ESP-IDF (Espressif IoT Development Framework) version 5.4.1.
**Q: How can I reproduce the crash?**
A: To reproduce the crash, follow these steps:
1. Compile and flash the minimal reproducible code below onto an ESP32-S3 board using PlatformIO with the latest stable espressif32 platform (which uses ESP-IDF v5.4.1).
2. Open the Serial Monitor.
3. Trigger the touch2Pin (GPIO 5).
4. Observe the crash and backtrace in the serial output.
**Q: What is the minimal reproducible code?**
A: The minimal reproducible code is provided below:
```c
#include <Arduino.h>
#include <driver/gpio.h>
#include <driver/mcpwm_prelude.h>
#include <hal/gpio_ll.h> // Needed for gpio_ll_set_level in ISR (though ISR is removed in this version)
// --- Debug Macro ---
#define DEBUG_HAAS true
#if DEBUG_HAAS
#define DEBUG_SERIAL_PRINT(...) Serial.printf(__VA_ARGS__)
#else
#define DEBUG_SERIAL_PRINT(...) do {} while (0)
#endif
// --- System State ---
typedef enum { STATE_IDLE, STATE_OUTPUT_BURSTING } SystemState;
volatile SystemState currentState = STATE_IDLE;
// --- Pins ---
const gpio_num_t led1Pin = GPIO_NUM_17;
const gpio_num_t touch1Pin = GPIO_NUM_1; // Unused
const gpio_num_t touch2Pin = GPIO_NUM_5; // Trigger
const gpio_num_t DIO1_0 = GPIO_NUM_9; // MCPWM Out 0
const gpio_num_t DIO1_1 = GPIO_NUM_11; // MCPWM Out 1 (Unused in minimal test)
const gpio_num_t EN1 = GPIO_NUM_10; // Output Enable
// --- MCPWM Config ---
uint32_t target_frequency = 1000000; // Original target freq (overridden in init)
uint16_t target_count = 100; // Original target count
float target_duty_cycle = 0.5f; // Original target duty
// --- Touch ---
volatile bool touch1detected = false;
volatile bool touch2detected = false;
const uint16_t threshold = 60000;
// --- MCPWM Handles ---
mcpwm_timer_handle_t mcpwm_timer = NULL;
mcpwm_oper_handle_t mcpwm_operator = NULL;
// mcpwm_cmpr_handle_t mcpwm_comparator_a = NULL; // Removed
mcpwm_gen_handle_t mcpwm_generator_0 = NULL;
// mcpwm_gen_handle_t mcpwm_generator_1 = NULL; // Removed
const int MCPWM_TIMER_RESOLUTION_HZ = 80 * 1000 * 1000; // 80MHz
// const int MCPWM_DEAD_TIME_RESOLUTION_HZ = 80 * 1000 * 1000; // Removed
// const uint32_t MCPWM_DEAD_TIME_TICKS = 1; // Removed
// volatile bool pwm_burst_active = false; // Removed
// volatile uint16_t mcpwm_cycle_counter = 0; // Removed
// volatile uint16_t mcpwm_target_cycles = 0; // Removed
// --- Function Declarations ---
esp_err_t init_pwm_mcpwm(uint32_t frequency, float duty_cycle);
void deinit_pwm_mcpwm();
esp_err_t generate_mcpwm_burst(uint16_t cycles);
// --- ISRs ---
void IRAM_ATTR touch1_isr () { touch1detected = true; }
void IRAM_ATTR touch2_isr () { touch2detected = true; }
// static bool IRAM_ATTR mcpwm_timer_isr_handler(...) // Removed
// --- MCPWM Init (Minimal Failing Case) ---
esp_err_t init_pwm_mcpwm(uint32_t frequency, float duty_cycle) {
// --- Values Simplified ---
frequency = 20000; // 20 kHz
// ---------------------------
DEBUG_SERIAL_PRINT("--- init_pwm_mcpwm Start (EXTREME SIMPLE TEST) ---\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
DEBUG_SERIAL_PRINT("Input Params (Overridden): frequency=%lu\r\n", frequency); vTaskDelay(pdMS_TO_TICKS(10));
esp_err_t ret = ESP_OK;
// --- Timer Config (Simple) ---
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = MCPWM_TIMER_RESOLUTION_HZ,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
.period_ticks = (uint32_t)MCPWM_TIMER_RESOLUTION_HZ / frequency, // 4000
.intr_priority = 0,
.flags = { .update_period_on_empty = false, .update_period_on_sync = false }
};
DEBUG_SERIAL_PRINT("Timer Config: period_ticks=%lu\r\n", timer_config.period_ticks); vTaskDelay(pdMS_TO_TICKS(10));
// --- Operator Config (Simple) ---
mcpwm_operator_config_t oper_config = {
.group_id = 0,
.intr_priority = 0,
// Default flags (all false)
};
DEBUG_SERIAL_PRINT("Operator Config: Default flags\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Generator Config (Simple) ---
mcpwm_generator_config_t gen_config = {};
DEBUG_SERIAL_PRINT("Generator Config: Default flags\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Callbacks (None) ---
mcpwm_timer_event_callbacks_t cbs = {
.on_full = NULL,
.on_empty = NULL,
.on_stop = NULL
};
DEBUG_SERIAL_PRINT("Callbacks Config: All NULL\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- Variables locales ---
mcpwm_gen_timer_event_action_t timer_action = {};
DEBUG_SERIAL_PRINT("--- Starting MCPWM Object Creation ---\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 1. Create Timer ---
DEBUG_SERIAL_PRINT("Calling mcpwm_new_timer...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_new_timer(&timer_config, &mcpwm_timer);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to create MCPWM timer: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_new_timer OK. Handle: %p\r\n", mcpwm_timer); vTaskDelay(pdMS_TO_TICKS(10));
// --- 2. Create Operator ---
DEBUG_SERIAL_PRINT("Calling mcpwm_new_operator...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_new_operator(&oper_config, &mcpwm_operator);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to create MCPWM operator: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_new_operator OK. Handle: %p\r\n", mcpwm_operator); vTaskDelay(pdMS_TO_TICKS(10));
// --- 3. Connect Timer and Operator ---
DEBUG_SERIAL_PRINT("Calling mcpwm_operator_connect_timer...\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
ret = mcpwm_operator_connect_timer(mcpwm_operator, mcpwm_timer);
if (ret != ESP_OK) {
DEBUG_SERIAL_PRINT("[ERROR] Failed to connect MCPWM timer and operator: %s (%d)\r\n", esp_err_to_name(ret), ret); vTaskDelay(pdMS_TO_TICKS(10));
goto err_exit;
}
DEBUG_SERIAL_PRINT(" mcpwm_operator_connect_timer OK.\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 4. Comparators SKIPPED ---
DEBUG_SERIAL_PRINT("[DEBUG] Comparator creation/setting SKIPPED.\r\n"); vTaskDelay(pdMS_TO_TICKS(10));
// --- 5. Create Generators ---
DEBUG_SERIAL_PRINT("Creating Generator 0 (GPIO: %d)...\r\n", DIO1_0); vTaskDelay(pdMS_TO_TICKS(10));
gen