2025-07-18 11:18 AM - last edited on 2025-07-18 2:47 PM by Tesla DeLorean
As the title says, I've got a (circular) buffer of uint16_t values that I want to send to the DAC every time the timer updates. It seems like it's 95% working, in that if I manually trigger the DAC:
for (int i = 0; i < 10000; ++i)
{
LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_1);
HAL_Delay(1);
}
I can see my waveform on the output pin. But when I try to have the DMA send the values to the DAC, the output never changes.
One complication is that I need to run in two modes - in one mode I'm taking the ADC input, processing it, and sending that to the DAC, in the other mode (I'm calling "inject" mode) the ADC is ignored and the DAC values come from my memory buffer.
The code to switch between modes looks like this:
if (inject && !Inject)
{
// Turn on inject mode
// HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t*)Transfer, 4096, DAC_ALIGN_12B_R);
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
while (LL_DMA_IsEnabledChannel(DMA1, LL_DMA_CHANNEL_3)); // Wait till off
LL_DAC_Disable(DAC1, LL_DAC_CHANNEL_1);
//
LL_DAC_ClearFlag_DMAUDR1(DAC1);
LL_DMA_ClearFlag_TC3(DMA1);
LL_DMA_ClearFlag_HT3(DMA1);
LL_DMA_ClearFlag_TE3(DMA1);
//
LL_DAC_SetTriggerSource(DAC1, LL_DAC_CHANNEL_1, LL_DAC_TRIG_EXT_TIM4_TRGO);
LL_DAC_EnableTrigger(DAC1, LL_DAC_CHANNEL_1);
//
LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_3, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PDATAALIGN_HALFWORD);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MDATAALIGN_HALFWORD);
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_3, (uint32_t)&DAC1->DHR12R1);
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_3, (uint32_t)Transfer);
LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_3, 4096);
// Enable DMA request on DAC channel
LL_DAC_EnableDMAReq(DAC1, LL_DAC_CHANNEL_1);
// Enable DMA channel
LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_3);
LL_DAC_Enable(DAC1, LL_DAC_CHANNEL_1);
//
LL_TIM_SetTriggerOutput(TIM4, LL_TIM_TRGO_UPDATE);
// LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_1);
//
/*
for (int i = 0; i < 10000; ++i)
{
LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_1);
HAL_Delay(1);
}
*/
Inject = inject;
}
else if ((!inject) && Inject)
{
// Turn off inject mode
LL_DAC_SetTriggerSource(DAC1, LL_DAC_CHANNEL_1, LL_DAC_TRIG_SOFTWARE);
LL_DAC_DisableTrigger(DAC1, LL_DAC_CHANNEL_1);
// LL_DAC_Disable(DAC1, LL_DAC_CHANNEL_1);
//
LL_DAC_DisableDMAReq(DAC1, LL_DAC_CHANNEL_1);
LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_3);
//
Inject = inject;
}
Here are the init functions created from the IOC file:
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
static void MX_TIM4_Init(void)
{
/* USER CODE BEGIN TIM4_Init 0 */
/* USER CODE END TIM4_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM4_Init 1 */
/* USER CODE END TIM4_Init 1 */
htim4.Instance = TIM4;
htim4.Init.Prescaler = 239;
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 10;
htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim4) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim4, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim4, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM4_Init 2 */
/* USER CODE END TIM4_Init 2 */
}
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_HighFrequency = DAC_HIGH_FREQUENCY_INTERFACE_MODE_AUTOMATIC;
sConfig.DAC_DMADoubleDataMode = DISABLE;
sConfig.DAC_SignedFormat = DISABLE;
sConfig.DAC_SampleAndHold = DAC_SAMPLEANDHOLD_DISABLE;
sConfig.DAC_Trigger = DAC_TRIGGER_T4_TRGO;
sConfig.DAC_Trigger2 = DAC_TRIGGER_NONE;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
sConfig.DAC_ConnectOnChipPeripheral = DAC_CHIPCONNECT_EXTERNAL;
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 */
}
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_MultiModeTypeDef multimode = {0};
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.GainCompensation = 0;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure the ADC multi-mode
*/
multimode.Mode = ADC_MODE_INDEPENDENT;
if (HAL_ADCEx_MultiModeConfigChannel(&hadc1, &multimode) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
What am I doing wrong? Thanks for any help!
Solved! Go to Solution.
2025-07-21 8:04 AM - edited 2025-07-21 8:04 AM
Great catch! I changed the code to say
LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_PDATAALIGN_WORD);
LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_3, LL_DMA_MDATAALIGN_WORD);
and then I removed the dependency on the timer. I now have it so that similar to "mode 1", the program gets an interrupt when the ADC has a value, but in "mode 2" it discards the ADC value and simply tells the DAC to grab the next value:
else if (Inject)
{
LL_DAC_TrigSWConversion(DAC1, LL_DAC_CHANNEL_1);
numInjects++;
}
is that backwards? Should I be telling the DMA to send the next value to the DAC instead of telling the DAC to grab the next value from the DMA?
tim4_cnt=13, dma1_isr=0x0, dma1_ccr=0xAB1, dma1_cmar=0x20000330, dma1_cndtr=0xEDC, xfer=0x20000330, dma_cpar=0x50000808, dac_cr=0x3017, dac_sr=0x800, num_under=0, dac1.DHR12R1=0x48C, DMAMUX1_Channel2->CCR=0x6, num_transfer_errors=0, num_injects=4020147
tim4_cnt=31, dma1_isr=0x0, dma1_ccr=0xAB1, dma1_cmar=0x20000330, dma1_cndtr=0xEDC, xfer=0x20000330, dma_cpar=0x50000808, dac_cr=0x3017, dac_sr=0x800, num_under=0, dac1.DHR12R1=0x48C, DMAMUX1_Channel2->CCR=0x6, num_transfer_errors=0, num_injects=4022656
tim4_cnt=50, dma1_isr=0x0, dma1_ccr=0xAB1, dma1_cmar=0x20000330, dma1_cndtr=0xEDC, xfer=0x20000330, dma_cpar=0x50000808, dac_cr=0x3017, dac_sr=0x800, num_under=0, dac1.DHR12R1=0x48C, DMAMUX1_Channel2->CCR=0x6, num_transfer_errors=0, num_injects=4025123
tim4_cnt=68, dma1_isr=0x0, dma1_ccr=0xAB1, dma1_cmar=0x20000330, dma1_cndtr=0xEDC, xfer=0x20000330, dma_cpar=0x50000808, dac_cr=0x3017, dac_sr=0x800, num_under=0, dac1.DHR12R1=0x48C, DMAMUX1_Channel2->CCR=0x6, num_transfer_errors=0, num_injects=4027590
tim4_cnt=87, dma1_isr=0x0, dma1_ccr=0xAB1, dma1_cmar=0x20000330, dma1_cndtr=0xEDC, xfer=0x20000330, dma_cpar=0x50000808, dac_cr=0x3017, dac_sr=0x800, num_under=0, dac1.DHR12R1=0x48C, DMAMUX1_Channel2->CCR=0x6, num_transfer_errors=0, num_injects=4030106
2025-07-21 8:10 AM
> Should I be telling the DMA to send the next value to the DAC instead of telling the DAC to grab the next value from the DMA?
No.
You tell DAC to transfer the datum from holding to output register (that's called trigger in the DAC); DAC does that within a couple of its cycles and as it empties its holding register, DAC in turn raises the request to DMA to fill up its holding register.
JW
2025-07-21 8:40 AM
I'm wondering if there is a bug in STM32CubeIDE ... if I create a DMA channel (in IOC) for a memory-to-memory transfer, it writes this code for me:
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
/* Configure DMA request hdma_memtomem_dma1_channel1 on DMA1_Channel1 */
hdma_memtomem_dma1_channel1.Instance = DMA1_Channel1;
hdma_memtomem_dma1_channel1.Init.Request = DMA_REQUEST_MEM2MEM;
hdma_memtomem_dma1_channel1.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_memtomem_dma1_channel1.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.MemInc = DMA_MINC_ENABLE;
hdma_memtomem_dma1_channel1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_memtomem_dma1_channel1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_memtomem_dma1_channel1.Init.Mode = DMA_NORMAL;
hdma_memtomem_dma1_channel1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_memtomem_dma1_channel1) != HAL_OK)
{
Error_Handler( );
}
}
but if I instead create a DMA channel that targets DAC1, it does this:
static void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMAMUX1_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
It "forgets" to set up the DMA registers and the
DMA_HandleTypeDef hdma_dac1_ch1;
2025-07-21 8:46 AM
Maybe it inits DMA elsewhere, maybe somewhere in relationship with DAC.
I don't use Cube.
JW
2025-07-21 9:28 AM - edited 2025-07-21 10:28 AM
I'm giving up on getting DMA to work for this chip, just setting up TIM4 to generate an interrupt and setting the DAC value there. Thanks for helping me try to figure this out!
I can get it to work with interrupts but not at the speed I need to drive the DAC at.
2025-07-21 10:41 AM
That's what DMA is for.
If you can't get it working with Cube/HAL, just ditch Cube/HAL and go for register-level programming.
JW
2025-07-21 3:14 PM - edited 2025-07-21 3:15 PM
I've got it working now with register-level programming:
void MyInit_DAC()
{
__HAL_RCC_DAC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef GPIO_InitStruct = { 0 };
GPIO_InitStruct.Pin = GPIO_PIN_4;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
uint32_t tmpreg1;
/* Get the DAC MCR value */
tmpreg1 = DAC1->MCR;
/* Clear DAC_MCR_MODEx bits */
tmpreg1 &= ~(((uint32_t) (DAC_MCR_MODE1)));
/* Configure for the selected DAC channel: mode, buffer output & on chip peripheral connect */
/* Clear DAC_MCR_DMADOUBLEx */
tmpreg1 &= ~(((uint32_t) (DAC_MCR_DMADOUBLE1)));
/* Clear DAC_MCR_SINFORMATx */
tmpreg1 &= ~(((uint32_t) (DAC_MCR_SINFORMAT1)));
/* Configure for the selected DAC channel: Signed format */
/* Clear DAC_MCR_HFSEL bits */
tmpreg1 &= ~(DAC_MCR_HFSEL);
/* Configure for both DAC channels: high frequency mode */
/* Calculate MCR register value depending on DAC_Channel */
/* Write to DAC MCR */
DAC1->MCR = tmpreg1;
/* Get the DAC CR value */
tmpreg1 = DAC1->CR;
/* Clear TENx, TSELx, WAVEx and MAMPx bits */
/* DAC in normal operating mode hence clear DAC_CR_CENx bit */
/* Disable wave generation */
tmpreg1 &= ~(((uint32_t) (DAC_CR_MAMP1 | DAC_CR_WAVE1 | DAC_CR_TSEL1
| DAC_CR_TEN1 | DAC_CR_CEN1 | DAC_CR_WAVE1)));
/* Configure for the selected DAC channel: trigger */
/* Write to DAC CR */
DAC1->CR = tmpreg1;
/* Set STRSTTRIGSELx and STINCTRIGSELx bits according to DAC_Trigger & DAC_Trigger2 values */
MODIFY_REG(DAC1->STMODR,
(DAC_STMODR_STINCTRIGSEL1 | DAC_STMODR_STRSTTRIGSEL1), 0);
// Enable channel 1 of the DAC
DAC1->CR |= DAC_CR_EN1;
}
static void MyMX_DMA_Init_For_MemtoDAC(void)
{
/* DMA controller clock enable */
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMAMUX1EN);
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN);
uint32_t tmp;
/* Get the CR register value */
tmp = DMA1_Channel1->CCR;
/* Clear PL, MSIZE, PSIZE, MINC, PINC, CIRC, DIR and MEM2MEM bits */
tmp &= ((uint32_t)~(DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE |
DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC |
DMA_CCR_DIR | DMA_CCR_MEM2MEM));
/* Prepare the DMA Channel configuration */
tmp |= /*DMA_CCR_MEM2MEM | */
/*DMA_CCR_PINC |*/ DMA_CCR_MINC |
DMA_PDATAALIGN_WORD | DMA_MDATAALIGN_WORD |
DMA_NORMAL | // Not circular (for now)
DMA_PRIORITY_LOW |
DMA_MEMORY_TO_PERIPH; // "Read from memory"
// CIRCULAR
/* Write to DMA Channel CR register */
DMA1_Channel1->CCR = tmp;
/*
* "The mapping of resources to DMAMUX is hardwired.
DMAMUX is used with DMA1 and DMA2:
For category 3 and category 4 devices:
• DMAMUX channels 0 to 7 are connected to DMA1 channels 1 to 8
• DMAMUX channels 8 to 15 are connected to DMA2 channels 1 to 8
For category 2 devices:
• DMAMUX channels 0 to 5 are connected to DMA1 channels 1 to 6
• DMAMUX channels 6 to 11 are connected to DMA2 channels 1 to 6"
*/
/* Set peripheral request to DMAMUX channel */
DMAMUX1_Channel0->CCR = DMA_REQUEST_DAC1_CHANNEL1;
// LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_DAC1_CH1);
// MODIFY_REG(DMAMUX1_Channel0->CCR, DMAMUX_CxCR_DMAREQ_ID, LL_DMAMUX_REQ_DAC1_CH1);
/* Clear the DMAMUX synchro overrun flag */
DMAMUX1_ChannelStatus->CFR = 1;
// LL_DAC_EnableDMAReq(DAC1, LL_DAC_CHANNEL_1);
SET_BIT(DAC1->CR, DAC_CR_DMAEN1);
}
When I try to get the DMA to do his thing:
void CopyMemToDAC()
{
/* Disable the DMA */
DMA1_Channel1->CCR &= ~DMA_CCR_EN;
/* Clear the DMAMUX synchro overrun flag */
DMAMUX1_ChannelStatus->CFR = 1;
/* Clear all flags */
DMA1->IFCR = 0x0F;
uint32_t pre_status = DMA1->ISR;
/* Configure DMA Channel data length */
DMA1_Channel1->CNDTR = 2048; // "Number of data to transfer"
/* Configure DMA Channel destination address */
DMA1_Channel1->CPAR = (uint32_t)&DAC1->DHR12R1;
/* Configure DMA Channel source address */
DMA1_Channel1->CMAR = (uint32_t)Transfer;
/* Enable the Peripheral */
DMA1_Channel1->CCR |= DMA_CCR_EN;
WRITE_REG(DAC1->SR, DAC_SR_DMAUDR1);
LL_DMA_ClearFlag_TC3(DMA1);
LL_DMA_ClearFlag_HT3(DMA1);
LL_DMA_ClearFlag_TE3(DMA1);
for (int i = 0 ; i < 100 ; i++)
{
SET_BIT(DAC1->SWTRIGR, DAC_SWTRIGR_SWTRIG1);
HAL_Delay(1);
}
uint32_t status = DMA1->ISR;
uint32_t cpar = DMA1_Channel1->CPAR;
uint32_t cmar = DMA1_Channel1->CMAR;
uint32_t cndtr = DMA1_Channel1->CNDTR;
uint32_t ccr = DMA1_Channel1->CCR;
uint32_t dac_sr = DAC1->SR;
uint32_t x = status;
}
What I see is:
DMA1->ISR = 0
DMA1_Channel1->CPAR = 0x50000808
DMA1_Channel1->CMAR = 0x20000330
DMA1_Channel1->CNDTR = 2048
DMA1_Channel1->CCR = 0xa91
DAC1->SR = 0x800
Any idea where I can go from here?
2025-07-21 10:41 PM
As always, start with reading out and checking DAC, DMA, DMAMUX, relevant GPIO registers. You can also compare them to the already working previous version.
You don't seem to set DAC register bits now at all.
JW
2025-07-22 6:34 AM - edited 2025-07-22 7:36 AM
Sorry, I'm a little confused by your response.
"You don't seem to set DAC register bits now at all." -> the first code snippet is for a function named MyInit_DAC() that sets DAC1->MCR, DAC1->STMODR, and DAC1->CR. Are there other register bits I need to be setting?
"compare them to the already working previous version" -> I have never had a version that successfully drove the DAC from DMA
"reading out and checking DAC, DMA, DMAMUX, relevant GPIO registers" -> at the end of my post I listed the values for DMA1->ISR, DMA1_Channel1->CPAR, DMA1_Channel1->CMAR, DMA1_Channel1->CNDTR, DMA1_Channel1->CCR, and DAC1->SR. Those values were captured at the end of a function named CopyMemToDAC() which is where I set the parameters into DMA1 and then software-trigger DAC1 repeatedly. Are there other registers I can capture that would help debug this problem?
Thanks!
p.s. I am reading up on the DAC and I think the problem with this code is that you can't trigger a DMA request with a software trigger. I have to use the timer.
2025-07-22 9:13 AM
I don't know which change did it, but it's working now! For reference, here's my code:
void MyInit_DAC()
{
__HAL_RCC_DAC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// Initialize GPIO A4 is an analog output
LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_4, LL_GPIO_MODE_ANALOG);
LL_GPIO_SetPinPull(GPIOA, LL_GPIO_PIN_4, LL_GPIO_PULL_NO);
// MCR is reset to zero, and all HAL did was clear some bits.
DAC1->MCR = 0;
// The top half of CR is for channel 2, the bottom half has these bits:
// [14] CEN1: DAC channel1 calibration enable (0 = normal operating mode)
// [13] DMAUDRIE1: DAC channel 1 DMA underrun interrupt enable (0 = interrupt disabled)
// [12] DMAEN1: DAC channel1 DMA enable (we will set this in CopyMemToDAC())
// [11:8] MAMP1: DAC channel1 mask/amplitude selector (not used, leave 0)
// [7:6] WAVE1: DAC channel1 noise/triangle wave generation enable (not used, leave 0)
// [5:2] TSEL1: DAC channnel1 trigger selection (0 = SWTRIG1)
// [1] TEN1: DAC channel1 trigger enable (0 = disabled)
// [0] EN1: DAC channel1 enable
DAC1->CR = DAC_CR_EN1;
}
void MyMX_DMA_Init_For_MemtoDAC(void)
{
/* DMA controller clock enable */
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMAMUX1EN);
SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_DMA1EN);
uint32_t tmp;
/* Get the CR register value */
tmp = DMA1_Channel1->CCR;
/* Clear PL, MSIZE, PSIZE, MINC, PINC, CIRC, DIR and MEM2MEM bits */
tmp &= ((uint32_t)~(DMA_CCR_PL | DMA_CCR_MSIZE | DMA_CCR_PSIZE |
DMA_CCR_MINC | DMA_CCR_PINC | DMA_CCR_CIRC |
DMA_CCR_DIR | DMA_CCR_MEM2MEM));
/* Prepare the DMA Channel configuration */
tmp |= DMA_CCR_MINC | // Memory increment yes (peripheral increment no)
DMA_PDATAALIGN_WORD | // Peripheral takes a 32-bit word
DMA_MDATAALIGN_HALFWORD | // Memory supplies a 16-bit halfword
DMA_CIRCULAR | // Circular (once memory buffer is exhausted, go back to top and send again)
DMA_PRIORITY_LOW |
DMA_MEMORY_TO_PERIPH;
/* Write to DMA Channel CR register */
DMA1_Channel1->CCR = tmp;
/*
* "The mapping of resources to DMAMUX is hardwired.
DMAMUX is used with DMA1 and DMA2:
For category 3 and category 4 devices:
• DMAMUX channels 0 to 7 are connected to DMA1 channels 1 to 8
• DMAMUX channels 8 to 15 are connected to DMA2 channels 1 to 8
For category 2 devices:
• DMAMUX channels 0 to 5 are connected to DMA1 channels 1 to 6
• DMAMUX channels 6 to 11 are connected to DMA2 channels 1 to 6"
*/
/* Set peripheral request to DMAMUX channel */
DMAMUX1_Channel0->CCR = DMA_REQUEST_DAC1_CHANNEL1;
/* Clear the DMAMUX synchro overrun flag */
DMAMUX1_ChannelStatus->CFR = 1;
}
void CopyMemToDAC()
{
// Disable channel 1 of the DAC while we set up the transfer
DAC1->CR &= ~DAC_CR_EN1;
// Put some known value as the DAC output
DAC1->DHR12R1 = 123;
// Disable DMA1 channel 1 while we update its settings
DMA1_Channel1->CCR &= ~DMA_CCR_EN;
/* Clear the DMAMUX synchro overrun flag */
DMAMUX1_ChannelStatus->CFR = 1;
/* Clear all flags */
DMA1->IFCR = 0x0F;
/* Configure DMA1 Channel 1 data length */
DMA1_Channel1->CNDTR = 4096; // "Number of data to transfer"
/* Configure DMA1 Channel 1 destination address */
DMA1_Channel1->CPAR = (uint32_t) &DAC1->DHR12R1;
/* Configure DMA1 Channel 1 source address */
DMA1_Channel1->CMAR = (uint32_t) Transfer;
// Clear any DAC under-run errors
WRITE_REG(DAC1->SR, DAC_SR_DMAUDR1);
// Clear other errors
LL_DMA_ClearFlag_TC1(DMA1);
LL_DMA_ClearFlag_HT1(DMA1);
LL_DMA_ClearFlag_TE1(DMA1);
// Enable DMA1 channel 1
DMA1_Channel1->CCR |= DMA_CCR_EN;
// Enable DMA on DAC1 channel 1
SET_BIT(DAC1->CR, DAC_CR_DMAEN1);
// Tell DAC1 channel 1 to trigger on dac_chx_trg5; according to table 186 on page 718
// this sets the source to TIM4_TRGO
DAC1->CR = (DAC1->CR & ~DAC_CR_TSEL1_Msk) | (5 << DAC_CR_TSEL1_Pos) | DAC_CR_TEN1;
// Re-enable channel 1 of the DAC to start the transfer
DAC1->CR |= DAC_CR_EN1;
}