cancel
Showing results for 
Search instead for 
Did you mean: 

ADC using DMA

BCoch
Senior

I'm trying to use the LL interface to read 2 ADC channels (0 & 1) using DMA. They're configured pretty much as default, 12-bit samples into 16-bit memory variables. I don't see any errors. The transfer completes, as does the EOS. There's data in the DR. However only a single byte makes it into memory. (There should be 4 bytes.)

Any thoughts on where the misconfiguration might be?

adc.c generated code

void MX_ADC1_Init(void)
{
  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};
  LL_ADC_InitTypeDef ADC_InitStruct = {0};
 
  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
 
  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);
  
  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  /**ADC1 GPIO Configuration  
  PA0   ------> ADC1_IN0
  PA1   ------> ADC1_IN1 
  */
  GPIO_InitStruct.Pin = TUBE_I_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(TUBE_I_GPIO_Port, &GPIO_InitStruct);
 
  GPIO_InitStruct.Pin = TUBE_HV_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(TUBE_HV_GPIO_Port, &GPIO_InitStruct);
 
  /* ADC1 DMA Init */
  
  /* ADC1 Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_ADC1);
 
  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
 
  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
 
  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);
 
  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
 
  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
 
  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
 
  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);
 
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  */
  ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
  ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_2RANKS;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
  LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
  LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH);
  LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE);
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_3CYCLES_5);
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_2, LL_ADC_SAMPLINGTIME_3CYCLES_5);
  LL_ADC_DisableIT_EOC(ADC1);
  LL_ADC_DisableIT_EOS(ADC1);
  ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV4;
  ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
  ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
  ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
  LL_ADC_Init(ADC1, &ADC_InitStruct);
  /** Configure Regular Channel 
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_0);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, LL_ADC_SAMPLINGTIME_COMMON_1);
  /** Configure Regular Channel 
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_1);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SAMPLINGTIME_COMMON_1);
 
}

My code:

static void configureDma(void)
{
	// Select ADC as DMA transfer request.
	LL_DMAMUX_SetRequestID(DMAMUX1, LL_DMAMUX_CHANNEL_0, LL_DMAMUX_REQ_ADC1);
 
	// DMA transfer addresses and size.
	LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1,
	                       LL_ADC_DMA_GetRegAddr(ADC1, LL_ADC_DMA_REG_REGULAR_DATA),
	                       (uint32_t)rawAdc,
	                       LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC1_NUM_CHANNELS);
 
	LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1); // Enable transfer complete interrupt.
	LL_DMA_EnableIT_HT(DMA1, LL_DMA_CHANNEL_1); // Enable half transfer interrupt.
	LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1); // Enable transfer error interrupt.
 
	// Enable
	LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
}
 
static void activateAdc(void)
{
	__IO uint32_t wait_loop_index = 0;
 
    /* Enable ADC internal voltage regulator. */
    LL_ADC_EnableInternalRegulator(ADC1);
 
    /* Delay for ADC internal voltage regulator stabilization.                */
    /* Compute number of CPU cycles to wait for, from delay in us.            */
    /* Note: Variable divided by 2 to compensate partially                    */
    /*       CPU processing cycles (depends on compilation optimization).     */
    /* Note: If system core clock frequency is below 200kHz, wait time        */
    /*       is only a few CPU processing cycles.                             */
    wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
    while(wait_loop_index != 0)
    {
      wait_loop_index--;
    }
 
    // DMA must be disabled for calibration.
    const uint32_t dma_tx_mode = LL_ADC_REG_GetDMATransfer(ADC1);
    LL_ADC_REG_SetDMATransfer(ADC1, LL_ADC_REG_DMA_TRANSFER_NONE);
 
    /* Run ADC self calibration */
    LL_ADC_StartCalibration(ADC1);
 
    /* Poll for ADC effectively calibrated */
    while (LL_ADC_IsCalibrationOnGoing(ADC1) != 0)
    {
    }
 
    /* Delay between ADC end of calibration and ADC enable.                   */
    /* Note: Variable divided by 2 to compensate partially                    */
    /*       CPU processing cycles (depends on compilation optimization).     */
    wait_loop_index = (ADC_DELAY_CALIB_ENABLE_CPU_CYCLES >> 1);
    while(wait_loop_index != 0)
    {
      wait_loop_index--;
    }
 
    // Re-enable DMA.
    LL_ADC_REG_SetDMATransfer(ADC1, dma_tx_mode);
 
    /* Enable ADCs */
    LL_ADC_Enable(ADC1);
 
    /* Poll for ADCs ready to convert */
    while (LL_ADC_IsActiveFlag_ADRDY(ADC1) == 0)
    {
    }
}
 
