Electrical – STM32 : reset the I2S DMA address pointer

dacdmai2ssoundstm32f4

For my application I have a DAC communicating with an STM32F4 through I2S. On the microcontroller's side, informations are send to the DAC through a DMA stream, with a circular buffer.
On an external interrupt, I would like to pause the DMA, change some sound parameters and then resume the DMA stream.

My interrupt routine is basically like that :

void HAL_GPIO_EXTI_Callback(uint16_t pin)
{
    if(pin == GPIO_PIN_13)
    {
        HAL_I2S_DMAPause(&hi2s2);
        changeSound(&sound);
        HAL_I2S_DMAResume(&hi2s2);
    }
}

And I have 2 interrupt routines triggered on DMA TxHalf/TxCplt that fill each half of my DMA buffer with the sound. (EDIT : during my research I also learned that there is a "double buffer mode" that allows to do that with some advantages, but that's off topic.)

What I want to avoid is to transfer some remaining samples of the old sound after my Exti interrupt. So my idea was to fill the whole buffer after changing the sound, and then reset the memory pointer of the DMA stream to the beginning of my buffer.

It would look something like that :

void HAL_GPIO_EXTI_Callback(uint16_t pin)
{
    if(pin == GPIO_PIN_13)
    {
        HAL_I2S_DMAPause(&hi2s2);
        changeSound(&sound);
        fillBuffer( address_of_1st_half, &sound);
        fillBuffer( address_of_2nd_half, &sound);
        *reset_DMA(&hi2s2)*; <==== this is the function I'm looking for
        HAL_I2S_DMAResume(&hi2s2);
    }
}

nb : as you can see I'm using CubeMX's HAL drivers

I have looked into the HAL datasheet for such a function but I haven't found any.

The only thing that is close to that is using DMAStop() and then Transmit_DMA() again. But I'm afraid that would take too much time…

If anybody has an idea I'd be glad to hear it!

Thanks

Best Answer

Ok so I might not have found a way of actually reset the DMA pointer without disabling the stream (yet) but I found more information, and a way of circumventing the problem.

  • When you use circular mode with pointer incrementation, the address of the data to be transferred is calculated from the DMA_SxNDTR register (which contains the remaining number of data to be sent) and the DMA_SxM0AR register (which contains the ase address for the DMA stream). The pointer is the base address offsetted by the size of the buffer minus the DMA_SxNDTR value. If you reset DMA_SxNDTR to the size of the data, you reset the pointer to the base address (from what I could understand, I could be wrong). However, you can't change the value of the register unless the stream is disabled. So to reset the pointer, you would have to disable the stream, reset DMA_SxNDTR and then reactivate the stream.
  • You could however read the DMA_SxNDTR register to know in which part of the buffer you are, so that you can refill only the other half. In that case, the peripheral would receive the remains of the old sound, then start reading the new sound safely.
  • A third possibility would be to read the DMA_SxNDTR and start refilling the buffer only when there's no data left to transfer. It adds in latency but avoids the old sound remains to be played.

The last solution would look like that :

void HAL_GPIO_EXTI_Callback(uint16_t pin)
{
    if(pin == GPIO_PIN_13)
    {
        changeSound(&sound);
        while(DMA1_Stream4->NDTR >= 1);
        HAL_I2S_DMAPause(&hi2s2);
        fillBuf(&buf, &sound);
        HAL_I2S_DMAResume(&hi2s2);
    }
}

I haven't tried it yet so I can't be sure if it works or not, I'll update this answer as soon as I do.

Sources :