4、软件触发采样:定时器触发与CPU轮询的优缺点
好,咱们接着聊电流采样。上一节讲了硬件触发,这一节咱们聊聊软件触发。说白了,就是让软件来决定“什么时候去读ADC”。
软件触发里,最常用的两种方式:定时器触发和CPU轮询。这两种方式,我都在项目里用过,各有各的脾气。今天就把它们的优缺点掰开揉碎了讲清楚。
4.1 定时器触发:让硬件帮你“定时”干活
定时器触发,顾名思义,就是让一个定时器产生事件,然后这个事件去触发ADC采样。你想想看,这就像你设了个闹钟,闹钟一响,ADC就开始干活。
我个人习惯,在需要固定频率采样的场合,首选定时器触发。比如电机控制里的电流环,采样频率通常是10kHz、20kHz,这时候用定时器就特别合适。
核心优势:采样间隔精确,不受CPU负载影响。
我在项目中遇到过这样的情况:CPU正在处理一个突发的通信中断,如果这时候用CPU轮询,采样间隔就会被拉长,导致电流波形失真。但用定时器触发,ADC该什么时候采就什么时候采,CPU忙不忙跟它没关系。
定时器触发的配置要点
配置定时器触发,其实就三步:
- 选定时器:一般用通用定时器,比如STM32的TIM3、TIM4。别用基本定时器,它没有触发输出功能。
- 设频率:根据你的采样频率,算好定时器的重装载值和预分频值。比如系统时钟72MHz,想要20kHz采样,那重装载值就是3600-1。
- 连ADC:把定时器的触发输出(TRGO)连接到ADC的触发输入。不同芯片的映射关系不一样,记得查手册。
// 以STM32F1为例,配置TIM3触发ADC1
// 定时器配置
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 3600 - 1; // 20kHz采样
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 72MHz / 72 = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// 配置触发输出
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
// ADC配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
ADC_InitStructure.ADC_ExternalTrigConvCmd = ENABLE;
ADC_Init(ADC1, &ADC_InitStructure);
小提示:定时器触发有个“坑”——触发信号有延迟。从定时器事件产生到ADC真正开始采样,中间有几个时钟周期的延迟。如果你对采样时刻要求特别严,比如做高频注入,这个延迟必须补偿。
4.2 CPU轮询:简单粗暴,但别滥用
CPU轮询,就是让CPU不停地去检查“现在该采样了吗?” 说白了,就是CPU自己在那“死等”。
嗯,这里要注意,CPU轮询不是不能用,但得看场合。我见过一些新手工程师,不管三七二十一,上来就用轮询。结果呢?CPU被占得死死的,其他任务全卡住了。
什么时候用轮询?
我个人觉得,轮询适合以下场景:
- 采样频率很低:比如1kHz以下,CPU大部分时间都在闲着,轮询一下无所谓。
- 系统任务简单:就一个电机控制,没有通信、没有显示、没有其他乱七八糟的任务。
- 调试阶段:临时测一下波形,懒得配置定时器,直接轮询搞定。
警告:千万别在高速采样(10kHz以上)且任务繁重的系统里用轮询。我曾经在一个项目里试过,CPU负载直接飙到90%以上,电机转起来一顿一顿的,最后不得不改成定时器触发。
轮询的代码实现
轮询的代码很简单,但写不好容易出问题。给你看个反面教材:
// 错误示范:死等
while(1)
{
while(!(ADC1->SR & ADC_SR_EOC)); // 死等转换完成
current = ADC1->DR;
// 处理电流数据...
}
这种写法,CPU啥也不干,就盯着ADC寄存器看。你想想看,这得多浪费?
正确的做法是:
// 改进版:带超时机制的轮询
uint32_t timeout = 1000;
while(!(ADC1->SR & ADC_SR_EOC))
{
if(--timeout == 0)
{
// 超时处理,防止死锁
Error_Handler();
break;
}
}
current = ADC1->DR;
避坑指南:我曾经在一个产品里忘了加超时机制,结果有一次ADC硬件出了点小毛病,EOC标志一直不置位,整个系统就卡死在轮询里了。从那以后,我所有的轮询代码都加了超时保护。
4.3 两种方式的对比
说了这么多,咱们来做个对比,一目了然:
| 对比项 | 定时器触发 | CPU轮询 |
|---|---|---|
| 采样精度 | 高,硬件控制 | 低,受CPU负载影响 |
| CPU占用 | 低,触发后自动采样 | 高,CPU一直忙 |
| 实现复杂度 | 中等,需配置定时器 | 简单,几行代码搞定 |
| 灵活性 | 固定频率,不易动态调整 | 灵活,可随时改变采样策略 |
| 适用场景 | 高频、固定周期采样 | 低频、调试、简单系统 |
4.4 我的选择建议
说了这么多,到底该用哪种?我的建议是:
- 量产产品:用定时器触发。稳定、可靠、省心。
- 原型验证:先用轮询快速验证功能,再改成定时器触发。
- 特殊需求:比如需要根据电机转速动态调整采样频率,那轮询可能更灵活。
最后说一句,没有绝对的好与坏,只有合不合适。你想想看,一个10kHz的电流环,你用轮询跑得动吗?跑得动,但代价是CPU啥也干不了。反过来,一个100Hz的温度采样,你非要用定时器触发,那也有点杀鸡用牛刀了。
好了,这一节就到这里。下一节咱们聊聊DMA传输,这可是个好东西,能让你的采样效率再上一个台阶。