Code works fine with static allocation of my class, crashes when dynamically allocating it

AnselmeC
Posts: 3
Joined: Wed Nov 13, 2024 5:08 am

Code works fine with static allocation of my class, crashes when dynamically allocating it

Postby AnselmeC » 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 :

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
Please see bellow the QualityCheck_Audio class :

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);
}
}
Class AudioDac :

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();
}
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 !

MicroController
Posts: 2668
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Code works fine with static allocation of my class, crashes when dynamically allocating it

Postby MicroController » Sun Dec 29, 2024 1:15 pm

You may be corrupting some memory, e.g. through a stack overflow happening as a result of the recursive function calling.

Who is online

Users browsing this forum: No registered users and 1 guest