反馈:让系统知道自己做得对不对

给电机一个固定的 PWM,它确实会转起来。但转起来以后,程序知道轮子现在有多快吗?

不知道。

程序只知道自己写出了一个数字,比如 30。这个 30 是给驱动器的命令,不是轮子的实际速度。电池电压变了,齿轮摩擦变了,小车从桌面落到地面了,轮子的速度都会变;程序却还以为"我已经给过 30,一切应该没问题"。

这就是没有反馈的控制:只发出动作,不检查结果。

只发命令,事情会变成什么样

把电机想成一辆看不见的车。你对它说:"请以 30 的速度前进。"它不会回答你现在到底跑了多快。你只能假设它听懂了,而且路面、风、负载和电池都没有变化。

text 复制代码
程序 ──命令:PWM=30──> 电机 ──> 轮子

这条线只有一个方向。程序把命令送出去,事情就结束了。电机有没有真的转、转得够不够快、左右轮是不是一样快,程序都没有证据。

现实里有很多类似的做法:

  • 水龙头拧到某个角度,却不看水桶里到底有多少水;
  • 加热器开到某个档位,却不看房间温度;
  • 灯设置成某个亮度,却不看环境变暗以后人眼还能不能看清。

这些办法在条件不变时也许凑合能用,条件一变化,原来的数字就不再可靠。

反馈,就是把结果送回来

反馈没有那么神秘。它只是多问了一句:"刚才那件事,实际做成什么样了?"

text 复制代码
目标 ──> 做出动作 ──> 得到结果
  ^                      |
  |------ 看结果再调整 ---|

目标是我们希望得到的结果,实际结果是传感器测到的结果。两者不一样,就说明动作还需要调整;两者接近,就先保持当前动作。

空调设定 26 摄氏度,温度传感器测到 30 摄氏度,空调继续制冷;测到 26 摄氏度附近,压缩机就停下来或减小工作量。空调不是"猜"房间温度,而是不断测量、比较和修正。

电路里也有反馈。稳压电源会观察输出电压,再和目标电压比较;输出低了,就增加一点能量,输出高了,就收回一点。芯片内部的许多自动调节,本质上也是把结果送回判断位置。

反馈不保证结果一定变好。结果送回来以后,如果调整方向反了,偏差会越来越大;调整得太猛,结果会在目标附近来回摆动。反馈真正提供的是一条回路,让系统有机会根据现实修正自己的动作。

如果调整的方向总是让偏差变小,这就是常说的负反馈:速度低了就加力,速度高了就减力;温度低了就加热,温度高了就少加热。负反馈倾向于把结果拉回目标附近。

如果调整的方向反过来,那就叫做正反馈。对于之前所说的情况,那就是速度低了却把 PWM 再减小,温度高了却继续加热,这条回路虽然也在"根据结果动作",却是在放大错误。所以说反馈这个词本身只说明"结果被送回来了",回来的结果该怎样影响下一步,还要看方向。(可以想一想在什么场景下需要正反馈qwq)

编码器为什么在这里出现

回到小车。PWM 能告诉驱动器"这一周期里通电多久",却不能告诉程序轮子实际转了多少。编码器恰好把轮子的转动变成 A、B 两相脉冲,TIM4 再把脉冲变成有方向的计数。

于是,程序终于有了两件可以放在一起比较的东西:

text 复制代码
target_speed   目标速度:希望轮子达到多快
actual_speed   实际速度:编码器数出来的速度

没有编码器时,actual_speed 这一栏是空的。程序再聪明,也只能根据 PWM 猜速度;电机换了、负载变了,猜测就跟着失效。

有了编码器,控制过程就变成:

text 复制代码
设定目标速度
      |
      v
给出 PWM -> 电机转动 -> 编码器测量实际速度
      ^                         |
      |----- 比较并调整 PWM ----|

PWM 负责"让它动",编码器负责"告诉我们动成什么样"。一个负责施加动作,一个负责提供证据,两者缺一不可。

最简单的速度反馈

先不考虑复杂的数学,只写出最直白的判断:实际速度慢了,PWM 加一点;实际速度快了,PWM 减一点。

c 复制代码
/* 每 100ms 统计一次脉冲,下面的数字就是这个时间段内的目标脉冲数 */
int16_t target_speed = 100;
int16_t actual_speed;
int16_t pwm = 30;

while (1)
{
    Delay_ms(100);
    actual_speed = Encoder_ReadDelta();

    if (actual_speed < target_speed)
    {
        pwm++;
    }
    else if (actual_speed > target_speed)
    {
        pwm--;
    }

    Motor_SetPercent(pwm);
}

