BLDC控制框图

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

实验波形图

六步控制PWM与换相

image.png

反电动势过零检测波形

image.png
image.png

问题

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

image.png
问题都出现在spm.h这个文件里,declaration is incompatible 定义不兼容
后来发现,这个头文件在即在keil的了 AC78xx 系列 MCU 的 CMSIS pack中有,又在你自己的工程文件里有,类似于重复定义,定义不兼容。
所以办法是,把包含这个头文件的路径删除。
image.png
编译成功
image.png

软件工程架构

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

启动阶段

该模块主要是为了在启动阶段,不知道转子位置的情况下让电机先转起来,有以下两种流程:
1、预定位->开环->开环切换到闭环
2、预定位->直接闭环
BLDC无感控制 - 图11

无感启动预定位阶段

我们都只知道对于无感控制来讲,获取转子位置是非常重要的;
而对于BLDC的无感控制,最常用的的方法是:六部换相+反电动势过零点检测。
但是要想测反电动势测得准,精度高,就需要反电动势数值大。
这就要求在起步阶段要先把电机拉到指定位置上,先让它转起来(开环控制),等到能准确获取反电动势过零点的时候,再切换到无感控制上(闭环)。
那么怎么把电机拉到指定位置上呢?具体实现代码如下:

  1. /*!
  2. * @brief Pre-position function in bldc sensorless control.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @return none
  6. */
  7. void BLDC_Preposition(BLDC_VARS_CTRL *bldcCtrl)
  8. {
  9. uint8_t delay = INIT_PREPOSITION_DELAY ;
  10. for (bldcCtrl->setDuty = MIN_DUTY_PREPOSITION; bldcCtrl->setDuty < MAX_DUTY_PREPOSITION; bldcCtrl->setDuty += STEP_DUTY_PREPOSITION)
  11. {
  12. delay -= INIT_PREPOSITION_DELAY_STEP;
  13. if (delay <= MIN_PREPOSITION_DELAY)
  14. {
  15. delay = MIN_PREPOSITION_DELAY;
  16. }
  17. UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));
  18. bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
  19. MosDriver(bldcCtrl->pBemf->currentDriverVector);
  20. mdelay(delay);
  21. }
  22. bldcCtrl->pBemf->kickTimeThreshold = BLDC_SENSORLESS_INIT_DELAY_TIME;
  23. }

其中核心的命令就一个:

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

其实就是直接选六步中的一步,如,这里选择的是A+B-。
image.png
这样就可以把转子拉到指定位置上了。

无感启动开环阶段

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

  1. /*!
  2. * @brief bldc openkick function in bldc sensorless control, it is usually executed after obtaining the initial rotor position.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void BLDC_Bemf_OpenKick(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. if (bldcCfg->motorDir != 1)//反转
  11. {
  12. bldcCtrl->pBemf->currentDriverVector--;
  13. if (bldcCtrl->pBemf->currentDriverVector <= (uint8_t)INVALID_VECTOR_MIN)
  14. {
  15. bldcCtrl->pBemf->currentDriverVector = (uint8_t)CH_PWM_BL_ON;
  16. }
  17. MosDriver(bldcCtrl->pBemf->currentDriverVector);
  18. }
  19. else//正转
  20. {
  21. bldcCtrl->pBemf->currentDriverVector++;
  22. if (bldcCtrl->pBemf->currentDriverVector >= (uint8_t)INVALID_VECTOR_MAX)
  23. {
  24. bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
  25. }
  26. MosDriver(bldcCtrl->pBemf->currentDriverVector);
  27. }
  28. if (bldcCtrl->setDuty <= MAX_DUTY_SENSORLESS_START)
  29. {
  30. bldcCtrl->setDuty += STARTUP_DUTY_STEP;
  31. }
  32. else
  33. {
  34. bldcCtrl->setDuty = MAX_DUTY_SENSORLESS_START;
  35. }
  36. UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));
  37. }

其实就是六步换相,刚刚我们已将把转子拉到下图这个位置上了
image.png
下一步我们要先判断是让电机正转还是反转:
正转就按图示向下的顺序,当其大于INVALID_VECTOR_MAX时,拉回到最开始位置;
反转就图示向上的顺序,当其小于INVALID_VECTOR_MIN时,就跳到最后的位置。

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

  1. if (bldcCtrl->setDuty <= MAX_DUTY_SENSORLESS_START)
  2. {
  3. bldcCtrl->setDuty += STARTUP_DUTY_STEP;
  4. }
  5. else
  6. {
  7. bldcCtrl->setDuty = MAX_DUTY_SENSORLESS_START;
  8. }
  9. UpDatePwmDuty(DUTY_PERIOD(bldcCtrl->setDuty));

启动开环阶段完成。

从开环切入闭环控制

  1. /*!
  2. * @brief drag motor to RUN state through open kick.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void MotorKickDrag(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. if (bldcCtrl->pBemf->kickTimeThreshold > BLDC_SENSORLESS_MIN_DELAY_TIME)
  11. {
  12. bldcCtrl->bldcStartTime++;
  13. if (bldcCtrl->bldcStartTime > bldcCtrl->pBemf->kickTimeThreshold)
  14. {
  15. bldcCtrl->bldcStartTime = 0;
  16. if (bldcCtrl->pBemf->kickTimeThreshold > BLDC_SENSORLESS_MIN_DELAY_TIME)
  17. {
  18. bldcCtrl->pBemf->kickTimeThreshold -= BLDC_SENSORLESS_STEP_DELAY_TIME;
  19. }
  20. else
  21. {
  22. bldcCtrl->pBemf->kickTimeThreshold = BLDC_SENSORLESS_MIN_DELAY_TIME;
  23. }
  24. BLDC_Bemf_OpenKick(&g_bldcVarsCtrl, pBldcVarsCfg);
  25. }
  26. }
  27. else
  28. {
  29. if ((bldcCtrl->pBemf->currentDriverVector + bldcCfg->motorDir) >= (uint8_t)INVALID_VECTOR_MAX)
  30. {
  31. bldcCtrl->pBemf->currentDriverVector = (uint8_t)AH_PWM_BL_ON;
  32. }
  33. else if ((bldcCtrl->pBemf->currentDriverVector + bldcCfg->motorDir) <= (uint8_t)INVALID_VECTOR_MIN)
  34. {
  35. bldcCtrl->pBemf->currentDriverVector = (uint8_t)CH_PWM_BL_ON;
  36. }
  37. else
  38. {
  39. bldcCtrl->pBemf->currentDriverVector += bldcCfg->motorDir;
  40. }
  41. MosDriver(bldcCtrl->pBemf->currentDriverVector);
  42. SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);
  43. bldcCtrl->pBemf->changePhaseTime = 0;
  44. bldcCtrl->pBemf->saveChangePhaseTime = FIRST_COMMUTATE_TIME;
  45. bldcCtrl->pBemf->afterFlowTime = FIRST_STEP_DRAG_TIME;
  46. bldcCtrl->pBemf->forceChangePhaseFlag = 0;
  47. bldcCtrl->startupStatus = STARTUP_READY;
  48. }
  49. }

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

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

  1. /*!
  2. * @brief BLDC motor startup function, include bldc hall and bldc sensorless control.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void BLDC_Startup(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. #if (defined BLDC_HALL)
  11. if (bldcCtrl->bldcStartTime == 0)
  12. {
  13. BLDC_Hall_Startup(bldcCtrl, bldcCfg, &g_bldc_speedCmd);
  14. }
  15. if (bldcCtrl->bldcStartTime++ > 20)
  16. {
  17. bldcCtrl->bldcStartTime = 0;
  18. }
  19. #elif (defined BLDC_SENSORLESS)
  20. if (bldcCtrl->pBemf->prepositonIndex < 3) /* preposition */
  21. {
  22. bldcCtrl->pBemf->prepositonIndex++;
  23. BLDC_Preposition(bldcCtrl);
  24. }
  25. else /* drag startup */
  26. {
  27. #if (defined NOKICK_STARTUP)
  28. MotorDirectDrag(bldcCtrl);
  29. #else
  30. MotorKickDrag(bldcCtrl, bldcCfg);
  31. #endif
  32. }
  33. #else
  34. #endif
  35. }

