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!

1 REPLY 1
cbcooper
Senior

I picked the wrong label, this is on a NUCLEO-G491RE board which has the STM32G491 chip.

 

I added an interface so that the STM32 could report back to the PC on the values of some registers.

TIM4->CNT increments as expected.

DMA1->ISR is always 0x0

DMA1_Channel3->CMAR is always 0x20000330 which is the address of my circular buffer

DMA1_Channel3->CNDTR is always 0x1000 which is the size, in bytes, of the circular buffer

DMA1_Channel3->CCR is always 0x5B1

  109876543210

= 010110110001

   | || ||   |

   | || ||   += Bit 0 = channel enable

   | || |+= Bit 4 = direction (read from memory)

   | || += Bit 5 = circular mode enabled

   | |+= Bit 7 = memory increment mode enabled

   | += Bit 9/8 = 01 = peripheral size 16 bits

   += Bit 11/10 = 01 = memory size 16 bits

DMA1_Channel3->CPAR is always 0x50000808 which I think is right for DAC1

DAC1->CR is always 0x1017

5432109876543210

0001000000010111

   |         |||

   |         ||+= bit 0 = channel 1 enabled

   |         |+= bit 1 = channel 1 trigger enabled

   |         += bit 5..2 = 0101 = dac_ch1_trg5 = TIM4_TRGO

   += bit 12 = channel 1 DMA enabled

DAC1->SR is always 0x2800

5432109876543210

0010100000000000

  | |

  | += bit 11 = DAC channel 1 ready

  += bit 13 = DAC channel DMA underrun

The doc says DMA underrun means "the currently selected trigger is driving DAC channel1 conversion at a frequency higher than the DMA service capability rate"

which I don't believe, I've got TIM4 prescaler set to 23990, I think I'm running at 72 Mhz so TIM4 is incrementing at 3,000 hz with a period of 100 so it's trying to update the DAC at 30 Hz.

But if I enable the DAC's DMA underrun interrupt and in the ISR clear the underrun and increment a counter, there are definitely underruns occuring, and I think it happens every time TIM4 rolls over.