cancel
Showing results for 
Search instead for 
Did you mean: 

Problem with DAC+DMA End-of-Transmission Interrupts on STM32L452RET6P

MBertocchi
Associate III

 

Spoiler

Hi Friends, I have a problem using the DAC with the DMA. I have configured the DAC's DMA in normal mode and I want it to generate an interrupt at the end of data transmission to let me know that it has sent all the data to the output. The data is modified at the output through Timer 6. This is how the DAC is configured:

static void MX_DAC1_Init(void)
{

  /* USER CODE BEGIN DAC1_Init 0 */

  /* USER CODE END DAC1_Init 0 */

  DAC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN DAC1_Init 1 */

  /* USER CODE END DAC1_Init 1 */

  /** DAC Initialization
  */
  hdac1.Instance = DAC1;
  if (HAL_DAC_Init(&hdac1) != HAL_OK)
  {
    Error_Handler();
  }

  /** DAC channel OUT1 config
  */
  sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
  sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
  sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
  sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_DISABLE;
  sConfig.DAC_UserTrimming = DAC_TRIMMING_FACTORY;
  if (HAL_DAC_ConfigChannel(&hdac1, &sConfig, DAC_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN DAC1_Init 2 */
  /* USER CODE END DAC1_Init 2 */
}

this is how the dma is configured:

void HAL_DAC_MspInit(DAC_HandleTypeDef* hdac)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hdac->Instance==DAC1)
  {
  /* USER CODE BEGIN DAC1_MspInit 0 */

  /* USER CODE END DAC1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_DAC1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**DAC1 GPIO Configuration
    PA4     ------> DAC1_OUT1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* DAC1 DMA Init */
    /* DAC_CH1 Init */
    hdma_dac_ch1.Instance = DMA1_Channel3;
    hdma_dac_ch1.Init.Request = DMA_REQUEST_6;
    hdma_dac_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma_dac_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_dac_ch1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_dac_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_dac_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_dac_ch1.Init.Mode = DMA_NORMAL;
    hdma_dac_ch1.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_dac_ch1) != HAL_OK)
    {
      Error_Handler();
    }
    __HAL_LINKDMA(hdac,DMA_Handle1,hdma_dac_ch1);
    /* DAC1 interrupt Init */
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
  /* USER CODE BEGIN DAC1_MspInit 1 */
  /* USER CODE END DAC1_MspInit 1 */
  }
}

This is the function that handles end-of-transmission interrupts:

 

void DMA1_Channel3_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
  //INTERRUPT DI FINE TRASMISSION DMA
  if (DMA1->ISR & DMA_ISR_TCIF3)
  {
    HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1);
    DMA1->IFCR = DMA_IFCR_CTCIF3; // Reset del flag TCIF3
    HAL_DMA_IRQHandler(&hdma_dac_ch1);
  }
}

it all seems to work, but it signals the end-of-transmission interrupt when there are still two values ​​of the array to be transmitted. When the array is composed of only two values, there is never an error. Please help me because I can't solve the problem.

 

HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, &DAC_VALUE, LUNGHEZZA, DAC_ALIGN_12B_R);

function to start data transmission.

1 ACCEPTED SOLUTION

Accepted Solutions

Hi @KnarfB ,

Thanks for the example. 

This confirms what I wrote above: if the highest level is the last sample, DMA's Transfer Complete comes *one* trigger cycle before the last sample is output. 

Non-triggered mode of DAC, and DMA triggered by timer rather than by DAC, can be used to achieve what @MBertocchi probably wants to achieve; or, if a signal/interrupt is desired one cycle after the last sample is output (at the falling edge of the above sawtooth signal), the output values table could be rearranged accordingly; or indirectly a timer interrupt could be used.

JW

View solution in original post

7 REPLIES 7

> it signals the end-of-transmission interrupt when there are still two values ​​of the array to be transmitted

Why do you think so? 

Have you read the DAC chapter in RM?

JW

I did some tests using a defined signal to transmit on the DAC and a physical pin that goes high when an interrupt is generated. The array signal that is transmitted on the DAC is composed of 10 different points and when it transmits the eighth value I see that the physical pin goes high...so I think it is not handling the end-of-transmission interrupt correctly. If instead I create an array of only two points when it has transmitted the second point it generates an interrupt and the pin goes high, so it seems to work correctly, but only up to two points.

 

help me understand what you mean by RM in the DAC chapter.

The Transfer Complete interrupt from DMA comes when DMA transfers the last of data to the target. However, if DAC is set to be triggered from an external trigger (timer), data are written (by DMA) to DAC's Holding register, and are moved (by DAC itself) from it to the actual DAC (Output register) one trigger period later. 

This is described in the DAC chapter in the Reference Manual (RM).

I don't know why do you observe two sample lag. I don't use Cube.

Try a very slow timer period to see this effect more prominently.

I think it is not correct to say that I observe a delay of two samples, but rather that the DMA thinks it has transmitted all the data to the DAC when in reality there are still two data points to send out.

As @waclawek.jan said, the DMA transfer complete indicates that the DMA is done, which will be before the peripheral has finished the output. Some peripherals do have extra flags for that, like TXE vs. TC flags in USART. But I don't see this for DAC. 

 

I can confirm @MBertocchi observations like when transferring a 16-step sawtooth using circular DMA:

sawtooth.png

orange is the DAC output, blue a GPIO/LED toggled in the interrupt. 

The code is at register-level, no HAL involved:

 

