cancel
Showing results for 
Search instead for 
Did you mean: 

Driving DAC from circular buffer using DMA and TIM - why is it not working?

cbcooper
Senior

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!

19 REPLIES 19

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

 

 

waclawek.jan
Super User

> 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

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;

 

waclawek.jan
Super User

Maybe it inits DMA elsewhere, maybe somewhere in relationship with DAC.

I don't use Cube.

JW

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.

 

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

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?

 

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

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.

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;
}