Using DMA in circular mode to buffer ADC values triggered by TIM "trgo" on STM32U575xx
Hello,
I'm working on an application on the STM32U575 platform, involving the usage of the ADC1, GPDMA and TIM15 peripherals.
During operations, the TIM15 is running in OC mode and has an effective period of 1kHz. The trgo event of said timer should also drive the sampling of a single channel on ADC1, which then sends samples to a buffer via DMA.
I'm using the latest version of the U5 HAL.
I managed to accomplish the first part of the operation (i.e. TIM15 --> ADC1), which works fine in interrupt mode, however I'm unable to implement a good solution involving the use of DMA. The issues I'm facing is overrun on the ADC1 in certain implementations, and DMA related errors in others.
What I tried so far:
>> Using GPDMA in simple mode, with a single buffer, kicking off operations after initialization and restarting it afterwards when conversion is complete.
// Operation is started once after initialization
HAL_ADC_Start_DMA(&hadc1, (uint16_t *)buffer, 64);
// Start TIM15
HAL_TIM_OC_Start(&htim15, TIM_CHANNEL_2);
/* ... in stm32u5xx_it.c ... */
void GPDMA1_Channel1_IRQHandler(){
HAL_DMA_IRQHandler(&hdma2);
// Restart the operations
HAL_ADC_Start_DMA(&hadc1, (uint16_t *)buffer, 64);
}This method seems to be unreliable (maybe it isn't) and will result in OVR issues after a couple of successfully completed DMA runs.
>> Adapting the method above with different parameters from ADC and DMA side results in the same issue.
>> I then tried to use the GPDMA in circular linked list mode to implement a double buffering method, which IMHO may also be more elegant, and I almost managed to make it work (that is, it makes a couple of successful runs and populates both buffers), but the only callback I get is related to a DMA busy error.
I'm posting the code I'm currently using, complete with linked list, dma and ADC configuration; I'd be more than happy to provide further details if needed.
ADC Related configuration:
/* MX_ADC1_Init */
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.Resolution = ADC_RESOLUTION_14B;
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.DMAContinuousRequests = ENABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIG_EDGE_RISING;
hadc1.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma2);
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC4_SAMPLINGTIME_COMMON_1;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
HAL_Delay(1);
HAL_ADCEx_Calibration_Start(&hadc1, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED);
HAL_Delay(1);
/* ADC_Msp_Init */
void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(hadc->Instance==ADC1)
{
/** Initializes the peripherals clock
*/
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADCDAC;
PeriphClkInit.AdcDacClockSelection = RCC_ADCDACCLKSOURCE_HSI;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* Peripheral clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**ADC1 GPIO Configuration
PC2 ------> ADC1_IN3
*/
GPIO_InitStruct.Pin = VBUS_SENSE_Pin | GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(VBUS_SENSE_GPIO_Port, &GPIO_InitStruct);
NVIC_EnableIRQ(ADC1_IRQn);
}Linked list initalization
DMA_NodeTypeDef ADCNode_Circular_1, ADCNode_Circular_2;
DMA_QListTypeDef ADCQueue;
extern uint16_t adc_values_1[64], adc_values_2[64];
/**
* @brief DMA Linked-list ADCQueue configuration
* @param None
* @retval None
*/
HAL_StatusTypeDef MX_ADCQueue_Config(void)
{
HAL_StatusTypeDef ret = HAL_OK;
DMA_NodeConfTypeDef pNodeConfig;
pNodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
pNodeConfig.Init.Request = GPDMA1_REQUEST_ADC1;
pNodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
pNodeConfig.Init.Direction = DMA_PERIPH_TO_MEMORY;
pNodeConfig.Init.SrcInc = DMA_SINC_FIXED;
pNodeConfig.Init.DestInc = DMA_DINC_INCREMENTED;
pNodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
pNodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_HALFWORD;
pNodeConfig.Init.SrcBurstLength = 1;
pNodeConfig.Init.DestBurstLength = 1;
pNodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
pNodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
pNodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
pNodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
pNodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
pNodeConfig.SrcAddress = &(ADC1->DR);
pNodeConfig.DstAddress = (uint32_t) adc_values_1;
pNodeConfig.DataSize = 4*64;
/* Build ADCNode Node #1 */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &ADCNode_Circular_1);
/* Insert ADCNode #1 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&ADCQueue, &ADCNode_Circular_1);
pNodeConfig.SrcAddress = &(ADC1->DR);
pNodeConfig.DstAddress = (uint32_t) adc_values_2;
/* Build ADCNode Node #2 */
ret |= HAL_DMAEx_List_BuildNode(&pNodeConfig, &ADCNode_Circular_2);
/* Insert ADCNode #2 to Queue */
ret |= HAL_DMAEx_List_InsertNode_Tail(&ADCQueue, &ADCNode_Circular_2);
ret |= HAL_DMAEx_List_SetCircularModeConfig(&ADCQueue, &ADCNode_Circular_1);
return ret;
}DMA initialization
hdma2.Instance = GPDMA1_Channel1_NS;
if (MX_ADCQueue_Config() != HAL_OK)
{
Error_Handler();
}
hdma2.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
hdma2.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
hdma2.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT1;
hdma2.InitLinkedList.TransferEventMode = DMA_TCEM_LAST_LL_ITEM_TRANSFER;
hdma2.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
if (HAL_DMAEx_List_Init(&hdma2) != HAL_OK)
{
Error_Handler();
}
if (HAL_DMA_ConfigChannelAttributes(&hdma2, DMA_CHANNEL_NPRIV) != HAL_OK)
{
Error_Handler();
}
HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
Finally, kickoff operations during main():
HAL_DMAEx_List_SetCircularMode(&ADCQueue);
if (HAL_DMAEx_List_LinkQ(&hdma2, &ADCQueue) != HAL_OK)
{
Error_Handler();
}
if (HAL_OK != HAL_ADC_Start_DMA(&hadc1, (uint16_t *)adc_values_1, 64))
Error_Handler();
HAL_TIM_OC_Start(&htim15, TIM_CHANNEL_2);Thanks to anyone who can help!!
