手写STM32 FOC记录-----添加编码器电机开环转动
1、硬件配置
硬件处理:ST官方开发板X-NUCLEO-IHM16M1,由于电机驱动使用的是STSPIN830,它在硬件上做了限制,不能使用互补PWM控制电机,需要将MODE 置为高,具体操作是拿掉开发板上的R2 电阻,让MODE引脚强制置高


2、添加磁编码器
完成以上这些步骤,就可以接上电机,驱动电机开环转动。在此之前,需要先添加编码器同步电机转动电角度。我使用的电机是自己手攒的,编码器是磁编码器,是我从其他电机上抠下来的,具体型号是SC60224,它可以是PWM的输出方式,使用定时器的PWM输入捕获方式,获取磁编的角度。编码器的PWM输出时序图如下:

CUBEMX配置TIM3的CH1的PWM输入捕获模式,具体方法如下:
先配置通道

再配置时间和中断触发方式

然后配置输入PWM输入IO口为PA6;

最后记得打开TIM3定时器中断,生成代码。
这里简单说明下PWM输入捕获流程:
1. PWM信号由TIMx_CH1进入,依据是上升沿还是是下降沿从而触发TI1FP1或TI1FP2。
2. 当捕捉到第一次上升沿时,触发TI1FP1,计数器复位,计数值CNT清零。
3. 当捕捉到下降沿时,触发TI1FP2,CNT计数值在TIMx_CH2中被记录到TIMx_CCR2寄存器中。
4. 当再次捕捉到上升沿时,触发TI1FP1,CNT计数值在TIMx_CH1中被记录到TIMx_CCR1寄存器中,同时计数器复位,计数值清零。
5. 依据TIMx_CCR1、TIMx_CCR2的值便可以计算PWM的周期、频率、占空比。TIMx_CCR1的值就是周期, TIMxCCR1/TIMxCCR2的值就是占空比。
生成代码之后,编写初始化函数PWM_encoder_init,主函数调用


再添加中断回调函数,中断回调中计算PWM周期,频率和占空比。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM3)
{
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_1)
{
pwm_encoder.period=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
pwm_encoder.frq=pwm_encoder.period*0.1f;
pwm_encoder.duty=((float)pwm_encoder.high_time/(float)pwm_encoder.period);
}
if(htim->Channel==HAL_TIM_ACTIVE_CHANNEL_2)
{
pwm_encoder.high_time=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
}
}
}
3、测试编码器数据
编写编码器角度测试函数,主函数调用,编译下载,并使用vofa+输出波形,看看波形是否随着电机转动而变化:

void test_PWM_encoder(void)
{
float theta = 0;
DQ_Def test_dq;
AlphaBeta_Def test_ab;
SVPWM_Def svpwm_out;
test_dq.d = 0.0f;
test_dq.q = 0.2f;
for (theta = 0; theta < 6.2831853f; theta += 0.275f)
{
pwm_encoder.angle = pwm_encoder.duty * 360.0f; //计算角度0~360
pwm_encoder.angle_rad = pwm_encoder.duty*2*PI; //计算角度,弧度制
vofa_JustFloat_output(pwm_encoder.angle,pwm_encoder.angle_rad,0.0f,0.0f);
inverseParkTransform(&test_dq,&test_ab,theta);
SVPWM(&test_ab,&svpwm_out); //电机会转动
}
}

说明下,如果theta 是增加则表明电机正转,角度输出斜率应该是正,反之为负。若对应不上,有一个简单办法,就是倒一下UVW三相中的任意两相就可以。
4、电角度零点与机械角度零点对应
首先明确一个公式,电角度=机械角度×极对数。光有这个公式还不够,需要保证FOC电角度零点跟机械角度零点对应,才能保证电机正常启动。
要找机械角度零点比较简单,读取编码器的值为0时,就是机械零点,但是寻找电角度零点稍微复杂点,需要的操作步骤是,首先,给定ud= 0.5,uq=0,注意这里是D轴给电流,而Q轴没有电流,然后指定theta = 0,该角度给到SVPWM中,此时电机会上劲,停到的位置点即为电角度零点,最后,读取机械角度零点,此时FOC电角度零点跟机械角度零点对应上了。具体代码编写一个Motor_Align预定位找零点的函数如下:

void Motor_Align(float ud) //电机预定位,找电角度零点
{
float theta = 0;
DQ_Def align_dq;
AlphaBeta_Def align_ab;
SVPWM_Def svpwm_out;
align_dq.d = ud;
align_dq.q = 0.0f;
theta = 0;
inverseParkTransform(&align_dq,&align_ab,theta);
SVPWM(&align_ab,&svpwm_out); //电机停到电角度零点
pwm_encoder.angle_rad_offset = pwm_encoder.duty*2*PI;//获取当前机械角度
}
可以通过debug的方式找到电角度零点之后,填入pwm_encoder.angle_rad_offset,需要在角度获取函数中,算电角度时,减去这个零点偏差

//获取编码器的机械角度,真实的物理角度
//获取电角度,FOC电角度
void Get_PWM_Encoder_Angles(void)
{
pwm_encoder.angle = pwm_encoder.duty * 360.0f;
pwm_encoder.angle_rad = pwm_encoder.duty*2*PI;
pwm_encoder.angle_rad_offset = 6.045f; //手动找到的零点
if(pwm_encoder.angle_rad>=pwm_encoder.angle_rad_offset) //减去零点偏置
{
pwm_encoder.angle_rad = pwm_encoder.angle_rad - pwm_encoder.angle_rad_offset;
}
else
{
pwm_encoder.angle_rad = 2*PI - pwm_encoder.angle_rad_offset + pwm_encoder.angle_rad;
}
pwm_encoder.electronic_angle = pwm_encoder.angle_rad*MOTOR_POLE;
}
当然,如果是其他接口的编码器,比如SPI或这IIC接口,这些编码器都可以手动写零点位置,这样操作起来就比较简单,找到电角度之后,直接在当前位置写磁编码的零点就好了。
5、电机开环转动
FOC电角度准确了,驱动电机转动就很简单了,实时将获取到的电角度传到SVPWM中,输出PWM即可驱动电机转动

void motor_open_loop_control(float uq) //开环控制电机
{
float theta = 0.0f;
DQ_Def open_loop_dq;
AlphaBeta_Def open_loop_ab;
SVPWM_Def svpwm_out;
open_loop_dq.d = 0.0f;
open_loop_dq.q = uq;
Get_PWM_Encoder_Angles(); //获取电角度
theta = pwm_encoder.electronic_angle;
inverseParkTransform(&open_loop_dq,&open_loop_ab,theta);
SVPWM(&open_loop_ab,&svpwm_out);
}
在主函数调用motor_open_loop_control接口函数,编译下载到硬件平台中,电机就开环转动起来了。
观看视频效果请转至https://www.bilibili.com/video/BV1WN411i7va/?spm_id_from=333.999.0.0&vd_source=4c57166c6142504cc135c2135bf9844e