咱们不看hall那部分,只看下边无感部分。
上来先调用了三次预定位函数,三次过后,判断你是直接切入的闭环阶段,还是经过开环再切入的闭环。
以上便是启动阶段的所有代码了

直接进入闭环

  1. /*!
  2. * @brief drag motor dircet to RUN state, get into close loop control fast.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @return none
  6. */
  7. void MotorDirectDrag(BLDC_VARS_CTRL *bldcCtrl)
  8. {
  9. UpDatePwmDuty(DUTY_PERIOD(DRAG_DUTY));
  10. MosDriver(bldcCtrl->pBemf->currentDriverVector);
  11. SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);
  12. bldcCtrl->pBemf->changePhaseTime = 0;
  13. bldcCtrl->pBemf->saveChangePhaseTime = FIRST_COMMUTATE_TIME;
  14. bldcCtrl->pBemf->afterFlowTime = FIRST_STEP_DRAG_TIME;
  15. bldcCtrl->pBemf->forceChangePhaseFlag = 0;
  16. bldcCtrl->startupStatus = STARTUP_READY;
  17. }

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

在无感BLDC控制中,采集到的三相反电动势电机中值电压ACMP中进行比较,所以这就牵涉到三相反电动势采集时候的切换,采用轮询的方式与电机中值电压比较。
so,什么时候切换通道,进行轮询?
切换函数如下:

  1. /*!
  2. * @brief switch acmp compared channel according to current magnetic vector, it is usually executed in bldc sensorless control.
  3. *
  4. * @param[in] curDriverValue: current magnetic vector in bldc sensorless control
  5. * @return none
  6. */
  7. void SwitchAcmpChannel(uint8_t curDriverValue)
  8. {
  9. switch (curDriverValue)
  10. {
  11. case AH_PWM_BL_ON:
  12. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 2);
  13. break;
  14. case AH_PWM_CL_ON:
  15. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 1);
  16. break;
  17. case BH_PWM_CL_ON:
  18. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 0);
  19. break;
  20. case BH_PWM_AL_ON:
  21. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 2);
  22. break;
  23. case CH_PWM_AL_ON:
  24. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 1);
  25. break;
  26. case CH_PWM_BL_ON:
  27. ACMP_PositiveInputSelect(ACMP_CHANNEL0, 0);
  28. break;
  29. default:
  30. break;
  31. }
  32. }

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

Bemf

这个模块主要功能是实现反电动势过零点检测并实现换相。
BLDC无感控制 - 图14

