2025-11-30 9:42 AM
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
}
}
Solved! Go to Solution.
2025-12-07 5:29 AM
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) {
}
}
2025-11-30 11:00 AM
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.
2025-11-30 11:04 AM
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.
2025-12-07 5:29 AM
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) {
}
}