无刷电机强拖程序解析

一、强拖概述​

强拖​(也称为开环拖动)是一种无刷电机(BLDC/PMSM)的无传感器启动方法或电机测试方法,主要用于电机启动或低速运行阶段,在没有位置传感器(如编码器、霍尔)的情况下,通过固定电压/频率比 (V/F) ​或者计算一个模拟角度,同时进行电流闭环 (I/F) 强制驱动电机旋转。

1.1 V/F 启动简介

V/F 启动,是保持电压 (V) 与频率 (F) 的比值恒定。并不需要控制器采集包括电机自身任何的外部信息,它仅需要设置目标电频率 (可以去我之前文章搜搜电频率是啥,目前可粗浅的将电频率看作为转速) 以生成虚拟的电机位置角度,和目标调制的电压即可运行,这里所谓的调制电压可以看作母线电压的百分比。

  • 优点:无需采集电流。
  • 缺点:负载突变时易失步,恢复速度慢,转速会随负载波动。

1.2 I/F 启动简介

I/F 启动,保持电流(I)与频率(F)的比值恒定。通过调节电流实现转矩控制,简单来说就是根据配置生成虚拟的电机位置角度,和 FOC 的电流闭环同时控制。

  • 优点:负载突变稳定,恒速。
  • 缺点:需实时电流检测。

1.3 区别

简单来说,V/F和I/F最大的区别是,I/F 增加了电流闭环,根据电流闭环生成调制 PWM 输出。而 V/F 的调制比是固定的开环控制。

二、代码介绍

2.1 较简单的强拖与切换无感程序 (量产过)

2.1.1 参数和变量

#define FORCE_START             0           // 强拖启动 切换时会有噪音
#define FORCE_ACC               (200)       // 强拖加速度//todo:转成物理单位量
#define FORCE_RPM_MAX           (500*23)    // 强拖最大速度(电速度)
#define FORCE_SPEED_MIN         (1000)      // 强拖介入速度(x/32768*100%)
#define FORCE_SWITCH            (50*23)     // 强拖切换速度(电速度) 周期的角度增量(dpp)

stru_force_start force_start = {
     // 加速步进的周期数
    .acc_plus_periods = FORCE_ACC,

     // 最大角速度(通过rpm2dpp宏将RPM转换为角度/周期)也就是每次调用自增的角度
    .thetadpp_max = rpm2dpp(FORCE_RPM_MAX)   
};

其中: 

#define rpm2dpp(rpm)       (((s32)rpm << 16) / (s16)PWM_FREQ / 60 )  // 将 RPM 转换为每 PWM 周期的角度增量(dpp)

1 rpm 为一转,也就是360°。对于这个代码框架来说 0~360° 映射到 0~65535,所以首先将 rpm 乘 65535,然后除 pwm 周期和 60。因为 rpm 是每分钟转速,我们需要算出每秒的转速。

最终得出 thetadpp_max 的值。

2.1.2 强拖主要程序

在以上的强拖逻辑中, force_start.thetadpp < force_start.thetadpp_max 时,需要先缓慢增加 force_start.thetadpp 的值,以模拟电机启动时转速放缓的情况。之后每个周期就按照 thetadpp_max 的值进行累加,以在强拖阶段达到我们的目标转速。

