手搓FOC-AS5600强拖对齐与电零位标定
STM32 手搓 FOC:AS5600 磁编码器强拖对齐与电零位标定实测
标签:STM32、STM32F103、FOC、AS5600、磁编码器、SPWM、无刷电机、嵌入式
仓库:https://github.com/kaka12331/stm32-simple-foc-as5600
这篇只讲一件事:对齐
FOC 的文章我写过几篇,Clark / Park 变换、SVPWM 扇区判断、马鞍波,理论部分那边讲透了:
- 深入浅出死磕 FOC 算法,从理论到实战全攻略
- STM32 纯手写 FOC:空间变换、SVPWM 与相电流采样
这篇一个公式都不重复。只讲一个所有 FOC 教程都会一笔带过、但你不做对电机就绝对不转的步骤:
编码器读到的角度,和电机的电角度零点,怎么对上。
你把 AS5600 磁铁贴到转子上,读回来 0~4095 的数。但这个 0 落在电机的哪一相上?编码器数值增大对应电机正转还是反转?这两个问题不解决,你后面所有的 Park 变换都是在拿错误的角度做运算。
先说清定位:这是一个理解对齐与开环力矩跟随的教学工程,不是带电流环 / 速度环的伺服方案。没有相电流采样,抗不了大扰动。想要完整方案看上面两篇。
硬件与时基
| 项目 | 配置 |
|---|---|
| 主控 | STM32F103 |
| 三相 PWM | TIM1 CH1/2/3 → PA8 / PA9 / PA10,复用推挽 |
| PWM 频率 | 24 kHz(Prescaler = 3-1,ARR = 1000) |
| 磁编码器 | AS5600,软件 I²C,12 bit(0~4095) |
| 控制中断 | TIM2,1 ms(Prescaler = 72-1,Period = 1000-1) |
| 极对数 | MOTOR_PP = 7 |
#definePWM_PERIOD1000// TIM1 ARR#defineMOTOR_PP7// 电机极对数,按你的电机改floattarget_voltage=0.4f;// 运行时的开环驱动电压int8_tmotor_direction=1;// 1 正转 / -1 反转,对齐时自动测出MOTOR_PP必须改成你自己电机的极对数。云台电机常见 7 或 11,写错的表现是:电机能转但转速和你预期差好几倍,或者剧烈抖动。数一下定子槽和磁钢,或者查电机手册。
一、调制方式:这里是 SPWM,不是 SVPWM
先讲清楚,免得和我另一篇混淆。Set_Phase_Voltage()用的是正弦调制(SPWM):
voidSet_Phase_Voltage(floatangle,floatvoltage){floatU_max=12.0f;// 母线电压floatdc_coefficient=(voltage/U_max);if(dc_coefficient>0.5f)dc_coefficient=0.5f;// 安全限幅uint16_tpwm_a=(uint16_t)((sinf(angle)+1.0f)*0.5f*dc_coefficient*PWM_PERIOD);uint16_tpwm_b=(uint16_t)((sinf(angle+2.094395f)+1.0f)*0.5f*dc_coefficient*PWM_PERIOD);uint16_tpwm_c=(uint16_t)((sinf(angle-2.094395f)+1.0f)*0.5f*dc_coefficient*PWM_PERIOD);TIM1->CCR1=pwm_a;TIM1->CCR2=pwm_b;TIM1->CCR3=pwm_c;}2.094395就是 2π/3,也就是 120° 相位差。(sin + 1) * 0.5把 [-1, 1] 映射到 [0, 1] 的占空比。
SPWM 比 SVPWM 少榨出约 15% 的母线电压利用率,但它的好处是——一眼就能看懂,没有扇区判断、没有矢量合成。做对齐验证的时候,你要排查的变量越少越好。等对齐这一步跑通了,把这个函数换成 SVPWM 是独立的一步,不影响前面的标定逻辑。
U_max = 12.0f是硬编码的母线电压假设,你的供电不是 12 V 就要改,否则算出来的占空比和实际电压对不上。
那句if (dc_coefficient > 0.5f) dc_coefficient = 0.5f是安全限幅:开环状态下没有电流反馈,占空比给大了电机就是纯粹的堵转发热。
二、强拖对齐:四步测出方向和零位偏置
核心就是这个函数,上电只跑一次:
voidFOC_Align_Sensor(void){// 步骤 A: 强行拉到电角度 0 度Set_Phase_Voltage(0.0f,2.5f);Delay_ms(800);uint16_tstart_raw=AS5600_Read_RawAngle();// 步骤 B: 强行拉到电角度 90 度Set_Phase_Voltage(PI/2.0f,2.5f);Delay_ms(800);uint16_tend_raw=AS5600_Read_RawAngle();// 释放电压Set_Phase_Voltage(0.0f,0.0f);// 步骤 C: 判断方向if(end_raw>start_raw){motor_direction=1;}else{motor_direction=-1;}// 步骤 D: 计算零位偏置floatstart_mech_angle=((float)start_raw/4095.0f)*2.0f*PI;zero_angle_offset=start_mech_angle*MOTOR_PP*motor_direction;}为什么对齐用 2.5 V,运行只用 0.4 V
这是最容易被忽略、也最关键的一个细节。
对齐阶段需要「强拖」——不管转子当前在哪,都要用足够大的力矩把它硬拽到指定电角度,而且要拽得住、不能被摩擦力和齿槽转矩拖偏。2.5 V 是为了这个。
运行阶段只要维持一点保持力矩,0.4 V 就够。如果运行也给 2.5 V,电机会烫得没法碰。
配套的是那两个Delay_ms(800):给转子足够时间转过去并且停稳。太短的话转子还在振荡就去读编码器,读到的是振荡中间的某个值,零位偏置直接就错了。
方向判断的原理
从电角度 0° 拉到 90°,转子会跟着转过去。
- 如果编码器读数变大(
end_raw > start_raw),说明「电角度增加」对应「编码器读数增加」,两者同向,direction = 1 - 反之说明磁铁贴反了或者三相线序反了,
direction = -1
有了这个符号,后面就不用去物理上换线序了,软件里乘个负号就行。
零位偏置的含义
start_raw是「电角度 = 0 时编码器的读数」。把它换算成机械角、乘极对数,就得到了编码器零点和电角度零点之间的固定偏差。
之后每次算电角度,减掉它就对齐了:
elec_angle=mech_angle*MOTOR_PP*motor_direction-zero_angle_offset;三、1 ms 中断里的四行核心
对齐做完,运行期的逻辑短得出奇:
voidTIM2_IRQHandler(void){if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET){// 1. 读机械角encoder_raw=AS5600_Read_RawAngle();mech_angle=((float)encoder_raw/4095.0f)*2.0f*PI;// 2. 换算电角度(用上对齐测出的方向和偏置)elec_angle=mech_angle*MOTOR_PP*motor_direction-zero_angle_offset;// 3. 驱动角 = 当前电角度 + 90°floattarget_driving_angle=elec_angle+(float)motor_direction*(PI/2.0f);// 4. 输出Set_Phase_Voltage(target_driving_angle,target_voltage);TIM_ClearITPendingBit(TIM2,TIM_IT_Update);}}第 3 步的 +90° 就是 FOC 的全部精髓:磁场始终超前转子磁极 90° 电角度,此时产生的力矩最大。转子转到哪,驱动磁场就跟到哪,永远保持 90° 领先。
注意+90°也乘了motor_direction——方向反了的话,超前要变成滞后,否则就是负力矩,电机会往反方向死命顶。
这是开环跟随:只保证力矩方向对,不控制大小。没有电流采样,也就没有电流环。手去拧电机能感觉到明显的保持力,但抗不了大扰动,也没法精确定位。
四、主循环刷 OLED 为什么要降到 200 ms
while(1){mech_angle_deg=(uint32_t)((float)encoder_raw*360.0f/4095.0f);OLED_ShowNum(2,5,encoder_raw,4);// Raw:0~4095 原始值uint32_tshow_elec=(uint32_t)((elec_angle/(2.0f*PI))*360.0f)%360;OLED_ShowNum(3,6,show_elec,3);// Elec:电角度OLED_ShowNum(4,6,mech_angle_deg,3);// Phys:物理机械角Delay_ms(200);// 降低刷新频率,防止干扰主 CPU 效率}OLED 走软件 I²C,刷一次要好几毫秒。而 TIM2 中断每 1 ms 就要来一次,中断优先级又是 0(最高)。
如果主循环拼命刷屏,CPU 大部分时间耗在 I²C 上,中断虽然能抢占,但整体余量被吃光。200 ms 刷一次,人眼完全够看,CPU 也留出了余量。
调试时看这三个数就够了:
Raw转动电机应该在 0~4095 之间平滑变化,跳变说明磁铁没对准编码器中心或者距离太远Elec应该转一圈机械角变化 7 圈(极对数 = 7)Phys就是实打实的物理角度
复现与安全
- Keil 打开
Project.uvprojx,芯片STM32F103 - 先改
MOTOR_PP为你电机的极对数,改U_max为你的实际母线电压 - 确认 AS5600 的 I²C 引脚、三相 PWM 引脚与你的硬件一致
- 把
target_voltage先调到 0.2f 甚至更低,手扶牢电机再上电 - 上电后 OLED 显示
FOC Calibrating,能看到电机被拽两下,然后进入FOC Loop Open! - 电机应该有轻微的保持力矩
出问题怎么查
| 现象 | 检查 |
|---|---|
| 电机剧烈抖动 / 高频啸叫 | MOTOR_PP是否正确 |
| 电机反着顶 | 对齐时转子被卡住没转动,方向测错了——松开手重新上电 |
| 完全不转、只发烫 | 三相线序、驱动板 EN 使能、母线电压 |
Raw值跳变 | 磁铁与 AS5600 的距离和同心度(一般 0.5~3 mm) |
| 对齐后零位每次都不一样 | Delay_ms(800)太短,转子还没停稳 |
驱动板的使能和电流限制务必接好,开环状态下一旦三相直通,MOS 管几秒就报废。
这个工程的边界
说清楚它不能干什么,比吹它能干什么有用:
- 没有电流环,力矩大小不可控,只能控方向
- 没有速度环 / 位置环,不能做精确定位
- 开环抗扰差,大惯量负载会失步
- SPWM 而非 SVPWM,母线电压利用率低约 15%
它的价值在于:把「对齐」这一步单独拎出来跑通、看得见。OLED 上那三个数会告诉你编码器、电角度、机械角三者的关系。这一步踩实了,再往上加电流环才有意义——不然电流环调不出来你都不知道是环的问题还是角度就是错的。
相关文章
- FOC 理论全景:深入浅出死磕 FOC 算法
- SVPWM 与相电流采样:STM32 纯手写 FOC
- 电流采样的盲区问题:FOC 低侧采样与在线采样的「共模地狱」
仓库地址:https://github.com/kaka12331/stm32-simple-foc-as5600
标准外设库遵循其原协议。