void AdcGetValues(uint32_t * current_uA, uint32_t * voltage_V)
{
	rawAdc[0] = 0x1234;
	rawAdc[1] = 0x4321;
 
	/* Clear ADC End of Conversion Sequence flag. */
    LL_ADC_ClearFlag_EOS(ADC1);
	LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC1_NUM_CHANNELS);
 
    /* Start conversion. */
    LL_ADC_REG_StartConversion(ADC1);
 
    /* Wait for completion. */
    while (!LL_ADC_IsActiveFlag_EOS(ADC1))
    	;
 
    /* Convert the data. */
    *current_uA = (((uint32_t)rawAdc[0] * MAX_I_UA) + (MAX_ADC / 2U)) / MAX_ADC;
    *voltage_V  = (((uint32_t)rawAdc[1] * MAX_V_V) + (MAX_ADC / 2U)) / MAX_ADC;
}

Interrupt handler:

void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
	  /* Check whether DMA transfer complete caused the DMA interruption */
	  if(LL_DMA_IsActiveFlag_TC1(DMA1) == 1)
	  {
	    /* Clear flag DMA transfer complete */
	    LL_DMA_ClearFlag_TC1(DMA1);
 
	    /* Call interruption treatment function */
	  }
 
	  /* Check whether DMA half transfer caused the DMA interruption */
	  if(LL_DMA_IsActiveFlag_HT1(DMA1) == 1)
	  {
	    /* Clear flag DMA half transfer */
	    LL_DMA_ClearFlag_HT1(DMA1);
 
	    /* Call interruption treatment function */
	  }
 
	  /* Note: If DMA half transfer is not used, possibility to replace        */
	  /*       management of DMA half transfer and transfer complete flags by  */
	  /*       DMA global interrupt flag:                                      */
	  /* Clear flag DMA global interrupt */
	  /* (global interrupt flag: half transfer and transfer complete flags) */
	  // LL_DMA_ClearFlag_GI1(DMA1);
 
	  /* Check whether DMA transfer error caused the DMA interruption */
	  if(LL_DMA_IsActiveFlag_TE1(DMA1) == 1)
	  {
	    /* Clear flag DMA transfer error */
	    LL_DMA_ClearFlag_TE1(DMA1);
 
	    /* Call interruption treatment function */
	  }
 
  /* USER CODE END DMA1_Channel1_IRQn 0 */
  
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
 
  /* USER CODE END DMA1_Channel1_IRQn 1 */
}

1 ACCEPTED SOLUTION
7 REPLIES 7
Ozone
Lead II

This looks strange:

LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC1_NUM_CHANNELS);

Is it the number of bytes to transfer ?

For 12bit resolution and two channels, this should be 4.

I don't use Cube, nor it's LL stuff.

> LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ADC1_NUM_CHANNELS);

> Is it the number of bytes to transfer ?

> For 12bit resolution and two channels, this should be 4.

Yeah, maybe 4 bytes, but as 2 transfers, halfword each.

I don't Cube/LL either.

You may want to read out and check the DMA registers' content, just before enabling the transfer.

It's also always good to explicitly tell the STM32 model you are using.

JW

BCoch
Senior

Thanks guys.

ADC1_NUM_CHANNELS is defined as 2. (I thought the number is supposed to be the number of halfword transfers, not the number of bytes.)

There is no change in the behavior when I change this to 4.

The chip is the STM32G071RBT6.

-- Bruce.

You may want to read out and check the DMA registers' content, just before enabling the transfer.

Oh, in case of 'G0, also check the related DMAMUX channel setting.

JW

BCoch
Senior

Indeed checking the DMA CCR1 register immediately before the transfer, the transfer size, increment, etc. was all zeros!

I stepped through the MX_ADC1_Init function above, which is tool generated code, automatically called from the main program before getting info my code. The code appeared to be correct, but the register was unchanged!!!!

I tried calling the same function a second time, this time directly from my initialization code. This time those same statements modified the register as expected, and the subsequent ADC/DMA transfer worked fine!

WTF?!?!?!?!

Thanks Jan. The last link is the issue. I had just figured it out myself. Another annoyance with this bug is that any time you regenerate code, it puts the function calls back in the wrong order again!