// 函数功能:处理强制启动阶段的电机控制
// 参数:s32_Angle - 霍尔传感器或编码器反馈的当前电机角度(单位:定点数或度)
void force_start_handle(m_s32 s32_Angle) 
{
    static u16 cnt = 0;  // 静态计数器,用于控制加速周期

    // 1. 检查MOS管状态(是否关闭)
    if (MOS_CLOSED()) 
    {
        force_start.theta = 0;      // 重置目标角度为0
        cnt = 0;                    // 重置计数器为0
        force_start.thetadpp = 0;   // 重置角速度为0
    }

    // 2. 加速控制逻辑
    if (++cnt >= force_start.acc_plus_periods) // 计数器递增并检查是否达到加速周期
    {
        cnt = 0;  // 重置计数器
        if (force_start.thetadpp < force_start.thetadpp_max) 
        {
            force_start.thetadpp++;  // 逐步增加角速度(直到达到最大值)
        }
    }

    // 3. 更新目标角度
    force_start.theta += force_start.thetadpp; // 累加角度值(模拟电机转动)

    // 4. 计算角度偏差
    force_start.angle_delta_flex = force_start.theta - flex.theta; // 目标角度与柔性启动角度的偏差
    force_start.angle_delta_hall = force_start.theta - s32_Angle;  // 目标角度与传感器反馈角度的偏差
}

这段代码中所生成的角度波形是这样的。在刚刚启动的时候,force_start.thetadpp 逐步自增,也就是下图中左侧部分一样,三角波形周期从长变短,模拟电机真实启动的过程。

因为使用的是 uint16_t 类型储存变量,所以生成的三角波会在 0-65535 区间。但是我们是按照有符号数读取的,那么就是 -32768 至 32767 了。

2.1.3 强拖角度补偿和强拖切换观测器

当 MOS 管处于关管状态时使 force_start.flag = 2,代表当前是强拖启动状态。

等 force_start.thetadpp > rpm2dpp(FORCE_SWITCH) 也就是强拖的转速达到指定值时对 force_start.iq_sub++ 也就是在此刻强加目标电流,以防止强拖失败。

if ((flex.theta - force_start.theta < 500) && (force_start.theta - flex.theta < 500)) 这个判断是强拖给出的角度和观测角度误差小于 500 就切换为观测角度。

void force_check_handle()
{
    // 检测反电动势电压是否低于最低速度阈值
    if (bemf.vol_pu < FORCE_SPEED_MIN)
    {
        // 检查MOS管是否关闭(电机未通电)
        if (MOS_CLOSED())
        {
            force_start.flag = 2;   // 进入强制启动状态2
            force_start.iq_sub = 0; // 重置Q轴电流补偿值为0
        }
    }
    // 检查当前是否处于强制启动状态2
    if (2 == force_start.flag)
    {
        // 检查当前角速度是否超过切换阈值(FORCE_SWITCH转换为dpp单位)
        if (force_start.thetadpp > rpm2dpp(FORCE_SWITCH))
        {
            // 计数器递增,每2次循环执行一次
            if (++cnt > 1)
            {
                cnt = 0; // 重置计数器
                // 增加Q轴电流补偿值(上限30000)
                if (++force_start.iq_sub > 30000)
                {
                    force_start.iq_sub = 30000; // 限制最大值
                }
            }
            // 检查虚拟角度与实际角度偏差是否小于500(单位可能是Q16)
            if ((flex.theta - force_start.theta < 500) &&
                (force_start.theta - flex.theta < 500))
            {
                force_start.flag = 0; // 退出强制启动状态
            }
        }
    }
}

 2.1.4 强拖程序完整代码

#define rpm2dpp(rpm) (((s32)rpm << 16) / (s16)PWM_FREQ / 60) // 将 RPM 转换为每 PWM 周期的角度增量(dpp)

#define FORCE_START 0            // 强拖启动 切换时会有噪音
#define FORCE_ACC (200)          // 强拖加速度//todo:转成物理单位量
#define FORCE_RPM_MAX (500 * 23) // 强拖最大速度(电速度)
#define FORCE_SPEED_MIN (1000)   // 强拖介入速度(x/32768*100%)
#define FORCE_SWITCH (50 * 23)   // 强拖切换速度(电速度) 周期的角度增量(dpp)

stru_force_start force_start = {
    // 加速步进的周期数
    .acc_plus_periods = FORCE_ACC,

    // 最大角速度(通过rpm2dpp宏将RPM转换为角度/周期)也就是每次调用自增的角度
    .thetadpp_max = rpm2dpp(FORCE_RPM_MAX)};

