大家好!这节课我们来讲ADC的两种实用方案:
①“ADC规则组+DMA双缓冲+定时器更新触发”
②“ADC注入组+PWM触发”

一、初学者常见方案

1. 轮询

在主循环里一直读取ADC采样值

while(1) {
    value = ADC_Read();  
	process_data(value);
}

而ADC_Read()的底层实现为:启动转换 --> 等待转换完成 --> 返回

SET_BIT(hadc->Instance->CR2, ADC_CR2_SWSTART);
while(!__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOC));
return READ_REG(hadc->Instance->DR);

如果读取多个采样值,那么其他任务也就只能一级一级死等待。

while(1) {
	temp = read_adc(TEMP_CH);	
	current = read_adc(CURR_CH); 
	voltage = read_adc(VOLT_CH); 
	func_control();				
}

问题本质: 阻塞,实时性差

2. 中断

指每次采样转换都需要进中断处理(这里指ADC规则组采样模式)

void ADC_IRQHandler() { 		 
	value = ADC->DR;			 
	buffer[i++] = value;		
	if(i >= BUF_SIZE) i=0;		
}

那么高频率采样下中断的开销就大了

如果数据处理的时间>采样间隔时间,这样新数据就会覆盖旧数据
时间轴示例:

|--中断1--|-------处理-------|--中断2--|--中断3--|
		  ↑				   ↑		   ↑
	  数据未处理完	   新数据覆盖旧数据

问题本质: 高频中断会拉高CPU负载,并且存在数据覆盖风险

3. 单缓冲DMA传输

指将DMA配置为循环模式,缓冲区为一维数组buf[N],通过该缓冲区去存储采样值

DMA_Config_Circular(ADC->DR, buffer, BUF_SIZE);

但存在一个可能性问题,“数据竞争
因为DMA是硬件行为,会自动往缓冲区写数据,如果某时刻DMA在往缓冲区的A地址写数据时,这时CPU也同时在处理A地址数据,就会出现同一块地址下的数据冲突。

问题本质: DMA与CPU访问同一内存区域会存在数据竞争

二、本节课方案

1. ADC规则组 + DMA双缓冲 + 定时器更新触发

  1. 名词解释
    (1)ADC规则组
    定义:指ADC一种多通道采样模式,可以设定通道的采样顺序并按照该顺序采样。
    特点:所有规则通道共用一个数据寄存器,可以用DMA缓冲区指向这块地址实现采样搬运。
    触发方式:支持软件触发(手动启动)和硬件触发(定时器、外部引脚等)
    应用场景:常用于采集温度、母线电压、母线电流等

    (2)DMA双缓冲
    定义:所谓双缓冲是指两个缓冲区,这里用二维数组buf[2][N],即buf[0]表示第一个缓冲区,buf[1]表示第二个缓冲区,DMA会在后台自动切换这两个缓冲区。
    缓冲区处理:当触发DMA半传输中断时表示buf[0]已经存满了,这时DMA会转到buf[1]中存储,即这时可以去处理buf[0]中数据;当触发DMA全传输中断时表示buf[1]已经存满了,这时DMA会转到buf[0]重新开始存,即这时可以去处理buf[1]中数据。

    (3)定时器更新触发
    定义:指将ADC触发源配置为外部触发,也就是定时器的更新事件(计数器溢出/重载),即当定时器计数溢出时触发ADC采样。
    采样逻辑:定时器计数到ARR值后溢出 → 生成触发信号 → ADC启动一次规则组采样

  2. 工作流程
    step1:配置外设初始化(定时器、ADC、DMA这些)
    step2:定时器溢出触发ADC采样
    step3:DMA搬运采样数据到当前缓冲区
    step4:触发DMA半传输中断,处理buf[0]数据
    step5:触发DMA全传输中断,处理buf[1]数据

  3. 抽象代码设计

// 1. 定时器基础配置(TIMx为例)
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStruct);  // 设置周期/预分频
TIM_SelectOutputTrigger(TIMx, TIM_TRGOSource_Update); // TRGO由更新事件触发
TIM_Cmd(TIMx, ENABLE);						 // 启动定时器

// 2. ADC规则组配置(ADCx为例)
ADC_Init(ADCx, &ADC_InitStruct);			 // ADC基础模式设置
ADC_RegularChannelConfig(ADCx, ADC_Channel_0, 1, ADC_SampleTime); // 规则通道配置
ADC_DMACmd(ADCx, ENABLE);					 // 使能ADC DMA请求

// 3. DMA双缓冲配置(DMAx为例)
DMA_Init(DMAx, &DMA_InitStruct);			 // DMA基础配置
DMA_DoubleBufferModeConfig(DMAx, buf, DMA_Memory_0); // 双缓冲初始化
DMA_DoubleBufferModeCmd(DMAx, ENABLE);		 // 使能双缓冲模式
DMA_Cmd(DMAx, ENABLE);						 // 启动DMA

