Code works fine with static allocation of my class, crashes when dynamically allocating it
Posted: Tue Dec 24, 2024 9:12 pm
Hello,
I have a class called QualityCheck_Audio, which uses a class called AudioDAC(), which again uses the Audio.h library of Arduino to play MP3 files from SD card through I2S.
Everything is working fine when I statically allocate QualityCheck_Audio. After racking my brain several hours trying to figure out why suddenly my code was broken and was giving me "Guru Meditation Error: Core 1 panic'ed (InstrFetchProhibited). Exception was unhandled." errors, I realized that this error appeared when I changed to dynamic allocation of my class.
Below is the version of the code that uses static allocation, and that is not giving errors :
But when I switch to the below version :
Suddenly the code is crashing and gives me the following error, which likely is coming from an illegal memory access during the I2S functions of ESP32 :
Please see bellow the QualityCheck_Audio class :
Class AudioDac :
The error call stack starts in AudioDAC::setup() when Audio::setPinout() is called. I can't figure out why is dynamic allocation causing a different behaviour. Could this be bad memory alignmment due to polymorphism ?
Thank you for your help !
I have a class called QualityCheck_Audio, which uses a class called AudioDAC(), which again uses the Audio.h library of Arduino to play MP3 files from SD card through I2S.
Everything is working fine when I statically allocate QualityCheck_Audio. After racking my brain several hours trying to figure out why suddenly my code was broken and was giving me "Guru Meditation Error: Core 1 panic'ed (InstrFetchProhibited). Exception was unhandled." errors, I realized that this error appeared when I changed to dynamic allocation of my class.
Below is the version of the code that uses static allocation, and that is not giving errors :
Code: QualityCheckProcess_WORKS.h Select all
#ifndef QUALITYCHECK_PROCESS_H
#define QUALITYCHECK_PROCESS_H
#include "QualityCheck.h"
#include "QualityCheck/QualityCheck_LedBuzzerButton.h"
#include "QualityCheck/QualityCheck_SD.h"
#include "QualityCheck/QualityCheck_RTC.h"
#include "QualityCheck/QualityCheck_GPS.h"
#include "QualityCheck/QualityCheck_Power.h"
#include "QualityCheck/QualityCheck_Audio.h"
#include "QualityCheck/QualityCheck_BLE.h"
#include <vector>
class QualityCheckProcess {
public :
QualityCheckProcess();
void start();
void update();
private :
void goToNextCheck();
void onCurrentTestFinished(QualityCheck *sender, bool success);
std::vector<QualityCheck*> m_qualityChecks;
QualityCheck_Audio m_audioTest;
std::vector<String> m_failedChecks;
unsigned short m_qualityCheckIndex;
bool m_checksFinished;
};
#endif
Code: QualityCheckProcess_WORKS.cpp Select all
#include "QualityCheck/QualityCheckProcess.h"
QualityCheckProcess::QualityCheckProcess() : m_checksFinished(false) {
m_qualityChecks.push_back(&m_audioTest);
}
void QualityCheckProcess::start() {
Serial.printf("Free heap before allocation: %d\n", ESP.getFreeHeap());
Serial.printf("Free heap after allocation: %d\n", ESP.getFreeHeap());
m_qualityCheckIndex = -1;
goToNextCheck();
}
void QualityCheckProcess::update() {
if(!m_checksFinished)
m_qualityChecks[m_qualityCheckIndex]->update();
}
void QualityCheckProcess::onCurrentTestFinished(QualityCheck *sender, bool success) {
if(!success)
m_failedChecks.push_back(sender->getName());
goToNextCheck();
}
void QualityCheckProcess::goToNextCheck() {
m_qualityCheckIndex++;
if(m_qualityCheckIndex >= m_qualityChecks.size()) {
m_checksFinished = true;
Serial.println("------------QUALITY CHECKS FINISHED !-------------");
Serial.flush();
if(m_failedChecks.size() == 0)
Serial.println("------------ALL PASSED------------");
else
Serial.println("------------FAILED TESTS :------------");
for(int i = 0; i < m_failedChecks.size(); i++) {
Serial.println(" -> " + m_failedChecks[i]);
}
}
else {
m_qualityChecks[m_qualityCheckIndex]->setOnFinished([this](QualityCheck *sender, bool success) {
if(this)
this->onCurrentTestFinished(sender, success);
});
m_qualityChecks[m_qualityCheckIndex]->startTest();
}
}
But when I switch to the below version :
Code: QualityCheckProcess_FAILS.h Select all
#ifndef QUALITYCHECK_PROCESS_H
#define QUALITYCHECK_PROCESS_H
#include "QualityCheck.h"
#include "QualityCheck/QualityCheck_LedBuzzerButton.h"
#include "QualityCheck/QualityCheck_SD.h"
#include "QualityCheck/QualityCheck_RTC.h"
#include "QualityCheck/QualityCheck_GPS.h"
#include "QualityCheck/QualityCheck_Power.h"
#include "QualityCheck/QualityCheck_Audio.h"
#include "QualityCheck/QualityCheck_BLE.h"
#include <vector>
class QualityCheckProcess {
public :
QualityCheckProcess();
void start();
void update();
private :
void goToNextCheck();
void onCurrentTestFinished(QualityCheck *sender, bool success);
std::vector<QualityCheck*> m_qualityChecks;
std::vector<String> m_failedChecks;
unsigned short m_qualityCheckIndex;
bool m_checksFinished;
};
#endif
Code: QualityCheckProcess_FAILS.cpp Select all
#include "QualityCheck/QualityCheckProcess.h"
QualityCheckProcess::QualityCheckProcess() : m_checksFinished(false) {
m_qualityChecks.push_back(new QualityCheck_Audio());
}
void QualityCheckProcess::start() {
Serial.printf("Free heap before allocation: %d\n", ESP.getFreeHeap());
Serial.printf("Free heap after allocation: %d\n", ESP.getFreeHeap());
m_qualityCheckIndex = -1;
goToNextCheck();
}
void QualityCheckProcess::update() {
if(!m_checksFinished)
m_qualityChecks[m_qualityCheckIndex]->update();
}
void QualityCheckProcess::onCurrentTestFinished(QualityCheck *sender, bool success) {
if(!success)
m_failedChecks.push_back(sender->getName());
goToNextCheck();
}
void QualityCheckProcess::goToNextCheck() {
m_qualityCheckIndex++;
if(m_qualityCheckIndex >= m_qualityChecks.size()) {
m_checksFinished = true;
Serial.println("------------QUALITY CHECKS FINISHED !-------------");
Serial.flush();
if(m_failedChecks.size() == 0)
Serial.println("------------ALL PASSED------------");
else
Serial.println("------------FAILED TESTS :------------");
for(int i = 0; i < m_failedChecks.size(); i++) {
Serial.println(" -> " + m_failedChecks[i]);
}
}
else {
m_qualityChecks[m_qualityCheckIndex]->setOnFinished([this](QualityCheck *sender, bool success) {
if(this)
this->onCurrentTestFinished(sender, success);
});
m_qualityChecks[m_qualityCheckIndex]->startTest();
}
}
Suddenly the code is crashing and gives me the following error, which likely is coming from an illegal memory access during the I2S functions of ESP32 :
Code: Error_output.txt Select all
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.
Core 1 register dump:
PC : 0x4202a8e2 PS : 0x00060030 A0 : 0x8202aaee A1 : 0x3fcebbb0
A2 : 0xa5a5a5a5 A3 : 0x00000017 A4 : 0xa5a5a5a5 A5 : 0x00000000
A6 : 0x3fcec06c A7 : 0x00000000 A8 : 0xd2aa0ed4 A9 : 0x96969694
A10 : 0x3fca057c A11 : 0x00000000 A12 : 0x00000000 A13 : 0x00000001
A14 : 0x00060220 A15 : 0x00000001 SAR : 0x00000005 EXCCAUSE: 0x0000001c
EXCVADDR: 0xd2aa0ed4 LBEG : 0x40056f08 LEND : 0x40056f12 LCOUNT : 0x00000000
Backtrace: 0x4202a8df:0x3fcebbb0 0x4202aaeb:0x3fcebbd0 0x420140f2:0x3fcebc00 0x420033eb:0x3fcebc50 0x42006572:0x3fcebc80 0x42006371:0x3fcebcc0 0x420063e5:0x3fcebd00 0x42008095:0x3fcebd20 0x420280be:0x3fcebd40
#0 0x4202a8df in gpio_matrix_out_check_and_set at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/driver/i2s.c:212
(inlined by) gpio_matrix_out_check_and_set at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/driver/i2s.c:208
#1 0x4202aaeb in i2s_check_set_mclk at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/driver/i2s.c:267
(inlined by) i2s_set_pin at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/driver/i2s.c:314
#2 0x420140f2 in Audio::setPinout(unsigned char, unsigned char, unsigned char, signed char) at .pio/libdeps/esp32-s3-devkitc-1/ESP32-audioI2S/src/Audio.cpp:3990 (discriminator 6)
#3 0x420033eb in AudioDAC::setup() at src/AudioDAC.cpp:17
#4 0x42006572 in QualityCheck_Audio::startTest() at src/QualityCheck/QualityCheck_Audio.cpp:16
#5 0x42006371 in QualityCheckProcess::goToNextCheck() at src/QualityCheck/QualityCheckProcess.cpp:57
#6 0x420063e5 in QualityCheckProcess::start() at src/QualityCheck/QualityCheckProcess.cpp:22
#7 0x42008095 in setup() at src/main.cpp:11
#8 0x420280be in loopTask(void*) at D:/.platformio/packages/framework-arduinoespressif32/cores/esp32/main.cpp:42
Code: QualityCheck_Audio.h Select all
#ifndef QUALITYCHECK_AUDIO_H
#define QUALITYCHECK_AUDIO_H
#include "QualityCheck.h"
#include "AudioDAC.h"
#define QUALITY_CHECK_AUDIO_DURATION_S 6
class QualityCheck_Audio : public QualityCheck {
public :
QualityCheck_Audio();
void startTest() override;
void update() override;
private :
AudioDAC m_audioDAC;
};
#endif
Code: QualityCheck_Audio.cpp Select all
#include "QualityCheck/QualityCheck_Audio.h"
#include "driver/i2s.h" // Library of I2S routines, comes with ESP32 standard install
#include "driver/ledc.h"
QualityCheck_Audio::QualityCheck_Audio() : QualityCheck("AUDIO DAC") {
}
void QualityCheck_Audio::startTest() {
QualityCheck::startTest();
Serial.println("QualityCheck_Audio::startTest()");
Serial.println("Please plug headphones and check if you hear the sound.");
m_audioDAC.setup();
m_audioDAC.playSound("/sound/test.mp3", true);
m_audioDAC.setVolume(120);
}
void QualityCheck_Audio::update() {
m_audioDAC.update();
if((millis() - m_startTimestamp)/(float)1000 > QUALITY_CHECK_AUDIO_DURATION_S) {
finishTest(true);
}
}
Code: Untitled.cpp Select all
#include "AudioDAC.h"
#include <Arduino.h>
#include <Wire.h>
#include "driver/i2s.h"
#include "HardwareConfig.h"
#include "I2CReadWriteRegister.h"
#include <Audio.h>
AudioDAC::AudioDAC() {
}
void AudioDAC::setup() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
SPI.begin(SD_CLK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
m_audio.setPinout(I2S_BCLK_PIN, I2S_WCLK_PIN, I2S_DOUT_PIN);
if (!SD.begin(SD_CS_PIN)) {
Serial.println("SD Card Initialization Failed!");
} else {
Serial.println("SD Card Initialized successfully.");
}
reset();
configureClock();
configureAnalogBlocks();
powerUpDAC();
delay(2000);
}
void AudioDAC::setVolume(uint8_t volume) {
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_LEFT_DIGITAL_VOLUME, volume);
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_RIGHT_DIGITAL_VOLUME, volume);
}
void AudioDAC::playSound(String mp3FilePath, bool loop) {
if(m_audio.connecttoFS(SD, mp3FilePath.c_str())) {
Serial.println("Audio file connected !");
m_audio.setFileLoop(loop);
}
else
Serial.println("Audio file error.");
}
void AudioDAC::changeConfigPage(uint8_t page) {
writeRegister(AUDIO_DAC_I2C_ADDRESS, PAGE_SELECT_ADDRESS, page);
}
void AudioDAC::reset() {
changeConfigPage(0x00);
writeRegister(AUDIO_DAC_I2C_ADDRESS, REGISTER_RESET_ADDRESS, 0x01);
}
void AudioDAC::configureAnalogBlocks() {
changeConfigPage(0x01); //Change configuration page
writeRegister(AUDIO_DAC_I2C_ADDRESS, AVDD_WEAK_CONNECTION_ADDRESS, 1, 3, 3); //Disable AVDD weak connection
writeRegister(AUDIO_DAC_I2C_ADDRESS, LDO_CONTROL_REGISTER, 1, 0, 0); //Power up the AVDD
writeRegister(AUDIO_DAC_I2C_ADDRESS, LDO_CONTROL_REGISTER, 0, 3, 3); //Enable analog blocks
writeRegister(AUDIO_DAC_I2C_ADDRESS, LDO_CONTROL_REGISTER, 0, 4, 5); //Set LDO AVDD output to nominally 1.72V
writeRegister(AUDIO_DAC_I2C_ADDRESS, COMMON_MODE_CONTROL_REGISTER, 1, 0, 0); //LDOIN between 1.8V and 3.6V
writeRegister(AUDIO_DAC_I2C_ADDRESS, COMMON_MODE_CONTROL_REGISTER, 1, 1, 1); //HPL & HPR powered through AVDD supply.
writeRegister(AUDIO_DAC_I2C_ADDRESS, COMMON_MODE_CONTROL_REGISTER, 0, 4, 5); //HPL & HPR use full chip common mode
writeRegister(AUDIO_DAC_I2C_ADDRESS, COMMON_MODE_CONTROL_REGISTER, 0, 6, 6); //Full chip common mode to 0.9V
writeRegister(AUDIO_DAC_I2C_ADDRESS, ANALOG_INPUT_QUICK_CHARGING_REGISTER, 0x32, 0, 5); //6.4ms analog inputs power up time
writeRegister(AUDIO_DAC_I2C_ADDRESS, HEADPHONE_DRIVER_STARTUP_CONTROL_REGISTER, 1, 6, 7); //Soft routing step time to 50ms
writeRegister(AUDIO_DAC_I2C_ADDRESS, HEADPHONE_DRIVER_STARTUP_CONTROL_REGISTER, 5, 2, 5); //1 time constant
writeRegister(AUDIO_DAC_I2C_ADDRESS, HEADPHONE_DRIVER_STARTUP_CONTROL_REGISTER, 0, 0, 1); //Power up time with 25K resistance
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPL_ROUTING_SELECTION_REGISTER, 0, 2, 2); //Route IN1L to HPL
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPL_ROUTING_SELECTION_REGISTER, 1, 3, 3); //Route Left channel DAC reconstruction's filter positive terminal to HPL
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPR_ROUTING_SELECTION_REGISTER, 0, 2, 2); //Route IN1L to HPL
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPR_ROUTING_SELECTION_REGISTER, 1, 3, 3); //Don't route right channel DAC reconstruction's filter positive terminal to HPR
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPR_ROUTING_SELECTION_REGISTER, 0, 4, 4); //Don't route left channel DAC reconstruction's filter negative terminal to HPR
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPL_DRIVER_GAIN_REGISTER, 0, 6, 6); //Unmute HPL driver gain
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPL_DRIVER_GAIN_REGISTER, 58, 0, 5); //Set HPL driver gain to 0dB
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPR_DRIVER_GAIN_REGISTER, 0, 6, 6); //Unmute HPR driver gain
writeRegister(AUDIO_DAC_I2C_ADDRESS, HPR_DRIVER_GAIN_REGISTER, 58, 0, 5); //Set HPR driver gain to 0dB
writeRegister(AUDIO_DAC_I2C_ADDRESS, OUTPUT_DRIVER_POWER_CONTROl_REGISTER, 1, 4, 4); //Power up HPR
writeRegister(AUDIO_DAC_I2C_ADDRESS, OUTPUT_DRIVER_POWER_CONTROl_REGISTER, 1, 5, 5); //Power up HPL
}
void AudioDAC::powerUpDAC() {
changeConfigPage(0x00); //Change configuration page
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_1, 0, 0, 1); //Soft stepping is 1 step per 1 DAC world clock
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_1, 1, 2, 3); //Right DAC to right channel audio interface data
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_1, 1, 4, 5); //Left DAC to left channel audio interface data
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_1, 1, 6, 6); //Power up right DAC
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_1, 1, 7, 7); //Power up left DAC
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_2, 2, 0, 1); //Right channel volume controlled by left channel volume
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_2, 0, 2, 2); //Unmute right DAC
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_2, 0, 3, 3); //Unmute left DAC
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_2, 2, 4, 6); //Automute enabled if input data is SC for more than 200 consecutive inputs
writeRegister(AUDIO_DAC_I2C_ADDRESS, DAC_CHANNEL_SETUP_REGISTER_2, 0, 7, 7); //When right DAC channel is powered down, the data is zero
}
void AudioDAC::configureClock() {
changeConfigPage(0); //Go to configuration page 1
//Configure PLL
writeRegister(AUDIO_DAC_I2C_ADDRESS, ENABLE_PLL_REGISTER, 1, 4, 6); //Set P value to 1
writeRegister(AUDIO_DAC_I2C_ADDRESS, ENABLE_PLL_REGISTER, 1, 0, 3); //Set R to 1
writeRegister(AUDIO_DAC_I2C_ADDRESS, PLL_J_VALUE_REGISTER, 4, 0, 5); //Set J to 1
writeRegister(AUDIO_DAC_I2C_ADDRESS, PLL_D_VALUE_REGISTER, 0, 0, 5); //Set D to 0
writeRegister(AUDIO_DAC_I2C_ADDRESS, AUDIO_CLOCK_SOURCE_REGISTER, 3, 0, 1); //Set source clock as PLL
writeRegister(AUDIO_DAC_I2C_ADDRESS, AUDIO_CLOCK_SOURCE_REGISTER, 1, 2, 3); //BCLK -> PLL (2,8224MHz for BCLK)
writeRegister(AUDIO_DAC_I2C_ADDRESS, AUDIO_CLOCK_SOURCE_REGISTER, 1, 6, 6); //Set High Clock Range
writeRegister(AUDIO_DAC_I2C_ADDRESS, ENABLE_PLL_REGISTER, 1, 7, 7); //Enable PLL
writeRegister(AUDIO_DAC_I2C_ADDRESS, NDAC_VALUES_REGISTER, 2, 0, 6); //Set NDAC value to 4
writeRegister(AUDIO_DAC_I2C_ADDRESS, NDAC_VALUES_REGISTER, 1, 7, 7); //Power up NDAC divider
writeRegister(AUDIO_DAC_I2C_ADDRESS, MDAC_VALUES_REGISTER, 1, 0, 6); //Set MDAC value to 5
writeRegister(AUDIO_DAC_I2C_ADDRESS, MDAC_VALUES_REGISTER, 1, 7, 7); //Power up MDAC divider
//writeRegister(DOSR_MSB_REGISTER, 0, 0, 1); //Write 0 in ODSR MSB
//writeRegister(DOSR_LSB_REGISTER, 128, 0, 7); //Write 128 in ODSR LSB
writeRegister(AUDIO_DAC_I2C_ADDRESS, AUDIO_INTERFACE_SETTING_REGISTER_1, 3, 4, 5); //Set word as 32 bits
}
void AudioDAC::update() {
m_audio.loop();
}
Thank you for your help !