// 函数功能:处理强制启动阶段的电机控制
// 参数:s32_Angle - 霍尔传感器或编码器反馈的当前电机角度(单位:定点数或度)
void force_start_handle(m_s32 s32_Angle)
{
    static u16 cnt = 0; // 静态计数器,用于控制加速周期

    // 1. 检查MOS管状态(是否关闭)
    if (MOS_CLOSED())
    {
        force_start.theta = 0;    // 重置目标角度为0
        cnt = 0;                  // 重置计数器为0
        force_start.thetadpp = 0; // 重置角速度为0
    }

    // 2. 加速控制逻辑
    if (++cnt >= force_start.acc_plus_periods) // 计数器递增并检查是否达到加速周期
    {
        cnt = 0; // 重置计数器
        if (force_start.thetadpp < force_start.thetadpp_max)
        {
            force_start.thetadpp++; // 逐步增加角速度(直到达到最大值)
        }
    }

    // 3. 更新目标角度
    force_start.theta += force_start.thetadpp; // 累加角度值(模拟电机转动)

    // 4. 计算角度偏差
    force_start.angle_delta_flex = force_start.theta - flex.theta; // 目标角度与柔性启动角度的偏差
    force_start.angle_delta_hall = force_start.theta - s32_Angle;  // 目标角度与霍尔传感器反馈角度的偏差
}

void force_check_handle()
{
    // 检测反电动势电压是否低于最低速度阈值
    if (bemf.vol_pu < FORCE_SPEED_MIN)
    {
        // 检查MOS管是否关闭(电机未通电)
        if (MOS_CLOSED())
        {
            force_start.flag = 2;   // 进入强制启动状态2
            force_start.iq_sub = 0; // 重置Q轴电流补偿值为0
        }
    }
    // 检查当前是否处于强制启动状态2
    if (2 == force_start.flag)
    {
        // 检查当前角速度是否超过切换阈值(FORCE_SWITCH转换为dpp单位)
        if (force_start.thetadpp > rpm2dpp(FORCE_SWITCH))
        {
            // 计数器递增,每2次循环执行一次
            if (++cnt > 1)
            {
                cnt = 0; // 重置计数器
                // 增加Q轴电流补偿值(上限30000)
                if (++force_start.iq_sub > 30000)
                {
                    force_start.iq_sub = 30000; // 限制最大值
                }
            }
            // 检查虚拟角度与实际角度偏差是否小于500(单位可能是Q16)
            if ((flex.theta - force_start.theta < 500) &&
                (force_start.theta - flex.theta < 500))
            {
                force_start.flag = 0; // 退出强制启动状态
            }
        }
    }
}

2.2 某简易电机驱动的强拖程序(带预定位)

2.1.1 预定位

这段代码预定位的逻辑非常简单,仅靠延迟做实现。简单来说转子预定为就是给一个固定的角度输出给 FOC 控制一段时间,使转子拖动到这个角度。

对于控制逻辑相当于是:

  1. 施加一个固定的D轴电流(Id),产生定向磁场(如指向0°电角度)。
  2. 保持Q轴电流(Iq)为零(不产生转矩)。
  3. 持续 LOCK_COUNTER 个控制周期,确保转子被拉至目标位置。

LOCK_COUNTER 取值:
通常为几十到几百个PWM周期(例如20ms,若PWM频率=10kHz,则 LOCK_COUNTER=200)。

/* 初始锁定序列(用于磁场对齐) */
if (pmsm_mc_param.startup_lock_count < LOCK_COUNTER)
{
    pmsm_mc_param.startup_lock_count++; // 锁定计数器递增
}

2.1.2 加速度和切无感条件

同样,这段代码也有启动的加速度:

pmsm_mc_param.startup_ramp += OPENLOOP_RAMPSPEED_INCREASERATE; // 增加开环转速

