AC7811-BLDC无感控制代码详解

news2024/9/21 22:57:17

BLDC控制框图

BLDC 的控制电路对电机转子位置信号进行逻辑变换后产生脉宽调制 PWM 信号,驱动逆变器的功率开关管,从而控制 BLDC 电机各相绕组按一定顺序工作,在电机气隙中产生跳跃式旋转磁场。BLDC 转子旋转时,每转过 60°,逆变器开关管换流一次、定子磁场状态改变一次,因此 BLDC 共有 6 个磁场状态,三相各导通 120°,相电流为方波。

实验波形图

六步控制PWM与换相

反电动势过零检测波形

问题

直接打开工程文档编译报错

问题都出现在spm.h这个文件里,declaration is incompatible 定义不兼容

后来发现,这个头文件在即在keil的了 AC78xx 系列 MCU 的 CMSIS pack中有,又在你自己的工程文件里有,类似于重复定义,定义不兼容。

所以办法是,把包含这个头文件的路径删除。

编译成功

软件工程架构

出现区别的原因在于keil的CMSIS pack中已经包含了很多配置文件,就是上面右图中下边那几个绿色的四边形。你可以在keil的这里进行配置,非常的方便,但缺点是比较混乱,会与芯片厂商提供的工程文件出现一定的冲突。

启动阶段

该模块主要是为了在启动阶段,不知道转子位置的情况下让电机先转起来,有以下两种流程:

1、预定位->开环->开环切换到闭环

2、预定位->直接闭环

无感启动预定位阶段

我们都只知道对于无感控制来讲,获取转子位置是非常重要的;

而对于BLDC的无感控制,最常用的的方法是:六部换相+反电动势过零点检测。

但是要想测反电动势测得准,精度高,就需要反电动势数值大。

这就要求在起步阶段要先把电机拉到指定位置上,先让它转起来(开环控制),等到能准确获取反电动势过零点的时候,再切换到无感控制上(闭环)。

那么怎么把电机拉到指定位置上呢?具体实现代码如下:

/*!
* @brief Pre-position function in bldc sensorless control.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @return none
*/
void BLDC_Preposition(BLDC_VARS_CTRL *bldcCtrl)
 {
    uint8_t delay = INIT_PREPOSITION_DELAY ;

    for (bldcCtrl->setDuty = MIN_DUTY_PREPOSITION; bldcCtrl->setDuty < MAX_DUTY_PREPOSITION; bldcCtrl->setDuty += STEP_DUTY_PREPOSITION)
    {
        delay -= INIT_PREPOSITION_DELAY_STEP;
        if (delay <= MIN_PREPOSITION_DELAY)
        {
            delay = MIN_PREPOSITION_DELAY;
        }

        UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));

        bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
        MosDriver(bldcCtrl->pBemf->currentDriverVector);
        mdelay(delay);
    }
    bldcCtrl->pBemf->kickTimeThreshold = BLDC_SENSORLESS_INIT_DELAY_TIME;
 }

其中核心的命令就一个:

bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
MosDriver(bldcCtrl->pBemf->currentDriverVector);

其实就是直接选六步中的一步,如,这里选择的是A+B-。

这样就可以把转子拉到指定位置上了。

无感启动开环阶段

在上一步完成获取转自初始位置后(实际上把他拉到某一位置上),需要赶紧让转子转起来了。

/*!
* @brief bldc openkick function in bldc sensorless control, it is usually executed after obtaining the initial rotor position.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void BLDC_Bemf_OpenKick(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
    if (bldcCfg->motorDir != 1)//反转
    {
        bldcCtrl->pBemf->currentDriverVector--;
        if (bldcCtrl->pBemf->currentDriverVector <= (uint8_t)INVALID_VECTOR_MIN)
        {
            bldcCtrl->pBemf->currentDriverVector = (uint8_t)CH_PWM_BL_ON;
        }
        MosDriver(bldcCtrl->pBemf->currentDriverVector);
    }
    else//正转
    {
        bldcCtrl->pBemf->currentDriverVector++;
        if (bldcCtrl->pBemf->currentDriverVector >= (uint8_t)INVALID_VECTOR_MAX)
        {
            bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
        }
        MosDriver(bldcCtrl->pBemf->currentDriverVector);

    }

    if (bldcCtrl->setDuty <= MAX_DUTY_SENSORLESS_START)
    {
        bldcCtrl->setDuty += STARTUP_DUTY_STEP;
    }
    else
    {
        bldcCtrl->setDuty = MAX_DUTY_SENSORLESS_START;
    }
    UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));
}

其实就是六步换相,刚刚我们已将把转子拉到下图这个位置上了

下一步我们要先判断是让电机正转还是反转:

正转就按图示向下的顺序,当其大于INVALID_VECTOR_MAX时,拉回到最开始位置;

反转就图示向上的顺序,当其小于INVALID_VECTOR_MIN时,就跳到最后的位置。

然后就是逐渐的增加占空比,直到与设定的启动阶段最大占空比一致。

if (bldcCtrl->setDuty <= MAX_DUTY_SENSORLESS_START)
    {
        bldcCtrl->setDuty += STARTUP_DUTY_STEP;
    }
    else
    {
        bldcCtrl->setDuty = MAX_DUTY_SENSORLESS_START;
    }
    UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));

启动开环阶段完成。

从开环切入闭环控制

/*!
* @brief drag motor to RUN state through open kick.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void MotorKickDrag(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
    if (bldcCtrl->pBemf->kickTimeThreshold > BLDC_SENSORLESS_MIN_DELAY_TIME)
    {
        bldcCtrl->bldcStartTime++;
        if (bldcCtrl->bldcStartTime > bldcCtrl->pBemf->kickTimeThreshold)
        {
            bldcCtrl->bldcStartTime = 0;
            if (bldcCtrl->pBemf->kickTimeThreshold > BLDC_SENSORLESS_MIN_DELAY_TIME)
            {
                bldcCtrl->pBemf->kickTimeThreshold -= BLDC_SENSORLESS_STEP_DELAY_TIME;
            }
            else
            {
                bldcCtrl->pBemf->kickTimeThreshold = BLDC_SENSORLESS_MIN_DELAY_TIME;
            }
            BLDC_Bemf_OpenKick(&g_bldcVarsCtrl, pBldcVarsCfg);
        }
    }
    else
    {
        if ((bldcCtrl->pBemf->currentDriverVector + bldcCfg->motorDir) >= (uint8_t)INVALID_VECTOR_MAX)
        {
            bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
        }
        else if ((bldcCtrl->pBemf->currentDriverVector + bldcCfg->motorDir) <= (uint8_t)INVALID_VECTOR_MIN)
        {
            bldcCtrl->pBemf->currentDriverVector = (uint8_t)CH_PWM_BL_ON;
        }
        else
        {
            bldcCtrl->pBemf->currentDriverVector += bldcCfg->motorDir;
        }

        MosDriver(bldcCtrl->pBemf->currentDriverVector);
        SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);
        bldcCtrl->pBemf->changePhaseTime = 0;
        bldcCtrl->pBemf->saveChangePhaseTime = FIRST_COMMUTATE_TIME;
        bldcCtrl->pBemf->afterFlowTime = FIRST_STEP_DRAG_TIME;
        bldcCtrl->pBemf->forceChangePhaseFlag = 0;

        bldcCtrl->startupStatus = STARTUP_READY;
    }
}

上来先做一个判断,开环阶段有没有执行完,没执行完继续去执行;

执行完了直接切入就行,说实话,我没看出这个阶段和开环有啥区别,唯一不同的是进行了一些传参,还有最后一个标志位STARTUP_READY的切换。

以上三个阶段怎么联系起来?

/*!
* @brief BLDC motor startup function, include bldc hall and bldc sensorless control.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void BLDC_Startup(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
#if (defined BLDC_HALL)
    if (bldcCtrl->bldcStartTime == 0)
    {
        BLDC_Hall_Startup(bldcCtrl, bldcCfg, &g_bldc_speedCmd);
    }
    if (bldcCtrl->bldcStartTime++ > 20)
    {
        bldcCtrl->bldcStartTime = 0;
    }
#elif (defined BLDC_SENSORLESS)
    if (bldcCtrl->pBemf->prepositonIndex < 3)   /* preposition */
    {
        bldcCtrl->pBemf->prepositonIndex++;
        BLDC_Preposition(bldcCtrl);
    }
    else        /* drag startup */
    {
        #if (defined NOKICK_STARTUP)
        MotorDirectDrag(bldcCtrl);
        #else
        MotorKickDrag(bldcCtrl, bldcCfg);
        #endif
    }
