Help with setting up interrupts for realtime audio processing

Hi! I'm new to DMA and need some help to understand how I can achieve the following:

I'm using the Arduino_AdvancedAnalog library and the Giga board. I'm trying to learn some basic audio generation/processing and not sure how I should set up my code. I have gotten the examples to output sound but all the examples needs to poll dac.available() in the main loop. As my project grows I need the filling of the DMA buffers to be interrupt based and do the processing in blocks/buffers so the cpu can handle other stuff in between.

So basically what I want to achieve is to continuously output sound at 44.1kHz in a block/buffer based way. I understand I need two use two buffers, while buffer0 is being sent to the DAC using DMA buffer1 gets filled with new samples, when buffer0 completes the transfer buffer1 starts it's transfer while buffer0 is now being filled with the next block of samples.

Should I set up a timer interrupt in exact microseconds that triggers my processAudio() and then call the Dac.write() function from the AdvancedAnalog library?

Or is there a way to have the DMA transfer function trigger an interrupt that calls the processAudio() function every time it starts a transfer of a new buffer? So I can make sure that the next sample buffer is generated/calculated before it's time to send it to dac?

All my processAudio() function should do for now is generate a sinewave in blocks of 32 samples. This I know how to do with keeping track of the phase etc.

I'm I on the right track or is there another way this is usually done?

Thanks!

Anyone?

This is my code with some (not very usefull) help from chat gpt so far but it does not compile:

#include <Arduino_AdvancedAnalog.h>
#include <AdvancedDAC.h>

#define SAMPLE_RATE 44100
#define BUFFER_SIZE 64
#define FREQUENCY 1000 // Frequency of the sine wave in Hz

AdvancedDAC dac0(A12);

// Sine wave generation state variables
static float phase = 0.0;
const float phase_increment = 2 * PI * FREQUENCY / SAMPLE_RATE;

// Interrupt callback functions
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac);

// Declare the dac_descr_all as extern if it's available externally
extern dac_descr_t dac_descr_all[];

void setup() {
    Serial.begin(9600);

    while (!Serial) {
        // Wait for serial connection
    }

    if (!dac0.begin(AN_RESOLUTION_12, SAMPLE_RATE, BUFFER_SIZE, 64)) {
        Serial.println("Failed to start DAC0!");
        while (1);
    }

    // Enable the Transfer Complete interrupt
    __HAL_DMA_ENABLE_IT(&dac_descr_all[0].dma, DMA_IT_TC);

}

void dac_fill_buffer(AdvancedDAC &dac_out) {
    // Get a free buffer for writing
    SampleBuffer buf = dac_out.dequeue();
    
    // Calculate the sine wave values
    for (size_t i = 0; i < buf.size(); i++) {
        float value = 2047 + 2047 * sin(phase); // 12-bit DAC range: 0 to 4095
        buf.data()[i] = static_cast<uint16_t>(value);
        phase += phase_increment;

        // Wrap phase to avoid overflow
        if (phase >= 2 * PI) {
            phase -= 2 * PI;
        }
    }

    // Write the buffer to DAC
    dac_out.write(buf);
}

// DMA Transfer Complete Callback
extern "C" void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac) {
    dac_fill_buffer(dac0);
}


void loop() {
    // No need to poll here anymore
}

Am I on the right track or how does one do this kind of thing?

Surely you have the data sheets for the processor and it would have a full discussion of how it handles DMA. So, does that discussion give any clue that you can CLOCK the timing of output from the DMA circuit? Without that capability, you cannot get continuous output at a set clocking rate to create your audio.
If you do get DMA output at the rate you want, how will you change the digital data to analog and create audio which is AC.

I'm pretty sure all that functionality is there, I've seen the stm32h7 used for real time audio on youtube.

I do get proper sound output to the dac at 44.1kHz using the AdvancedAnalog library but all the examples calls the available() library function all the time in the loop to check if the DMA has a free buffer to write to and then I can do my processing and write it to the buffer.

The library has buffer queues and handles switching the which buffer is transfered using DMA automatically.

But if I add more code to the loop I can't ensure that the available function and my audio processing function is called in time to fill a new buffer.

There is this in the stm32h7xx_hal_dma.h file that I suspect is related:


/** @defgroup DMA_Exported_Functions_Group2 I/O operation functions
  * @brief   I/O operation functions
  * @{
  */
HAL_StatusTypeDef HAL_DMA_Start (DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);
HAL_StatusTypeDef HAL_DMA_Start_IT(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength);
HAL_StatusTypeDef HAL_DMA_Abort(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_Abort_IT(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, HAL_DMA_LevelCompleteTypeDef CompleteLevel, uint32_t Timeout);
void              HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma);
HAL_StatusTypeDef HAL_DMA_RegisterCallback(DMA_HandleTypeDef *hdma, HAL_DMA_CallbackIDTypeDef CallbackID, void (* pCallback)(DMA_HandleTypeDef *_hdma));
HAL_StatusTypeDef HAL_DMA_UnRegisterCallback(DMA_HandleTypeDef *hdma, HAL_DMA_CallbackIDTypeDef CallbackID);

And there's this in the AdvancedDAC.cpp (part of the AdvancedAnalog library)

void DMA1_Stream4_IRQHandler() {
    HAL_DMA_IRQHandler(&dac_descr_all[0].dma);
}

void DMA1_Stream5_IRQHandler() {
    HAL_DMA_IRQHandler(&dac_descr_all[1].dma);
}

I have read in the stm documentation that the DMA provides half transfer and full transfer complete interrupts. I just don't understand how I can utilize these in my own code to write what should be done when the transfer interrupt is done.

Or should I just use a timer based interrupt that I make sure calls my audio processing function and write the samples to the next DMA buffer in short enough intervals to be done before a DMA buffer is finished?

From what I've understood so far having the DMA transfer interrupt trigger the audio processing function for the next buffer is a better way to do it but I'm not sure.
I'm kind of new to all this and reading and understanding the HAL so maybe I'm misunderstanding it all.