#include <math.h>

#include <stm32l432xx.h>
#include <sysclk.h>

// PA4 --> DAC1_OUT1
// PA5 --> DAC1_OUT2

// TIM --> DMA2 Ch 4 Select 3 --> DAC Ch 1
// TIM --> DMA2 Ch 5 Select 3 --> DAC Ch 2

// 17.4.6 DAC trigger selection Table 75.: TIM6_TRGO == TSEL 000

// 100 kHz sample rate
#define Fsample 100000

#define DAC_BUFFER_SAMPLES 16

uint16_t bufferL[DAC_BUFFER_SAMPLES];

// int the user LED at pin PB3.
void init_LED(void)
{
    RCC->AHB2ENR |= RCC_AHB2ENR_GPIOBEN; // enable clock for peripheral component GPIOB
    (void)RCC->AHB2ENR;                  // ensure that the last write command finished and the clock is on
    // set pin to output mode (1). Reset defaults for other registers are okay here
    GPIOB->MODER = (GPIOB->MODER & ~GPIO_MODER_MODE3_Msk) | (1 << GPIO_MODER_MODE3_Pos);
}

void init_TIM6(void)
{
    RCC->APB1ENR1 |= RCC_APB1ENR1_TIM6EN; // enable peripheral clock
    (void)RCC->APB1ENR1;                  // ensure that the last write command finished and the clock is on

    // PSC[15:0] Prescaler register - divides the counter clock by factor PSC+1. (reset value: 0x0000)
    TIM6->PSC = 0;
    TIM6->ARR = (SystemCoreClock / Fsample) - 1;
    TIM6->EGR = TIM_EGR_UG;
    TIM6->CR2 = (TIM6->CR2 & ~TIM_CR2_MMS_Msk) | (2 << TIM_CR2_MMS_Pos); // The update event is selected as trigger output (TRGO)
    TIM6->CR1 |= TIM_CR1_CEN;                                            // enable the timer (start counting)
}

void init_DAC1(void)
{
    // DAC out are additional functions, no need to change reset defaults for GPIO

    RCC->APB1ENR1 |= RCC_APB1ENR1_DAC1EN; // enable peripheral clock
    (void)RCC->APB1ENR1;                  // ensure that the last write command finished and the clock is on

    // DAC1 MCR MODE2[2:0] shall be reset default (0000)): Channel is connected to external pin with Buffer enabled

    DAC1->CR = 0 << DAC_CR_TSEL1_Pos; // TIM6_TRGO (reset default)
    DAC1->CR |= DAC_CR_TEN1;          // trigger enable
    DAC1->CR |= DAC_CR_DMAEN1;        // DMA enable
    DAC1->CR |= DAC_CR_EN1;           // channel enable 
}

void DMA2_CH4_IRQHandler(void)
{
    if (DMA2->ISR & DMA_ISR_HTIF4)
    {
        DMA2->IFCR = DMA_IFCR_CHTIF4;
        GPIOB->BSRR = GPIO_BSRR_BR3;
    }
    if (DMA2->ISR & DMA_ISR_TCIF4)
    {
        DMA2->IFCR = DMA_IFCR_CTCIF4;
        GPIOB->BSRR = GPIO_BSRR_BS3;
    }
}

void init_DMA(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
    (void)RCC->AHB1ENR;

    DMA2_Channel4->CCR &= ~DMA_CCR_EN;

    // DAC1 Ch1 trigger is DMA2 Channel 4 select 3, see ref.man. Table 41.
    DMA2_CSELR->CSELR = (DMA2_CSELR->CSELR & ~DMA_CSELR_C4S_Msk) | (3 << DMA_CSELR_C4S_Pos);

    DMA2_Channel4->CCR = 0; // disable channel for setup
    DMA2_Channel4->CMAR = (uint32_t)bufferL;
    DMA2_Channel4->CPAR = (uint32_t)&DAC1->DHR12R1;
    DMA2_Channel4->CNDTR = (uint32_t)DAC_BUFFER_SAMPLES;
    DMA2_Channel4->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_CIRC | (1 << DMA_CCR_MSIZE_Pos) | (1 << DMA_CCR_PSIZE_Pos) | DMA_CCR_HTIE | DMA_CCR_TCIE | DMA_CCR_EN;

    NVIC_EnableIRQ(DMA2_Channel4_IRQn);
}

int main(void)
{
    sysclk_init_hsi_80();
    for(int i=0; i<DAC_BUFFER_SAMPLES; ++i) {
        bufferL[i] = 200 + 200 * i;
    } 
    init_LED();
    init_DAC1();
    init_DMA();
    init_TIM6();
    /* Loop forever */
    for (;;)
        ;
}

 

hth

KnarfB

Hi @KnarfB ,

Thanks for the example. 

This confirms what I wrote above: if the highest level is the last sample, DMA's Transfer Complete comes *one* trigger cycle before the last sample is output. 

Non-triggered mode of DAC, and DMA triggered by timer rather than by DAC, can be used to achieve what @MBertocchi probably wants to achieve; or, if a signal/interrupt is desired one cycle after the last sample is output (at the falling edge of the above sawtooth signal), the output values table could be rearranged accordingly; or indirectly a timer interrupt could be used.

JW

Thank you very much now I understand better, I will try to work and solve this problem to have an interrupt on the falling edge. I thought it was a bug of the STM32L452RE instead I understood that it was working correctly.

@KnarfB Thanks for the example.