Featured image of post DengFOCV3P尝试部署MFPCC算法

DengFOCV3P尝试部署MFPCC算法

使用AI Agent协助在DengFOCV3P驱动器上部署MFPCC算法

简述

近期Codex额度刷了很多,闲来无事,想把硕士期间研究的MFPCC电控算法部署到嵌入式平台上尝试一下。 选择的平台是如下的电机驱动板:

很早之前买的DengFOCV3P驱动器
这个控制器还是不错的,大家可以淘宝搜索购买。另外还有欧拉电子的FOC驱动器,我也深度使用过都很不错,不过这个性价比更高,这个才不到100CNY。此板是双路的FOC控制器,每一路电机都配备了2个INA240A2的电流传感器,线电流电流采样方案,第三相电流可以通过基尔霍夫电流定律计算出来,是foc驱动器的一种常用设计方案。驱动使用的是两颗ST的L6234PD,是集成的DMOS开关管,5A 峰值,并不是预驱+MOSFET的方案,此款驱动器是针对云台电机所优化设计的。 默认配备的是ESP32的驱动器,当然设计一个底座就可以更换控制器了。很适合自己项目要使用FOC驱动器时选择这款驱动器。 搭配2208云台电机,控制器是ESP32.使用Vscode端的PlatformIO进行开发。 硬件接线
轮胎是我之前做小车粘上去的。就是拿不下来了。。

MFPCC给Codex参考的2021年的TIE期刊张教授的MPFCC经典论文,即:Y. Zhang, J. Jin and L. Huang, “Model-Free Predictive Current Control of PMSM Drives Based on Extended State Observer Using Ultralocal Model,” in IEEE Transactions on Industrial Electronics, vol. 68, no. 2, pp. 993-1003, Feb. 2021, doi: 10.1109/TIE.2020.2970660.

https://ieeexplore.ieee.org/document/8984748

完全实现后 会进行开源。

2026-6-19一些问题:

目前是可以跑的,但是高速时有点问题: codex反馈说:

1
“高速时MFPCC算法 Iq*=0.10 A,实测有效 Iq 却很小且 Id 偏大,说明电流采样/电角度链路限制了 MFPCC 输出。”

即给定速度100RPM他却只能够跑到50RPM左右。说明MFPCC的实现还存在一些问题。 我让Codex加了一组实验,即测试一下FOC算法下能否稳定100RPM,结论是完全可以的。说明当前 MFPCC-ESO 工程存在实现或参数问题。后续我会深度探究一下问题所在。

2026-7-2实现效果

速度从0到600R的曲线,打印保存了CSV数据,使用Python脚本绘制了一下,和经典FOC算法进行对比:

MFPCC
FOC

目前的效果是 明显MFPCC的iq id跟踪效果会更差,这可能是由于内环电流环的MFPCC算法导致。即使外环都是一样。内环对于外环的响应程度是不一致的。 FOC的iq id跟踪效果比较好,但是速度会有一点毛刺效果,这可能与没有精细调参有原因。

讲一下核心实现吧: 用了 FreeRTOS,不是裸机 Bare Metal。 当前任务分配是:

Core0任务是mfpcc_current 实时电流控制任务 5 kHz MFPCC/ESO 电流环、Clarke/Park、预测控制、PWM 写入 Core1任务是Arduino loop() 串口命令、状态打印、AS5600 I2C 角度读取、速度外环 Core1的我adc_dma 任务 ADC DMA 读取、两相电流平均、通知 Core0 控制任务

200 µs 周期下,34 µs / 200 µs ≈ 17% Core0 实时预算。这样看来ESP32的CPU运算速度还是可以的。

MFPCC核心算法实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "mfpcc_controller.h"

#include <Arduino.h>

#include "math_utils.h"

void MfpccController::configure(float sampleTime, float ultraLocalAlpha,
                                float discreteEsoPole,
                                float voltageLimit,
                                float voltageSlewRate) {
  sampleTime_ = sampleTime;
  ultraLocalAlpha_ = ultraLocalAlpha;
  esoPole_ = discreteEsoPole;
  voltageLimit_ = voltageLimit;
  voltageStepLimit_ = voltageSlewRate * sampleTime;
  applyObserverConfiguration();
}

void MfpccController::applyObserverConfiguration() {
  observer_.configure(sampleTime_, ultraLocalAlpha_, esoPole_);
}