这段代码的切换判断条件也很有意思:

开环生成的角度 pmsm_foc_param.angle < π(180°)实际转子角度 smc1.theta < π(180°)

这两个条件同时满足才会切换:

if ((pmsm_foc_param.angle < M_PI) && (smc1.theta < M_PI))

全部代码如下: 

    /* 加速阶段 */
    else if (pmsm_mc_param.startup_ramp < END_SPEED_RADS_ELEC_COUNTER) 
    {
        pmsm_mc_param.startup_ramp += OPENLOOP_RAMPSPEED_INCREASERATE; // 增加开环转速
    }
    /* 准备切换到闭环 */
    else 
    {
        theta_error = pmsm_foc_param.angle - smc1.theta;  // 计算角度误差
#ifndef OPEN_LOOP_MODE
        // 检查角度是否在合理范围内(避免突变)
        if ((pmsm_foc_param.angle < M_PI) && (smc1.theta < M_PI)) 
        {
            pmsm_mc_param.change_mode = 1;  // 标记模式切换
            pmsm_mc_param.openloop = 0;     // 退出开环模式
        }
#endif

2.1.3 完整代码

static void calculate_park_angle(void)
{
if (pmsm_mc_param.openloop) 
{
    /* 初始锁定序列(用于磁场对齐) */
    if (pmsm_mc_param.startup_lock_count < LOCK_COUNTER) 
    {
        pmsm_mc_param.startup_lock_count++;  // 锁定计数器递增
    }
    /* 加速阶段 */
    else if (pmsm_mc_param.startup_ramp < END_SPEED_RADS_ELEC_COUNTER) 
    {
        pmsm_mc_param.startup_ramp += OPENLOOP_RAMPSPEED_INCREASERATE; // 增加开环转速
    }
    /* 准备切换到闭环 */
    else 
    {
        theta_error = pmsm_foc_param.angle - smc1.theta;  // 计算角度误差
#ifndef OPEN_LOOP_MODE
        // 检查角度是否在合理范围内(避免突变)
        if ((pmsm_foc_param.angle < M_PI) && (smc1.theta < M_PI)) 
        {
            pmsm_mc_param.change_mode = 1;  // 标记模式切换
            pmsm_mc_param.openloop = 0;     // 退出开环模式
        }
#endif
    }
    // 更新Park角度(累加开环转速)
    pmsm_foc_param.angle += pmsm_mc_param.startup_ramp;
    // 角度归一化(0~2π)
    if (pmsm_foc_param.angle > ANGLE_2PI)
        pmsm_foc_param.angle = pmsm_foc_param.angle - ANGLE_2PI;
}

2.3 南京凌欧官方代码框架强拖程序

2.3.1 强拖角度生成

预先设置目标转速和每周期增加的步长 (角度)

// 根据电机运行方向设置强拖频率方向
if (struFOC_CtrProc.bMotorDirtionCtrl == CW) // 顺时针方向
{
    // 设置正向强拖目标频率(最小服务频率)
    mOpenForceRun.wOpen2CloseFreq = struAppCommData.wSvcMinFreq;
}
else // 逆时针方向
{
    // 设置负向强拖目标频率
    mOpenForceRun.wOpen2CloseFreq = -struAppCommData.wSvcMinFreq;
}

// 设置强拖频率的加速步长(将用户单位转换为应用单位)
mOpenForceRun.nFreqAccStep = User2AppFreqTrans(FREQ_ACC);

// 设置强拖频率的减速步长(将用户单位转换为应用单位)
mOpenForceRun.nFreqDecStep = User2AppFreqTrans(FREQ_DEC);

// 执行频率爬坡生成(生成线性变化的频率参考值)
SpeedReferenceGen(&mOpenForceRun);

 其中 SpeedReferenceGen() 函数实现如下:

只做了简单的累加,并没有设置强拖刚开始的缓启动。

/*****************************************************************************
 * 函数名   : void SpeedReferenceGen(stru_OpenForceRunDef *this)
 * 说明     : 速度爬坡 
 * 设计思路 :1.速度爬坡
 * 参数     :无
 * 返回值   :无
 * 修改时间 :2020.08.17
 *****************************************************************************/
void SpeedReferenceGen(stru_OpenForceRunDef *this)
{
    s32 ax;
    ax = this->wOpen2CloseFreq << 7;
    if(this->wRampFreqRef < (ax))
    {
        this->wRampFreqRef += this->nFreqAccStep;
        if(this->wRampFreqRef >= ax)
        {
            this->wRampFreqRef = ax;
        }                                             
    }
    if(this->wRampFreqRef > ax)
    {
        this->wRampFreqRef -= this->nFreqDecStep;
        if(this->wRampFreqRef <= ax)
        {
            this->wRampFreqRef = ax;  
        }
    }
}

2.3.1 强拖切无感逻辑

根据强拖角度和和观测角度误差在指定范围内累加切换。

// 计算观测器估算角度与开环强拖角度的误差
struAppCommData.wThetaErr = struFluxOB_Param.wElectAngleEst - struFluxOB_Param.wElectAngleOpen;

// 取误差绝对值并右移16位(Q16格式转换为实际值)
struAppCommData.wThetaErr = ABS(struAppCommData.wThetaErr) >> 16;

// 检查角度误差是否在合理范围内(2000~18000对应10.98°~98.87°)
if ((struAppCommData.wThetaErr > 2000) && (struAppCommData.wThetaErr < 18000))
{
    // 误差在范围内,增加匹配计数器
    struAppCommData.nMatchCnt++;
}
else
{
    // 误差超出范围,重置匹配计数器
    struAppCommData.nMatchCnt = 0;
}

2.3.2 完整代码

南京的凌欧的代码框架在强拖前有预定位的配置,但是并没有在改函数内实现。

/*****************************************************************************
 * 函数名   : void StateOpen(void)
 * 说明     : 电机强拖程序
 * 设计思路 :1.给定电机强拖频率、强拖电流、强拖加速度,使电机从静止开始拖动到设定的强拖频率。           \
              2.当电机达到设定频率,且估算角度和强拖角度误差在(10.98°~ 98.87°)之内则判断为估算         \
                已经跟随到强拖角度,连续5次则从开环切换到闭环。                                          \
              3.由于强拖给定的是Id,所以在程序留了从Id-->Iq电流的切换时间,切换时间的长短依照实际电机负载\
                来调整。(总的原则,负载轻则切换时间短;负载重则切换时间长,对于灯扇类应用则可以适当延长,\
                等扇叶完全展开后再切入速度闭环。                                                         \
              4.开环状态用来测试电机极对数,计算公式:极对数N = 60f/Speed ,其中f为电机电频率,Speed为电机\
                机械转速。
 * 参数     :无
 * 返回值   :无
 * 修改时间 :2020.08.17
 *****************************************************************************/
void StateOpen(void)
{
    // 检查是否处于开环强拖状态(bOpenRunFlag=0表示未启动强拖)
    if (struAppCommData.bOpenRunFlag == 0)
    {
        // 根据电机运行方向设置强拖频率方向
        if (struFOC_CtrProc.bMotorDirtionCtrl == CW) // 顺时针方向
        {
            // 设置正向强拖目标频率(最小服务频率)
            mOpenForceRun.wOpen2CloseFreq = struAppCommData.wSvcMinFreq;
        }
        else // 逆时针方向
        {
            // 设置负向强拖目标频率
            mOpenForceRun.wOpen2CloseFreq = -struAppCommData.wSvcMinFreq;
        }

        // 设置强拖频率的加速步长(将用户单位转换为应用单位)
        mOpenForceRun.nFreqAccStep = User2AppFreqTrans(FREQ_ACC);
        // 设置强拖频率的减速步长(将用户单位转换为应用单位)
        mOpenForceRun.nFreqDecStep = User2AppFreqTrans(FREQ_DEC);

        // 执行频率爬坡生成(生成线性变化的频率参考值)
        SpeedReferenceGen(&mOpenForceRun);
        // 获取当前频率设定值(右移7位进行缩放)
        struMotorSpeed.wSpeedSet = mOpenForceRun.wRampFreqRef >> 7;
        // 将频率参考值转换为核心控制层单位
        struMotorSpeed.wSpeedRef = App2CoreFreqTrans(struMotorSpeed.wSpeedSet);

        // 检查是否达到设定的强拖目标频率
        if (struMotorSpeed.wSpeedSet == mOpenForceRun.wOpen2CloseFreq)
        {
            // 计算观测器估算角度与开环强拖角度的误差
            struAppCommData.wThetaErr = struFluxOB_Param.wElectAngleEst - struFluxOB_Param.wElectAngleOpen;
            // 取误差绝对值并右移16位(Q16格式转换为实际值)
            struAppCommData.wThetaErr = ABS(struAppCommData.wThetaErr) >> 16;

            // 检查角度误差是否在合理范围内(2000~18000对应10.98°~98.87°)
            if ((struAppCommData.wThetaErr > 2000) && (struAppCommData.wThetaErr < 18000))
            {
                // 误差在范围内,增加匹配计数器
                struAppCommData.nMatchCnt++;
            }
            else
            {
                // 误差超出范围,重置匹配计数器
                struAppCommData.nMatchCnt = 0;
            }

            // 检查是否连续匹配次数超过阈值(默认5次)
            if (struAppCommData.nMatchCnt > MATCH_TIME)
            {
                // 根据系统配置决定是否切换到闭环
#if (OPEN_RUN_STATUS == TRUE)
                {
                    // 配置为仅开环运行,不执行任何操作
                }
#elif (OPEN_RUN_STATUS == FALSE)
                {
                    // 切换到闭环运行模式
                    struFOC_CtrProc.eSysState = RUN;  // 设置系统状态为运行模式
                    struAppCommData.bOpenRunFlag = 1; // 标记开环强拖已完成

                    // 根据电机方向设置Q轴电流(转矩电流)
                    if (struFOC_CtrProc.bMotorDirtionCtrl == CW) // 顺时针方向
                    {
                        // 设置正向Q轴电流(经过两级单位转换)
                        struFOC_CurrLoop.nQCurrentSet = App2CoreCurTrans(User2AppCurTrans(U_START_CUR_SET_S));
                    }
                    else if (struFOC_CtrProc.bMotorDirtionCtrl == CCW) // 逆时针方向
                    {
                        // 设置负向Q轴电流(经过两级单位转换)
                        struFOC_CurrLoop.nQCurrentSet = -App2CoreCurTrans(User2AppCurTrans(U_START_CUR_SET_S));
                    }

                    // 设置D轴电流(励磁电流)
                    // 注:开环阶段使用Id强拖,闭环后Id逐渐降为0,Iq逐渐增加
                    struFOC_CurrLoop.nDCurrentSet = App2CoreCurTrans(User2AppCurTrans(U_START_CUR_SET_F));

                    // 设置电流环的加速斜率(经过两级单位转换)
                    struAppCommData.nCurrentACC = App2CoreCurTrans(User2AppCurTrans(OPEN2CLOSE_RUN_CURRENT_RAMP));
                    // 设置电流环的减速斜率(经过两级单位转换)
                    struAppCommData.nCurrentDEC = App2CoreCurTrans(User2AppCurTrans(OPEN2CLOSE_RUN_CURRENT_RAMP));

                    // 更新D轴电流环参数
                    CurrentLoopAxisD_Set();
                    // 更新Q轴电流环参数
                    CurrentLoopAxisQ_Set();
                }
#endif
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值