用定时器TIM3触发DMA方式的双通道ADC定时采样:
拷贝STM32CubeMX工程文件LED_Flash_PC12.ioc,修改为:Exp5_ADC1_2CH_DMA_TIM3_Trig.ioc
(1)配置ADC1的通道和参数
(2)配置ADC1的DMA
①通过点"Add"按钮,添加ADC1---DMA1 Channel 1。选择后ADC1后自动添加其DMA通道。
② DMA Request Settings:配置结果如下图所示。
Mode:Circular;设置DMA的传输模式为连续不断的循环模式。若只想访问一次后就不要访问了(或按指令操作来反问,也就是想要它访问的时候就访问,不要它访问的时候就停止),可以设置成通用模式:DMA_Mode_Normal。
Peripheral:Increment Address:不勾选。如果DMA通道有外设,可以通过DMA通道将数据输出。
Memory:勾选。DMA通过地址递增方式将数据存储到内部数据存储器中。
Data Width:Word。Word是32bits,Half Word是16bits。选择要与ADC转换结果的数据宽度相同。
(3)配置ADC1的NVIC:不做任何选择,按默认即可,如下图所示。DMA1中断已经默认强制选择了。我们在这里是采用TIM3的定时溢出事件触发ADC转换的,在DMA中断服务程序中读取数据,所以不需要使能ADC的中断。
(4)"User Constants"和"GPIO Settings"按默认即可。
(5)配置TIM3。配置结果如下图所示。
用其更新事件作为TRGO触发ADC。用鼠标点"Pinout & Configuration"点"Timers"点"TIM3""Mode"选项卡中,"Clock Source"选"Internal Clock""TIM3 Mode and Configuration"的"Configuration"菜单栏中,点"Parameter Settings""Trigger Output(TRGO)Parameters"下拉选项中,"Trigger Event Selection"选择"Update Event"。这样就为ADC的启动提供触发信号。72MHz的时钟信号经过(7199+1)和(39999+1)分频后,频率为0.25Hz,其周期为4秒,也就是说每4秒触发一次ADC转换。
(6) 为了观察程序运行,添加PC12接LED。
(7) ADC的时钟为12MHz
(8)配置完成,保存STM32CubeMX工程文件,点击"GENERATE CODE",生成代码工程框架并打开。
添加代码
(1) 在main.c里面添加ADC转换的相关变量
/* USER CODE BEGIN PV */
uint32_t ADC_Value[10]; //通道IN6、IN7采样5次的值
uint8_t i,j,ADC_DMA_ConvCpltFlag=0; //ADC1_DMA方式转换结束标志
uint32_t IN6_Value[5],IN7_Value[5]; //从DMA转换值中分离IN6和IN7的值
uint32_t IN6_AverageValue,IN7_AverageValue; //IN6和IN7的平均值
/* USER CODE END PV */
(2)开启定时器TIM3,通过TIM3启动ADC。开启DMA方式的ADC1
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim3); //启动TIM3基本定时功能,定时到触发ADC启动
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 10); //启动DMA方式的ADC转换,采样到10个之后触发DMA方式的ADC中断
/* USER CODE END 2 */
(3)在中断回调函数中做简单的数据处理
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle) //DMA方式的ADC中断回调函数
{
// HAL_TIM_Base_Stop(&htim3);
// HAL_ADC_Stop_DMA(&hadc1);
j=0; //将采样到的10个ADC转换值分离给IN6和IN7
for(i = 0; i < 10;i++)
{
IN6_Value[j]=ADC_Value[i];
i++;
IN7_Value[j]=ADC_Value[i];
j++;
}
ADC_DMA_ConvCpltFlag=1; //置DMA方式的ADC转换结束标志
}
/* USER CODE END 4 */
(4)在主程序中做复杂些的数据处理
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(GPIOB,LED1_Pin); //用LED1指示主程序运行
HAL_Delay(200); //每200msLED1闪烁一次
if(ADC_DMA_ConvCpltFlag==1) //判断DMA方式的ADC转换结束了没有
{
IN6_AverageValue=0; //一次DMA方式的ADC转换结束,计算两个通道的平均值
IN7_AverageValue=0;
for(i =0;i <5;i++)
{
IN6_AverageValue+=IN6_Value[i];
IN7_AverageValue+=IN7_Value[i];
}
IN6_AverageValue=IN6_AverageValue/5;
IN7_AverageValue=IN7_AverageValue/5;
// HAL_TIM_Base_Start(&htim3);
// HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 10);
ADC_DMA_ConvCpltFlag=0; //清除转换结束标志,以便判断下次中断
}
}
/* USER CODE END 3 */
在启动了TIM3定时器后,TIM3计数溢出事件将触发ADC启动转换,ADC转换按照规定的DMA方式进行,先转换IN6通道,再转换IN7通道,这就是扫描转换各个通道一次,等到下一次TIM3溢出事件再次启动ADC转换,这样反复5次,转换得到10个ADC转换值,将触发DMA中断,在DMA中断回调函数中做简单的数据处理,置DMA中断标志。在主程序中,通过LED1指示主程序的运行情况,检测到DMA中断后对采样到的数据做处理,并复位DMA中断标志。
这里你也许会问,DMA中断为什么是void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)这个函数呀?这个函数不是当开启AD的中断的时候才调用的吗? 对,是这样的。我们仔细分析一下开启AD的DMA中断函数,在里面就会发现这个函数也在啊。在main.c中找到HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 10);,在HAL_ADC_Start_DMA上点鼠标右键,跟踪其定义可以找到函数:HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length),该函数中有一句:
/* Set the DMA transfer complete callback */
hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;
DMA传输完成,自动调用名字为ADC_DMAConvCplt函数,在ADC_DMAConvCplt上点鼠标右键,跟踪其定义,进入到void ADC_DMAConvCplt(DMA_HandleTypeDef *hdma)函数里面可以找到
/* Conversion complete callback */
#if (USE_HAL_ADC_REGISTER_CALLBACKS == 1)
hadc->ConvCpltCallback(hadc);
#else
HAL_ADC_ConvCpltCallback(hadc);
#endif /* USE_HAL_ADC_REGISTER_CALLBACKS */
发现DMA方式的ADC转换,按照开辟的数据区大小,转换结果将数据区填满后,转换完成,还是调用HAL_ADC_ConvCpltCallback(hadc); 这个回调函数,在回调函数中对数据做初步处理。今后用到AD,不论是中断方式还是DMA方式,都可以直接调用这个回调函数了,不用再纠结了。需要注意的是,中断方式的ADC在回调函数中需要通过uhADCxConvertedValue = HAL_ADC_GetValue(AdcHandle); 获得ADC的转换值,而DMA方式的ADC,则通过DMA直接将转换值存放在用数组名开辟的片内RAM中,当数组存满数据后会触发DMA中断,在回调函数中直接从数组中取转换结果即可。
以上程序是连续启动ADC转换的,如果要想控制这个转换过程,可以通过以下语句实现:
HAL_TIM_Base_Stop(&htim3); //关闭定时器,停止溢出事件触发ADC
HAL_ADC_Stop_DMA(&hadc1); //停止DMA方式的ADC转换
HAL_TIM_Base_Start(&htim3); //启动定时器,定时溢出事件触发ADC
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&ADC_Value, 10); //启动DMA方式的ADC转换,得到10个转换值后中断
第三步:编译、下载、运行
为观察到变化效果,可以先将PA6(IN6)和PA7(IN7)悬空,此时测量到是干扰。程序运行后,可以看到LED持续闪烁,表明主程序一直在运行,不用设置断点,全速运行程序,在观察窗口中添加变量ADC_Value、IN6_Value、IN7_Value、IN6_AverageValue、IN7_AverageValue、i、j,可以看到,每隔4秒钟ADC_Value的值以组(IN6和IN7)为单位变化一次,因为TIM3定时4秒,所以每隔4秒触发一次ADC转换,转换结果通过DMA送给ADC_Value数组。需要20秒后,ADC_Value填满,触发DMA中断,IN6_Value和IN7_Value在中断回调函数中得到各自的转换结果,如下图所示。也可以将PA6和PA7接GND、3.3V,做进一步观察。