有个做牙科椅的客户,驱动靠背升降的有刷直流电机要在 0.5mm/s 的速度下平稳运行 —— 快到位置时医生要能微调,太快了患者会不舒服。母线 24V,电机 130W,减速比 1:80。
原来的方案是 PWM 开环调速,靠电位器给定占空比。问题是低速段占空比只有 8%,电机要么不动,要么一动就是 3mm/s 跳上去,中间没有过渡。医生抱怨"调不准"。
后来换成带编码器闭环的版本,以为问题会自动消失。确实好转了一些,但出现了新的抖动 —— 位置保持时电机在原地微振,发出"哒哒哒"的声音。
查了两周,问题不在编码器,在有刷电机的摩擦特性上。这个特性决定了「有刷 + 编码器」的调整思路与无刷完全不同。
母线电压 | 标称功率 | 峰值电流(倍流) | 额定(倍流) | 额定(非倍流) |
|---|---|---|---|---|
12V | 45W | 16A | 8A | 6A |
24V | 130W | 16A | 8A | 6A |
尺寸 55×55×17mm,裸板。带电刹接口(电磁刹车)、独立编码器接口、通讯隔离(RS485/CAN)。控制方式覆盖单/双电位器、开关、按键、0-3.3V 模拟信号、-3.3V~3.3V 差分模拟信号、PWM/频率、RS485/CAN。
这三个「带」在 8A 级裸板上不常见 —— 多数只做基本调速,刹车、编码器、隔离都要外挂。挤在 55×55mm 上,说明定位是"小体积但要完整功能"。
先说清「倍流」:16A 和 8A 不是范围的两端,是两个工况下的两个额定值。 倍流模式峰值 16A(靠电容热容吸收冲击),持续按 8A 算;非倍流降额到 6A 换更长寿命。选型必须明确用哪个,不能拿 16A 当持续能力。
有刷电机的电刷压在换向器上,产生恒定的摩擦力矩。关键在于这个力矩在静止时比运动时大。
设静摩擦 T_s = 0.12、库仑(动)摩擦 T_c = 0.08、负载力矩 T_L = 0.06(单位归一化)。那么:
T_m > T_s + T_L = 0.18T_m > T_c + T_L = 0.14中间这 0.04 的区间,就是粘滑窗口:
def slip_table(t_static, t_coulomb, t_load, targets):
need_moving = t_coulomb + t_load
need_static = t_static + t_load
print(f"{'稳态力矩':>9} {'运动所需':>9} {'起动所需':>9} {'状态':>6}")
for tq in targets:
if tq <= need_moving:
s = "停止"
elif tq < need_static:
s = "粘滑"
else:
s = "连续"
print(f"{tq:9.3f} {need_moving:9.3f} {need_static:9.3f} {s:>6}")
return need_moving, need_static
nm, ns = slip_table(0.120, 0.080, 0.060,
[0.10, 0.13, 0.14, 0.15, 0.17, 0.19, 0.25])
print(f"\n粘滑窗口 {ns-nm:.3f},占起动门槛 {(ns-nm)/ns*100:.0f}%")输出:
稳态力矩 运动所需 起动所需 状态
0.100 0.140 0.180 停止
0.130 0.140 0.180 停止
0.140 0.140 0.180 停止
0.150 0.140 0.180 粘滑
0.170 0.140 0.180 粘滑
0.190 0.140 0.180 连续
0.250 0.140 0.180 连续
粘滑窗口 0.040,占起动门槛 22%这张表说明了抖动的完整机理:
这个循环的频率取决于控制周期和积分速度,通常在 5~20Hz。 这正是听觉最敏感、机械最容易共振的频段 —— 客户听到的"哒哒哒"就是这个。
粘滑不是控制器的缺陷,是"静摩擦 > 动摩擦"这个物理事实与闭环控制相互作用的必然结果。 只要电机有电刷,只要有速度闭环,只要稳态力矩落在那个窗口里,就一定会发生。
无刷电机没有电刷,摩擦力矩只来自轴承:
对比项 | 有刷(本文型号) | 无刷(六步换相) |
|---|---|---|
摩擦来源 | 电刷压在换向器上 | 只有轴承 |
静/动摩擦比 | 1.50 | 1.11 |
粘滑窗口占起动门槛 | 22% | 约 6% |
低速转矩连续性 | 每换向周期有缺口 | 两相同时出力 |
换向火花 | 有 | 无 |
def window_ratio(t_static, t_coulomb, t_load, label):
ns = t_static + t_load
nm = t_coulomb + t_load
print(f"{label} 静/动比 {t_static/t_coulomb:.2f} "
f"粘滑窗口 {(ns-nm)/ns*100:4.1f}%")
window_ratio(0.120, 0.080, 0.060, "有刷(电刷压紧):")
window_ratio(0.020, 0.018, 0.060, "无刷(仅轴承): ")输出:
有刷(电刷压紧): 静/动比 1.50 粘滑窗口 22.2%
无刷(仅轴承): 静/动比 1.11 粘滑窗口 2.5%有刷的粘滑窗口 22.2%,无刷只有 2.5%,差 9 倍。
所以「有刷 + 编码器」不等于「无刷 + 编码器」。编码器只让控制器知道转速,改变不了电机的摩擦特性。
客户原来的预期是"加了编码器就稳了",实际是"从动不了变成抖了"。知道问题不等于能解决问题。
既然窗口是 (T_c+T_L, T_s+T_L),那就在指令上叠加一个固定量,把起动门槛降下来。
补偿量的取值有明确解:补偿到窗口闭合为止,即 T_comp = T_s - T_c。
T_s, T_c, T_L = 0.120, 0.080, 0.060
base_start = T_s + T_L
base_move = T_c + T_L
print(f"{'补偿量':>7} {'新起动门槛':>11} {'窗口宽度':>9} {'效果'}")
for comp in (0.00, 0.01, 0.02, 0.03, 0.04, 0.06):
new_start = base_start - comp
w = max(new_start - base_move, 0.0)
note = "窗口闭合" if w == 0 else ("明显收窄" if w < 0.02 else "")
print(f"{comp:7.2f} {new_start:11.3f} {w:9.3f} {note}")输出:
补偿量 新起动门槛 窗口宽度 效果
0.00 0.180 0.040
0.01 0.170 0.030
0.02 0.160 0.020 明显收窄
0.03 0.150 0.010 明显收窄
0.04 0.140 0.000 窗口闭合
0.06 0.120 0.000 窗口闭合补偿 0.04(正好等于 T_s - T_c)时窗口闭合。
这是前馈补偿的完整理论依据,不是靠试凑。工程上按这个值给,然后在此基础上做微调即可。
但要注意补偿的实现细节,这是最容易出错的地方:
def compensate(omega, target, t_comp, window_rad_s=5.0):
"""
补偿只在低速窗口内全额给,高速时线性退出。
window_rad_s: 退出判据,超过此速度且目标同向则不再补偿
"""
if target == 0:
return 0.0 # 目标为零,不补偿(否则会爬行)
sign = 1.0 if target > 0 else -1.0
if abs(omega) > window_rad_s:
return 0.0 # 已在高速,无需补偿
fade = 1.0 - abs(omega) / window_rad_s
return sign * t_comp * fade
print(f"{'转速rad/s':>11} {'目标':>6} {'补偿量':>8}")
for w in (0, 0.5, 1.0, 2.0, 4.0, 5.0, 8.0):
print(f"{w:11.1f} {3.0:6.1f} {compensate(w, 3.0, 0.04):8.4f}")输出:
转速rad/s 目标 补偿量
0.0 3.0 0.0400
0.5 3.0 0.0360
1.0 3.0 0.0320
2.0 3.0 0.0240
4.0 3.0 0.0080
5.0 3.0 0.0000
8.0 3.0 0.0000三个坑,每个都踩过:
坑一:目标为零时必须不补偿。 否则控制器一直输出补偿力矩,电机会"爬行"—— 停在某个位置后缓慢漂移。这是最危险的,因为在保持位置时电机在动。
坑二:必须做速度窗口衰减。 全额补偿到高速会导致超调,因为高速时不再需要那么多力矩去克服摩擦。
坑三:补偿值要随温度变。 电刷磨损、润滑脂粘度变化都会改变摩擦。新机和运行 2000 小时后的 T_s - T_c 可能差 30%。补偿值定死在出厂值上,长期运行必然失配。
客户最后采用的不是前馈,是位置环 + 速度内环的串级结构。
为什么位置环更根本?因为粘滑的本质是"速度误差在零速时无法累积"。速度环在零速时误差就是 target - 0 = target,但控制器一旦给出力矩没动,误差不增长,无法判断"还要加大"。位置环不同:位置不动,误差就在持续累积,积分必然越过起动门槛,不会卡死。
def position_loop(target_mm, dt=0.005, kp_pos=6.0, kp_vel=2.0, ki_vel=15.0,
v_max=1.0, t_static=0.004, t_coulomb=0.0025,
damping=0.02, steps=400):
"""
串级结构:位置环输出速度指令,速度环输出力矩
v_max: 最大速度 mm/s
headroom: 反电动势限制,速度越高可给出的加速度越小
"""
pos, vel, integ = 0.0, 0.0, 0.0
log = []
for s in range(1, steps + 1):
err = target_mm - pos
v_cmd = max(-v_max, min(v_max, kp_pos * err))
v_err = v_cmd - vel
integ += v_err * dt
f = kp_vel * v_err + ki_vel * integ
if abs(vel) < 1e-4: # 静止:静摩擦门槛
if abs(f) < t_static:
f = 0.0
else:
f -= t_static if f > 0 else -t_static
else: # 运动:只有库仑摩擦
f -= t_coulomb if f > 0 else -t_coulomb
f -= damping * vel
headroom = max(0.0, 1.0 - abs(vel) / v_max)
vel += f * headroom * 0.05
pos += vel * dt
log.append((s, pos, err, v_cmd, vel))
return log
log = position_loop(0.5)
print(f"{'步':>4} {'位置mm':>9} {'误差mm':>9} {'速度指令':>9} {'实际mm/s':>9}")
for s, pos, err, vc, vel in log:
if s <= 6 or s in (50, 100, 200, 300, 400):
print(f"{s:>4} {pos:9.4f} {err:9.4f} {vc:9.4f} {vel:9.4f}")输出:
步 位置mm 误差mm 速度指令 实际mm/s
1 0.0005 0.5000 1.0000 0.1036
2 0.0015 0.4995 1.0000 0.1901
3 0.0028 0.4985 1.0000 0.2636
4 0.0044 0.4972 1.0000 0.3271
5 0.0063 0.4956 1.0000 0.3824
6 0.0085 0.4937 1.0000 0.4312
50 0.1892 0.3156 1.0000 0.9596
100 0.4356 0.0694 0.4162 0.9937
200 0.4137 0.0879 0.5276 0.3319
300 0.4953 0.0048 0.0286 0.0053
400 0.4998 0.0002 0.0013 -0.0001三件事:
起动没有粘滑。 第 1 步就到 0.1036mm/s,之后单调爬升。积分项在起动阶段就已经积累足够,越过静摩擦后不会立刻掉回去。
第 100 到 200 步有一次回弹。 位置从 0.4356 退回 0.4137,误差从 0.0694 涨到 0.0879。原因是速度已达上限 1.0mm/s,减速需要距离,冲过头再拉回。这 0.09mm 的回弹就是操作者"感觉上的一顿"。
第 400 步稳在 0.4998mm,误差 0.2µm。 整个过程 2 秒(400×5ms),对牙椅靠背完全可接受。
对比三种方案:
方案 | 起动 | 稳态 | 位置精度 | 代价 |
|---|---|---|---|---|
纯速度环 | 粘滑 5~20Hz | 速度波动 | 无 | 无 |
速度环 + 前馈 | 粘滑消失 | 稳定 | 无 | 补偿值随磨损失配 |
位置环 + 速度内环 | 单调爬升 | 稳定 | 0.2µm | 需编码器、需整定 |
关键差别不在增益,在有没有"累积误差"这个自由度。 位置环的积分能把静摩擦当成一个必然存在的偏差慢慢吃掉;速度环在零速时误差封顶,吃不到东西。
回弹问题也有解:在速度指令上加斜率限制,或者引入减速段预判(v_cmd 按剩余距离开方给出,即 v = sqrt(2*a*s))。这属于轨迹规划层面,不在本文展开。
如果不想改结构,提高控制频率也能改善体验 —— 它减小每次粘滑的位移量:
def slip_amplitude(control_hz, v_target_mm_s=0.5, accel=2.0):
"""单次粘滑的位移量:检出延迟一个控制周期内的位移"""
dt = 1.0 / control_hz
return v_target_mm_s * dt + 0.5 * accel * dt ** 2
for hz in (50, 100, 500, 1000, 5000):
a = slip_amplitude(hz)
print(f"控制频率 {hz:>4}Hz 单次位移 {a*1000:6.1f} µm")输出:
控制频率 50Hz 单次位移 10.4 µm
控制频率 100Hz 单次位移 5.1 µm
控制频率 500Hz 单次位移 1.0 µm
控制频率 1000Hz 单次位移 0.5 µm
控制频率 5000Hz 单次位移 0.1 µm位移确实随频率下降,但要注意:位移小不等于听不见。 频率变高后抖动从"哒哒"变成"嗡",主观感受不同,机械磨损照旧。
而且提高控制频率占 CPU,可能与通信、保护逻辑抢时间。1kHz 对多数 Cortex-M 是合理上限。
客户的实际情况:从 200Hz 提到 1kHz,抖动位移降到 2.5µm,人耳听不见了。但根源还在,只是被藏起来。 后来还是改成了位置环。
回到具体产品。AQMD2408NS-MBE 有刷电机驱动器,电机控制方式为有刷直流电机的 PWM 调速(H 桥四象限):12V 45W / 24V 130W,峰值 16A(倍流)/ 额定 8A(倍流)或 6A(非倍流),55×55×17mm 裸板,带独立电刹接口、独立编码器接口、通讯隔离,控制方式覆盖电位器、模拟量、差分模拟、PWM/频率、RS485/CAN。
三个真正有用的点:
独立编码器接口。 上面位置环方案完全依赖编码器。接口独立(不复用电机线)意味着可以用差分信号,抗干扰远好于单端 —— 小体积板子上尤其重要,因为电机线就在旁边,PWM 的 dv/dt 会耦合进反馈。
电刹接口。 牙椅靠背断电时必须保持位置,不能自由滑落。电磁刹车断电默认抱死是安全底线。接口独立的价值在时序可控 —— 起动先释放再出力,停止先减速到零再抱死,避免带载硬抱(会打齿)。用相线顺带控制做不到这个时序。
通讯隔离。 医疗器械通常接上位机,485/CAN 是标准做法。通讯隔离保护的是总线侧 —— 某台设备通信地电位偏移时,防止故障扩散到整个网络。这与控制信号隔离是两件事(见第 03、14 篇)。
倍流/非倍流的选择。 牙椅升降是间歇工况(每次 3~5 秒,中间休息几十秒),倍流合适,拿 16A 峰值应对起动。连续运转(传送带)就该切非倍流 6A,牺牲峰值换寿命。选型时定,运行时改不了。
「12V 档能驱动 130W 负载吗?」
不能。12V 档标称 45W。130W / 12V = 10.8A,超过 8A 额定。必须用 24V 档。
「编码器精度要多高?」
按最低速算。0.5mm/s、减速比 1:80、电机 2mm/rev,N 线编码器下分辨率 2/(80N) mm。分辨 0.01mm 只需 N ≥ 2.5。但分辨率不等于稳定度 —— 低速稳定性取决于摩擦和环路配合,加线数解决不了粘滑。
「提高 PWM 频率能改善抖动吗?」
不能。PWM 频率影响电流纹波,不影响机械摩擦。抖动的来源是摩擦和闭环。
「换无刷是不是就彻底没这个问题?」
粘滑窗口从 22% 降到 2.5%,基本可以忽略。但无刷有自己的低速问题:霍尔分辨率限制、六步换相转矩脉动。没有"完全无抖动"的方案,只有"抖动在你的应用里可接受"的方案。
「补偿值能不能自动辨识?」
能。低速下缓慢增加力矩直到检出运动,记录此时的力矩就是起动门槛;再测运动中的平均力矩就是动摩擦。整个过程几秒钟,可以在开机自检时做。但要注意别在带负载的大机构上做,突然运动会伤人。
编码器解决"看不见",不解决"动不了"。
有刷低速的难点是机械性的:静摩擦比动摩擦大 33%。这个落差不会因为加反馈就消失,只会从"卡住不动"变成"来回抖"。
三条路,按根本性排序:
第一,位置环。 唯一能让起动和低速都不粘滑的结构。本例 0.2µm 稳态误差,2 秒收敛。代价是需要编码器和整定。
第二,前馈补偿。 有明确解法:T_comp = T_s - T_c 时窗口闭合。实现上有三个坑(零目标不补偿、速度窗口衰减、温漂失配),踩过一遍才能用。
第三,提高控制频率。 从 50Hz 到 1kHz,位移从 10.4µm 降到 0.5µm,听得见但根源还在。
那位客户最后用位置环 + 1kHz 控制频率,靠背升降的微调段能稳在 0.5mm/s,医生说"跟手了"。整个过程没换电机、没换驱动器,只改了控制结构。
有时候最贵的方案不是换硬件,是没人愿意花两周去搞明白那个"哒哒哒"到底从哪来的。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。