目录
[(3) TRGO](#(3) TRGO)
[(2) ADC_Settings](#(2) ADC_Settings)
当ADC只有一个输入通道,在转换结束后可以及时读出结果数据寄存器的内容。
当规则转换组有多个通道时,应该使用扫描转换模式(Scan Conversion Mode),ADC在转换完一个通道后立刻转换下一个通道,直到规则组内的通道序列转换完。规则转换只有一个转换结果数据寄存器,虽然可以设置在每个通道转换完之后就产生EOC事件中断,但是在多通道情况下,在EOC事件中断里读取转换结果数据可能是来不及的,更谈不上对数据进行显示或处理。
因此,如果规则转换组有多个输入通道,应该使用DMA,使转换结果数据通过DMA传输自动保存到缓冲区中,在一个规则组转换结束后再对数据进行处理,或者在采集多次数据后再处理。
一、示例说明
本文将继续使用旺宝红龙开发板STM32F407ZGT6 KIT V1.0。
本实例,为ADC3的规则组设置3个输入通道,使用扫描转换模式,通过DMA方式传输ADC转换结果数据。
图片是开发板原理图中关于板上可调电位器RV1与MCU管脚PF8的线路图。当ADC3_IN6时,复选为PF8。再设置ADC3_IN5和ADC3_IN4,依次复选到MCU管脚PF7和PF6,然后通过导线在开发板上的P7双排针接插件上,把PF8、PF7和PF6相互连接起来。这样一个电位器信号,就同时提供给PF8、PF7和PF6了,实现了1个ADC同时采集3个通道的信号输入了。
之所以这样处理,还有一个原因,那就是STM32F407ZGT6的ADC3没有任何内部信号通道。

二、工程配置
1、RCC、DEBUG、CodeGenerator
RCC:外部晶振25MHz,HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz;
DEBUG:Serial Wire;
CodeGenerator:勾选☑Generate peripheral initialization as a pair of '.c/.h' files per peripheral
2、USART6
配置PG9、PG14为USART6,全部参数默认;
3、TIM3
(1)Mode
选择internal clock。
(2)参数设置
- 预分频因子:49999。
- 计数器方向:Up.
- 计数器周期:499.
- 内部时钟分频:No.
- 自动重装载:启用。
(3) TRGO
- MSM bit:Disable。
- Trigger Event:Update Envent。

使用TIM3的TRGO信号作为ADC3的外部触发信号,TIM3的定时周期为500ms,ADC3以DMA模式启动转换,在ADC的转换完成中断里读取转换结果数据。
4、ADC3
(1)Mode
配置ADC3,并勾选IN4、IN5、IN6。分别对应管脚PF6、PF7、PF8。并且管脚PF8连接开发板上的可调电位器,+3.3V。
(2) ADC_Settings
- Clock Prescaler:PCLK2 divided by 4
- Resolution:12 bits(15 ADC Clock cycles)
- Data Alignment:Right alignment
- Scan Conversion Mode:Enabled
- Continuous Conversion Mode:Disabled. 启用连续转换模式后,ADC结束一个转换后立即启动一个新的转换。开不开启这种模式,对于结果是有区别的。至于什么样的区别,将在运行与调试里介绍。
- Discontinuous Conversion Mode:Disabled. 这种模式一般用于外部触发时,将一组输入通道分为多个短的序列,分批次转换。
- DMA Continuous Requests:Enabled
- End Of Conversion Selection:EOC flag at the end of single channel conversion
(3)ADC_Regular_ConversionMode
- Number Of Conversion:3
- External Trigger Conversion Source:Timer 3 Trigger Out event
- External Trigger Conversion Edge:Trigger detecton on the rising edge
- Rank1:通道6,采样周期15个周期;
- Rank2:通道5,采样周期15个周期;
- Rank2:通道4,采样周期15个周期;

开启扫描转换模式(Scan Conversion Mode)和DMA连续请求(DMA Continuous Requests)
设置转换个数为3,下面会自动生成3个Rank的设置,分别设置每个Rank的输入通道和采样时间,每个通道的采样时间可以不一样。3个Rank里模拟通道出现的顺序就是规则组转换的顺序。
ADC_Settings组里的参数End of Conversion Selection的设定值不变,仍然是在每个通道转换完之后产生EOC信号。

(4)DMA设置
ADC3只有一个DMA请求,为这个DMA请求配置DMA流DMA2 Stream0,设置DMA传输属性参数,DMA传输方向自动设置为Peripheral To Memory(外设到存储器)在DMA Request Settings组中将Mode(工作模式)设置为Circular(循环模式),将外设和存储器的数据宽度都设置为Word,因为ADC转换结果数据寄存器是32位的。存储器设置为地址自增加。

Mode:如果选择Normal时,运行的时候,只采集1次,就停下来,不再工作;如果选择Circular,那么就循环不停地采集。选择什么模式,对于结果是有区别的,至于什么样的区别,将在运行与调试介绍。
ADC的数据都是32位的,数据宽度一律选择word。
5、NVIC

在使用ADC3的DMA方式传输时发现:即使不开启ADC3的全局中断,DMA传输功能也能正常工作,所以在设置NVIC时可以关闭ADC3的全局中断。同理,TIM3的全局中断设不设置都可以,不影响结果。
三、软件设计
1、main.c
cpp
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
cpp
/* USER CODE BEGIN PV */
#define BATCH_DATA_LEN 3 // DMA data buffer length, must be an integer multiple channels
uint32_t dmaDataBuffer[BATCH_DATA_LEN]; // DMA data buffer
/* USER CODE END PV */
其中,dmaDataBuffer是DMA传输数据的缓冲区,是元素类型为uint32_t的数组,该数组的长度为BATCH_DATA_LEN。程序中定义BATCH_DATA_LEN的值为3,所以,发生DMA传输完成事件中断时,数组dmaDataBuffer中就存储了3个通道的一次转换结果,在DMA传输完成事件中断的回调函数里,就可以读出3个通道的转换结果。因为设置DMA工作模式为循环模式,所以会在定时器TIM3驱动下一直进行ADC转换和数据传输。
DMA缓冲区长度BATCH_DATA_LEN也可以设置为3的整数倍,例如,设置为30。那么发生DMA传输完成中断时,数组dmaDataBuffer就存储了3个通道10个采样点的数据。在实际的ADC数据采集中,一般是采集一定的数据点之后再处理、显示或存储,还可以使用双缓冲区进一步提高效率。
菜单设计,启动DMA模式的ADC3、启动定时器TIM3。
cpp
/* USER CODE BEGIN 2 */
//菜单设计
uint8_t hello1[] = "Demo14_3_ADC_dual_IN:TEST ADC多个通道切换输入.\r\n";
HAL_UART_Transmit(&huart6,hello1,sizeof(hello1),500); //阻塞模式
HAL_Delay(10);
uint8_t hello2[] = "ADC多通道和DMA传输。\r\n";
HAL_UART_Transmit_IT(&huart6,hello2,sizeof(hello2)); //非阻塞模式
HAL_Delay(10);
printf("当规则转换组有多个通道时,应该使用扫描转换模式(Scan Conversion Mode),\r\n");
printf("ADC在转换完一个通道后立刻转换下一个通道,直到规则组内的通道序列转换完。\r\n");
printf("如果规则转换组有多个输入通道,应该使用DMA,使转换结果数据通过DMA传输自动保存到缓冲区中,\r\n");
printf("在一个规则组转换结束后再对数据进行处理,或者在采集多次数据后再处理。\r\n");
printf("开发板上的可调电位器连接MCU管脚PF8.\r\n");
printf("ADC3_IN6对应MCU管脚PF8,是外部模拟量输入通道,接可调电阻产生的可变电压。\r\n");
printf("ADC3_IN5对应MCU管脚PF7,用导线把PF7连接PF8。\r\n");
printf("ADC3_IN4对应MCU管脚PF6,用导线把PF6连接PF8。\r\n\r\n");
HAL_ADC_Start_DMA(&hadc3, dmaDataBuffer, BATCH_DATA_LEN); // Start ADC1, DMA mode
HAL_TIM_Base_Start(&htim3); // Start TIM3
/* USER CODE END 2 */
ADC3采集完成回调函数。
cpp
/* USER CODE BEGIN 4 */
/* Callback function for DMA stream transfer completion event interrupt */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint32_t adcValue=0, Volt;
for(uint8_t i=0; i<BATCH_DATA_LEN;i++)
{
// The buffer contains the conversion results of 3 channels.
adcValue=dmaDataBuffer[i];
Volt=3300*adcValue;
Volt=Volt>>12;
printf("ADC[%d] = %ld\r\n",i,Volt );
}
}
//串口打印
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart6,(uint8_t *)&ch,1,0xFFFF);
return ch;
}
/* USER CODE END 4 */
通过跟踪分析函数HAL_ADC_Start_DMA()和DMA流中断处理函数HAL_DMA_IRQHandler()的源代码,可以发现DMA流中断事件与ADC回调函数之间的关系,DMA流的传输完成中断事件(DMA_IT_TC)关联着ADC的回调函数HAL_ADC_ConvCpltCallback()。
四、运行与调试
首次下载运行或复位后,串口助手显示程序最初的菜单项:

连续运行后,串口助手上不断刷新显示3个通道的采集值。调节开发板上的电位器,可以看到3个通道的电压值明显变化:

如果,DMA的模式修改为Normal,那么程序只采集一次ADC数据(见第一张图)。
如果, 当Continuous Conversion Mode设置为Enable时,ADC将在完成一次转换后立即开始下一次转换,无需额外的启动命令。这种模式适用于需要连续监测输入信号变化的应用。在这种模式下,数据更新速度会明显加快,因为转换过程是连续的 。现象就是,串口助手上显示频率更高的数据,字节好像快到跳起来了。
在禁用连续转换模式时,ADC执行单次转换操作。即完成设定的转换任务(单通道一次转换或多通道一轮转换)后,就会停止转换,若需要再次转换,需要重新启动ADC。
实际上在串口助手上除了看到数据更新的快之外,看不到其他的差别的。
、