如果你第一次把增量式编码器装到无刷电机上做FOC,大概率会遇到这样的场景:上电后给一个速度指令,电机不是纹丝不动,就是剧烈抖动,电流还大得吓人。这时候先别急着调PID,先把增量式编码器的零位校准做对。我在嵌入式电机控制的调试里踩过最多的坑,就是电角度没有对齐,导致电流环参数、速度环参数怎么调都白搭。这篇文章就把我在实际项目里用过的几种零位校准方法、踩过的坑和参数换算细节整理出来,给正在调FOC的朋友一个参考。
1. 为什么增量式编码器必须先校零位
1.1 增量式编码器的天生缺陷:上电不知道位置
增量式编码器输出的是A、B、Z三路脉冲信号。A/B两相相位差90°,用来测转向和位置增量;Z相每转一圈只来一个窄脉冲,作为机械零点参考。这套方案成本低、结构简单、响应快,是中小功率无刷电机驱动器里最常见的反馈方式。但它有一个天生的缺陷:它没有任何断电保存能力,上电之后计数器只能从某个随机值开始累加,你不知道当前转子到底在哪个机械角度上。也就是说,增量式编码器只能告诉你“从刚才到现在走了多少”,不能告诉你“你现在在哪儿”。
这一点和绝对式编码器是本质区别。绝对式编码器内部有格雷码盘,每个机械位置对应唯一的二进制码,上电一读就是绝对位置,不需要找零位。但绝对式编码器贵、线多,很多行业项目里并不愿意为它掏钱,所以增量式编码器依然是主流。既然用了增量式编码器,就必须在上电以后想办法把“当前机械位置”这个信息补回来,这就是零位校准存在的意义。
1.2 零位偏差对FOC控制环路的实际影响
FOC的核心在于坐标变换:先把三相电流做Clarke变换,得到αβ轴分量;再做Park变换,得到d/q轴分量。Park变换需要用到转子电角度。这个电角度如果错了,整个坐标变换就错了。
很多初次接触FOC的朋友以为,编码器计数可以直接映射成电角度:电角度=机械角度×极对数。这句话本身没错,但问题在于机械角度从哪儿算起。编码器的0计数点不一定对应A相绕组轴线的位置,中间还隔着一个安装角。你从编码器读到的“机械角度”,和定子坐标系里的“转子电角度”之间,差了一个固定的偏移量。这个偏移量就是我们要校准的零位。
零位不准会怎样?拿id=0控制来说,如果控制器认为的电角度比真实电角度超前Δθ,那么实际加在转子上的不是纯q轴电流,而是混入了一部分d轴电流。结果就是你以为全在出力,其实一部分电流变成了励磁或者去磁,还有一部分做了无用功。我实测过一组数据:Δθ在30°电角度时,电机带同样负载需要的电流大约增加15%左右,速度环和电流环开始明显抖动;到90°电角度时,q轴力矩分量直接降为0,电机纹丝不动;再继续偏到180°,电机会出现反向制动,给正指令反而往反方向使劲。
所以零位校准做得准不准,直接决定了FOC能不能出力、能不能稳定。这也是为什么我每次拿到一块新的电机板,第一件事永远是校零位,而不是先调PID。下面这张表能更直观地看出偏差对力矩的影响:
| 电角度偏差 Δθ | 实际输出力矩占比(近似) | 现象 |
|---|---|---|
| 0° | 100% | 正常 |
| 10° | 98.5% | 基本察觉不到 |
| 30° | 86.6% | 带载性能下降 |
| 60° | 50% | 明显无力、电流偏大 |
| 90° | 0% | 完全不出力 |
| 180° | -100% | 反向制动,可能反转 |
2. 校准前的准备:硬件确认与软件配置
2.1 编码器接线、信号处理与方向校验
在动手校零之前,先把硬件底子打好。我用过不少编码器,光电和磁的都有,接线不外乎A、B、Z三根信号线加电源地线。但实际工程里最大的坑,往往就在这三根线上。
先说上拉。很多增量式编码器是开集电极输出,必须外部接上拉电阻才能输出高电平。我一般取2.2kΩ到4.7kΩ,上拉到编码器工作电压。上拉电阻太小,信号沿变陡但耗电流增加;太大,边沿变缓,高速时可能丢脉冲。2500线的光电编码器工作在几百转每分钟时,一个脉冲周期也就几微秒,边沿不够陡很容易被定时器误判。
如果是长线传输或者现场干扰大,建议直接用差分输出的编码器。A反、B反、Z反四对差分线,抗干扰能力比单端强很多。我之前见过有人拿单端编码器穿过动力线电缆沟,结果一跑电机就误触发Z脉冲,位置跳变,查了整整两天才发现是干扰问题。用STM32F103这类芯片做FOC也一样,硬件上把编码器供电和电机供电分开走线,电源引脚就近加104电容退耦,能省去后面一大堆排查功夫。
方向校验也别忽略。正常接线后,手动正向转动电机转轴,观察编码器计数器应该递增;反方向应该递减。如果反了,交换A/B两路,或者在定时器配置里改成反向计数。别等到校完零位才发现方向反了,那时候排查起来很头痛。如果你用CubeMX初始化,编码器模式直接在TIM的Combined Channels里选Encoder Mode,计数模式选Edge Aligned即可,极性保持默认。
2.2 编码器参数与极对数的换算关系
零位校准最终要落到一个具体数值上:当转子处于某一个已知电角度(比如0°)时,编码器的计数是多少。这个计数和极对数、编码器线数直接相关,先把这些参数算清楚,后面才不会乱。
举个例子。我手头这块电机极对数为4,编码器是2500线。4倍频之后每转一圈计数器走2500×4=10000个数。一个电周期对应的机械角度是360°/4=90°,在这个90°范围内编码器走10000/4=2500个数。也就是说,如果零点偏了10个数,对应电角度就偏了360°×10/10000×4=1.44°。这个量级对力矩影响很小,所以工程上把零位精度做到1~2个编码器计数就完全够用了。
软件上,保存一个全局变量zero_count,每次读编码器后先减掉zero_count,再取模,再映射到电角度。下面这段是我常用的角度换算函数,用的是整数运算,避免在控制中断里引入浮点延迟:
#define ENC_PPR 2500U // 编码器物理线数 #define ENC_COUNTS_1X (ENC_PPR * 4U) // 4倍频后单圈计数 #define MOTOR_POLE_PAIRS 4U // 电机极对数 static int32_t enc_zero_count = 0; float get_elec_angle_deg(void) { int32_t count = get_encoder_count(); int32_t mech_count = count - enc_zero_count; if (mech_count < 0) { mech_count += ENC_COUNTS_1X; } // 机械角度,放大10倍用整数表示 int32_t mech_angle_01deg = mech_count * 3600 / ENC_COUNTS_1X; int32_t elec_angle_01deg = (mech_angle_01deg * MOTOR_POLE_PAIRS) % 3600; return (float)elec_angle_01deg * 0.1f; }要注意:机械角度模一圈等于10000,电角度只要模360的整数倍即可。用整数运算算完之后再转浮点,方便控制环使用。如果你用的是STM32定时器编码器模式,默认计数器是16位的,单圈计数如果超过65535就得开两个定时器级联,或者用软件在溢出中断里扩展位宽。2500线4倍频是10000,16位够用;但如果换5000线编码器,4倍频就是20000,也没超,不过要留意方向反转时的边界处理。
3. 三种零位校准方法实操对比
3.1 方法一:开环电压矢量定位法(最基础)
开环电压矢量定位法,也叫直流定位法。原理很简单:给电机施加一个固定方向的定子磁场,转子会像一个指南针一样,转到磁场指向的方向。
具体操作分几步。第一,确认电机空载或者轻载,联轴器最好脱开,避免负载带着转子跑偏。第二,关断速度环和位置环,只使能PWM输出和逆变器。第三,给一组固定的占空比组合,让合成电压矢量指向0°电角度方向。第四,等待转子稳定,一般500ms到1s。第五,读取编码器计数器,保存为zero_count。
实际操作时,我习惯把占空比限制在10%以内。比如Vbus是24V,那输出的电压矢量幅值就压在2.4V左右。这个电压要能克服转子的齿槽转矩和摩擦转矩,但又不能太大,太大了转子会有过冲,停下来也有误差。我测试过,通直流定位后转子大概需要200ms到500ms才能完全稳定。你可以在代码里先等500ms,然后连续读十次编码器计数,如果前后一致再写成零位。
这个方法优点是简单直接,适合手头代码还不完整、电流环还没调通的时候用。缺点也很明显:齿槽转矩会让转子卡在偏离磁场方向的位置,逆变器死区也会让实际电压矢量方向偏几度。综合下来,这个方法一般能做到±几度电角度的精度,作为粗调够用,如果后面要跑高速重载,最好再用下面的方法精调一次。
3.2 方法二:电流闭环动态寻零法(最推荐)
电流闭环动态寻零法,是我在量产和调试项目里用得最多的方法。它不需要示波器,不需要手转电机,写一个函数就能自动完成校准,精度还高。
原理是这样的:在电流环闭环的状态下,给定id_ref=0、iq_ref=一个小电流,再固定一个电角度指令θe_cmd。电机在力矩作用下会转动,一直转到转子真实电角度θe等于θe_cmd的位置,在这个位置电磁力矩为零,转子会稳定停住。这时候读取编码器计数,它对应的就是θe=θe_cmd的机械角,也就是零位。
void zero_calibrate_encode(void) { // 1. 使能电流环,id_ref=0,iq_ref取额定电流的20% set_id_ref(0.0f); set_iq_ref(0.2f * MOTOR_RATED_CURRENT); // 2. 固定电角度指令为0 set_elec_angle_cmd(0.0f); // 3. 等待转子稳定 HAL_Delay(800); // 4. 读取编码器计数器,即为零位 enc_zero_count = get_encoder_count(); // 5. 关断输出 disable_motor(); }注意几个细节:iq_ref不要给太大,给到0.2倍额定电流左右足够了。太大了转子转得快,停下来的时候可能会过冲,影响精度;太小了克服不了摩擦,转子会停在半路。我一般取0.1到0.3倍额定电流之间,具体看电机大小和现场负载。
还有一个提升精度的技巧:做两次校准取平均。第一次用θe_cmd=0°,记录count_0;第二次用θe_cmd=180°,记录count_180。理想情况下count_180应该比count_0多半个电周期对应的计数,也就是(10000/4)/2=1250。如果实际差值和理论值不一致,说明两次都存在同样的静摩擦误差,取平均可以抵消一部分。具体做法是:如果count_180>count_0,则校准后的zero_count=(count_0 + (count_180 - 1250))/2;如果count_180<count_0,就把count_180先加一圈再算。这个操作实测能把零位精度从±3°电角度提高到±1°以内。
另外,如果电机是内置式永磁同步电机,凸极效应会引入一个二倍频的磁阻转矩,单纯用iq和θe_cmd定位会有一个固定的偏置角。处理方式有两种:要么改用d轴电流定位法,给定id_ref=I_ref、iq_ref=0,然后扫描θe_cmd,观察转子在哪一个角度不动,那个角度就是d轴方向;要么根据磁阻模型做角度补偿。对于大多数表贴式电机,用上面的方法已经足够。
3.3 方法三:霍尔信号辅助对齐法(最快速)
如果你电机上已经有了霍尔传感器,也可以用来辅助快速对齐。霍尔信号每60°电角度变一次,所以它能把转子位置定位到一个60°扇区内。利用这个扇区信息,我可以快速给出一个大致的电角度,然后施加对应方向的电流矢量,让转子对齐到扇区中心。
步骤是这样的:先读霍尔状态,确定转子在哪个60°扇区。然后计算该扇区中心对应的电角度θ_center。接着给定id=0、iq=小电流、θe_cmd=θ_center,让转子转到扇区中心并停止。随后读取编码器计数,作为粗零位。最后再用方法二精调一次。
这个方法的好处是,在带一定负载或者需要快速上电启动的场景下,不用等转子长时间转动到位,粗调几毫秒就完成。但霍尔定位精度只能到±30°电角度,必须配合后续精调才能达到FOC的要求。我一般只在做无感切换或者上电起步阶段用霍尔粗定位,真正跑控制还是以编码器零位为准。
三种方法各有适用场景,我整理成了一张对比表:
| 校准方法 | 实现难度 | 精度 | 适用情况 | 注意事项 |
|---|---|---|---|---|
| 开环电压矢量定位 | 低 | 一般,几度电角度误差 | 空载/轻载,快速粗调 | 电流不能大,防烧绕组 |
| 电流闭环动态寻零 | 中 | 高,可到1°以内 | 空载,适合出厂标定 | 转子会转动,不能带重载 |
| 霍尔辅助对齐 | 低 | 较粗,±30°内 | 带霍尔、需要快速粗定位 | 后续仍需精调 |
4. 校准中常见问题与排查口诀
4.1 校准结果时好时坏,零位值跳变
这是最让人抓狂的问题:明明上电校了三次,三次拿到的zero_count都不一样,差得还不小。我遇到过的情况主要有三类。
第一类,Z信号毛刺或者共模干扰。Z信号每圈只有一个脉冲,最容易受到噪声干扰。如果Z一毛刺就触发外部中断去清零计数器,位置自然乱跳。解决手段:硬件上给Z加RC低通,比如100Ω串电阻加1nF对地电容;软件上不要用上升沿直接清零,可以用连续两次采样确认电平稳定后再处理。
第二类,校准电流太小或者供电不稳。校准电流托不住负载,转子停在随机位置,读数当然随机。看看母线电压是不是在抖动,电机负载是不是忽大忽小。校准时最好空载,用稳压源供电。
第三类,机械连接问题。联轴器打滑、顶丝松了,编码器和电机轴之间有了相对转动,这是硬伤。换一个联轴器重新锁紧就好。这个我印象特别深,有一回校准值反复跳,折腾了一晚上,最后发现是编码器顶丝没拧紧。
4.2 校准后电流大、力矩小甚至堵转
如果校准流程跑完了,zero_count也写进去了,但电机还是没力,主要查三件事。
第一,零点偏差过大。用方法一校准的话,可能有几度到十几度的偏差。直接用方法二再校一次,对比两次zero_count差多少,如果差很多,说明第一次确实偏了。
第二,编码器方向反了。方向不对,电角度符号是反的,FOC把坐标变换转成了“负反馈”。检查方式:固定一个小iq,给定一个小角度步进,观察转子是不是往预期方向走。如果反了,交换A/B或者软件取反。
第三,极对数填错。极对数写错是灾难级的,特别是那种“极数”和“极对数”搞混的情况。12极电机的极对数是6,不是12。填错之后电角度换算完全是错误的,轻则电机抖动,重则直接过流。用示波器看反电动势,数过一个电周期里的过零次数,就能准确得到极对数。
4.3 校准完成后正常,运行一段时间又乱跑
校好好好跑的,跑了几分钟突然失灵,这种问题分两类:一类是累计误差,一类是单圈基准被破坏。
累计误差的根源是增量式编码器没有绝对位置记忆,长时间运行如果有些脉冲被干扰漏掉,位置误差会越积越大。解决办法:利用Z信号,每圈到Z的时候把当前计数校正回预期值,或者直接把计数器清零。Z信号在这里就是“每圈一次的位置锚点”。
单圈基准被破坏,比如Z信号本身受干扰,或者初始化时Z沿被漏捕。我习惯在初始化里专门加一个“等待Z并校正zero_count”的流程:让电机低速转一圈,捕获Z信号触发点,把zero_count修正为进入Z时记录的计数,这样即使前面校的零位有一点偏差,也能被Z信号拉回来。
另外,如果你用的是磁编码器,温度漂移也可能导致位置缓慢漂移。这时候校准值只是“常温下的零位”,温度变了就有偏移。工程上只能选温漂小的器件,或者定期重新校准。这里也附上排查速查表,方便对照:
| 现象 | 可能原因 | 处理办法 |
|---|---|---|
| 零位值每次上电不一致 | 校准电流太小、Z干扰、机械松动 | 加大校准电流、滤波、紧固 |
| 电流大、力矩小、抖动 | 零位偏差大、方向反、极对数错 | 重新精调、换向、确认极对数 |
| 运行一段时间乱跑 | 累计误差、Z基准被破坏、温漂 | 用Z校正、加滤波、选低温漂器件 |
5. 一点个人经验总结
5.1 我的校准习惯:上电自检 + Flash记忆
这几年调下来,我觉得增量式编码器零位校准这件事,最容易被忽略但其实最影响效果的就是一个字:稳。电压要稳,电流要稳,转子停稳了再读数。很多看起来是控制算法的问题,最后查来查去都是校准没做好。
我现在的习惯是:在初始化代码里内置一个自动寻零函数,上电先跑一遍方法二,校准值同时存到RAM和Flash。如果机械结构没动过,下次上电直接读Flash里的zero_count用;如果换过电机、拆过联轴器,就重新校准。这样既保证了精度,又兼顾了产线效率。
5.2 别忽略低速抖动背后的真凶
最后再分享一个小技巧:如果校准之后低速运行还是有一顿一顿的感觉,别急着反复校零位,多半是电流采样噪声或者编码器线数不够导致的力矩波动。把采样滤波做好、编码器换高线数,比你在零位上来回折腾有效得多。
FOC这个东西,很多时候问题不是出在算法本身,而是出在“位置到底准不准”这个底层问题上。把零位校准这件事做扎实,后面调参、跑高速、带负载都会顺畅很多。