给电机一个固定的 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 是动作,编码器是回来的消息,目标速度和实际速度之间的差值则告诉程序下一步该怎么改。无论是温度、电压、亮度还是轮速,稳定的控制都离不开这条"测量、比较、调整、再测量"的回路。
最后蜘蛛说
有没有想给自己领导加反馈的,我的意思是,要多给领导反馈。