4、软件触发采样:定时器触发与CPU轮询的优缺点

好,咱们接着聊电流采样。上一节讲了硬件触发,这一节咱们聊聊软件触发。说白了,就是让软件来决定“什么时候去读ADC”。

软件触发里,最常用的两种方式:定时器触发CPU轮询。这两种方式,我都在项目里用过,各有各的脾气。今天就把它们的优缺点掰开揉碎了讲清楚。

4.1 定时器触发:让硬件帮你“定时”干活

定时器触发,顾名思义,就是让一个定时器产生事件,然后这个事件去触发ADC采样。你想想看,这就像你设了个闹钟,闹钟一响,ADC就开始干活。

我个人习惯,在需要固定频率采样的场合,首选定时器触发。比如电机控制里的电流环,采样频率通常是10kHz、20kHz,这时候用定时器就特别合适。

核心优势:采样间隔精确,不受CPU负载影响。

我在项目中遇到过这样的情况:CPU正在处理一个突发的通信中断,如果这时候用CPU轮询,采样间隔就会被拉长,导致电流波形失真。但用定时器触发,ADC该什么时候采就什么时候采,CPU忙不忙跟它没关系。

定时器触发的配置要点

配置定时器触发,其实就三步:

  1. 选定时器:一般用通用定时器,比如STM32的TIM3、TIM4。别用基本定时器,它没有触发输出功能。
  2. 设频率:根据你的采样频率,算好定时器的重装载值和预分频值。比如系统时钟72MHz,想要20kHz采样,那重装载值就是3600-1。
  3. 连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传输,这可是个好东西,能让你的采样效率再上一个台阶。