这里没有急着把脉冲数换算成每分钟转数。只要每次都用相同的 100ms 统计窗口,脉冲数就能先作为速度快慢的指标。代码已经有了完整回路的骨架:测量实际速度,和目标速度比较,真正改变 PWM。下一轮统计时,新的速度又会进入同一个判断。

这段示例假定电机只向一个方向转,并且已经约定这个方向的 delta 是正数。要控制反方向,目标值和实际值的正负号也要一起约定;不能把反转时的负数直接拿来和正的目标速度比较。

但直接这样写,还差几道现实里的护栏。

PWM 不能越界

PWM 百分比不可能小于 0,也不可能大于 100。调节后要把它限制在有效范围:

c 复制代码
if (pwm > 100)
{
    pwm = 100;
}
else if (pwm < 0)
{
    pwm = 0;
}

否则,电机已经全速运行,因为硬件出错,或者不合理的目标(比如目标速度比最大实际速度还要大),那么 pwm++ 就会继续让变量一直变大;甚至在某个瞬间,变量一旦溢出,数字可能突然绕回负数,这很有可能就变成某个"无法解释的bug"了。

接近目标时,不必每次都改

编码器的计数会有一点波动。目标速度是 100,实际速度可能一会儿是 99,一会儿是 101。如果每次都严格比较,PWM 会加一下、减一下,电机就在目标附近反复抖动。

可以留出一个小范围:

c 复制代码
int16_t error = target_speed - actual_speed;

if (error > 2)
{
    pwm++;
}
else if (error < -2)
{
    pwm--;
}

实际速度在 98 到 102 之间时,先保持当前 PWM。这个范围某种程度上是适应测量本身有的分辨率和波动,系统不必为了一个很小的数字变化不停动作。

一次改多少也很重要

每次只改 1,变化温和,但到达目标可能比较慢;一次改 20,速度上升很快,却可能越过目标,再反向改回来。调整步长越大,反应越快,也越容易在目标附近来回摆动。

c 复制代码
if (error > 2)
{
    pwm += 2;
}
else if (error < -2)
{
    pwm -= 2;
}

先把步长写成一个看得懂的数字,观察实际速度如何变化,再决定是否需要更细的调节。反馈回路不是把一个数字塞进函数就结束了,它是一轮一轮地测量和修正。

这条回路什么时候才算正常

可以给电机一个目标速度,记录每次测到的 actual_speed 和当前 pwm,观察几个现象:

  • 刚开始速度低于目标,PWM 应该逐步增加;
  • 接近目标后,PWM 的变化应该变慢或停住;
  • 负载突然变大,速度下降,PWM 应该再次增加;
  • 目标速度降低,PWM 应该逐步减小;
  • 断开编码器信号时,程序不应该假装自己仍然知道真实速度。

最后一点很重要。反馈回路相信的是测量结果,测量线断了、供电不对或计数器没有工作时,回路拿到的就不是现实。一个错误的反馈,比没有反馈更容易把系统带到奇怪的地方。

最后橘猫说

反馈让系统从"我已经发出命令了"走到"我确认结果真的发生了"。小车里,PWM 是动作,编码器是回来的消息,目标速度和实际速度之间的差值则告诉程序下一步该怎么改。无论是温度、电压、亮度还是轮速,稳定的控制都离不开这条"测量、比较、调整、再测量"的回路。

最后蜘蛛说

有没有想给自己领导加反馈的,我的意思是,要多给领导反馈。

相关推荐
..Dauntless..12 小时前
【stm32】串口——在MCU内部的工作过程
stm32·单片机·嵌入式硬件
Code_Solitude16 小时前
开发板点灯实验源码
stm32·单片机·嵌入式硬件
Zw-awa1 天前
编码器读到的到底是什么:从脉冲到速度
stm32
笨笨饿1 天前
140_AI新手村MCP与Skills是干嘛的
开发语言·人工智能·python·stm32·单片机·嵌入式硬件·物联网
沐欣工作室_lvyiyi2 天前
基于云平台的输液智能监测控制系统设计与实现(论文+源码)
stm32·单片机·单片机毕业设计·电子信息工程毕业设计
EatFan2 天前
当 CubeMX 遇上 AI Agent:用 MCP 让 AI 直接生成 STM32 HAL 工程
人工智能·stm32·嵌入式硬件·cubemx·stm32cubemx·hal·mcp
千秋岁rr2 天前
13.SPI
stm32
LCG元2 天前
STM32L431 QSPI驱动W25Q256实战:内存映射模式配置、4字节地址模式陷阱与读写吞吐实测
stm32·单片机·嵌入式·flash
lhbweilai2 天前
深挖FreeRTOS内核实现(上)
stm32·单片机·链表