过零点检测代码实现

  1. /*!
  2. * @brief Check black EMF over zero point to determine when to change phase.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void BemfOverZeroCheck(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. if (bldcCfg->motorDir == 1)
  11. {
  12. switch (bldcCtrl->pBemf->currentDriverVector)
  13. {
  14. case AH_PWM_BL_ON:
  15. if (C_PHASE_BEMF == 0)
  16. {
  17. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  18. }
  19. break;
  20. case AH_PWM_CL_ON:
  21. if (B_PHASE_BEMF != 0)
  22. {
  23. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  24. }
  25. break;
  26. case BH_PWM_CL_ON:
  27. if (A_PHASE_BEMF == 0)
  28. {
  29. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  30. }
  31. break;
  32. case BH_PWM_AL_ON:
  33. if (C_PHASE_BEMF != 0)
  34. {
  35. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  36. }
  37. break;
  38. case CH_PWM_AL_ON:
  39. if (B_PHASE_BEMF == 0)
  40. {
  41. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  42. }
  43. break;
  44. case CH_PWM_BL_ON:
  45. if (A_PHASE_BEMF != 0)
  46. {
  47. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  48. }
  49. break;
  50. }
  51. }
  52. else
  53. {
  54. switch (bldcCtrl->pBemf->currentDriverVector)
  55. {
  56. case AH_PWM_BL_ON:
  57. if (C_PHASE_BEMF != 0)
  58. {
  59. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  60. }
  61. break;
  62. case AH_PWM_CL_ON:
  63. if (B_PHASE_BEMF == 0)
  64. {
  65. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  66. }
  67. break;
  68. case BH_PWM_CL_ON:
  69. if (A_PHASE_BEMF != 0)
  70. {
  71. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  72. }
  73. break;
  74. case BH_PWM_AL_ON:
  75. if (C_PHASE_BEMF == 0)
  76. {
  77. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  78. }
  79. break;
  80. case CH_PWM_AL_ON:
  81. if (B_PHASE_BEMF != 0)
  82. {
  83. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  84. }
  85. break;
  86. case CH_PWM_BL_ON:
  87. if (A_PHASE_BEMF == 0)
  88. {
  89. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  90. }
  91. break;
  92. }
  93. }
  94. }

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

  1. switch (bldcCtrl->pBemf->currentDriverVector)
  2. {
  3. case AH_PWM_BL_ON:
  4. if (C_PHASE_BEMF == 0)
  5. {
  6. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  7. }
  8. break;
  9. }

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

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

C向的反电动势与电机中值电压比较之后的结果。
判断为0后,开始准备换相,就是这个命令:

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

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

就是这个函数PrepareNextChangePhase():
下一次换相其实没什么特别的,还是根据正反转,对六步换相++—

  1. if (motorDir == 1)
  2. {
  3. curDriverValue++;
  4. if (curDriverValue >= (uint8_t)INVALID_VECTOR_MAX)
  5. {
  6. curDriverValue = (uint8_t)AH_PWM_BL_ON;
  7. }
  8. }
  9. else
  10. {
  11. curDriverValue--;
  12. if (curDriverValue <= (uint8_t)INVALID_VECTOR_MIN)
  13. {
  14. curDriverValue = (uint8_t)CH_PWM_BL_ON;
  15. }
  16. }

重要的在后边:

  1. g_bemfControl.nextDriverVector = curDriverValue;
  2. g_bemfControl.delayTime = (3 * g_bemfControl.saveChangePhaseTime) >> 3;
  3. g_bemfControl.overZero_Flag = 1;

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

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

要先捋一下这几个换相时间:
changePhaseTime 当前换相时间(其实就是计数器的计数值,一个计数单元是12us)
saveChangePhaseTime当前换相时间,存下来为换相延时用
lastChangePhaseTime上一次换相时间,用来确定FlowTime?
afterFlowTime不检测过零点时,FlowTime之后
aveChangePhaseTime当前换相时间的平均值
delayTime换相延迟时间
commutationTime换相时间的缓冲区
BLDC无感控制 - 图15
越理感觉越乱,什么鬼这是。
是不是这个saveChangePhaseTime,指的是上次换相到这次换相之间的时间间隔?
image.png
也就是两个红线之间的时间。

开始换相

在知道下一次换相目标和需要的延时事件后,开始换相
先判断电机状态是不是‘准备好’了:

  1. if (g_mcStatus == RUN)

之后直接执行换相

  1. MosDriver(bldcCtrl->pBemf->nextDriverVector);

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

  1. SwitchAcmpChannel(bldcCtrl->pBemf->currentDriverVector);

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

  1. bldcCtrl->pBemf->lastChangePhaseTime = bldcCtrl->pBemf->saveChangePhaseTime;
  2. bldcCtrl->pBemf->saveChangePhaseTime = bldcCtrl->pBemf->changePhaseTime;
  3. bldcCtrl->pBemf->afterFlowTime = ((bldcCtrl->pBemf->saveChangePhaseTime + bldcCtrl->pBemf->lastChangePhaseTime)) >> 3;
  4. ChangePhaseTimeCalc(&g_bldcVarsCtrl);
  5. bldcCtrl->pBemf->changePhaseTime = 0;
  6. bldcCtrl->pBemf->overZero_Flag = 0;
  7. bldcCtrl->pBemf->afterFlow_Flag = 0;
  8. bldcCtrl->pBemf->forceChangePhaseFlag = 0;

强制换相

检查一下换相间隔,是否需要强制换相。
FORCE_COMMUTATE_TIME强制换相的时间阈值,超过这个时间阈值,强制换相标志位置1.

  1. /*!
  2. * @brief Check change phase interval to determine whether forced change phase is in needed.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void ForceChangePhaseCheck(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. if (bldcCtrl->pBemf->forceChangePhaseFlag == 0)
  11. {
  12. if (bldcCtrl->pBemf->changePhaseTime > FORCE_COMMUTATE_TIME)
  13. {
  14. bldcCtrl->pBemf->forceChangePhaseFlag = 1;
  15. bldcCtrl->pBemf->afterFlow_Flag = 1;
  16. if (bldcCtrl->pBemf->overZero_Flag == 0)
  17. {
  18. PrepareNextChangePhase(bldcCfg->motorDir, bldcCtrl->pBemf->currentDriverVector);
  19. }
  20. }
  21. }
  22. }

串联起来

  1. /*!
  2. * @brief Bemf over zero point detect and change phase function.
  3. *
  4. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  5. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  6. * @return none
  7. */
  8. void BemfCheckFun(BLDC_VARS_CTRL *bldcCtrl, BLDC_VARS_CFG *bldcCfg)
  9. {
  10. ForceChangePhaseCheck(bldcCtrl, bldcCfg);//看看需不需要强制换相
  11. if (bldcCtrl->pBemf->afterFlow_Flag == 0)
  12. {
  13. if (bldcCtrl->pBemf->changePhaseTime > bldcCtrl->pBemf->afterFlowTime)
  14. {
  15. bldcCtrl->pBemf->afterFlow_Flag = 1;
  16. }
  17. }
  18. else//不需要强制换相,检查检查过零点
  19. {
  20. if (bldcCtrl->pBemf->overZero_Flag == 0)
  21. {
  22. BemfOverZeroCheck(bldcCtrl, bldcCfg);
  23. }
  24. else//检查到过零点标志位了
  25. {
  26. if (bldcCtrl->pBemf->delayTime > 0)//实现具体的30电角度的延时
  27. {
  28. bldcCtrl->pBemf->delayTime--;
  29. }
  30. if ((bldcCtrl->pBemf->delayTime == 0) || (bldcCtrl->pBemf->forceChangePhaseFlag == 1))
  31. {//延时时间为0了,或者需要强制换相了,开始执行换相
  32. DisableInterrupts//换相的时候,一定要保证不能被中断给打断了
  33. BldcBemfChangePhase(bldcCtrl);
  34. EnableInterrupts
  35. }
  36. }
  37. }
  38. }

