打印
[应用相关]

ad采集 cube配置 dma_STM32CubeMX

[复制链接]
941|22
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

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

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


使用特权

评论回复
沙发
尽快回复过|  楼主 | 2022-1-28 23:50 | 只看该作者
(1)配置ADC1的通道和参数

使用特权

评论回复
板凳
尽快回复过|  楼主 | 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转换结果的数据宽度相同。

使用特权

评论回复
5
尽快回复过|  楼主 | 2022-1-28 23:56 | 只看该作者

使用特权

评论回复
6
尽快回复过|  楼主 | 2022-1-29 11:23 | 只看该作者
配置ADC的DMA

(3)配置ADC1的NVIC:不做任何选择,按默认即可,如下图所示。DMA1中断已经默认强制选择了。我们在这里是采用TIM3的定时溢出事件触发ADC转换的,在DMA中断服务程序中读取数据,所以不需要使能ADC的中断。

使用特权

评论回复
7
尽快回复过|  楼主 | 2022-1-29 11:24 | 只看该作者

使用特权

评论回复
8
尽快回复过|  楼主 | 2022-1-29 11:29 | 只看该作者
配置ADC的NVIC

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

(5)配置TIM3。配置结果如下图所示。

使用特权

评论回复
9
尽快回复过|  楼主 | 2022-1-29 11:30 | 只看该作者

使用特权

评论回复
10
尽快回复过|  楼主 | 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转换。

使用特权

评论回复
11
尽快回复过|  楼主 | 2022-1-29 11:33 | 只看该作者
(6) 为了观察程序运行,添加PC12接LED。

(7) ADC的时钟为12MHz

使用特权

评论回复
12
尽快回复过|  楼主 | 2022-1-29 11:34 | 只看该作者
配置ADC的时钟

(8)配置完成,保存STM32CubeMX工程文件,点击"GENERATE CODE",生成代码工程框架并打开。

使用特权

评论回复
13
尽快回复过|  楼主 | 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 */

使用特权

评论回复
14
尽快回复过|  楼主 | 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 */

使用特权

评论回复
15
尽快回复过|  楼主 | 2022-1-29 11:37 | 只看该作者
(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 */

使用特权

评论回复
16
尽快回复过|  楼主 | 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 */

使用特权

评论回复
17
尽快回复过|  楼主 | 2022-1-29 11:39 | 只看该作者
在启动了TIM3定时器后,TIM3计数溢出事件将触发ADC启动转换,ADC转换按照规定的DMA方式进行,先转换IN6通道,再转换IN7通道,这就是扫描转换各个通道一次,等到下一次TIM3溢出事件再次启动ADC转换,这样反复5次,转换得到10个ADC转换值,将触发DMA中断,在DMA中断回调函数中做简单的数据处理,置DMA中断标志。在主程序中,通过LED1指示主程序的运行情况,检测到DMA中断后对采样到的数据做处理,并复位DMA中断标志。

使用特权

评论回复
18
尽快回复过|  楼主 | 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 */

使用特权

评论回复
19
尽快回复过|  楼主 | 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中断,在回调函数中直接从数组中取转换结果即可。

使用特权

评论回复
20
尽快回复过|  楼主 | 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个转换值后中断

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

39

主题

547

帖子

0

粉丝