// 4. 中断处理
void DMAx_IRQHandler(void ){
	if(DMA_GetIntStatus(DMAx_INT_HTX3, DMAx)){   //DMA半传输中断
		Process_Data(buf[0]);
		DMA_ClearFlag(DMAx_FLAG_HT3, DMAx);
	}
	if(DMA_GetIntStatus(DMAx_INT_TXC3, DMAx)){   //DMA全传输中断
		Process_Data(buf[1]);
		DMA_ClearFlag(DMAx_FLAG_TC3, DMAx);
	}
}

2. ADC注入组 + PWM触发

  1. 名词解释
    (1)ADC注入组
    定义:ADC的一种高优先级采样模式,可以打断当前规则组转换,优先执行注入组转换。
    特点:同规则组一样都可以设置通道的采样顺序,但每个注入组通道有独立的数据寄存器,所以一般不结合DMA使用。并且每个ADC模块最多4个注入通道,即ADC1、ADC2、ADC3一共12个注入通道
    触发方式:同规则组一样,支持软件触发+硬件触发。
    应用场景:FOC相电流采样、过流/过压紧急检测、传感器突发信号采集等

    (2)触发分析
    例如配置PWM周期(Period)= 1ms(1000us)
    占空比(Duty Cycle)= 10% → Pulse = 100(100us高电平)
    PWM信号波:

    高电平   _ _ _ _ _  
    低电平  |  		|_ _ _ _ _ _ _ _ _ 
    	    ↑		↑
    	    t=0	    t=100us
    	 (上升沿)    (下降沿)
    

    ①如果配置ADC为PWM上升沿触发,那么ADC就会在PWM信号的上升沿(t=0) 时开始采样
    ②如果配置ADC为PWM比较事件触发,那么ADC就会在“PWM上升沿+Pulse值 对应的时间”(t=100us)时开始采样,在实际应用中,一般用PWM的比较事件 触发方式比较多。

    问: 为什么选PWM的比较事件触发方式比较多?
    答: 因为这种方式可以精确控制采样时刻,也就是通过设置PWM的Pulse值(占空比),去控制ADC采样点在PWM周期内的位置;还可以避开开关噪声,像在电机驱动或DC-DC这种功率电路中,MOSFET开关切换瞬间会产生瞬态电压电流,形成振荡噪声,所以PWM比较事件触发可以将采样点延迟到开关噪声之后,确保采集到稳定信号。

  2. 抽象代码设计

// 1. PWM定时器基础配置(TIMx为例)
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStruct);  // 设置周期/预分频
TIM_OC1Init(TIMx, &TIM_OCStruct);            // 配置PWM占空比
TIM_SelectOutputTrigger(TIMx, TIM_TRGOSource_OC1Ref); // TRGO由比较匹配触发
TIM_Cmd(TIMx, ENABLE);                       // 启动定时器

// 2. ADC注入组配置(ADCx为例)
ADC_Init(ADCx, &ADC_InitStruct);             // ADC基础模式设置
ADC_InjectedSequencerLengthConfig(ADCx, 1);   // 注入组通道数
ADC_InjectedChannelConfig(ADCx, ADC_Channel_0, 1, ADC_SampleTime); // 通道配置
ADC_ExternalTrigInjectedConvConfig(ADCx, ADC_ExternalTrigInjecConv_T1_TRGO); // PWM触发
ADC_Cmd(ADCx, ENABLE);                       // 启动ADC

// 3. ADC注入组中断服务函数
void ADC_IRQHandler(void) {
    if (ADC_GetITStatus(ADCx, ADC_IT_JEOC)) {  // 检查注入组转换完成中断
        g_adc_value = ADC_GetInjectedConversionValue(ADCx, ADC_InjectedChannel_1);
        ADC_ClearITPendingBit(ADCx, ADC_IT_JEOC);  // 清除中断标志
    }
}

问: 为什么上面刚说完初学者用的中断使用不合理,反而现在又要用中断方式了?
答: 上面说的中断不合理指的是规则组,因为规则组连续采样时频率很高,存在数据覆盖风险,而这里用的是注入组中断方式,它不是连续采样,是由外部事件触发,用于处理关键数据采样,对实时性要求高,必须通过中断立即响应,并且中断频率是我们自己可控的,其次每个注入组通道有独立的数据寄存器,不用担心数据覆盖风险,所以这里采用中断方式。

三、视频讲解

ADC高效采集方案:规则/注入组 + DMA双缓冲 + 定时器/PWM触发

ADC高效采集方案:规则/注入组 + DMA双缓冲 + 定时器/PWM触发


四、技术交流

那么,ADC这节课就先讲到这里,我们下节课再见!
感兴趣同学欢迎加入嵌入式技术交流群,联系主页wx:Lntt-xbc

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