2017-02-16 08:19 PM
Hi,
On my larger project, I would like to be able to generate a timer that will trigger an ADC read on each rising edge. I was able to tackle this problem by creating a smaller project and executing it! Super excited.
My excitement was a bit dampened when I realized how many interrupts were firing fromthe timer. For this test, I am currently operating my timer at ~123kHz which equates to roughly 8.13uS. While this is fine running on a while(1) loop example, I don't think it will go well with my larger project running FreeRTOS.
Now comes the question:
Is there a way to set this up where the timer can generate a DMA to trigger the ADC read using the DMA? I would imagine this is the best path forward to minimize CPU usage if possible. That way, I am not firing my interrupts every 8.13uS. If not, what is the best engineering practice?
Any example code is greatly appreciated! I just couldn't seem to find any post in this community trying to user a timer with DMA access to trigger an ADC DMA.
According to RM0393 (Page 776), it looks as if I should be able to doan event trigger on TIM15:
• Interrupt/DMA generation on the following events:
– Update: counter overflow, counter initialization (by software or internal/external trigger)
– Trigger event (counter start, stop, initialization or count by internal/external trigger)
– Input capture
– Output compare
– Break input (interrupt request)
Note: I am currently not DMA-ing the ADC in this example. This will be done on the larger project!
I am not actually grabbing the ADC value but toggling a pin to know that the ADC is being called on the rising edge of the timer.
Main:
int main(void)
{
/* Clock Inits*/
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_TIM15_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim15);
HAL_ADC_Start_IT(&hadc1);
/* USER CODE END 2 */
/* Infinite loop */
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Callbacks:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (hadc->Instance==ADC1)
{
HAL_GPIO_TogglePin(USER_ADC_TOGGLE_PIN_GPIO_Port, USER_ADC_TOGGLE_PIN_Pin);
}
}�?�?�?�?�?�?�?
Interrupt:
void TIM1_BRK_TIM15_IRQHandler(void)
{
HAL_GPIO_TogglePin(USER_TIMER_TOGGLE_PIN_GPIO_Port, USER_TIMER_TOGGLE_PIN_Pin);
HAL_TIM_IRQHandler(&htim15);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Timer / ADC Setup:
static void MX_TIM15_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
htimInstance = TIM15;
htimInit.Prescaler = 47;
htimInit.CounterMode = TIM_COUNTERMODE_UP;
htimInit.Period = 1;
htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htimInit.RepetitionCounter = 0;
if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
}
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
/**Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
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.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = ENABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
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 Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
#•-interruptdma-generation-on-the-following-events:-–-up
Solved! Go to Solution.
2017-02-17 03:21 PM
Based on my last statement/question, I decided to do a bit more digging and trial and error.
It turns out I am capable of tying the two 'pins' together internally without having to physically go outside of the MCU and back into another pin all the while bypassinginterrupt. Sounds too good to be true haha.
I configured it in the following fashion:
I was capable of reading out ADC values at a 1Msps while offloading all the work to the DMA.
I'll post my code in hopes that it may help someone else!
Main:
uint32_t DMA_ADCvalues[1024];
int main(void)
{
/* Clocks and Initializations of Peripherals */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim15);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)DMA_ADCvalues, 1024);
/* USER CODE END 2 */
while(1);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Callbacks:
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
//HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_GPIO_TogglePin(USER_TEST_GPIO_Port, USER_TEST_Pin);
}�?�?�?�?�?�?�?�?�?
ADC/Timer Setup:
/* ADC1 init function */
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
/**Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
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.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/**Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.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();
}
}
/* TIM15 init function */
static void MX_TIM15_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
htimInstance = TIM15;
htimInit.Prescaler = 0;
htimInit.CounterMode = TIM_COUNTERMODE_UP;
htimInit.Period = 63;
htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htimInit.RepetitionCounter = 0;
if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
2017-02-17 12:01 AM
No interrupt is needed when directly going to the HW assisted functions, 1 Mega samples per second is achievable.
And to debug it, make the timer generate 1 MHz pulses on an output channel. Connect this output pin to the ADC trigger input channel. Use the ADC DMA to store the sampled data into a RAM buffer. This way, can cut the trigger wire and debug easily, can manually trigger the ADC or check the Timer pulses without cascading the ADC/DMA.
2017-02-17 03:23 AM
Thanks for the quick response KIC8462852 EPIC204278916.
I want to make sure I am understanding you correctly.
With your proposed setup, I will need to drive a PWM on an external physical pin @ 1MHz (to Achieve 1 Mega Sample / Second for argument sake) into an external physical input pin for the ADC (EXTI line 11)?
Thanks! I am just trying to wrap my head around this!
2017-02-17 07:59 AM
You got it right. And it works if all the settings are correctly done. (the ADC sampling cycle must be faster than the pulse frequency).
This way, later on, you can also save power as probably SYSCLK won't need to be so high due to some interrupt latency...
2017-02-17 09:03 AM
Thanks. That makes perfect sense and looking at the STM32Cube, the
ADC1_EXTI11 pin showed up
once I selected 'Regular Conversion Trigger' on ADC1.Unfortunately it seems that I have to choose between 'Regular Conversion Trigger' for ADC1 or CAN1 master mode. Both are necessary.
I can get Injected Conversion Trigger (ADC1_EXTI15) but I don't think that is exactly what I want as I want to be sampling at a higher rate. (I believe injection lets me interrupt my current ADC read to prioritize another channel but sample rate stays the same) This seems like another question for another thread.
Thanks again for your support!!!!
2017-02-17 03:21 PM
Based on my last statement/question, I decided to do a bit more digging and trial and error.
It turns out I am capable of tying the two 'pins' together internally without having to physically go outside of the MCU and back into another pin all the while bypassinginterrupt. Sounds too good to be true haha.
I configured it in the following fashion:
I was capable of reading out ADC values at a 1Msps while offloading all the work to the DMA.
I'll post my code in hopes that it may help someone else!
Main:
uint32_t DMA_ADCvalues[1024];
int main(void)
{
/* Clocks and Initializations of Peripherals */
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim15);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)DMA_ADCvalues, 1024);
/* USER CODE END 2 */
while(1);
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
Callbacks:
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
//HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
HAL_GPIO_TogglePin(USER_TEST_GPIO_Port, USER_TEST_Pin);
}�?�?�?�?�?�?�?�?�?
ADC/Timer Setup:
/* ADC1 init function */
static void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig;
/**Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
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.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.NbrOfDiscConversion = 1;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T15_TRGO;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/**Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_8;
sConfig.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();
}
}
/* TIM15 init function */
static void MX_TIM15_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig;
TIM_OC_InitTypeDef sConfigOC;
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;
htimInstance = TIM15;
htimInit.Prescaler = 0;
htimInit.CounterMode = TIM_COUNTERMODE_UP;
htimInit.Period = 63;
htimInit.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htimInit.RepetitionCounter = 0;
if (HAL_TIM_PWM_Init(&htim15) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_OC1;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_ENABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim15, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim15, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 0;
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.BreakFilter = 0;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
if (HAL_TIMEx_ConfigBreakDeadTime(&htim15, &sBreakDeadTimeConfig) != HAL_OK)
{
Error_Handler();
}
}
�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
2017-02-19 10:51 AM
Good job! It is the mature way of doing it, less GPIO, less debugging facilities, ready for final implementation.
2017-12-21 08:19 AM
Wow! I am impressed at how you laid out your question. This is how forum knowledge is built! Kudos for taking the time to post code in such an organized and thoughtful way.