Skip to main content
Visitor II
November 30, 2025
Solved

Basic DAC + DMA configuration for on the stm32F446re without HAL-libaries

  • November 30, 2025
  • 2 replies
  • 206 views

Hi,

 

I'm using a Nucleo-F446RE Board and I'm trying to set up a basic DAC with DMA configuration without HAL-libaries.

The configuration without DMA works just fine, I can measure correct voltages with my oscilloscope on PA4 as intended. My following DMA configuration sets the PA4 always on 0V, nothing happens.

I checked the debugger and all registers are being properly set, DMA and DAC both have their clock and values.

It just doesn't copy the values into DAC->DHR12R1. I also checked the Flags in DMA_LISR, but no error there.

I'm probably missing somehow on how to start the whole process, so my last iteration of code tried to mess with software triggers. I also tried all combinations of PSIZE and MSIZE, as well as uint8_t and uint32_t arrays instead of uint16_t. I feel like I followed the stream configuration procedure from the reference manual, but I probably missed some small, gritty detail.

Anybody got code for a similar MCU or sees the problem in my configuration?

 

#include "stm32f4xx.h"

#define DAC_SIZE 8

void delay(volatile uint32_t count) {
 while(count--) __asm__("nop");
}

//uint8_t dac_buffer[DAC_SIZE] = {50, 0, 30, 60, 120, 150, 180, 240};
uint16_t dac_buffer[DAC_SIZE] = {2000, 512, 1024, 1536, 2048, 2560, 3072, 4095};