#else
#endif

}

咱们不看hall那部分,只看下边无感部分。

上来先调用了三次预定位函数,三次过后,判断你是直接切入的闭环阶段,还是经过开环再切入的闭环。

以上便是启动阶段的所有代码了

直接进入闭环

/*!
* @brief drag motor dircet to RUN state, get into close loop control fast.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @return none
*/
void MotorDirectDrag(BLDC_VARS_CTRL *bldcCtrl)
{
    UpDatePwmDuty(DUTY_PERIOD(DRAG_DUTY));
    MosDriver(bldcCtrl->pBemf->currentDriverVector);
    SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);
    bldcCtrl->pBemf->changePhaseTime = 0;
    bldcCtrl->pBemf->saveChangePhaseTime = FIRST_COMMUTATE_TIME;
    bldcCtrl->pBemf->afterFlowTime = FIRST_STEP_DRAG_TIME;
    bldcCtrl->pBemf->forceChangePhaseFlag = 0;

    bldcCtrl->startupStatus = STARTUP_READY;
}

反电动势过零点检测通道切换

在无感BLDC控制中,采集到的三相反电动势电机中值电压ACMP中进行比较,所以这就牵涉到三相反电动势采集时候的切换,采用轮询的方式与电机中值电压比较。

so,什么时候切换通道,进行轮询?

切换函数如下:

/*!
* @brief switch acmp compared channel according to current magnetic vector, it is usually executed in bldc sensorless control.
*
* @param[in] curDriverValue: current magnetic vector in bldc sensorless control
* @return none
*/
void SwitchAcmpChannel(uint8_t curDriverValue)
{
    switch (curDriverValue)
    {
    case AH_PWM_BL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 2);
    break;

    case AH_PWM_CL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 1);
    break;

    case BH_PWM_CL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 0);
    break;

    case BH_PWM_AL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 2);
    break;

    case CH_PWM_AL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 1);
    break;

    case CH_PWM_BL_ON:
    ACMP_PositiveInputSelect(ACMP_CHANNEL0, 0);
    break;

    default:
    break;
    }
}

直接根据所处的磁场位置,切换ACMP+的通道。

Bemf

这个模块主要功能是实现反电动势过零点检测并实现换相。

过零点检测代码实现

/*!
* @brief Check black EMF over zero point to determine when to change phase.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void BemfOverZeroCheck(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
    if (bldcCfg->motorDir == 1)
    {
        switch (bldcCtrl->pBemf->currentDriverVector)
        {
        case AH_PWM_BL_ON:
        if (C_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case AH_PWM_CL_ON:
        if (B_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case BH_PWM_CL_ON:
        if (A_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case BH_PWM_AL_ON:
        if (C_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case CH_PWM_AL_ON:
        if (B_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case CH_PWM_BL_ON:
        if (A_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;
        }
    }
    else
    {
        switch (bldcCtrl->pBemf->currentDriverVector)
        {
        case AH_PWM_BL_ON:
        if (C_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case AH_PWM_CL_ON:
        if (B_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case BH_PWM_CL_ON:
        if (A_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case BH_PWM_AL_ON:
        if (C_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case CH_PWM_AL_ON:
        if (B_PHASE_BEMF != 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;

        case CH_PWM_BL_ON:
        if (A_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;
        }
    }
}

这部分代码逻辑就是,先判断正转反转,再判断处于什么换相状态,比如A+B-时,就要检测C相的反电动势是否为零。

switch (bldcCtrl->pBemf->currentDriverVector)
        {
        case AH_PWM_BL_ON:
        if (C_PHASE_BEMF == 0)
        {
            PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
        }
        break;
		}

这里的C_PHASE_BEMF就来自于ACMP的DR寄存器。

#define C_PHASE_BEMF                    ((ACMP0->DR)&ACMP0_DR_O_Msk)     /*!< get phase C over zero detect results */

C向的反电动势与电机中值电压比较之后的结果。

判断为0后,开始准备换相,就是这个命令:

PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);

下一次换相与延时的具体时间确定

就是这个函数PrepareNextChangePhase():

下一次换相其实没什么特别的,还是根据正反转,对六步换相++--

if (motorDir == 1)
    {
        curDriverValue++;
        if (curDriverValue >= (uint8_t)INVALID_VECTOR_MAX)
        {
            curDriverValue = (uint8_t)AH_PWM_BL_ON;
        }
    }
    else
    {
        curDriverValue--;
        if (curDriverValue <= (uint8_t)INVALID_VECTOR_MIN)
        {
            curDriverValue = (uint8_t)CH_PWM_BL_ON;
        }
    }

重要的在后边:

	g_bemfControl.nextDriverVector = curDriverValue;

    g_bemfControl.delayTime = (3 * g_bemfControl.saveChangePhaseTime) >> 3;

    g_bemfControl.overZero_Flag = 1;

也就是这个 g_bemfControl.delayTime,我们要知道30°的电角度,对应的时间是多少,这个值就是delayTime,延迟30电角度之后再换相。具体为什么*3/8,我一直没搞懂。

换相时间那个结构体中有好几类换相时间:

	uint32_t changePhaseTime;                       /*!< Current vector affect time count, units: 12us */
    uint32_t saveChangePhaseTime;                   /*!< Current vector affect time count saved for commutate delay, units: 12us */
    uint32_t lastChangePhaseTime;                   /*!< Last vector affect time count to determine after flow time, units: 12us */
    uint32_t afterFlowTime;                         /*!< After flow time while don't detect over zero point, units: 12us */
    uint32_t aveChangePhaseTime;                    /*!< Current vector affect average time count, units: 12us */
    uint32_t delayTime;                             /*!< Bldc delay 30 degree elec angle time count, units: 12us */
    BUFFER_TYPE commutationTime;                    /*!< Buffer to storage commutate time */

要先捋一下这几个换相时间:

changePhaseTime 当前换相时间(其实就是计数器的计数值,一个计数单元是12us)

saveChangePhaseTime当前换相时间,存下来为换相延时用

lastChangePhaseTime上一次换相时间,用来确定FlowTime?

afterFlowTime不检测过零点时,FlowTime之后

aveChangePhaseTime当前换相时间的平均值

delayTime换相延迟时间

commutationTime换相时间的缓冲区

越理感觉越乱,什么鬼这是。

是不是这个saveChangePhaseTime,指的是上次换相到这次换相之间的时间间隔?

也就是两个红线之间的时间。

开始换相

在知道下一次换相目标和需要的延时事件后,开始换相

先判断电机状态是不是‘准备好’了:

if (g_mcStatus == RUN)

之后直接执行换相

 MosDriver(bldcCtrl->pBemf->nextDriverVector);

之后不要忘记轮询切换过零点的检测通道

SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);

然后是各种换相时间的更新以及各种标志位的清零:

