2023-12-18 03:24 PM - edited 2023-12-18 03:32 PM
Using STM32G030F6 and STM32CubeMX, I am trying to do a sequence of single channel conversions on each edge of a PWM signal (generated by an internal timer). PWM is setup fine and working on TIM3 channel 1. TIM3 TRGO is set to OC1REF. ADC is set up to start conversion on Timer 3 Trigger Out (both edges). DMA is setup up for ADC. As soon as I enable the timer, I get one ADC conversion complete callback with 500 valid samples, but that is all.
Any suggestions on how to make it so that the conversion sequence will automatically re-start the DMA on each timer trigger?
EDIT: I suspect this code actually does one sample each PWM edge then interrupts after 500 edges (due to continuous mode not set). What I actually want is to do 500 samples on each edge and interrupt after all 500 samples are complete.
EDIT 2: I also know that calling HAL_ADC_Start_DMA(&hadc1, (uint32_t*)gADCData, 500); in the conversion complete callback will restart the ADC/DMA. But ideally it would be nice to avoid calling that inside the interrupt.
Relevant code is below:
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
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.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.LowPowerAutoPowerOff = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T3_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISINGFALLING;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_19CYCLES_5;
hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.OversamplingMode = ENABLE;
hadc1.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_4;
hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_2;
hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* ADC1 clock enable */
__HAL_RCC_ADC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0 ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA5 ------> ADC1_IN5
PA12 [PA10] ------> ADC1_IN16
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_5|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA1_Channel2;
hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_NORMAL;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
/* TIM3 init function */
void MX_TIM3_Init(void)
{
/* USER CODE BEGIN TIM3_Init 0 */
/* USER CODE END TIM3_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
htim3.Instance = TIM3;
htim3.Init.Prescaler = 63;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 713;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OC_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1REF;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 356;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_TIMING;
sConfigOC.Pulse = 0;
if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_OC_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_3) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM3_Init 2 */
/* USER CODE END TIM3_Init 2 */
HAL_TIM_MspPostInit(&htim3);
}
// And in main...
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)gADCData, 500);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
2023-12-18 03:37 PM
Replying to my own topic: Maybe a better option is to leave the ADC free running and trigger the DMA conversion on the timer edges. Although not sure that is possible under hardware control. It also means the ADC conversions are not syncrhonised with the timer.
2023-12-18 03:57 PM
Just set the DMA to circular mode.
2023-12-18 05:25 PM
Thanks for the suggestion. Wouldn't setting DMA to circular mode just mean that it would keep sampling constantly forever (assuming continuous conversion mode)?
I want to just get a burst of X number of samples after a timer edge so need the DMA to stop after x samples.
2023-12-19 06:35 AM - edited 2023-12-19 06:38 AM
My guess is, that after DMA is finished, you leave timer to run and trigger further ADC conversions, which results in ADC overrun, and that in turn may prevent DMA to be triggered.
Verify by reading out ADC status register.
Read ADC chapter of RM of your STM32. The ADCs in various STM32 models differ quite a lot, I am not familiar with all of them.
JW
2023-12-19 05:26 PM
That is a good suggestion regarding overrun. I dont think it is the issue in my case. I think the problem is I want the DMA to automatically restart, but they cannot be automatically restarted. Need to enable and at least set the transfer count.
2023-12-19 07:39 PM
It seems that you are misunderstanding how the DMA works. It does not transfer the data on it's own mind, but based on the requests by peripheral. Therefore, if it is enabled, but there are no requests, it just sits calm and does nothing.
In your case the requests are generated by ADC at the end of conversion. If you want to capture those samples as fast as possible, you have to run the ADC in continuous mode, but then the problem is that it will continue running forever. The other option is to generate a 500 trigger pulses, by chaining in another timer. The easiest way would be to configure that timer in trigger and one pulse modes and using a repetition counter set to the number of required pulses.
So the whole chain could look like this:
TIM3 => TIM1 => ADC1 => DMA1