简介:使用TIM+DMA+ADC可实现高频、大量的数据采集,节省CPU资源,本文将讲解如何结合三者进行高频、大量的ADC采集。
概述:本文大致介绍使用定时器触发ADC采集转换,转换数据通过DMA通道传输存入内存空间。可在DMA中断中处理相关数据。
优点:ADC采用频率可调范围广,可一次性大量采集,DMA传输完成后及时处理,提升数据的实时性。
一、硬件配置
所有硬件配置均通过STM32cubeMX自动生成,再根据自己需求对各寄存器参数进行微调,故本文不再对各硬件功能、性能参数做介绍,相关知识可查看数据手册。
(一)、TIM配置
本列中,定时器用于事件更新触发ADC转换。此处使用TIM3,在TIM3中断函数中更新事件,达到触发ADC转换的目的(下文ADC配置中,外部触发源需要对应选择TIM3触发)。
使用STM32cubeMX生成代码如下:(通过调整定时器更新中断频率调整ADC采集周期)
tiemr3_init()
{
TIM_SlaveConfigTypeDef sSlaveConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM3_Init 1 */
/* USER CODE END TIM3_Init 1 */
timer3.Instance = TIM3;
timer3.Init.Prescaler = 8; //预分频值
timer3.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数
timer3.Init.Period = 100;//自动重装载值
timer3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;//时钟分频系数
timer3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//自动重装载使能
if (HAL_TIM_Base_Init(&timer3) != HAL_OK)
{
Error_Handler();
}
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;//触发模式
sSlaveConfig.InputTrigger = TIM_TS_ITR0;//内部触发器0为触发源
if (HAL_TIM_SlaveConfigSynchro(&timer3, &sSlaveConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;//更新事件作为触发输出
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&timer3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
__HAL_RCC_TIM3_CLK_ENABLE();
/* TIM3 interrupt Init */
HAL_NVIC_SetPriority(TIM3_IRQn, 3, 0);//中断配置
HAL_NVIC_EnableIRQ(TIM3_IRQn);
}
(二)、DMA配置
DMA工作原理不做过多讲解,简单说来就是可以将一个地址的数据内容搬运至另一个地址内存中,而在这个过程中不需要CPU的介入,两个地址可以分别是:外设寄存器地址与存储器地址、存储器地址与存储器地址、外设寄存器地址与外设寄存器地址。
这里推荐一篇文章:https://blog.csdn.net/as480133937/article/details/104927922 作者详细讲解了DMA的工作原理,内容通俗易懂。
DMA配置代码如下:
dma_init()
{
hdma_adc.Instance = DMA1_Channel1;
hdma_adc.Init.Request = DMA_REQUEST_0;//指定通道选择请求
hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;//传输方向
hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;//外围寄存器地址不自增
hdma_adc.Init.MemInc = DMA_MINC_ENABLE;//内存自增
hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;//数据宽度——半字-16位
hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc.Init.Mode = DMA_CIRCULAR;//DMA_NORMAL;
hdma_adc.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_adc) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(hadc,DMA_Handle,hdma_adc);//连接DMA与ADC句柄
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
(三)、ADC配置
ADC配置与使用软件触发时配置相同,仅需注意外部触发源需要选择定时器3的触发器0作为触发源,其余参数根据自己需求修改。
ADC代码配置如下:
adc_init()
{
ADC_ChannelConfTypeDef sConfig = {0};
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc.Instance = ADC1;
hadc.Init.OversamplingMode = DISABLE;
hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
hadc.Init.Resolution = ADC_RESOLUTION_12B;//12位
hadc.Init.SamplingTime = ADC_SAMPLETIME_39CYCLES_5;//采样时间
hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.ContinuousConvMode = ENABLE;//DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
hadc.Init.DMAContinuousRequests = DISABLE;//DMA转换为连续模式
hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc.Init.LowPowerAutoWait = DISABLE;
hadc.Init.LowPowerFrequencyMode = DISABLE;
hadc.Init.LowPowerAutoPowerOff = DISABLE;
if (HAL_ADC_Init(&hadc) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel to be converted.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
二、软件结合
(一)、中断函数
1、定时器中断函数:
在中断中给出更新事件,触发ADC转换。
void TIM3_IRQHandler()
{
HAL_TIM_GenerateEvent(&timer3,TIM_EVENTSOURCE_UPDATE);
HAL_TIM_IRQHandler(&timer3);
}
此处生成软件事件直接放在中断函数里面,在cubeMX自动生成的文件中,有中断回调函数,可在回调函数里生成软件事件。个人不习惯,故放在外面。
2、DMA传输结束中断函数:
DMA在每次传输完成后会进入DMA中断,可在该中断中处理数据或根据需求进行相应动作。
void DMA1_Channel1_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
/* USER CODE END DMA1_Channel1_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_adc);
/* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
/* USER CODE END DMA1_Channel1_IRQn 1 */
}
(二)、数据转换
数据转换与传输不是一次触发就永远执行的,定时器生成的触发事件仅触发ADC进行数据转换,但并没有将数据传输至存储器中,需要使用下面函数启动转换传输数据。在一组数据传输完成后需要重新启动方可转换。(*pData为缓存空间,length为数据个数,可动态调整通道传输个数)
HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length)
三、总结
第一次使用定时器+DMA进行ADC采集,单个模块的使用很简单,但是要将三个联系在一起,且不习惯使用cubeMX的情况下,还是有许多疑问。在此记录一下,文中不尽之处欢迎指正,欢迎探讨。
最后,DMA真的很实用,且方便。