bldcCtrl->pBemf->lastChangePhaseTime = bldcCtrl->pBemf->saveChangePhaseTime;
bldcCtrl->pBemf->saveChangePhaseTime = bldcCtrl->pBemf->changePhaseTime;
bldcCtrl->pBemf->afterFlowTime = ((bldcCtrl->pBemf->saveChangePhaseTime + bldcCtrl->pBemf->lastChangePhaseTime)) >> 3;

ChangePhaseTimeCalc(&g_bldcVarsCtrl);

bldcCtrl->pBemf->changePhaseTime = 0;
bldcCtrl->pBemf->overZero_Flag = 0;
bldcCtrl->pBemf->afterFlow_Flag = 0;
bldcCtrl->pBemf->forceChangePhaseFlag = 0;

强制换相

检查一下换相间隔,是否需要强制换相。

FORCE_COMMUTATE_TIME强制换相的时间阈值,超过这个时间阈值,强制换相标志位置1.

/*!
* @brief Check change phase interval to determine whether forced change phase is in needed.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void ForceChangePhaseCheck(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
    if (bldcCtrl->pBemf->forceChangePhaseFlag == 0)
    {
        if (bldcCtrl->pBemf->changePhaseTime > FORCE_COMMUTATE_TIME)
        {
            bldcCtrl->pBemf->forceChangePhaseFlag = 1;
            bldcCtrl->pBemf->afterFlow_Flag = 1;

            if (bldcCtrl->pBemf->overZero_Flag == 0)
            {
                PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
            }
        }
    }
}

串联起来

/*!
* @brief Bemf over zero point detect and change phase function.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @return none
*/
void BemfCheckFun(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
{
    ForceChangePhaseCheck(bldcCtrl, bldcCfg);//看看需不需要强制换相

    if (bldcCtrl->pBemf->afterFlow_Flag == 0)
    {
        if (bldcCtrl->pBemf->changePhaseTime > bldcCtrl->pBemf->afterFlowTime)
        {
            bldcCtrl->pBemf->afterFlow_Flag = 1;
        }
    }
    else//不需要强制换相,检查检查过零点
    {
        if (bldcCtrl->pBemf->overZero_Flag == 0)
        {
            BemfOverZeroCheck(bldcCtrl, bldcCfg);
        }
        else//检查到过零点标志位了
        {
            if (bldcCtrl->pBemf->delayTime > 0)//实现具体的30电角度的延时
            {
                bldcCtrl->pBemf->delayTime--;
            }

            if ((bldcCtrl->pBemf->delayTime == 0) || (bldcCtrl->pBemf->forceChangePhaseFlag == 1))
            {//延时时间为0了,或者需要强制换相了,开始执行换相
                DisableInterrupts//换相的时候,一定要保证不能被中断给打断了
                BldcBemfChangePhase(bldcCtrl);
                EnableInterrupts
            }
        }
    }
}

ADC

这部分代码主要为实现以下功能:

  • 获取总线电压;
  • 获取总线电流,电流环PI控制调用

获取总线电压

/*!
* @brief Bldc control bus voltage sample, get bus voltage feedback.
*
* @param[in] adc: pointer to BLDC_ADC_TYPE structure
* @return none
*/
void Get_VoltageValue(BLDC_ADC_TYPE *adc)
{
    adc->busVoltageAD = (g_ADCRegularBuffer[1] + g_ADCRegularBuffer[3]) >> 1;
    adc->busVoltageTrue = (uint16_t)((uint32_t)(adc->busVoltageAD * (VBUS_ATTENUATE_FACTOR * VSVREF * 10)) / ADC_RANGE);        /* 120=12V */
    //VBUS_ATTENUATE_FACTOR这个数值,是你的电阻分压网络缩小的那个倍数
}

这个函数没什么特别的,就是电压AD数值的一个转换。注意得出的电压数值是放大10倍的。

获取总线电流

这个要稍微复杂一些,因为牵涉到一个过流的保护判断,还有滤波

/*!
* @brief Bldc control bus current sample, get bus current feedback.
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] adc: pointer to BLDC_ADC_TYPE structure
* @return none
*/
void Get_CurrentValue(BLDC_VARS_CTRL *bldcCtrl, BLDC_ADC_TYPE *adc)
{
    adc->busCurAD = g_ADCRegularBuffer[0];//获取ADC采样值

    if (adc->busCurAD > adc->busCurOffset)//判断获取到的采样值是不是大于 总线电流的零点漂移busCurOffset 
    {
        if (adc->busCurAD > (int16_t)(adc->busCurOffset + LIMIT_PEAK_CUR_AD))//是的话,再判断有没有超过设定的峰值电流值
        {
            adc->busOverCurCnt++;//超过了,开始计次,看超过了几次,后边保护策略中会有判断超过10次,过流保护
        }
        else//没有超过电流峰值,要先把计数值清零
        {
            if (adc->busOverCurCnt-- <= 0)
            {
                adc->busOverCurCnt = 0;
            }
        }
        adc->busCurAD = adc->busCurAD - adc->busCurOffset;//如果没有超过设定值,那么busCurAD就是 采集到的AD值-零点漂移
    }
    else
    {
        adc->busCurAD = 0;//如果采集到的电流AD还没零点飘逸的AD值大,直接拉为0
    }

    StorageDataCyclic(&adc->busCur, adc->busCurAD);
    adc->busCurAD = Filter_AverageCalc(adc->busCur.array, BUFFER_SIZE);//滤波,求平均值

    adc->motorFeedbackCur = adc->busCurAD * Get_EffectiveDuty(bldcCtrl) / 10000; /* effective duty in 10000 times */
    //Get_EffectiveDuty()有效占空比?不太懂这个值

    adc->busCurTrue =  (uint16_t)((int32_t)adc->motorFeedbackCur * 100 / CURRENT_AD_COF);    /* real current in 100 times */
    //电流计算公式:AD反馈值*100/CURRENT_AD_COF
    //CURRENT_AD_COF = (RSHUNT * OP_AMPLIFICATION_GAIN * ADC_RANGE / VSVREF) = 采样电阻 * 放大倍数 * ADC最大范围 / 参考电压
    
    adc->busPowerTrue = (uint16_t)(((uint32_t)adc->busCurTrue * adc->busVoltageTrue) / 100); /* real power in 10 times */
    //功率计算

    adc->motorFeedbackCurOld = adc->motorFeedbackCur;
}

电流环PI调用

/*!
* @brief Bldc bus current sample and current loop PI control, executed in the RUN state
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @param[in] adc: pointer to BLDC_ADC_TYPE structure
* @return none
*/
void BusCurrent_Control(BLDC_VARS_CTRL *bldcCtrl, BLDC_ADC_TYPE *adc)
{
    Get_CurrentValue(bldcCtrl, adc);//获取总线电流

    bldcCtrl->ibusFdkPu = adc->motorFeedbackCur * CURRENT_PU_COF;//电流环反馈值,这里的CURRENT_PU_COF与上面的CURRENT_AD_COF不一样
    // CURRENT_PU_COF = (uint16_t)(((uint32_t)32768 * VSVREF) / (RSHUNT * OP_AMPLIFICATION_GAIN * ADC_RANGE * MAX_CURRENT))
    //区别在于,求出AD值转化成电流值之后,又乘了一个比例关系 32768/MAX_CURRENT  2的15次方=32768 这属于Q15定点数类型的表达方式
    
    BLDC_CurrentLoop_Calculate(pBldcVarsCfg, &g_bldc_acrPid, bldcCtrl);//进行电流环PI控制的一些计算
    #if (defined BLDC_HALL)
    bldcCtrl->currentPwmPu = Q15_Sat(bldcCtrl->currentPwmPu, MATH_IQ(MAX_DUTY_WITH_HALL / DUTY_BASE_VALUE), MATH_IQ(MIN_DUTY_WITH_HALL / DUTY_BASE_VALUE));
    #else
    bldcCtrl->currentPwmPu = Q15_Sat(bldcCtrl->currentPwmPu, MATH_IQ(MAX_DUTY_SENSORLESS / DUTY_BASE_VALUE), MATH_IQ(MIN_DUTY_SENSORLESS / DUTY_BASE_VALUE));
    #endif

    UpDatePwmDuty(Q15_PERIOD(bldcCtrl->currentPwmPu));//PI调节之后的输出结果,用来进行PWM占空比的调节
}

Q15定点数

挺复杂一个东西,简单理解:浮点数在运算的时候是很慢很慢的,所以可以采用Q格式进行浮点数据到定点的转化,节约CPU时间。

浮点数据转化为Q15,将数据乘以2^15;Q15数据转化为浮点数据,将数据除以2^15。

具体可参考这个网址:Q格式(Q15)DSP上浮点数据定点化处理 - rockstone - 博客园

以前做总线电流采样的时候,几乎不会去考虑计算时间的问题,直接AD值到实际电流值的转换就行了,但牵涉到电流环PI调节,有大量运算的情况,就需要去考虑这个问题了,这里采用的就是把浮点数转化为Q格式。

PWM

这部分代码主要功能是:

进行PWM的一些输出控制,如PWM的停止输出、PWM占空比的调节、PWM直接驱动MOS的开关等

停止PWM输出

拢共分三步:PWM2的所以通道停止输出、PWM占空比设为0、PWM2软件同步

/*!
* @brief Clear pwm duty and stop bldc motor.
*
* @param[in] none
* @return none
*/
void BLDC_Stop(void)
{
    PWM2->CHOSWCR = ALL_CH_OFF;
    UpDatePwmDuty(0);

    PWM_SoftwareSync(PWM2);
}

PWM在10000次中的有效占空比

意思就是说,你设定的PWM占空比输出值,并不代表就是实际的PWM占空比输出值,这个函数就是计算出实际的占空比输出。

/*!
* @brief Get PWM module effective duty in 10000 times
*
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @return pwm effective duty
*/
uint16_t Get_EffectiveDuty(BLDC_VARS_CTRL *bldcCtrl)
{
    uint32_t temp_PwmCh0Val;
    uint32_t temp_PwmCh1Val;
    uint32_t temp_PwmMcvr;
#if (PWM_MODE_SELECT == PWM_COUNT_UP_DOWN_MODE)//PWM向上向下计数

#else
    temp_PwmCh0Val = PWM2->CHANNELS[0].CHV;
    temp_PwmCh1Val = PWM2->CHANNELS[1].CHV;
    temp_PwmMcvr = PWM2->MCVR;//MCVR是PWM计数器最大计数值
    #if (PHASE_UVW_POLARITY == ACTIVE_HIGH)
    bldcCtrl->pwmOutDuty = 10000 * (temp_PwmCh1Val - temp_PwmCh0Val) / temp_PwmMcvr;//这一部分牵涉到PWM的 组合输出模式 看下面的举例
    #else
    bldcCtrl->pwmOutDuty = 10000 * (temp_PwmCh0Val - temp_PwmCh1Val) / temp_PwmMcvr;
    #endif
#endif
    /* enlarge 10000 times, 1234 means 12.34% */
    return bldcCtrl->pwmOutDuty;
}

PWM的组合输出模式

在组合模式下,将偶数通道(n)和相邻的奇数通道(n + 1)组合以在通道(n)输出中产生 PWM 信号。在组合模式下,PWM 周期由(MCVR - CNTIN + 0x0001) 确定 ,且 PWM 脉冲宽度(占空比)由(|CH(n+1)V - CH(n)V|)确定。

用在这里就是:10000*(3-1)/ 5 = 4000 就是40%的占空比

PWM占空比的更新(正常情况与电流PID)

这个就没什么可说的,就是调用一下PWM占空比设置函数PWM_SetChannelValue()

/*!
* @brief Update PWM output duty by manual settig or current PID calculate.
*
* @param[in] pwmDuty: range: 0 ~ PWMx->MCVR, corresponding to 0 ~ 100% duty cycle
* @return none
*/
void UpDatePwmDuty(uint16_t pwmDuty)
{
    if (pwmDuty > (PWM2->MCVR - 1))
    {
        pwmDuty = (PWM2->MCVR - 1);
    }
    else if (pwmDuty < 1)
    {
        pwmDuty = 1;
    }
    else
    {

    }
#if (defined BLDC_AFTER_FLOW_CONTROL)
    if (g_bemfControl.commutateState == 1)
    {
        if (g_bemfControl.changePhaseTime > ((g_bemfControl.afterFlowTime * AFTER_FLOW_ANGLE_NUMERATOR) >> AFTER_FLOW_ANGLE_PSRC))
        {
            switch (g_bemfControl.currentDriverVector)
            {
            case AH_PWM_BL_ON:
            PWM2->CHOSWCR = CH0_MODULATION_CH3_ON;
            break;

            case AH_PWM_CL_ON:
            PWM2->CHOSWCR = CH0_MODULATION_CH5_ON;
            break;

            case BH_PWM_CL_ON:
            PWM2->CHOSWCR = CH2_MODULATION_CH5_ON;
            break;

            case BH_PWM_AL_ON:
            PWM2->CHOSWCR = CH2_MODULATION_CH1_ON;
            break;

            case CH_PWM_AL_ON:
            PWM2->CHOSWCR = CH4_MODULATION_CH1_ON;
            break;

            case CH_PWM_BL_ON:
            PWM2->CHOSWCR = CH4_MODULATION_CH3_ON;
            break;

            default:
            break;
            }
            g_bemfControl.commutateState = 2;
        }
    }
#endif

#if (PWM_MODE_SELECT == PWM_COUNT_UP_DOWN_MODE)

#else
    #if (PHASE_UVW_POLARITY == ACTIVE_HIGH)
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL1, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL3, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL5, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL0, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL2, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL4, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    #else
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL1, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL3, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL5, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL0, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL2, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL4, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
    #endif
#endif

    PWM_SoftwareSync(PWM2);
}

直接控制MOS开关

根据需要直接改变PWM输出通道,来打开对应的MOS。

/*!
* @brief update PWM channel output by six step square wave magnetic vector to driver MOS.
*
* @param[in] ChangePhaseVector: current magnetic vector in bldc control
* @return none
*/
void MosDriver(uint8_t ChangePhaseVector)
{
#if (defined BLDC_AFTER_FLOW_CONTROL)
    AfterFlow_Control(ChangePhaseVector, pBldcVarsCfg->motorDir);
#else
    switch (ChangePhaseVector)
    {
    case AH_PWM_BL_ON:
    PWM2->CHOSWCR = CH0_MODULATION_CH3_ON;
    PWM_SoftwareSync(PWM2);
    break;

    case AH_PWM_CL_ON:
    PWM2->CHOSWCR = CH0_MODULATION_CH5_ON;
    PWM_SoftwareSync(PWM2);
    break;

    case BH_PWM_CL_ON:
    PWM2->CHOSWCR = CH2_MODULATION_CH5_ON;
    PWM_SoftwareSync(PWM2);
    break;

    case BH_PWM_AL_ON:
    PWM2->CHOSWCR = CH2_MODULATION_CH1_ON;
    PWM_SoftwareSync(PWM2);
    break;

    case CH_PWM_AL_ON:
    PWM2->CHOSWCR = CH4_MODULATION_CH1_ON;
    PWM_SoftwareSync(PWM2);
    break;

    case CH_PWM_BL_ON:
    PWM2->CHOSWCR = CH4_MODULATION_CH3_ON;
    PWM_SoftwareSync(PWM2);
    break;

    default:
    break;
    }
#endif
}

Speed

这个模块主要实现的是速度环速度设置上的功能。

设定目标转速

从rpm到Q15

/*!
* @brief The per-unit value of speed targe value is calculated according to the actual value of the speed command unit rpm.
*
* @param[in] command: pointer to SPEED_RAMP_TYPE structure
* @param[in] speedCmdRpm: Given speed command unit rpm
* @return none
*/
void BLDC_ASR_SetTargetRpm(SPEED_RAMP_TYPE *command, int16_t speedCmdRpm)
{
    command->speedTarget = (int32_t)(speedCmdRpm) * 32768 / MOTOR_MAX_SPEED_RPM;
    //speedTarget = 给定的实际转速rpm / 最大转速rpm * 32678 这里好像又转化成Q15定点数了
}

直接Q15

/*!
* @brief The per-unit value of speed targe value is calculated according to the speed command unit pu.
*
* @param[in] command: pointer to SPEED_RAMP_TYPE structure
* @param[in] speedCmdPu: Given speed command unit pu
* @return none
*/
void BLDC_ASR_SetTargetPu(SPEED_RAMP_TYPE *command, int16_t speedCmdPu)
{
    command->speedTarget = speedCmdPu;
}

设定目标加速度

/*!
* @brief The per-unit value of speed ramp value is calculated according to the speed command unit pu.
*
* @param[in] command: pointer to SPEED_RAMP_TYPE structure
* @param[in] speedCmdPu: Given speed command unit pu
* @return none
*/
void BLDC_ASR_SetRampPu(SPEED_RAMP_TYPE *command, int16_t speedCmdPu)
{
    command->speedTargetRamp = speedCmdPu;
}

获取目标转速值

/*!
* @brief Get the speed target value.
*
* @param[in] speedRamp: pointer to SPEED_RAMP_TYPE structure
* @return none
*/
int16_t BLDC_ASR_GetTargetPu(SPEED_RAMP_TYPE *speedRamp)
{
    return speedRamp->speedTarget;
}

获取反馈速度值

以rpm为单位

/*!
* @brief Get the feedback speed unit in Rpm.
*
* @param[in] speed: pointer to SPEED_RAMP_TYPE structure
* @return none
*/
int16_t BLDC_ASR_GetFbkRpm(SPEED_RAMP_TYPE *speed)
{
    return speed->speedFbkRpm;
}

以pu为单位

/*!
* @brief Get the feedback speed unit in Pu.
*
* @param[in] speed: pointer to SPEED_RAMP_TYPE structure
* @return none
*/
int16_t BLDC_ASR_GetFbkPu(SPEED_RAMP_TYPE *speed)
{
    return speed->speedFbk;
}

后面还有很多的这种转换,就不再写了,只写重点程序!

根据设定目标速度计算加速度?

这个函数实现的是,各一个设定的rpm速度,然后可以计算出到达这一速度,所使用的加速度。

但我一直没搞懂speedTargetRamp这个参数到底什么意思?斜坡函数?加速度?

/*!
* @brief Speed ramp calculation. calculate the ramp output according to the speed target value.
*
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @param[in] speedRamp: pointer to SPEED_RAMP_TYPE structure
* @return none
*/
void BLDC_ASR_RampCalc(BLDC_VARS_CFG *bldcCfg, SPEED_RAMP_TYPE *speedRamp)
{
    static uint16_t s_countSpdCmd = 0;

    //先判断设定的这个速度有没有超过设定的速度阈值
	BLDC_ASR_TargetLimit(bldcCfg->motorDir, speedRamp);

    s_countSpdCmd++;
    /* calculate speed ramp every 10ms, 9 = 10 - 1, s_countSpdCmd range is 1~10, then back to 0, and becomes to 1 after '++' */
    if (s_countSpdCmd > 9)
    {
        s_countSpdCmd = 0;
        if (bldcCfg->motorDir == FORWARD_ROTATE)//正转
        {
            if ((speedRamp->speedTarget - speedRamp->speedTargetRamp) > speedRamp->speedAcceleration)
            {
                speedRamp->speedTargetRamp += speedRamp->speedAcceleration;
            }
            else if ((speedRamp->speedTargetRamp - speedRamp->speedTarget) > speedRamp->speedDeceleration)
            {
                speedRamp->speedTargetRamp -= speedRamp->speedDeceleration;
            }
            else
            {
                speedRamp->speedTargetRamp = speedRamp->speedTarget;
            }
        }
        else if (bldcCfg->motorDir == REVERSE_ROTATE)
        {
            if ((speedRamp->speedTargetRamp - speedRamp->speedTarget) > speedRamp->speedAcceleration)
            {
                speedRamp->speedTargetRamp -= speedRamp->speedAcceleration;
            }
            else if ((speedRamp->speedTarget - speedRamp->speedTargetRamp) > speedRamp->speedDeceleration)
            {
                speedRamp->speedTargetRamp += speedRamp->speedDeceleration;
            }
            else
            {
                speedRamp->speedTargetRamp = speedRamp->speedTarget;
            }
        }
        else
        {
        }
    }
}

计算转速

/*!
* @brief get motor elec freq in bldc control with sensorless.
*
* @param[in] motorDir: motor run direction
* @return elec freq in Q15
*/
int16_t BLDC_Bemf_SpeedGet(int16_t motorDir)
{
    uint32_t s_elecFreq = 0;
    uint32_t s_elecFreqPu = 0;

    if (g_bemfControl.aveChangePhaseTime)//只要平均换相时间不为0
    {
        s_elecFreq = 166667 / (int32_t)(g_bemfControl.aveChangePhaseTime * TIMER_XUS);   
        /* 166667 refers 6 sector time in us, 166667 = (6 / 10^-6) */
        //这个也比较好理解 aveChangePhaseTime是平均换相间隔(其实是一个次数值) 所以要乘以时间单元TIMER_XUS
        //得到的才是换一次相,费的时间(单位是us)
        //转一圈6次换相 所以还要*6,再把us转化成s  就是上面这个公式
    }

    s_elecFreqPu = motorDir * MATH_IQ(s_elecFreq * 1.0 / BASE_FREQ);//转化成Q15格式*2^15

    return s_elecFreqPu;

Protector

这个模块主要是为实现电机保护策略方面的功能。

过流检测

/*!
* @brief DC Bus over current protection. When the DC Bus current exceeds
*        the threshold value, DC Bus over current fault is reported.
*
* @param[in] protect: pointer to PROTECTOR_TYPE structure
* @param[in] adc: pointer to BLDC_ADC_TYPE structure
* @return none
*/
void BLDC_Over_Current_Check(PROTECTOR_TYPE *protect, BLDC_ADC_TYPE *adc)
{
    if (adc->busOverCurCnt > BLDC_OVER_CURRENT_DBC)//电流超过电流阈值十次
    {
        protect->faultFlag.all |= (1 << OVER_CURRENT_IBUS_FAULT);//故障标志位置1
    }
    else
    {

    }
}

过压欠压检测

电压保护策略方面,主要就是过压与欠压的检测(过压与欠压原理相似放一起说)

代码给不同的电压设定了三个状态:DETECT_FAULT、DETECT_OK、DETECT_UNKNOWN

除了设定这样一个恢复阈值之外,还牵涉了一个s_debounce参数,这个参数是用来消抖的,简单理解,就是延时,不会因为电压的跳变,突然就出现保护。

/*!
* @brief DC Bus over voltage protection. When the bus voltage is higher than
*        threshold value, Undervoltage fault is reported, and it will return
*        to normal operation after the bus voltage rises to the recovery threshold.
*
* @param[in] protect: pointer to PROTECTOR_TYPE structure
* @param[in] volt: DC Bus voltage value, 10 times the real value
* @return none
*/
void BLDC_Over_Voltage_Check(PROTECTOR_TYPE *protect, uint16_t volt)
{
    static int16_t s_vbusStatus = BUS_VOLTAGE_NORMAL;
    static int16_t s_prevVbusStatus = BUS_VOLTAGE_NORMAL;
    static int16_t s_debounce = 0;
//    static int16_t s_status = DETECT_OK;

    if (volt > OVER_VBUS_THRESHOLD)
    {
        s_vbusStatus = BUS_VOLTAGE_OVER;
    }
    else if (volt < OVER_VBUS_RECOVERY_THRESHOLD)
    {
        if (s_vbusStatus == BUS_VOLTAGE_OVER)
        {
            s_vbusStatus = BUS_VOLTAGE_RECOVER;
        }
    }
    else
    {
    }

    if (s_vbusStatus != s_prevVbusStatus)
    {
        s_prevVbusStatus = s_vbusStatus;
        s_debounce = 0;
    }
    else
    {
        if (s_vbusStatus == BUS_VOLTAGE_RECOVER)
        {
            if (s_debounce < 10)
            {
                s_debounce++;
            }
            else
            {
//                g_mcStatus = STOP;
                s_vbusStatus = BUS_VOLTAGE_NORMAL;
                protect->faultFlag.all &= ~(1 << VBUS_OVER_VOLTAGE_FAULT);
            }
        }
        else if (s_vbusStatus == BUS_VOLTAGE_NORMAL)
        {
            if (s_debounce < 10)
            {
                s_debounce++;
            }
            else
            {
//                s_status = DETECT_OK;
                protect->faultFlag.all &= ~(1 << VBUS_OVER_VOLTAGE_FAULT);
            }
        }
        else
        {
            if (s_debounce < 10)
            {
                s_debounce++;
            }
            else
            {
//                s_status = DIAGNOSTIC_FAIL;
                protect->faultFlag.all |= (1 << VBUS_OVER_VOLTAGE_FAULT);
            }
        }
    }

//    return s_status;
}

串起来

故障检查以什么样的时基单元进行检查。

/*!
* @brief Motor protection main function. The corresponding protection functions
*        are implemented according to different time bases.
*
* @param[in] none
* @return none
*/
void BLDC_Motor_Protection(void)
{

#if (defined BLDC_OVER_CURRENT_CHECK)
    /* DC Bus over current protection , 1ms*/
    BLDC_Over_Current_Check(&g_bldc_protector, &g_bldc_adSample);
#endif

#if (defined BLDC_UNDER_VOLTAGE_CHECK)
    /* DC Bus under voltage protection, 1ms */
    BLDC_Under_Voltage_Check(&g_bldc_protector, g_bldc_adSample.busVoltageTrue);
#endif

#if (defined BLDC_OVER_VOLTAGE_CHECK)
    /* DC Bus over voltage protection, 1ms */
    BLDC_Over_Voltage_Check(&g_bldc_protector, g_bldc_adSample.busVoltageTrue);
#endif

    if (g_bldc_protector.faultFlag.all)//检测到故障位了,电机停转
    {
        BLDC_Stop();
        g_mcStatus = FAULT;
    }

    return;
}

PID

这里只用到了PI调节,比例与积分,其实就是下面公式的实现

PI计算

/*!
* @brief Pid calculate of motor control.
*
* @param[in] pid: pointer to PID_TYPE structure
* @return none
*/
int16_t PID_Calculate(PID_TYPE *pid)
{
    int32_t min;
    int32_t max;

    pid->pPidOutput->errPu = pid->pPidInput->refPu - pid->pPidInput->fbkPu;//误差 设定值-反馈值
    /* proportional term */
    pid->pPidOutput->upPu = MATH_Mpy(pid->pPidOutput->errPu, pid->pPidCoef->kpPu);//比例:kp*误差 再转化成Q15格式
    /* integral term */
    pid->pPidOutput->uiPu += MATH_Mpy(pid->pPidOutput->errPu, pid->pPidCoef->kiPu);//积分:ki*kp 再转化为Q15格式

    /* saturate integral output with a dynamic limit 带有动态限制的饱和积分输出*/
    max = pid->pPidInput->maxPu - pid->pPidOutput->upPu;
    max = Q15_Sat(max, max, 0);

    min = pid->pPidInput->minPu - pid->pPidOutput->upPu;
    min = Q15_Sat(min, 0, min);

    pid->pPidOutput->uiPu = Q15_Sat(pid->pPidOutput->uiPu, max, min);

    /* calculate total output */
    pid->pPidOutput->outPu = pid->pPidOutput->upPu + pid->pPidOutput->uiPu;//计算总的比例 与 积分的变化

    /* saturate total output 饱和总输出 */
    pid->pPidOutput->outPu = Q15_Sat(pid->pPidOutput->outPu, pid->pPidInput->maxPu, pid->pPidInput->minPu);

    return (int16_t)pid->pPidOutput->outPu;
}

有几个疑问的计算公式:

#define Q15_Max(x, y)                   (x > y ? y : x)//很显然不太对啊这
#define Q15_Min(x, y)                   (x < y ? y : x)
#define Q15_Sat(A, P, N)                (Q15_Max(Q15_Min(A, N), P))//取最大值?

速度环

/*!
* @brief Speed loop processing of motor control.
*
* @param[in] speed: pointer to SPEED_RAMP_HANDLE structure
* @param[in] pid: pointer to PID_TYPE structure
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @return none
*/
void BLDC_SpeedLoop_Calculate(SPEED_RAMP_TYPE *speed, PID_TYPE *pid, BLDC_VARS_CTRL *bldcCtrl)
{
    Set_BLDC_PidRef(pid, speed->speedTargetRamp);//设定速度参考值
    Set_BLDC_PidFbk(pid, speed->speedFbk);//设定速度反馈值
    bldcCtrl->ibusRefPu = PID_Calculate(pid);//ibusRefPu电流环参考值 是不是意味着其实速度环还是靠电流环来调节占空比的
}

电流环

/*!
* @brief Current loop processing of motor control.
*
* @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
* @param[in] pid: pointer to PID_TYPE structure
* @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
* @return none
*/
void BLDC_CurrentLoop_Calculate(BLDC_VARS_CFG *bldcCfg, PID_TYPE *pid, BLDC_VARS_CTRL *bldcCtrl)
{
    Set_BLDC_PidRef(pid, (bldcCfg->motorDir * bldcCtrl->ibusRefPu));
    Set_BLDC_PidFbk(pid, bldcCtrl->ibusFdkPu);
    bldcCtrl->currentPwmPu = PID_Calculate(pid);
}

归根结底:速度环的控制,最后都要换在电流环上。

Contral_api

这个模块是电机控制参数的一些初始化,比如速度环与电流环的PI值、电机初始状态等,没什么特别的。

Bldc_app

这个模块除了一些电机、板子和控制参数的初始化外,还有任务调度方面的函数与代码,这里重点讲一讲。

电机状态切换

/*!
* @brief The Main state machine, used to set the running state of the bldc control system.
*
* @param[in] none
* @return none
*/
void BldcStateMachine(void)
{
    switch (g_mcStatus)
    {
    case POWER_ON:
    BLDC_Parameters_Initialize();
//    BLDC_ASR_CommandInit(pBldcVarsCfg, &g_bldc_speedCmd);
    BLDC_ASR_CommandSet(pBldcVarsCfg, &g_bldc_speedCmd);
    #if (defined DATA_ACCESS_FLASH_ENABLE)
    g_flashOperationStatus = Eflash_DataAccess_DefaultSave(&g_dataAccessFlash);
    if (g_flashOperationStatus == EFLASH_STATUS_SUCCESS)
    {
        Eflash_DataAccess_PowerOnRecovery(&g_dataAccessFlash);
        Eflash_DataAccessEflashInit(&g_dataAccessFlash);
        g_mcStatus = IDLE;
    }
    #else
    g_mcStatus = IDLE;
    #endif
    break;

    case IDLE:
    BLDC_Parameters_Initialize();
    #if (defined DATA_ACCESS_FLASH_ENABLE)
    if (g_dataAccessFlash.eraseEnable == 1)
    {
        g_mcStatus = FLASH_TASK;
    }
    #endif

    if ((g_bldc_speedCmd.speedTarget * pBldcVarsCfg->motorDir) < 0)
    {
        g_bldc_speedCmd.speedTarget = 0 - g_bldc_speedCmd.speedTarget;
        g_bldc_speedCmd.speedTargetRamp = 0 - g_bldc_speedCmd.speedTargetRamp;
    }

    break;

    case START:
    BLDC_Startup(&g_bldcVarsCtrl, pBldcVarsCfg);
    if (BLDC_Get_Startup_Status(&g_bldcVarsCtrl) == STARTUP_READY)
    {
        g_mcStatus = RUN;
    }
    else if (BLDC_Get_Startup_Status(&g_bldcVarsCtrl) == STARTUP_FAIL)
    {
        g_mcStatus = IDLE;
    }
    else
    {

    }
    break;

    case RUN:
    BLDC_SpeedTask();
    break;

    case STOP:
    BLDC_Stop();
    BLDC_Parameters_Initialize();
//    BLDC_ASR_CommandInit(pBldcVarsCfg, &g_bldc_speedCmd);
    BLDC_ASR_CommandSet(pBldcVarsCfg, &g_bldc_speedCmd);
    g_mcStatus = IDLE;
    break;

    case FAULT:
    BLDC_Stop();
    if (g_bldc_protector.faultFlag.all == 0)
    {
        g_mcStatus = STOP;
    }
    break;

    #if (defined DATA_ACCESS_FLASH_ENABLE)
    case FLASH_TASK:
    g_flashOperationStatus = Eflash_DataAccessEflashProcess(&g_dataAccessFlash);
    if (g_flashOperationStatus == EFLASH_STATUS_SUCCESS)
    {
        g_mcStatus = IDLE;

        g_flashOperationStatus = EFLASH_STATUS_ACK;
        Eflash_DataAccessEflashInit(&g_dataAccessFlash);
        g_flashTaskFinish = 1;
    }
    break;
    #endif

    default:
    break;
    }
}

task_scheduler

此文件提供基于毫秒时基函数的任务调度。

核心

最关键的核心函数

/*!
* @brief Task scheduler main processer, called by Main function.
*
* @param[in] none
* @return none
*/
void Task_Scheduler(void)
{
    /* Notice: the delay of each task sequency cannot be the same! */
    /*
    * We can add a new time base task as the follow type:
    * SCH_Add_Task(New_task_function, task_sequence_number, task_time_base);
    * where New_task_function is the name of the task name;
    * task_sequence_number is the position of the task in all task sequences;
    * task_time_base is the time base of the task, indicating its execution cycle.
    */
    /* 1ms time base tasks */
    SCH_Add_Task(Task0_1ms, 0, 1);
    /* 2ms time base tasks */
    SCH_Add_Task(Task1_2ms, 1, 2);
    /* 10ms time base tasks, reserve */
    SCH_Add_Task(Task2_10ms, 2, 10);
    /* 100ms time base tasks, reserve */
    SCH_Add_Task(Task3_100ms, 3, 100);
    /* 1s time base tasks, reserve */
    SCH_Add_Task(Task4_1000ms, 4, 1000);
    /* 2s time base tasks, only used in FOC */
    SCH_Add_Task(Task5_2000ms, 5, 2000);

    while (1)
    {
        SCH_Dispatch_Tasks();//任务调度
        MC_Keys_Read();//按键扫描
    }
}

任务调度

*!
* @brief Dispatch the tasks.
*
* @param[in] none
* @return none
*/
void SCH_Dispatch_Tasks(void)
{
    uint8_t index;
    /* Dispatches (runs) the next task (if one is ready) */
    for (index = 0; index < SCH_TASKS_SEQUENCE_MAX; index++)
    {
        if (g_SchTask[index].runOrder > 0)
        {
            /* Run the task */
            (*g_SchTask[index].pTask)();
            /* Reset / reduce runOrder flag */
            g_SchTask[index].runOrder -= 1;
            /* Periodic tasks will be scheduled to run again */

            /* if this is a 'one shot' task, remove it from the array */
            if (g_SchTask[index].period == 0)
            {
                SCH_Delete_Task(index);
            }
        }
    }

    /* Report system status */
    SCH_Report_Status();
}

key

这个就是按键控制逻辑了,就一个函数:

/*!
* @brief Get the response of pressing each key on the control board.
*        The keys action are judged by the ADC value:
*        when ADC value is in [ 0, START_STOP_MAX ], it corresponds to the Start / Stop key;
*        when ADC value is in [ FORWARD_REVERSE_MIN, FORWARD_REVERSE_MAX ], it corresponds to the Forward / Reverse key;
*        when ADC value is in [ FAST_MIN, FAST_MAX ], it corresponds to the Fast key;
*        when ADC value is in [ SLOW_MIN, SLOW_MAX ], it corresponds to the Slow key.
*
* @param[in] none
* @return none
*/
void MC_Keys_Read(void)
{
    if ((!GPIO_GetPinValue(KEY_START_STOP)) && (!s_keyStartPressed))//开始、停止
    {
        s_keyStartPressed = 1;
    }
    else if (s_keyStartPressed && GPIO_GetPinValue(KEY_START_STOP))
    {
        s_keyStartPressed = 0;
        if (g_mcStatus == IDLE)
        {
            g_mcStatus = START;
        }
        else
        {
            g_mcStatus = STOP;
        }
    }
    else if ((!GPIO_GetPinValue(KEY_DIRECTION)) && (!s_keyDirPressed))//方向
    {
        s_keyDirPressed = 1;
    }
    else if ((GPIO_GetPinValue(KEY_DIRECTION)) && (s_keyDirPressed))
    {
        s_keyDirPressed = 0;
        if (pBldcVarsCfg->motorDir == 1)
        {
            pBldcVarsCfg->motorDir = -1;
        }
        else
        {
            pBldcVarsCfg->motorDir = 1;
        }
//            g_bldc_speedCmd.speedTarget = MATH_Abs(g_bldc_speedCmd.speedTarget) * pBldcVarsCfg->motorDir;
    }
    else if ((!GPIO_GetPinValue(KEY_SPEED_UP)) && (!s_keyUpPressed))//加速
    {
        s_keyUpPressed = 1;
    }
    else if ((GPIO_GetPinValue(KEY_SPEED_UP)) && (s_keyUpPressed))
    {
        s_keyUpPressed = 0;
        g_bldc_speedCmd.speedTarget += (pBldcVarsCfg->motorDir * SPEED_CHANGE_STEP);
    }
    else if ((!GPIO_GetPinValue(KEY_SPEED_DOWN)) && (!s_keyDownPressed))//减速
    {
        s_keyDownPressed = 1;
    }
    else if ((GPIO_GetPinValue(KEY_SPEED_DOWN)) && (s_keyDownPressed))
    {
        s_keyDownPressed = 0;
        g_bldc_speedCmd.speedTarget -= (pBldcVarsCfg->motorDir * SPEED_CHANGE_STEP);
    }
    else
    {
        /* do nothing */
    }
}

BLDC无感方波控制函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/138607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云游戏三重门:体验、生态和硬件

配图来自Canva可画 如今硬件已经成为制约游戏体验的最主要因素之一。而云游戏的提出&#xff0c;则被看做是解决这种矛盾的其中一种重要手段。对于玩家来说&#xff0c;云游戏意味着既不再需要价格高昂的硬件设备&#xff0c;也不再需要体量庞大的游戏本体&#xff0c;就能获得…

ssd重装系统的详细教程

当我们给电脑更换安装了新的固态硬盘&#xff0c;原来的系统没有了。那么电脑新固态ssd怎么安装win7系统&#xff0c;下面小编就教下大家ssd重装系统教程&#xff0c;希望大家都可以学会哦。 工具/原料&#xff1a; 系统版本&#xff1a;Windows7 品牌型号&#xff1a;惠普星…

PDF怎么转换成excel免费?快收藏这几个方法

在我们日常处理的的工作文件中&#xff0c;PDF文件的数量是越来越高的&#xff0c;而且因为PDF文件比较方便观看&#xff0c;所以很多数据文件也都是PDF格式的&#xff0c;不过PDF文件是不可编辑的&#xff0c;所以在一定程度上还是有限制的&#xff0c;这样我们就不能及时修改…

数据的存储(2)大小端字节序存储

TIPS 1. 2. 3. *是解应用操作符&#xff0c;*指针变量&#xff0c;对指针变量进行解应用操作&#xff0c;固然没问题。但是要知道的是&#xff1a;也可以直接对最最原始的地址进行解应用操作&#xff0c;如*字符串常量&#xff0c;*数组名&#xff0c;*&a等等&#…

云开发项目如何管理资产下的设备?

在开发 SaaS 应用前&#xff0c;开发者需要先在 涂鸦 IoT 开发平台 上创建云开发项目并进行必要的配置。下面我将为大家介绍管理资产的详细操作方法。 云开发支持以资产为维度&#xff0c;对不同资产下的设备进行权限分隔和资产内设备的统一管理。 新建资产 资产&#xff08…

dpdk-lvs的一次线上故障排查报告

背景 我们内部基于 dpdk 自研的高性能负载均衡器 dpdk-lvs 已经在多个机房部署上线&#xff0c;运行正常&#xff0c;但近期有多个金融相关的业务反馈&#xff0c;服务数据包在经过dpdk-lvs转发后&#xff0c;会出现hang住的情况。 问题 1、dpdk-lvs 已经在多个机房上线&…

服务搭建篇(五) Redis单机/redis-cluster集群搭建

一. Redis集群简介 redis集群是一个由多个主从节点群组成的分布式服务器群&#xff0c;它具有复制、高可用和分片特性。Redis集群不需要sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式&#xff0c;这种集群模式没有中心节点&#xff0c;可水平扩…

C语言重点解剖指针和数组要点速记

1.指针指向的是最低字节地址。 2.每一次跑程序&#xff0c;变量的地址都会是随机的&#xff0c;这是一种保护机制。基本上不可以使用地址直接访问变量。 3.以下是一段有意思的代码。 4.在栈上开辟变量&#xff0c;地址由高到低变化&#xff0c;值得注意的是&#xff0c;不是连…

云原生|kubernetes|kube-bench安全检测工具的部署和使用

前言&#xff1a; 安全是一个绕不开的话题&#xff0c;那么&#xff0c;在云原生领域&#xff0c;在kubernetes内更加的需要安全。毕竟没有人愿意自己的项目是千疮百孔&#xff0c;适当的安全可以保证项目或者平台稳定高效的运行。 安全性是一个永远不会消失的问题&#xff0c…

基于springboot+mybatis+mysql+vue软件缺陷管理系统

基于springbootmybatismysqlvue软件缺陷管理系统一、系统介绍二、功能展示1.主页2.个人中心3.缺陷管理4.项目管理5.系统管理6.统计分析三、代码展示四、其它1.其他系统实现2.获取源码一、系统介绍 系统主要功能&#xff1a; 开发人员&#xff1a;主页、个人中心&#xff08;我…

Java记录2:Java的三种注释类型

Java 注释 文章目录Java 注释一、单行注释 //二、多行注释 /*三、文档注释用于注解说明解释程序的文字就是注释&#xff0c;注释提高了代码的阅读性&#xff08;可读性&#xff09;。   注释是一个程序员必须要具有的良好编程习惯。将自己的思想通过注释先整理出来&#xff0…

EasyExcel写数字格式数据默认为科学计数法解决方案

问题背景 我们项目有个场景是excel下载&#xff0c;需要将数字类型的数据由字符格式转化为数字格式&#xff0c;但由于数字较长&#xff0c;利用easyExcel写入文件后&#xff0c;数字类型会默认展示为科学计数法。下面我们来看看demo演示。 demo演示 准备以下代码 public c…

2015-2022机器人方向课程教学评价成绩和任务汇总

←机器人工程或机器人方向毕业设计汇总篇→↓2022↑https://zhangrelay.blog.csdn.net/article/details/124856849ROS机器人程序设计课程反思-2022终篇-https://blog.csdn.net/ZhangRelay/article/details/127295957由于系统更新&#xff0c;分为两个部分2015-20182018-2022学生…

2023首场CSDN直播丨顶象邀您共话验证码顶层能力设计

1.12 15:00 验证码技术解析——业务安全大讲堂直播https://live.csdn.net/room/dingxiangtech/gqzj6MEr “我不是机器人”本应是不言自明的事情。但随着黑灰产的出现&#xff0c;诈骗行为和刷票行为呈指数级增长。 为了遏制这种现象&#xff0c;第一代验证码出现了。由此也开…

你需要知道的无代码数据分析工具

Nov. 2022, Vincy当今市场上有无数种的无代码分析工具&#xff0c;允许开发人员和非开发人员使用拖放的方式构建图表和仪表盘。此列表涵盖了 4 种针对不同的用例和行业的无代码数据分析工具。这些工具可以帮助用户节省时间并根据数据做出明智的决策。Footprint AnalyticsFootpr…

burp抓包mumu模拟器

1、生成android系统证书 step1:burp导出der证书 step2:生成pem格式证书 openssl x509 -inform DER -in mybpcert.der -out PortSwiggerCA.pem 生成hash并显示 openssl x509 -inform PEM -subject_hash_old -in PortSwiggerCA.pem|head -1 mv PortSwiggerCA.pem 9a5ba575.0…

mysql 存储过程批量删除重复数据

mysql 存储过程批量删除重复数据 表结构&#xff1a; LOAD DATA INFILE /usr/local/phone_imsi_12 replace INTO TABLE tbl_imsi2number_new FIELDS TERMINATED BY \t ENCLOSED BY (number,imsi); 先用SQL语句来进行去重操作&#xff1a; delete from tbl_imsi2number_new …

115.(leaflet之家)leaflet空间判断-点与矩形的空间关系

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>

PCBA方案——红外医用额温枪方案

本方案讲述一款国产8位CMOS单芯片ZHW3548开发的额温枪方案&#xff0c;该CMOS芯片内置8K*16bits一次性可编程OTP ROM&#xff08;只能分为4K和4K两次使用&#xff09;&#xff0c;内置256*8bits数据存储器SRAM&#xff0c;一个带有1路全差分模拟信号输入的24位ADC&#xff0c;低…

【LeetCode每日一题】——258.各位相加

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【题目进阶】九【时间频度】十【代码实现】十一【提交结果】一【题目类别】 数学 二【题目难度】 简单 三【题目编号】 258.各位相加 四【题目描述】 给…