ADC

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

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

    获取总线电压

    ```c /*!
  • @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倍的。

    获取总线电流

    这个要稍微复杂一些,因为牵涉到一个过流的保护判断,还有滤波 ```c /*!
  • @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 {

    1. if (adc->busCurAD > (int16_t)(adc->busCurOffset + LIMIT_PEAK_CUR_AD))//是的话,再判断有没有超过设定的峰值电流值
    2. {
    3. adc->busOverCurCnt++;//超过了,开始计次,看超过了几次,后边保护策略中会有判断超过10次,过流保护
    4. }
    5. else//没有超过电流峰值,要先把计数值清零
    6. {
    7. if (adc->busOverCurCnt-- <= 0)
    8. {
    9. adc->busOverCurCnt = 0;
    10. }
    11. }
    12. adc->busCurAD = adc->busCurAD - adc->busCurOffset;//如果没有超过设定值,那么busCurAD就是 采集到的AD值-零点漂移

    } else {

    1. 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调用

    ```c /*!

  • @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软件同步 ```c /*!

  • @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占空比输出值,这个函数就是计算出实际的占空比输出。 ```c /*!

  • @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

  1. temp_PwmCh0Val = PWM2->CHANNELS[0].CHV;
  2. temp_PwmCh1Val = PWM2->CHANNELS[1].CHV;
  3. temp_PwmMcvr = PWM2->MCVR;//MCVR是PWM计数器最大计数值
  4. #if (PHASE_UVW_POLARITY == ACTIVE_HIGH)
  5. bldcCtrl->pwmOutDuty = 10000 * (temp_PwmCh1Val - temp_PwmCh0Val) / temp_PwmMcvr;//这一部分牵涉到PWM的 组合输出模式 看下面的举例
  6. #else
  7. bldcCtrl->pwmOutDuty = 10000 * (temp_PwmCh0Val - temp_PwmCh1Val) / temp_PwmMcvr;
  8. #endif

endif

  1. /* enlarge 10000 times, 1234 means 12.34% */
  2. return bldcCtrl->pwmOutDuty;

}

  1. <a name="mead0"></a>
  2. ### PWM的组合输出模式
  3. 在组合模式下,将偶数通道(n)和相邻的奇数通道(n + 1)组合以在通道(n)输出中产生 PWM 信号。在组合模式下,PWM 周期由(MCVR - CNTIN + 0x0001) 确定 ,且 PWM 脉冲宽度(占空比)由(|CH(n+1)V - CH(n)V|)确定。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/29409159/1669344993754-aaf38c0f-d2dd-4002-9ec1-cd6b5ad50df0.png#averageHue=%23f7f5f1&clientId=ua71c8c13-203c-4&from=paste&height=151&id=ufa17c8ad&originHeight=169&originWidth=823&originalType=binary&ratio=1&rotation=0&showTitle=false&size=67740&status=done&style=none&taskId=uaf971508-ee8d-4cc3-940d-8ecf8d150c6&title=&width=737)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/29409159/1669345033945-1932a8d0-6115-4575-b009-5b83f827f7a4.png#averageHue=%23cba97d&clientId=ua71c8c13-203c-4&from=paste&height=236&id=u03a3e20e&originHeight=342&originWidth=787&originalType=binary&ratio=1&rotation=0&showTitle=false&size=19117&status=done&style=none&taskId=u330cf1b5-c588-4861-a50f-e786954d5c7&title=&width=542)<br />用在这里就是:10000*(3-1)/ 5 = 4000 就是40%的占空比
  4. <a name="VC0iT"></a>
  5. ## PWM占空比的更新(正常情况与电流PID)
  6. 这个就没什么可说的,就是调用一下PWM占空比设置函数PWM_SetChannelValue()
  7. ```c
  8. /*!
  9. * @brief Update PWM output duty by manual settig or current PID calculate.
  10. *
  11. * @param[in] pwmDuty: range: 0 ~ PWMx->MCVR, corresponding to 0 ~ 100% duty cycle
  12. * @return none
  13. */
  14. void UpDatePwmDuty(uint16_t pwmDuty)
  15. {
  16. if (pwmDuty > (PWM2->MCVR - 1))
  17. {
  18. pwmDuty = (PWM2->MCVR - 1);
  19. }
  20. else if (pwmDuty < 1)
  21. {
  22. pwmDuty = 1;
  23. }
  24. else
  25. {
  26. }
  27. #if (defined BLDC_AFTER_FLOW_CONTROL)
  28. if (g_bemfControl.commutateState == 1)
  29. {
  30. if (g_bemfControl.changePhaseTime > ((g_bemfControl.afterFlowTime * AFTER_FLOW_ANGLE_NUMERATOR) >> AFTER_FLOW_ANGLE_PSRC))
  31. {
  32. switch (g_bemfControl.currentDriverVector)
  33. {
  34. case AH_PWM_BL_ON:
  35. PWM2->CHOSWCR = CH0_MODULATION_CH3_ON;
  36. break;
  37. case AH_PWM_CL_ON:
  38. PWM2->CHOSWCR = CH0_MODULATION_CH5_ON;
  39. break;
  40. case BH_PWM_CL_ON:
  41. PWM2->CHOSWCR = CH2_MODULATION_CH5_ON;
  42. break;
  43. case BH_PWM_AL_ON:
  44. PWM2->CHOSWCR = CH2_MODULATION_CH1_ON;
  45. break;
  46. case CH_PWM_AL_ON:
  47. PWM2->CHOSWCR = CH4_MODULATION_CH1_ON;
  48. break;
  49. case CH_PWM_BL_ON:
  50. PWM2->CHOSWCR = CH4_MODULATION_CH3_ON;
  51. break;
  52. default:
  53. break;
  54. }
  55. g_bemfControl.commutateState = 2;
  56. }
  57. }
  58. #endif
  59. #if (PWM_MODE_SELECT == PWM_COUNT_UP_DOWN_MODE)
  60. #else
  61. #if (PHASE_UVW_POLARITY == ACTIVE_HIGH)
  62. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL1, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  63. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL3, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  64. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL5, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  65. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL0, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  66. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL2, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  67. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL4, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  68. #else
  69. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL1, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  70. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL3, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  71. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL5, (PWM_PERIOD_VALUE - pwmDuty) >> 1);
  72. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL0, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  73. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL2, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  74. PWM_SetChannelValue(PWM2, PWM_CHANNEL_CHANNEL4, (PWM_PERIOD_VALUE + pwmDuty) >> 1);
  75. #endif
  76. #endif
  77. PWM_SoftwareSync(PWM2);
  78. }

直接控制MOS开关

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

  1. /*!
  2. * @brief update PWM channel output by six step square wave magnetic vector to driver MOS.
  3. *
  4. * @param[in] ChangePhaseVector: current magnetic vector in bldc control
  5. * @return none
  6. */
  7. void MosDriver(uint8_t ChangePhaseVector)
  8. {
  9. #if (defined BLDC_AFTER_FLOW_CONTROL)
  10. AfterFlow_Control(ChangePhaseVector, pBldcVarsCfg->motorDir);
  11. #else
  12. switch (ChangePhaseVector)
  13. {
  14. case AH_PWM_BL_ON:
  15. PWM2->CHOSWCR = CH0_MODULATION_CH3_ON;
  16. PWM_SoftwareSync(PWM2);
  17. break;
  18. case AH_PWM_CL_ON:
  19. PWM2->CHOSWCR = CH0_MODULATION_CH5_ON;
  20. PWM_SoftwareSync(PWM2);
  21. break;
  22. case BH_PWM_CL_ON:
  23. PWM2->CHOSWCR = CH2_MODULATION_CH5_ON;
  24. PWM_SoftwareSync(PWM2);
  25. break;
  26. case BH_PWM_AL_ON:
  27. PWM2->CHOSWCR = CH2_MODULATION_CH1_ON;
  28. PWM_SoftwareSync(PWM2);
  29. break;
  30. case CH_PWM_AL_ON:
  31. PWM2->CHOSWCR = CH4_MODULATION_CH1_ON;
  32. PWM_SoftwareSync(PWM2);
  33. break;
  34. case CH_PWM_BL_ON:
  35. PWM2->CHOSWCR = CH4_MODULATION_CH3_ON;
  36. PWM_SoftwareSync(PWM2);
  37. break;
  38. default:
  39. break;
  40. }
  41. #endif
  42. }

Speed

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

设定目标转速

从rpm到Q15

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

直接Q15

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

设定目标加速度

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

获取目标转速值

  1. /*!
  2. * @brief Get the speed target value.
  3. *
  4. * @param[in] speedRamp: pointer to SPEED_RAMP_TYPE structure
  5. * @return none
  6. */
  7. int16_t BLDC_ASR_GetTargetPu(SPEED_RAMP_TYPE *speedRamp)
  8. {
  9. return speedRamp->speedTarget;
  10. }

获取反馈速度值

以rpm为单位

  1. /*!
  2. * @brief Get the feedback speed unit in Rpm.
  3. *
  4. * @param[in] speed: pointer to SPEED_RAMP_TYPE structure
  5. * @return none
  6. */
  7. int16_t BLDC_ASR_GetFbkRpm(SPEED_RAMP_TYPE *speed)
  8. {
  9. return speed->speedFbkRpm;
  10. }

以pu为单位

  1. /*!
  2. * @brief Get the feedback speed unit in Pu.
  3. *
  4. * @param[in] speed: pointer to SPEED_RAMP_TYPE structure
  5. * @return none
  6. */
  7. int16_t BLDC_ASR_GetFbkPu(SPEED_RAMP_TYPE *speed)
  8. {
  9. return speed->speedFbk;
  10. }

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

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

这个函数实现的是,各一个设定的rpm速度,然后可以计算出到达这一速度,所使用的加速度。
但我一直没搞懂speedTargetRamp这个参数到底什么意思?斜坡函数?加速度?

  1. /*!
  2. * @brief Speed ramp calculation. calculate the ramp output according to the speed target value.
  3. *
  4. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  5. * @param[in] speedRamp: pointer to SPEED_RAMP_TYPE structure
  6. * @return none
  7. */
  8. void BLDC_ASR_RampCalc(BLDC_VARS_CFG *bldcCfg, SPEED_RAMP_TYPE *speedRamp)
  9. {
  10. static uint16_t s_countSpdCmd = 0;
  11. //先判断设定的这个速度有没有超过设定的速度阈值
  12. BLDC_ASR_TargetLimit(bldcCfg->motorDir, speedRamp);
  13. s_countSpdCmd++;
  14. /* calculate speed ramp every 10ms, 9 = 10 - 1, s_countSpdCmd range is 1~10, then back to 0, and becomes to 1 after '++' */
  15. if (s_countSpdCmd > 9)
  16. {
  17. s_countSpdCmd = 0;
  18. if (bldcCfg->motorDir == FORWARD_ROTATE)//正转
  19. {
  20. if ((speedRamp->speedTarget - speedRamp->speedTargetRamp) > speedRamp->speedAcceleration)
  21. {
  22. speedRamp->speedTargetRamp += speedRamp->speedAcceleration;
  23. }
  24. else if ((speedRamp->speedTargetRamp - speedRamp->speedTarget) > speedRamp->speedDeceleration)
  25. {
  26. speedRamp->speedTargetRamp -= speedRamp->speedDeceleration;
  27. }
  28. else
  29. {
  30. speedRamp->speedTargetRamp = speedRamp->speedTarget;
  31. }
  32. }
  33. else if (bldcCfg->motorDir == REVERSE_ROTATE)
  34. {
  35. if ((speedRamp->speedTargetRamp - speedRamp->speedTarget) > speedRamp->speedAcceleration)
  36. {
  37. speedRamp->speedTargetRamp -= speedRamp->speedAcceleration;
  38. }
  39. else if ((speedRamp->speedTarget - speedRamp->speedTargetRamp) > speedRamp->speedDeceleration)
  40. {
  41. speedRamp->speedTargetRamp += speedRamp->speedDeceleration;
  42. }
  43. else
  44. {
  45. speedRamp->speedTargetRamp = speedRamp->speedTarget;
  46. }
  47. }
  48. else
  49. {
  50. }
  51. }
  52. }

计算转速

  1. /*!
  2. * @brief get motor elec freq in bldc control with sensorless.
  3. *
  4. * @param[in] motorDir: motor run direction
  5. * @return elec freq in Q15
  6. */
  7. int16_t BLDC_Bemf_SpeedGet(int16_t motorDir)
  8. {
  9. uint32_t s_elecFreq = 0;
  10. uint32_t s_elecFreqPu = 0;
  11. if (g_bemfControl.aveChangePhaseTime)//只要平均换相时间不为0
  12. {
  13. s_elecFreq = 166667 / (int32_t)(g_bemfControl.aveChangePhaseTime * TIMER_XUS);
  14. /* 166667 refers 6 sector time in us, 166667 = (6 / 10^-6) */
  15. //这个也比较好理解 aveChangePhaseTime是平均换相间隔(其实是一个次数值) 所以要乘以时间单元TIMER_XUS
  16. //得到的才是换一次相,费的时间(单位是us)
  17. //转一圈6次换相 所以还要*6,再把us转化成s 就是上面这个公式
  18. }
  19. s_elecFreqPu = motorDir * MATH_IQ(s_elecFreq * 1.0 / BASE_FREQ);//转化成Q15格式*2^15
  20. return s_elecFreqPu;

Protector

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

过流检测

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

过压欠压检测

电压保护策略方面,主要就是过压与欠压的检测(过压与欠压原理相似放一起说)
代码给不同的电压设定了三个状态:DETECT_FAULT、DETECT_OK、DETECT_UNKNOWN
BLDC无感控制 - 图17
除了设定这样一个恢复阈值之外,还牵涉了一个s_debounce参数,这个参数是用来消抖的,简单理解,就是延时,不会因为电压的跳变,突然就出现保护。

  1. /*!
  2. * @brief DC Bus over voltage protection. When the bus voltage is higher than
  3. * threshold value, Undervoltage fault is reported, and it will return
  4. * to normal operation after the bus voltage rises to the recovery threshold.
  5. *
  6. * @param[in] protect: pointer to PROTECTOR_TYPE structure
  7. * @param[in] volt: DC Bus voltage value, 10 times the real value
  8. * @return none
  9. */
  10. void BLDC_Over_Voltage_Check(PROTECTOR_TYPE *protect, uint16_t volt)
  11. {
  12. static int16_t s_vbusStatus = BUS_VOLTAGE_NORMAL;
  13. static int16_t s_prevVbusStatus = BUS_VOLTAGE_NORMAL;
  14. static int16_t s_debounce = 0;
  15. // static int16_t s_status = DETECT_OK;
  16. if (volt > OVER_VBUS_THRESHOLD)
  17. {
  18. s_vbusStatus = BUS_VOLTAGE_OVER;
  19. }
  20. else if (volt < OVER_VBUS_RECOVERY_THRESHOLD)
  21. {
  22. if (s_vbusStatus == BUS_VOLTAGE_OVER)
  23. {
  24. s_vbusStatus = BUS_VOLTAGE_RECOVER;
  25. }
  26. }
  27. else
  28. {
  29. }
  30. if (s_vbusStatus != s_prevVbusStatus)
  31. {
  32. s_prevVbusStatus = s_vbusStatus;
  33. s_debounce = 0;
  34. }
  35. else
  36. {
  37. if (s_vbusStatus == BUS_VOLTAGE_RECOVER)
  38. {
  39. if (s_debounce < 10)
  40. {
  41. s_debounce++;
  42. }
  43. else
  44. {
  45. // g_mcStatus = STOP;
  46. s_vbusStatus = BUS_VOLTAGE_NORMAL;
  47. protect->faultFlag.all &= ~(1 << VBUS_OVER_VOLTAGE_FAULT);
  48. }
  49. }
  50. else if (s_vbusStatus == BUS_VOLTAGE_NORMAL)
  51. {
  52. if (s_debounce < 10)
  53. {
  54. s_debounce++;
  55. }
  56. else
  57. {
  58. // s_status = DETECT_OK;
  59. protect->faultFlag.all &= ~(1 << VBUS_OVER_VOLTAGE_FAULT);
  60. }
  61. }
  62. else
  63. {
  64. if (s_debounce < 10)
  65. {
  66. s_debounce++;
  67. }
  68. else
  69. {
  70. // s_status = DIAGNOSTIC_FAIL;
  71. protect->faultFlag.all |= (1 << VBUS_OVER_VOLTAGE_FAULT);
  72. }
  73. }
  74. }
  75. // return s_status;
  76. }

串起来

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

  1. /*!
  2. * @brief Motor protection main function. The corresponding protection functions
  3. * are implemented according to different time bases.
  4. *
  5. * @param[in] none
  6. * @return none
  7. */
  8. void BLDC_Motor_Protection(void)
  9. {
  10. #if (defined BLDC_OVER_CURRENT_CHECK)
  11. /* DC Bus over current protection , 1ms*/
  12. BLDC_Over_Current_Check(&g_bldc_protector, &g_bldc_adSample);
  13. #endif
  14. #if (defined BLDC_UNDER_VOLTAGE_CHECK)
  15. /* DC Bus under voltage protection, 1ms */
  16. BLDC_Under_Voltage_Check(&g_bldc_protector, g_bldc_adSample.busVoltageTrue);
  17. #endif
  18. #if (defined BLDC_OVER_VOLTAGE_CHECK)
  19. /* DC Bus over voltage protection, 1ms */
  20. BLDC_Over_Voltage_Check(&g_bldc_protector, g_bldc_adSample.busVoltageTrue);
  21. #endif
  22. if (g_bldc_protector.faultFlag.all)//检测到故障位了,电机停转
  23. {
  24. BLDC_Stop();
  25. g_mcStatus = FAULT;
  26. }
  27. return;
  28. }

PID

这里只用到了PI调节,比例与积分,其实就是下面公式的实现
BLDC无感控制 - 图18

PI计算

  1. /*!
  2. * @brief Pid calculate of motor control.
  3. *
  4. * @param[in] pid: pointer to PID_TYPE structure
  5. * @return none
  6. */
  7. int16_t PID_Calculate(PID_TYPE *pid)
  8. {
  9. int32_t min;
  10. int32_t max;
  11. pid->pPidOutput->errPu = pid->pPidInput->refPu - pid->pPidInput->fbkPu;//误差 设定值-反馈值
  12. /* proportional term */
  13. pid->pPidOutput->upPu = MATH_Mpy(pid->pPidOutput->errPu, pid->pPidCoef->kpPu);//比例:kp*误差 再转化成Q15格式
  14. /* integral term */
  15. pid->pPidOutput->uiPu += MATH_Mpy(pid->pPidOutput->errPu, pid->pPidCoef->kiPu);//积分:ki*kp 再转化为Q15格式
  16. /* saturate integral output with a dynamic limit 带有动态限制的饱和积分输出*/
  17. max = pid->pPidInput->maxPu - pid->pPidOutput->upPu;
  18. max = Q15_Sat(max, max, 0);
  19. min = pid->pPidInput->minPu - pid->pPidOutput->upPu;
  20. min = Q15_Sat(min, 0, min);
  21. pid->pPidOutput->uiPu = Q15_Sat(pid->pPidOutput->uiPu, max, min);
  22. /* calculate total output */
  23. pid->pPidOutput->outPu = pid->pPidOutput->upPu + pid->pPidOutput->uiPu;//计算总的比例 与 积分的变化
  24. /* saturate total output 饱和总输出 */
  25. pid->pPidOutput->outPu = Q15_Sat(pid->pPidOutput->outPu, pid->pPidInput->maxPu, pid->pPidInput->minPu);
  26. return (int16_t)pid->pPidOutput->outPu;
  27. }

有几个疑问的计算公式:

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

速度环

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

电流环

  1. /*!
  2. * @brief Current loop processing of motor control.
  3. *
  4. * @param[in] bldcCfg: pointer to BLDC_VARS_CFG structure
  5. * @param[in] pid: pointer to PID_TYPE structure
  6. * @param[in] bldcCtrl: pointer to BLDC_VARS_CTRL structure
  7. * @return none
  8. */
  9. void BLDC_CurrentLoop_Calculate(BLDC_VARS_CFG *bldcCfg, PID_TYPE *pid, BLDC_VARS_CTRL *bldcCtrl)
  10. {
  11. Set_BLDC_PidRef(pid, (bldcCfg->motorDir * bldcCtrl->ibusRefPu));
  12. Set_BLDC_PidFbk(pid, bldcCtrl->ibusFdkPu);
  13. bldcCtrl->currentPwmPu = PID_Calculate(pid);
  14. }

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

Contral_api

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

Bldc_app

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

电机状态切换

BLDC无感控制 - 图19

  1. /*!
  2. * @brief The Main state machine, used to set the running state of the bldc control system.
  3. *
  4. * @param[in] none
  5. * @return none
  6. */
  7. void BldcStateMachine(void)
  8. {
  9. switch (g_mcStatus)
  10. {
  11. case POWER_ON:
  12. BLDC_Parameters_Initialize();
  13. // BLDC_ASR_CommandInit(pBldcVarsCfg, &g_bldc_speedCmd);
  14. BLDC_ASR_CommandSet(pBldcVarsCfg, &g_bldc_speedCmd);
  15. #if (defined DATA_ACCESS_FLASH_ENABLE)
  16. g_flashOperationStatus = Eflash_DataAccess_DefaultSave(&g_dataAccessFlash);
  17. if (g_flashOperationStatus == EFLASH_STATUS_SUCCESS)
  18. {
  19. Eflash_DataAccess_PowerOnRecovery(&g_dataAccessFlash);
  20. Eflash_DataAccessEflashInit(&g_dataAccessFlash);
  21. g_mcStatus = IDLE;
  22. }
  23. #else
  24. g_mcStatus = IDLE;
  25. #endif
  26. break;
  27. case IDLE:
  28. BLDC_Parameters_Initialize();
  29. #if (defined DATA_ACCESS_FLASH_ENABLE)
  30. if (g_dataAccessFlash.eraseEnable == 1)
  31. {
  32. g_mcStatus = FLASH_TASK;
  33. }
  34. #endif
  35. if ((g_bldc_speedCmd.speedTarget * pBldcVarsCfg->motorDir) < 0)
  36. {
  37. g_bldc_speedCmd.speedTarget = 0 - g_bldc_speedCmd.speedTarget;
  38. g_bldc_speedCmd.speedTargetRamp = 0 - g_bldc_speedCmd.speedTargetRamp;
  39. }
  40. break;
  41. case START:
  42. BLDC_Startup(&g_bldcVarsCtrl, pBldcVarsCfg);
  43. if (BLDC_Get_Startup_Status(&g_bldcVarsCtrl) == STARTUP_READY)
  44. {
  45. g_mcStatus = RUN;
  46. }
  47. else if (BLDC_Get_Startup_Status(&g_bldcVarsCtrl) == STARTUP_FAIL)
  48. {
  49. g_mcStatus = IDLE;
  50. }
  51. else
  52. {
  53. }
  54. break;
  55. case RUN:
  56. BLDC_SpeedTask();
  57. break;
  58. case STOP:
  59. BLDC_Stop();
  60. BLDC_Parameters_Initialize();
  61. // BLDC_ASR_CommandInit(pBldcVarsCfg, &g_bldc_speedCmd);
  62. BLDC_ASR_CommandSet(pBldcVarsCfg, &g_bldc_speedCmd);
  63. g_mcStatus = IDLE;
  64. break;
  65. case FAULT:
  66. BLDC_Stop();
  67. if (g_bldc_protector.faultFlag.all == 0)
  68. {
  69. g_mcStatus = STOP;
  70. }
  71. break;
  72. #if (defined DATA_ACCESS_FLASH_ENABLE)
  73. case FLASH_TASK:
  74. g_flashOperationStatus = Eflash_DataAccessEflashProcess(&g_dataAccessFlash);
  75. if (g_flashOperationStatus == EFLASH_STATUS_SUCCESS)
  76. {
  77. g_mcStatus = IDLE;
  78. g_flashOperationStatus = EFLASH_STATUS_ACK;
  79. Eflash_DataAccessEflashInit(&g_dataAccessFlash);
  80. g_flashTaskFinish = 1;
  81. }
  82. break;
  83. #endif
  84. default:
  85. break;
  86. }
  87. }

task_scheduler

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

核心

最关键的核心函数

  1. /*!
  2. * @brief Task scheduler main processer, called by Main function.
  3. *
  4. * @param[in] none
  5. * @return none
  6. */
  7. void Task_Scheduler(void)
  8. {
  9. /* Notice: the delay of each task sequency cannot be the same! */
  10. /*
  11. * We can add a new time base task as the follow type:
  12. * SCH_Add_Task(New_task_function, task_sequence_number, task_time_base);
  13. * where New_task_function is the name of the task name;
  14. * task_sequence_number is the position of the task in all task sequences;
  15. * task_time_base is the time base of the task, indicating its execution cycle.
  16. */
  17. /* 1ms time base tasks */
  18. SCH_Add_Task(Task0_1ms, 0, 1);
  19. /* 2ms time base tasks */
  20. SCH_Add_Task(Task1_2ms, 1, 2);
  21. /* 10ms time base tasks, reserve */
  22. SCH_Add_Task(Task2_10ms, 2, 10);
  23. /* 100ms time base tasks, reserve */
  24. SCH_Add_Task(Task3_100ms, 3, 100);
  25. /* 1s time base tasks, reserve */
  26. SCH_Add_Task(Task4_1000ms, 4, 1000);
  27. /* 2s time base tasks, only used in FOC */
  28. SCH_Add_Task(Task5_2000ms, 5, 2000);
  29. while (1)
  30. {
  31. SCH_Dispatch_Tasks();//任务调度
  32. MC_Keys_Read();//按键扫描
  33. }
  34. }

任务调度

  1. *!
  2. * @brief Dispatch the tasks.
  3. *
  4. * @param[in] none
  5. * @return none
  6. */
  7. void SCH_Dispatch_Tasks(void)
  8. {
  9. uint8_t index;
  10. /* Dispatches (runs) the next task (if one is ready) */
  11. for (index = 0; index < SCH_TASKS_SEQUENCE_MAX; index++)
  12. {
  13. if (g_SchTask[index].runOrder > 0)
  14. {
  15. /* Run the task */
  16. (*g_SchTask[index].pTask)();
  17. /* Reset / reduce runOrder flag */
  18. g_SchTask[index].runOrder -= 1;
  19. /* Periodic tasks will be scheduled to run again */
  20. /* if this is a 'one shot' task, remove it from the array */
  21. if (g_SchTask[index].period == 0)
  22. {
  23. SCH_Delete_Task(index);
  24. }
  25. }
  26. }
  27. /* Report system status */
  28. SCH_Report_Status();
  29. }

key

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

  1. /*!
  2. * @brief Get the response of pressing each key on the control board.
  3. * The keys action are judged by the ADC value:
  4. * when ADC value is in [ 0, START_STOP_MAX ], it corresponds to the Start / Stop key;
  5. * when ADC value is in [ FORWARD_REVERSE_MIN, FORWARD_REVERSE_MAX ], it corresponds to the Forward / Reverse key;
  6. * when ADC value is in [ FAST_MIN, FAST_MAX ], it corresponds to the Fast key;
  7. * when ADC value is in [ SLOW_MIN, SLOW_MAX ], it corresponds to the Slow key.
  8. *
  9. * @param[in] none
  10. * @return none
  11. */
  12. void MC_Keys_Read(void)
  13. {
  14. if ((!GPIO_GetPinValue(KEY_START_STOP)) && (!s_keyStartPressed))//开始、停止
  15. {
  16. s_keyStartPressed = 1;
  17. }
  18. else if (s_keyStartPressed && GPIO_GetPinValue(KEY_START_STOP))
  19. {
  20. s_keyStartPressed = 0;
  21. if (g_mcStatus == IDLE)
  22. {
  23. g_mcStatus = START;
  24. }
  25. else
  26. {
  27. g_mcStatus = STOP;
  28. }
  29. }
  30. else if ((!GPIO_GetPinValue(KEY_DIRECTION)) && (!s_keyDirPressed))//方向
  31. {
  32. s_keyDirPressed = 1;
  33. }
  34. else if ((GPIO_GetPinValue(KEY_DIRECTION)) && (s_keyDirPressed))
  35. {
  36. s_keyDirPressed = 0;
  37. if (pBldcVarsCfg->motorDir == 1)
  38. {
  39. pBldcVarsCfg->motorDir = -1;
  40. }
  41. else
  42. {
  43. pBldcVarsCfg->motorDir = 1;
  44. }
  45. // g_bldc_speedCmd.speedTarget = MATH_Abs(g_bldc_speedCmd.speedTarget) * pBldcVarsCfg->motorDir;
  46. }
  47. else if ((!GPIO_GetPinValue(KEY_SPEED_UP)) && (!s_keyUpPressed))//加速
  48. {
  49. s_keyUpPressed = 1;
  50. }
  51. else if ((GPIO_GetPinValue(KEY_SPEED_UP)) && (s_keyUpPressed))
  52. {
  53. s_keyUpPressed = 0;
  54. g_bldc_speedCmd.speedTarget += (pBldcVarsCfg->motorDir * SPEED_CHANGE_STEP);
  55. }
  56. else if ((!GPIO_GetPinValue(KEY_SPEED_DOWN)) && (!s_keyDownPressed))//减速
  57. {
  58. s_keyDownPressed = 1;
  59. }
  60. else if ((GPIO_GetPinValue(KEY_SPEED_DOWN)) && (s_keyDownPressed))
  61. {
  62. s_keyDownPressed = 0;
  63. g_bldc_speedCmd.speedTarget -= (pBldcVarsCfg->motorDir * SPEED_CHANGE_STEP);
  64. }
  65. else
  66. {
  67. /* do nothing */
  68. }
  69. }

BLDC无感方波控制

BLDC无感控制 - 图20