int main(void) {
 // GPIOA Clock aktivieren (PA0 als analog)
 RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
 GPIOA->MODER |= (0b11 << (4*2)); // PA4 = Analog Mode fuer DAC


 // DAC Clock
 RCC->APB1ENR |= RCC_APB1ENR_DACEN;

 // DMA Clock
 RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;



 /* 5. DMA1 Stream5 konfigurieren (DAC1) */
 DMA1_Stream5->CR = 0; // Reset
 while (DMA1_Stream5->CR & DMA_SxCR_EN) {
 }
 DMA1_Stream5->PAR = (uint32_t)&(DAC->DHR12R1); // Peripherieadresse
 DMA1_Stream5->M0AR = (uint32_t)dac_buffer; // Speicheradresse
 DMA1_Stream5->NDTR = DAC_SIZE; // Anzahl Daten X
 DMA1_Stream5->CR |= (0b111 << DMA_SxCR_CHSEL_Pos); // Channel 7 = DAC1 X
 DMA1_Stream5->CR |= DMA_SxCR_PL_1; // Priority High X
 //FIFO usage => direct mode not needed?
	DMA1_Stream5->CR |= (0b01 << DMA_SxCR_PSIZE_Pos); // aktuell auf 16bit, 32bit und 8bit wurde ausprobiert
 DMA1_Stream5->CR |= (0b01 << DMA_SxCR_MSIZE_Pos);

 DMA1_Stream5->CR |= DMA_SxCR_MINC; // Speicher inkrementieren X
 DMA1_Stream5->CR |= DMA_SxCR_CIRC; // Circular Mode X
 DMA1_Stream5->CR |= DMA_SxCR_DIR_0; // Memory -> Peripheral X
 //DMA1_Stream5->CR |= (1 << DMA_SxCR_PFCTRL_Pos); // forbidden in circular mode anyways
 DMA1_Stream5->CR |= DMA_SxCR_EN; // Stream aktiv X

 // DAC1 DMA enable
 DAC->CR |= DAC_CR_DMAEN1;

 //DAC1 einschalten
 DAC->CR |= DAC_CR_EN1;

 DAC->CR |= DAC_CR_TEN1; // Enable trigger
 DAC->CR |= (0b111 << 3); // Select software trigger (TSEL1 = 111)

 //DAC->DHR12R1 = 0; // test, ob es ueberhaupt etwas ausgibt

 while(1) {
 	delay(1000);
 	DAC->SWTRIGR |= DAC_SWTRIGR_SWTRIG1; // Trigger DAC manually

 }
}

 

    This topic has been closed for replies.
    Best answer by Michael8278

    In the end i reverse-engineered a HAL-Example...

    My lesson learned is: It only works with Timers and only with update in CR2 configured.

    Here my working more or less minimal configuration with TIM6, for anybody struggling with the same problem:

    #include "stm32f4xx.h"
    
    #define DAC_SIZE 8
    
    void delay(volatile uint32_t count) {
     while(count--) __asm__("nop");
    }
    
    //uint8_t dac_buffer[DAC_SIZE] = {50, 0, 30, 60, 120, 150, 180, 240};
    volatile uint16_t dac_buffer[DAC_SIZE] = {0, 4000, 0, 4000, 0, 4000, 0, 4000};
    
    volatile uint32_t cb_half = 0;
    volatile uint32_t cb_full = 0;
    volatile uint32_t error_flag = 0;
    volatile uint32_t tim6_cnt = 0;
    volatile uint32_t dma_cnt = 0;
    
    
    void TIM6_DAC_IRQHandler(void)
    {
    	tim6_cnt++;
     TIM6->SR &= ~TIM_SR_UIF; // Clear flag
    }
    
    //void DMA1_Stream5_IRQHandler(void)
    //{
    //	dma_cnt++;
    // // ---------------- Half Transfer ----------------
    // if (DMA1->HISR & DMA_HISR_HTIF5) // HT flag set?
    // {
    // DMA1->HIFCR |= DMA_HIFCR_CHTIF5; // Clear HT flag
    // cb_half++;
    
    // }
    //
    // // ---------------- Transfer Complete ----------------
    // if (DMA1->HISR & DMA_HISR_TCIF5) // TC flag set?
    // {
    // DMA1->HIFCR |= DMA_HIFCR_CTCIF5; // Clear TC flag
    // cb_full++; // Set user flag / handle buffer
    // }
    //
    //}
    
    
    void TIM6_Init()
    {
     // clk
     RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    
    
     TIM6->PSC = 0; // PSC = prescaler
     TIM6->ARR = 500; // ARR = auto-reload
    
     TIM6->DIER |= TIM_DIER_UIE;
     NVIC_SetPriority(TIM6_DAC_IRQn, 1);
     NVIC_EnableIRQ(TIM6_DAC_IRQn);
    
     //enable
     TIM6->CR2 = 0x20;
     TIM6->CR1 |= TIM_CR1_CEN;
    }
    
    
    
    int main(void) {
     // GPIOA Clock aktivieren (PA0 als analog)
     RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
     GPIOA->MODER |= (0b11 << (4*2)); // PA4 = Analog Mode fuer DAC
    
    
     // DAC Clock
     RCC->APB1ENR |= RCC_APB1ENR_DACEN;
    
     // DMA Clock
     RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
    
     TIM6_Init();
    
     /* 5. DMA1 Stream5 konfigurieren (DAC1) */
     DMA1_Stream5->CR = 0; // Reset
     while (DMA1_Stream5->CR & DMA_SxCR_EN) {
     }
     DMA1_Stream5->PAR = (uint32_t)&(DAC->DHR12R1); // Peripherieadresse
     DMA1_Stream5->M0AR = (uint32_t)dac_buffer; // Speicheradresse
     DMA1_Stream5->NDTR = DAC_SIZE; // Anzahl Daten X
     DMA1_Stream5->CR |= (0b111 << DMA_SxCR_CHSEL_Pos); // Channel 7 = DAC1 X
     //DMA1_Stream5->CR |= DMA_SxCR_PL_1; // Priority High X
     //FIFO usage => direct mode not needed?
    	DMA1_Stream5->CR |= (0b01 << DMA_SxCR_PSIZE_Pos); // aktuell auf 16bit, 32bit und 8bit wurde ausprobiert
     DMA1_Stream5->CR |= (0b01 << DMA_SxCR_MSIZE_Pos);
    
     DMA1_Stream5->CR |= DMA_SxCR_MINC; // Speicher inkrementieren X
     DMA1_Stream5->CR |= DMA_SxCR_CIRC; // Circular Mode X
     DMA1_Stream5->CR |= DMA_SxCR_DIR_0; // Memory -> Peripheral X
     //DMA1_Stream5->CR |= (1 << DMA_SxCR_PFCTRL_Pos); // forbidden in circular mode anyways
     DMA1_Stream5->CR |= (1 << 3); // HTIE
     DMA1_Stream5->CR |= (1 << 4); // TCIE
     DMA1_Stream5->CR |= (1 << 2); // TEIE
     DMA1_Stream5->CR |= (1 << 1); // TEIE
     DMA1_Stream5->CR |= DMA_SxCR_EN; // Stream aktiv X
    
     NVIC_SetPriority(DMA1_Stream5_IRQn, 1);
     NVIC_EnableIRQ(DMA1_Stream5_IRQn);
     // DAC1 DMA enable
     DAC->CR |= DAC_CR_DMAEN1;
    
     //DAC1 einschalten
     DAC->CR |= DAC_CR_EN1;
    
     DAC->CR |= DAC_CR_TEN1; // Enable trigger
     DAC->CR |= (0b000 << 3); // sel tim6
    
    
     while(1) {
     }
    }

     

    2 replies

    Super User
    November 30, 2025

    With TSEL=7, you have selected software trigger, so you'll have to set DAC_SWTRIGR_SWTRIG1 for each value. If that's not what you want, select a different trigger and enable the TRGO event from that peripheral if using a timer.

    TDK_0-1764529175295.png

     

    Visitor II
    November 30, 2025

    I know, I didn't want this to be a final solution, I wanted to trigger _anything_. No value has been triggered with my setup, it's always 0.

    I will try setting up a Timer 6 TRGO event next, although this makes the code more complicated and more error-prone.

    Michael8278AuthorAnswer
    Visitor II
    December 7, 2025

    In the end i reverse-engineered a HAL-Example...

    My lesson learned is: It only works with Timers and only with update in CR2 configured.

    Here my working more or less minimal configuration with TIM6, for anybody struggling with the same problem:

    #include "stm32f4xx.h"
    
    #define DAC_SIZE 8
    
    void delay(volatile uint32_t count) {
     while(count--) __asm__("nop");
    }
    
    //uint8_t dac_buffer[DAC_SIZE] = {50, 0, 30, 60, 120, 150, 180, 240};
    volatile uint16_t dac_buffer[DAC_SIZE] = {0, 4000, 0, 4000, 0, 4000, 0, 4000};
    
    volatile uint32_t cb_half = 0;
    volatile uint32_t cb_full = 0;
    volatile uint32_t error_flag = 0;
    volatile uint32_t tim6_cnt = 0;
    volatile uint32_t dma_cnt = 0;
    
    
    void TIM6_DAC_IRQHandler(void)
    {
    	tim6_cnt++;
     TIM6->SR &= ~TIM_SR_UIF; // Clear flag
    }
    
    //void DMA1_Stream5_IRQHandler(void)
    //{
    //	dma_cnt++;
    // // ---------------- Half Transfer ----------------
    // if (DMA1->HISR & DMA_HISR_HTIF5) // HT flag set?
    // {
    // DMA1->HIFCR |= DMA_HIFCR_CHTIF5; // Clear HT flag
    // cb_half++;
    
    // }
    //
    // // ---------------- Transfer Complete ----------------
    // if (DMA1->HISR & DMA_HISR_TCIF5) // TC flag set?
    // {
    // DMA1->HIFCR |= DMA_HIFCR_CTCIF5; // Clear TC flag
    // cb_full++; // Set user flag / handle buffer
    // }
    //
    //}
    
    
    void TIM6_Init()
    {
     // clk
     RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    
    
     TIM6->PSC = 0; // PSC = prescaler
     TIM6->ARR = 500; // ARR = auto-reload
    
     TIM6->DIER |= TIM_DIER_UIE;
     NVIC_SetPriority(TIM6_DAC_IRQn, 1);
     NVIC_EnableIRQ(TIM6_DAC_IRQn);
    
     //enable
     TIM6->CR2 = 0x20;
     TIM6->CR1 |= TIM_CR1_CEN;
    }
    
    
    
    int main(void) {
     // GPIOA Clock aktivieren (PA0 als analog)
     RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
     GPIOA->MODER |= (0b11 << (4*2)); // PA4 = Analog Mode fuer DAC
    
    
     // DAC Clock
     RCC->APB1ENR |= RCC_APB1ENR_DACEN;
    
     // DMA Clock
     RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
    
     TIM6_Init();
    
     /* 5. DMA1 Stream5 konfigurieren (DAC1) */
     DMA1_Stream5->CR = 0; // Reset
     while (DMA1_Stream5->CR & DMA_SxCR_EN) {
     }
     DMA1_Stream5->PAR = (uint32_t)&(DAC->DHR12R1); // Peripherieadresse
     DMA1_Stream5->M0AR = (uint32_t)dac_buffer; // Speicheradresse
     DMA1_Stream5->NDTR = DAC_SIZE; // Anzahl Daten X
     DMA1_Stream5->CR |= (0b111 << DMA_SxCR_CHSEL_Pos); // Channel 7 = DAC1 X
     //DMA1_Stream5->CR |= DMA_SxCR_PL_1; // Priority High X
     //FIFO usage => direct mode not needed?
    	DMA1_Stream5->CR |= (0b01 << DMA_SxCR_PSIZE_Pos); // aktuell auf 16bit, 32bit und 8bit wurde ausprobiert
     DMA1_Stream5->CR |= (0b01 << DMA_SxCR_MSIZE_Pos);
    
     DMA1_Stream5->CR |= DMA_SxCR_MINC; // Speicher inkrementieren X
     DMA1_Stream5->CR |= DMA_SxCR_CIRC; // Circular Mode X
     DMA1_Stream5->CR |= DMA_SxCR_DIR_0; // Memory -> Peripheral X
     //DMA1_Stream5->CR |= (1 << DMA_SxCR_PFCTRL_Pos); // forbidden in circular mode anyways
     DMA1_Stream5->CR |= (1 << 3); // HTIE
     DMA1_Stream5->CR |= (1 << 4); // TCIE
     DMA1_Stream5->CR |= (1 << 2); // TEIE
     DMA1_Stream5->CR |= (1 << 1); // TEIE
     DMA1_Stream5->CR |= DMA_SxCR_EN; // Stream aktiv X
    
     NVIC_SetPriority(DMA1_Stream5_IRQn, 1);
     NVIC_EnableIRQ(DMA1_Stream5_IRQn);
     // DAC1 DMA enable
     DAC->CR |= DAC_CR_DMAEN1;
    
     //DAC1 einschalten
     DAC->CR |= DAC_CR_EN1;
    
     DAC->CR |= DAC_CR_TEN1; // Enable trigger
     DAC->CR |= (0b000 << 3); // sel tim6
    
    
     while(1) {
     }
    }