写 FOC 电机控制时,角度和速度这两个量就像控制器的“眼睛”。角度算不准,电流环解耦就是错的;速度算不准,速度环就是抖的。很多初学者拿到一套 STM32F4 的电机开发板,跟着教程调通了开环,一到闭环就出问题,很大概率是编码器这一环没吃透:TIM3 编码器模式怎么配?四倍频到底做了什么?机械角度和电角度怎么换算?速度又该怎么测?本文围绕这些核心问题,从编码器工作原理讲到 STM32F4 上的实战代码,全程使用 TIM3 编码器模式进行讲解,涵盖 CubeMX 配置、角度计算、M 法测速、零点对齐和常见坑点,适合正在学习 FOC 的开发者参考。
1. FOC 控制与编码器的关系
1.1 FOC 控制到底在控制什么
FOC,全称 Field-Oriented Control,中文常叫“磁场定向控制”或“矢量控制”。它的核心思路,是把三相交流电机内部的定子电流分解成两个正交的直流分量:一个负责产生磁场的“d 轴电流”,一个负责产生力矩的“q 轴电流”。通过这种解耦,三相永磁同步电机(PMSM)和无刷直流电机(BLDC)可以像直流电机一样,用电流环、速度环、位置环进行高性能控制。
这个解耦过程不是凭空完成的,它需要实时知道转子磁场的位置。因为 d 轴和 q 轴是建立在转子磁场方向上的坐标系,如果不知道转子当前位置,就无法把三相电流准确地投影到这个旋转坐标系中。在 FOC 算法里,从三相静止坐标系变换到两相静止坐标系是 Clarke 变换,从两相静止坐标系变换到旋转坐标系是 Park 变换。Park 变换需要输入一个角度,这个角度就是电角度,而电角度最终来源于编码器反馈的机械角度。
所以,FOC 控制系统中最基本的信息链路是:
编码器 → 机械角度 → 电角度 → Park 变换 / SVPWM → 三相电压输出只要编码器角度误差偏大,Park 变换出来的 d/q 轴电流就不再是“真”的 d/q 轴电流,电流环控制效果就会明显退化,严重时电机噪声大、发热高、甚至失步。
1.2 编码器在 FOC 中的三个关键作用
编码器在 FOC 系统里的作用,可以精简成三个词:角度反馈、速度反馈、相序判断。
角度反馈是给电流环用的。FOC 的电流环需要知道电角度,才能把采集到的相电流转换到旋转坐标系。速度反馈是给速度环用的。速度环通常位于电流环外层,它的输出是 q 轴指令电流,速度反馈精度直接影响转速稳态误差和动态响应。相序判断则体现在电机能不能正常启动上:如果编码器正反转方向和电机绕组相序不匹配,FOC 会直接反转或堵转,这是新手非常容易踩的坑。
值得注意的是,FOC 对编码器的最低分辨率要求并不算苛刻。只要电角度分辨率能到几度以内,速度环性能一般就能接受。但是,编码器反馈延迟、零点漂移和信号毛刺对性能的影响却是致命的,这也是本文后续要重点展开的部分。
1.3 正交增量式编码器的工作方式
电机控制中常用的编码器有增量式编码器、绝对值编码器和磁编码器。增量式编码器因为性价比高、结构简单,在小体积电机和低成本驱动方案中应用最广。
增量式编码器输出两路正交方波信号,一般称为 A 相和 B 相。电机每转一个单位角度,A 相和 B 相分别输出一定数量的方波脉冲,两路信号在相位上相差 90°。正是这个 90° 相位差,让控制器可以同时判断转速和转向:A 相超前 B 相 90° 时正转,B 相超前 A 相 90° 时反转。
除了 A 和 B 两相,很多增量式编码器还有 Z 相(零位信号)。Z 相每圈输出一个脉冲,用于确定绝对零位。在 FOC 项目中,Z 相可以作为编码器零点对齐的参考,但很多简化的驱动方案不使用 Z 相,而是通过软件开环对齐的方式确定零点,后面会详细说明。
2. 编码器线数、四倍频与 TIM 编码器模式
2.1 编码器线数与单圈分辨率
编码器“线数”这个词,指的是编码器每旋转一圈,A 相(或 B 相)输出的脉冲个数。常见的线数有 250 线、500 线、1000 线、2000 线、2500 线、4096 线等。需要注意,线数不等于最终的位置分辨率,因为控制器可以对 A、B 两路的上升沿和下降沿同时计数,从而把每圈分辨率提升到线数的 4 倍。
单圈分辨率 = 编码器线数 × 4
这个“×4”的过程,就是大家常说的“四倍频”。例如一个 1000 线编码器,四倍频后每圈可以产生 4000 个计数脉冲。对 7 对极电机来说,机械角度每变化 0.09°,电角度就变化 0.63°,足以满足大部分 FOC 应用。
2.2 四倍频原理
四倍频的原理并不复杂。A、B 两路正交信号在一个完整周期内有 4 个电平跳变边沿:A 上升沿、A 下降沿、B 上升沿、B 下降沿。如果控制器对这 4 个边沿都进行计数,那么每个编码器线数周期就能计 4 个数,相当于把分辨率放大到原来的 4 倍。
实际判断转向时,控制器需要在这 4 个跳变边沿上检测另一路信号的电平状态。例如在 A 相上升沿时检测 B 相是高电平还是低电平,B 相电平决定当前是正转还是反转。这个过程如果用纯 GPIO 中断来实现也可以,但会占用大量 CPU 资源,因为高速旋转时中断频率很高。更好的办法是利用 STM32 定时器自带的编码器接口硬件,自动完成方向判断和计数。
2.3 STM32F4 定时器编码器模式为何适合四倍频
STM32F4 系列的大多数高级和通用定时器都支持编码器接口模式。定时器把这个本来需要逐边沿判断逻辑的活儿集成进了硬件,CPU 只需要定期读取计数器寄存器TIMx->CNT就能获得带方向的位置信息。
在编码器接口模式下,定时器的 CH1 和 CH2 分别对应编码器的 A、B 相。定时器可以配置为三种计数模式之一:
- TI1 模式:只在 A 相边沿计数,相当于二倍频;
- TI2 模式:只在 B 相边沿计数,相当于二倍频;
- TI1 + TI2 模式:在 A、B 两相的所有边沿计数,相当于四倍频。
FOC 项目中使用最广的就是 TI1 + TI2 模式,也就是 CubeMX 里的Encoder Mode: TI1 and TI2。硬件根据两路信号的相位关系自动决定计数器加计数还是减计数,软件完全不需要额外判断方向。
在 STM32F4 中,TIM3 的 CH1 和 CH2 通常映射到 PA6 和 PA7,这是非常常用的编码器输入引脚。不过不同封装、不同型号引脚可能不同,使用前需要查阅对应芯片的数据手册或 CubeMX 的引脚分配。
2.4 TIM3 编码器模式的 CubeMX 配置
下面以 STM32F407VET6 为例,演示 CubeMX 中 TIM3 编码器模式的具体配置步骤。
第一步,在 Pinout & Configuration 视图中选中 TIM3,将 Channel1 和 Channel2 的模式都设置为Encoder Mode。这一步很关键:Channel1 和 Channel2 必须同时配置为 Encoder Mode,否则编码器模式不会生效。
第二步,在 TIM3 的 Parameter Settings 中设置以下参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Prescaler | 0 | 编码器模式要求预分频为 0,否则丢脉冲 |
| Counter Mode | 由硬件自动处理 | 编码器模式下方向由硬件判断 |
| Counter Period | 65535 | 16 位定时器最大计数值,充分利用计数范围 |
| Encoder Mode | TI1 and TI2 | 四倍频工作模式 |
| IC1/IC2 Filter | 0 或 10 | 数字输入滤波,抗毛刺;高速时建议 0 |
IC 滤波器是一个值得关注的参数。如果电机 PWM 对编码器信号造成干扰,可以通过增大输入滤波值来滤除毛刺。但滤波会引入信号延迟,编码器频率越高,延迟造成的影响越明显。因此实际项目中,滤波值需要根据编码器输出频率和现场干扰情况综合调整。
第三步,生成代码后,在用户代码中调用:
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); __HAL_TIM_SET_COUNTER(&htim3, 0);HAL_TIM_Encoder_Start的作用是使能 TIM3 的编码器接口,TIM_CHANNEL_ALL表示两个通道同时使能。第二行把计数器清零,保证角度计算从 0 开始。
3. 角度计算:机械角度与电角度
3.1 机械角度的获取
编码器计数器的值本身并不直接等于角度,需要结合编码器四倍频后的单圈总脉冲数进行换算。机械角度的换算公式如下:
机械角度 = 计数值 / 单圈总脉冲数 × 360°其中单圈总脉冲数 = 编码器线数 × 4。假如编码器是 1000 线,四倍频后每圈 4000 个脉冲,那么计数器的值与角度的对应关系就是每 1 个脉冲对应 0.09° 机械角度。
实际编码器安装后,机械零点一般不是 0°。也就是说,当电机处于我们认定的机械零位时,计数器不一定等于 0。因此在软件上通常要定义一个零点偏移量zero_offset,每次读取实际角度后先减去这个偏移量,再做换算。
下面是一个通用性较好的机械角度读取函数:
// 文件路径:Core/Inc/encoder.h #ifndef ENCODER_H #define ENCODER_H #include "main.h" #define ENCODER_LINES 1000u // 编码器线数 #define ENCODER_PPR (4u * ENCODER_LINES) // 四倍频后每圈脉冲数 #define MOTOR_POLE_PAIRS 7u // 电机极对数 float Encoder_GetMechanicalAngle_Deg(void); float Encoder_GetElectricalAngle_Deg(void); float Encoder_GetSpeed_RPM(void); void Encoder_AlignToZero(float electrical_angle_deg); #endif// 文件路径:Core/Src/encoder.c #include "encoder.h" static int32_t s_zero_offset = 0; float Encoder_GetMechanicalAngle_Deg(void) { // 使用 int16_t 处理有符号角度,兼容正反转 int16_t cnt = (int16_t)(__HAL_TIM_GET_COUNTER(&htim3) - s_zero_offset); float deg = (float)cnt * 360.0f / (float)ENCODER_PPR; if (deg < 0.0f) { deg += 360.0f; } return deg; }这里使用(int16_t)强转的目的是把计数器的无符号形式转成有符号形式,从而让反向旋转时角度呈现负值,便于后续归一化。当然,这种写法成立的前提是ENCODER_PPR小于 32768,即单圈总脉冲数不能超过int16_t的表示范围。常见 1000 线、2000 线、2500 线、4096 线编码器四倍频后为 4000~16384,符合这个条件。
3.2 电机极对数与电角度
机械角度是转子实际转过的物理角度,但在 FOC 算法中,Park 变换需要的不是机械角度,而是电角度。电角度和机械角度之间相差一个“极对数”的倍数:
电角度 = 机械角度 × 极对数“极对数”这个概念新手容易混淆。一个电机的极数是 N,那么极对数就是 N / 2。例如一个 14 极电机,极对数是 7。每对磁极在空间上对应 360° 电角度。转子每旋转一圈,定子磁场经历 7 个完整的电周期,也就是电角度变化了 7 × 360°。
极对数是 FOC 参数配置中最容易出错的点之一。极对数填错时,电机电流环虽然看起来在运行,但输出力矩会明显波动,电机噪声大、效率低,严重时转子根本无法平稳旋转。如果电机是向厂家定制或自己绕制的,最好通过电机说明书或反电动势波形确认极对数,不要依赖猜测。
3.3 电角度的归一化处理
由于电角度是机械角度的多倍,计算得到的数值经常会超过 360°。而角度本身是周期性的,超过 360° 需要映射回 0°~360° 范围,否则后续的 sin/cos 计算会浪费精度。归一化可以使用fmodf实现:
float Encoder_GetElectricalAngle_Deg(void) { float elec_deg = Encoder_GetMechanicalAngle_Deg() * (float)MOTOR_POLE_PAIRS; elec_deg = fmodf(elec_deg, 360.0f); if (elec_deg < 0.0f) { elec_deg += 360.0f; } return elec_deg; }有些工程习惯把电角度换算成弧度再传给 FOC 算法,两种方式等价。推荐的实践是:底层驱动统一输出角度值,FOC 算法内部决定用角度还是弧度。这样分层清晰,也方便调试。
3.4 编码器零点与 FOC 零点的对齐
机械角度到电角度的换算公式成立的前提,是编码器零点与电机反电动势零点一致。但实际安装时,编码器机械零位通常和电机电气零位没有必然关系。如果不做零点对齐,计算出的电角度会有固定偏移,FOC 就无法稳定工作。
工程中最常用的对齐方法是“开环强制对齐”:
- 给电机施加一个已知方向和大小的电流矢量,例如让 q 轴电流为固定值,d 轴电流为 0。
- 等待转子稳定锁死,此时转子会被拉到电角度某个固定位置。
- 读取当前编码器计数值,把它作为零点偏移量保存下来。
代码示例思路如下:
void Encoder_AlignToZero(float electrical_angle_deg) { // 1. 开环控制电机,让转子锁到指定电角度 // 具体实现和电机驱动相关,例如: // Motor_OpenLoopSetElectricalAngle(electrical_angle_deg, 0.2f); HAL_Delay(300); // 等待转子稳定 // 2. 读取当前计数器值,作为零点偏移 s_zero_offset = __HAL_TIM_GET_COUNTER(&htim3); }为了消除电流力矩波动造成的误差,可以在对齐过程中采集多次计数值取平均。更好的做法是分别对齐到多个不同电角度,然后根据多组数据进行拟合,不过对小项目来说,一次平均已经足够。
4. 转速计算:M 法测速实战
4.1 M 法测速原理
FOC 速度环需要实时转速反馈。最常用且最容易实现的方法是 M 法测速,也叫“测频法”。M 法的原理是:在固定的时间窗口 T 内,统计编码器计数器的变化量 ΔN,然后换算成转速。
转速计算公式如下:
转速(转/秒)= ΔN / 单圈总脉冲数 / T 转速(转/分)= 转速(转/秒) × 60M 法在高速段精度很好,因为单位时间内脉冲数多,量化误差小。但在低速段,如果时间窗口内只有几个脉冲甚至没有脉冲,速度更新就会变得迟钝,精度也差。对于 FOC 控制,通常速度环执行周期就是 1ms~10ms,M 法已经能满足大部分中高速应用。
4.2 核心代码实现
下面是通过 M 法实现测速的完整示例,核心思路是每次调用时读取当前计数值,和上一次计算差值,再除以两次调用的时间间隔:
float Encoder_GetSpeed_RPM(void) { static int16_t s_last_cnt = 0; static uint32_t s_last_tick = 0; int16_t cur_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim3); uint32_t cur_tick = HAL_GetTick(); int16_t diff = (int16_t)(cur_cnt - s_last_cnt); uint32_t dt = cur_tick - s_last_tick; s_last_cnt = cur_cnt; s_last_tick = cur_tick; if (dt == 0) { return 0.0f; } // 每秒转速 = diff / ENCODER_PPR / (dt / 1000) float speed_rps = (float)diff / (float)ENCODER_PPR * 1000.0f / (float)dt; return speed_rps * 60.0f; }这里有一个细节值得注意:__HAL_TIM_GET_COUNTER(&htim3)返回的是uint16_t类型,把它强转成int16_t后,即使计数器发生了回绕,差值diff依然能正确表示真实脉冲变化。这个技巧在处理 16 位定时器回绕时非常关键,也是新手最容易写错的地方。
如果直接使用无符号相减再转浮点,会遇到两个问题:一是反向旋转时差值是负数,无符号运算会得到一个很大的正数;二是正转跨越 65535 回绕到 0 时,直接相减会得到一个负的脉冲差。使用int16_t强转后这两个问题都能自动解决,因为它相当于把计数器映射到了 -32768~32767 的环形空间。
4.3 溢出与方向处理
M 法测速的溢出问题,核心要点是确保相邻两次采样之间计数器的变化不超过 32768 个脉冲。大多数情况下这个条件都成立,因为 32768 个脉冲意味着电机在 1ms 内至少转了好几圈,已经远超一般电机应用范围。
还需要注意符号问题。diff大于 0 表示正转,小于 0 表示反转。转速函数直接返回带符号的 RPM 值,速度环可以据此判断当前是在加速还是减速、正转还是反转。
4.4 速度滤波
原始转速数据往往带有量化噪声,直接送速度环会导致电流指令波动,进而产生明显的电机噪声。常用做法是在速度环最外层加一阶低通滤波:
#define SPEED_LPF_ALPHA 0.1f float Speed_LowPassFilter(float raw_speed) { static float filtered_speed = 0.0f; filtered_speed += (raw_speed - filtered_speed) * SPEED_LPF_ALPHA; return filtered_speed; }这个滤波器的系数SPEED_LPF_ALPHA需要根据控制周期和期望的截止频率调整。系数越小,滤波越强,但动态响应也越慢。实际调试中可以从 0.05~0.2 之间尝试,观察速度环阶跃响应后再微调。
5. 完整工程示例与运行验证
5.1 工程目录结构
下面给出一个基于 STM32CubeMX 生成的 Keil 5 工程示例,目录结构如下:
Project/ ├── Core/ │ ├── Inc/ │ │ ├── encoder.h │ │ ├── main.h │ │ └── usart.h │ └── Src/ │ ├── encoder.c │ ├── main.c │ ├── usart.c │ └── system_stm32f4xx.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F4xx_HAL_Driver/ └── MDK-ARM/ ├── Project.uvprojx └── ...这个工程里,TIM3 用于编码器输入,USART1 用于串口打印调试信息。
5.2 编码器驱动完整代码
为了让示例更完整,把编码器驱动合并为一个模块。文件encoder.c包含初始化、机械角度、电角度、转速读取和零点对齐函数。
#include "encoder.h" static int32_t s_zero_offset = 0; void Encoder_Start(void) { HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); __HAL_TIM_SET_COUNTER(&htim3, 0); s_zero_offset = 0; } float Encoder_GetMechanicalAngle_Deg(void) { int16_t cnt = (int16_t)(__HAL_TIM_GET_COUNTER(&htim3) - s_zero_offset); float deg = (float)cnt * 360.0f / (float)ENCODER_PPR; if (deg < 0.0f) { deg += 360.0f; } return deg; } float Encoder_GetElectricalAngle_Deg(void) { float elec_deg = Encoder_GetMechanicalAngle_Deg() * (float)MOTOR_POLE_PAIRS; elec_deg = fmodf(elec_deg, 360.0f); if (elec_deg < 0.0f) { elec_deg += 360.0f; } return elec_deg; } float Encoder_GetSpeed_RPM(void) { static int16_t s_last_cnt = 0; static uint32_t s_last_tick = 0; int16_t cur_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim3); uint32_t cur_tick = HAL_GetTick(); int16_t diff = (int16_t)(cur_cnt - s_last_cnt); uint32_t dt = cur_tick - s_last_tick; s_last_cnt = cur_cnt; s_last_tick = cur_tick; if (dt == 0) { return 0.0f; } float speed_rps = (float)diff / (float)ENCODER_PPR * 1000.0f / (float)dt; return speed_rps * 60.0f; } void Encoder_AlignToZero(float electrical_angle_deg) { // 开环给电机施加指定电角度电流矢量,使转子稳定到该位置 // Motor_SetOpenLoopCurrent(electrical_angle_deg, 0.3f); HAL_Delay(300); s_zero_offset = __HAL_TIM_GET_COUNTER(&htim3); }Encoder_AlignToZero中的电机开环控制部分和具体驱动强相关,这里使用注释占位。实际项目中,需要把电机控制代码接入这个位置。
5.3 主循环逻辑
主函数中完成系统初始化和编码器启动后,循环读取角度和转速,并通过串口输出。
int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); MX_TIM3_Init(); Encoder_Start(); while (1) { float mech_deg = Encoder_GetMechanicalAngle_Deg(); float elec_deg = Encoder_GetElectricalAngle_Deg(); float speed_rpm = Encoder_GetSpeed_RPM(); printf("Mech: %.2f deg, Elec: %.2f deg, Speed: %.1f RPM\r\n", mech_deg, elec_deg, speed_rpm); HAL_Delay(10); } }如果使用串口打印,需要确保在usart.c中已经重定向fputc函数,这样printf才能将格式化字符串输出到串口。这属于基础配置,不再展开。
5.4 预期输出
手动缓慢转动电机轴时,串口输出大致如下:
Mech: 12.34 deg, Elec: 86.38 deg, Speed: 12.5 RPM Mech: 15.67 deg, Elec: 109.69 deg, Speed: 18.2 RPM Mech: 20.01 deg, Elec: 140.07 deg, Speed: 25.6 RPM观察电角度可以发现,机械角度增长 1°,电角度增长 7°(本例极对数为 7),符合电角度等于机械角度乘极对数的规律。如果出现角度跳变、速度抖动或计数方向相反等问题,可以对照下一节的排查表处理。
6. 常见问题与排查思路
编码器调试过程中遇到的问题,很多都有固定的排查模式。下面整理了一份实际问题清单,方便对照处理。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| TIM3 计数器一直为 0 | 编码器 AB 信号未接入;TIM3 通道未配置为 Encoder Mode | 用示波器检查 PA6/PA7 波形;CubeMX 中确认 CH1/CH2 均配置为 Encoder Mode |
| 电机正转但计数方向相反 | A、B 相接反 | 交换 A、B 接线,或者修改 IC1Polarity/IC2Polarity |
| 角度跳变到负值 | 零点偏移未处理;ENCODER_PPR超过 int16_t 范围 | 增加零点偏移;换用 32 位计数方案 |
| 转速数值在 0 附近来回跳 | 编码器信号毛刺;机械抖动;测速周期过短 | 增大 IC 输入滤波值;增加速度低通滤波;适当延长测速周期 |
| FOC 电流抖动、电机噪声大 | 极对数设置错误;零点偏移未校准确 | 重新确认极对数;重做零点对齐并观察电角度波形 |
| 高速时角度误差突然变大 | 编码器信号频率超过定时器计数能力;输入滤波延迟过大 | 计算最高编码器频率 = 线数 × 4 × 最高转速(RPS);减小输入滤波值;确认 TIM 时钟配置 |
| Keil5 烧录程序 STM32F4 失败 | 设备型号选错;Flash 下载算法缺失;SWD 接线问题 | 在 Target Options 中选择对应芯片型号;在 Flash Download 中添加 STM32F4xx Flash 算法;检查 SWDIO/SWCLK 接线 |
还有一个值得特别提示的坑:编码器信号属于高速方波信号,和电机驱动桥的高压、大电流回路在布局上要尽量分离。如果布线不规范,编码器信号很容易被 PWM 噪声干扰,导致计数毛刺,这类问题通过软件滤波只能缓解,不能根治,最好从硬件上解决。
7. 工程最佳实践与后续建议
7.1 编码器与电机选型的搭配
编码器线数并不是越高越好。线数越高,单圈分辨率越高,但信号频率也越高,对输入滤波、信号隔离、导线屏蔽的要求就相应提升。对于中小功率 BLDC/PMSM 的 FOC 驱动,1000 线到 2500 线是比较常见的选型范围。如果电机空间有限、又希望上电就能知道绝对位置,也可以考虑 SPI/I2C 接口的磁编码器,它们的安装方式和使用逻辑与正交增量式编码器差异很大,需要单独适配。
7.2 零点标定流程
零点标定建议放在整机出厂校准流程中,而不是每次上电都做。常见的工程做法是:
- 上电后首先开环给一个固定电角度电流矢量,转子稳定后记录编码器偏移。
- 将偏移写入 Flash 或 EEPROM,下次开机直接加载。
- 如果标定过程中发现编码器安装位置发生机械变化,需要重新标定。
零点偏移和电角度计算是两个独立的问题:偏移解决的是“零点在哪”,极对数解决的是“角度放大多少倍”。把这两个参数分离管理,会减少很多调试困惑。
7.3 从开环到闭环的调试路径
如果你正在从零调 FOC,建议遵循“开环 → 角度 → 电流 → 速度”的顺序逐步推进。
第一步,先让电机开环旋转,确认驱动板和电机本体没问题。第二步,把编码器角度读准,用手转动电机,用示波器或上位机观察机械角度与电角度波形是否平滑。第三步,接入电流环,重点看 d/q 轴电流是否能解耦,是否出现高频振荡。第四步,再加速度环,此时编码器测速的稳定性和速度滤波时间常数会明显影响系统表现。
建议在每一步都加上控制变量:改动一个参数后只观察一个指标,不要同时调整多个参数,否则出了问题难以定位。
7.4 后续可以继续深入的方向
本文使用 TIM3 编码器模式完成了四倍频、机械角度、电角度和 M 法测速,这是 FOC 中编码器应用的核心闭环。接下来如果你时间充裕,可以继续研究 T 法测速和 M/T 法测速,它们在低速段的性能会更好;也可以研究基于观测器的无感 FOC 方案,其中滑模观测器和龙贝格观测器是常见的两条路线。不过无论走哪条路线,角度和速度的可信度永远是第一位的。先把编码器反馈做扎实,后面的控制算法才谈得上性能。