void MfpccController::reset(const AlphaBeta& measuredCurrent) {
  observer_.reset(measuredCurrent);
}

AlphaBeta MfpccController::update(
    const AlphaBeta& measuredCurrent,
    const AlphaBeta& currentReferenceK2,
    const AlphaBeta& previouslyAppliedVoltage) {
  // 先使用 u(k-1) 和 i(k) 更新 ESO,得到 i_hat(k+1)、F_hat(k+1)。
  // 这一步把数字控制器的一拍计算/更新延迟包含在预测中。
  observer_.update(measuredCurrent, previouslyAppliedVoltage);

  // 论文式(20):
  // u*(k) = [i*(k+2)-i_hat(k+1)]/(alpha*Ts) - F_hat(k+1)/alpha
  const float inverseAlpha = 1.0F / ultraLocalAlpha_;
  const float inverseAlphaTs =
      1.0F / (ultraLocalAlpha_ * sampleTime_);
  AlphaBeta requested =
      (currentReferenceK2 - observer_.estimatedCurrent()) *
          inverseAlphaTs -
      observer_.estimatedDisturbance() * inverseAlpha;
  requested = motor_math::limitMagnitude(requested, voltageLimit_);

  // ESO 会把采样噪声看成快速扰动。限制相邻周期的电压矢量增量,
  // 避免这种高频估计直接变成电机可感知的电磁力矩脉动。
  const AlphaBeta voltageIncrement =
      motor_math::limitMagnitude(
          requested - previouslyAppliedVoltage, voltageStepLimit_);
  return motor_math::limitMagnitude(
      previouslyAppliedVoltage + voltageIncrement, voltageLimit_);
}

void MfpccController::setUltraLocalAlpha(float value) {
  ultraLocalAlpha_ = constrain(value, 20.0F, 1000.0F);
  applyObserverConfiguration();
}

void MfpccController::setEsoPole(float value) {
  esoPole_ = constrain(value, 0.05F, 0.95F);
  applyObserverConfiguration();
}

void MfpccController::setVoltageLimit(float value) {
  voltageLimit_ = constrain(value, 0.05F, 3.0F);
}

ESO观测器核心算法实现:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include "eso_observer.h"

#include <Arduino.h>

void ExtendedStateObserver::configure(float sampleTime,
                                      float ultraLocalAlpha,
                                      float discretePole) {
  sampleTime_ = sampleTime;
  ultraLocalAlpha_ = ultraLocalAlpha;
  discretePole_ = constrain(discretePole, 0.05F, 0.95F);

  // 论文令连续 ESO 两个极点都为 -omega0:
  // beta1=2*omega0, beta2=omega0^2。
  // 前向欧拉离散后,若指定离散极点 z0,则
  // omega0=(1-z0)/Ts,beta01=Ts*beta1,beta02=Ts*beta2。
  const float oneMinusPole = 1.0F - discretePole_;
  beta01_ = 2.0F * oneMinusPole;
  beta02_ = oneMinusPole * oneMinusPole / sampleTime_;
}

void ExtendedStateObserver::reset(const AlphaBeta& measuredCurrent) {
  estimatedCurrent_ = measuredCurrent;
  estimatedDisturbance_ = {};
}

void ExtendedStateObserver::update(
    const AlphaBeta& measuredCurrent,
    const AlphaBeta& previouslyAppliedVoltage) {
  const AlphaBeta error = estimatedCurrent_ - measuredCurrent;
  const AlphaBeta currentBeforeUpdate = estimatedCurrent_;
  const AlphaBeta disturbanceBeforeUpdate = estimatedDisturbance_;

  // 对应论文式(13),alpha、beta 两个轴完全独立,写成矢量形式。
  estimatedCurrent_ =
      currentBeforeUpdate +
      (disturbanceBeforeUpdate +
       previouslyAppliedVoltage * ultraLocalAlpha_) *
          sampleTime_ -
      error * beta01_;
  estimatedDisturbance_ =
      disturbanceBeforeUpdate - error * beta02_;
}

体验

Codex开发嵌入式的体验很好。 PlatformIO的Vscode插件,体验也非常不错,配置环境超级简单,可以完美替代Arduino以及ESP-IDF等。

Licensed MIT OR GPL3.0 WHATEVERS ON GITHUB_PAGE SHOW YOU