[应用相关] ad采集 cube配置 dma_STM32CubeMX

[复制链接]
 楼主| 尽快回复过 发表于 2022-1-28 23:41 | 显示全部楼层 |阅读模式

用定时器TIM3触发DMA方式的双通道ADC定时采样:

拷贝STM32CubeMX工程文件LED_Flash_PC12.ioc,修改为:Exp5_ADC1_2CH_DMA_TIM3_Trig.ioc


 楼主| 尽快回复过 发表于 2022-1-28 23:50 | 显示全部楼层
(1)配置ADC1的通道和参数
4274761f410a53b051.png
 楼主| 尽快回复过 发表于 2022-1-28 23:52 | 显示全部楼层
配置ADC通道参数

(2)配置ADC1的DMA

①通过点"Add"按钮,添加ADC1---DMA1 Channel 1。选择后ADC1后自动添加其DMA通道。

② DMA Request Settings:配置结果如下图所示。
 楼主| 尽快回复过 发表于 2022-1-28 23:53 | 显示全部楼层
Mode:Circular;设置DMA的传输模式为连续不断的循环模式。若只想访问一次后就不要访问了(或按指令操作来反问,也就是想要它访问的时候就访问,不要它访问的时候就停止),可以设置成通用模式:DMA_Mode_Normal。

Peripheral:Increment Address:不勾选。如果DMA通道有外设,可以通过DMA通道将数据输出。

Memory:勾选。DMA通过地址递增方式将数据存储到内部数据存储器中。

  Data Width:Word。Word是32bits,Half Word是16bits。选择要与ADC转换结果的数据宽度相同。
 楼主| 尽快回复过 发表于 2022-1-28 23:56 | 显示全部楼层
 楼主| 尽快回复过 发表于 2022-1-29 11:23 | 显示全部楼层
配置ADC的DMA

(3)配置ADC1的NVIC:不做任何选择,按默认即可,如下图所示。DMA1中断已经默认强制选择了。我们在这里是采用TIM3的定时溢出事件触发ADC转换的,在DMA中断服务程序中读取数据,所以不需要使能ADC的中断。
 楼主| 尽快回复过 发表于 2022-1-29 11:24 | 显示全部楼层
 楼主| 尽快回复过 发表于 2022-1-29 11:29 | 显示全部楼层
配置ADC的NVIC

(4)"User Constants"和"GPIO Settings"按默认即可。

(5)配置TIM3。配置结果如下图所示。
 楼主| 尽快回复过 发表于 2022-1-29 11:30 | 显示全部楼层
 楼主| 尽快回复过 发表于 2022-1-29 11:31 | 显示全部楼层
配置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转换。
 楼主| 尽快回复过 发表于 2022-1-29 11:33 | 显示全部楼层
(6) 为了观察程序运行,添加PC12接LED。

(7) ADC的时钟为12MHz

6788561f4b57673586.png
 楼主| 尽快回复过 发表于 2022-1-29 11:34 | 显示全部楼层
配置ADC的时钟

(8)配置完成,保存STM32CubeMX工程文件,点击"GENERATE CODE",生成代码工程框架并打开。
 楼主| 尽快回复过 发表于 2022-1-29 11:35 | 显示全部楼层
添加代码

(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 */
 楼主| 尽快回复过 发表于 2022-1-29 11:36 | 显示全部楼层
(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 */
 楼主| 尽快回复过 发表于 2022-1-29 11:37 | 显示全部楼层
  1. (3)在中断回调函数中做简单的数据处理

  2. /* USER CODE BEGIN 4 */

  3. void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle) //DMA方式的ADC中断回调函数

  4. {
  5. // HAL_TIM_Base_Stop(&htim3);

  6. // HAL_ADC_Stop_DMA(&hadc1);

  7. j=0; //将采样到的10个ADC转换值分离给IN6和IN7

  8. for(i = 0; i < 10;i++)

  9. {
  10. IN6_Value[j]=ADC_Value[i];

  11. i++;

  12. IN7_Value[j]=ADC_Value[i];

  13. j++;

  14. }

  15. ADC_DMA_ConvCpltFlag=1; //置DMA方式的ADC转换结束标志

  16. }

  17. /* USER CODE END 4 */
 楼主| 尽快回复过 发表于 2022-1-29 11:38 | 显示全部楼层
(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 */
 楼主| 尽快回复过 发表于 2022-1-29 11:39 | 显示全部楼层
在启动了TIM3定时器后,TIM3计数溢出事件将触发ADC启动转换,ADC转换按照规定的DMA方式进行,先转换IN6通道,再转换IN7通道,这就是扫描转换各个通道一次,等到下一次TIM3溢出事件再次启动ADC转换,这样反复5次,转换得到10个ADC转换值,将触发DMA中断,在DMA中断回调函数中做简单的数据处理,置DMA中断标志。在主程序中,通过LED1指示主程序的运行情况,检测到DMA中断后对采样到的数据做处理,并复位DMA中断标志。
 楼主| 尽快回复过 发表于 2022-1-29 11:40 | 显示全部楼层
这里你也许会问,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 */
 楼主| 尽快回复过 发表于 2022-1-29 11:41 | 显示全部楼层
发现DMA方式的ADC转换,按照开辟的数据区大小,转换结果将数据区填满后,转换完成,还是调用HAL_ADC_ConvCpltCallback(hadc); 这个回调函数,在回调函数中对数据做初步处理。今后用到AD,不论是中断方式还是DMA方式,都可以直接调用这个回调函数了,不用再纠结了。需要注意的是,中断方式的ADC在回调函数中需要通过uhADCxConvertedValue = HAL_ADC_GetValue(AdcHandle); 获得ADC的转换值,而DMA方式的ADC,则通过DMA直接将转换值存放在用数组名开辟的片内RAM中,当数组存满数据后会触发DMA中断,在回调函数中直接从数组中取转换结果即可。
 楼主| 尽快回复过 发表于 2022-1-29 11:41 | 显示全部楼层
以上程序是连续启动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个转换值后中断
您需要登录后才可以回帖 登录 | 注册

本版积分规则

44

主题

598

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部