3. 坐标变换理论(下):Matlab/Simulink坐标变换仿真搭建、C语言坐标变换代码实现、变换结果验证。
上一讲我们把 Clarke 和 Park 变换的数学公式推了一遍。说实话,光看公式容易晕。我当年学的时候也是,纸上算得明明白白,一到写代码就懵了。
这一讲咱们来点实在的。先在 Simulink 里搭个模型验证理论,再用 C 语言把算法落地。最后,我会教你一套验证结果的方法——这步很关键,能帮你发现很多隐藏的 bug。
3.1 为什么需要仿真验证?
你想想看,公式推导得再漂亮,不跑一遍怎么知道对不对?
我在项目里见过太多人,写完坐标变换代码直接往工程里塞。结果电机转起来嗡嗡响,电流波形乱七八糟。查了半天,最后发现是 Clarke 变换里一个符号搞反了。
所以我的习惯是:先仿真,再写代码,最后用仿真数据验证代码。三步走,稳得很。
核心思路:用 Simulink 生成一组标准输入输出数据,作为“金标准”。然后让 C 代码跑同样的输入,对比输出是否一致。
3.2 Matlab/Simulink 仿真搭建
我们搭一个最简单的验证模型。输入三相对称正弦电流,经过 Clarke 和 Park 变换,观察输出波形。
3.2.1 模型结构
打开 Simulink,新建一个空白模型。我们需要这几个模块:
- Sine Wave × 3:生成三相电流 ia, ib, ic。幅值设为 1,频率 50Hz,相位互差 120°。
- Fcn 模块:实现 Clarke 变换公式。
- Fcn 模块:实现 Park 变换公式。
- Scope:观察波形。
具体参数设置如下:
| 信号 | 表达式 | 说明 |
|---|---|---|
| ia | sin(2*pi*50*t) | A 相电流 |
| ib | sin(2*pi*50*t - 2*pi/3) | B 相电流,滞后 120° |
| ic | sin(2*pi*50*t + 2*pi/3) | C 相电流,超前 120° |
3.2.2 Clarke 变换模块
在 Fcn 模块里输入:
% 等幅值 Clarke 变换
alpha = u[0] - 0.5*u[1] - 0.5*u[2];
beta = (sqrt(3)/2)*(u[1] - u[2]);
注意 u[0], u[1], u[2] 分别对应 ia, ib, ic 的输入端口。
小提示:我习惯用等幅值变换。等功率变换虽然数学上更优雅,但等幅值变换在调试时更直观——输入幅值 1,输出 alpha 幅值也是 1,一眼就能看出对不对。
3.2.3 Park 变换模块
Park 变换需要输入 theta(转子角度)。我们用一个 Ramp 模块模拟匀速旋转:斜率设为 2*pi*50,这样 theta 就以 50Hz 的速度线性增长。
% Park 变换
d = u[0]*cos(u[2]) + u[1]*sin(u[2]);
q = -u[0]*sin(u[2]) + u[1]*cos(u[2]);
这里 u[0]=alpha, u[1]=beta, u[2]=theta。
3.2.4 仿真结果分析
运行仿真,观察 Scope 波形:
- alpha/beta:应该是幅值为 1 的正弦波,相位相差 90°。
- d/q:如果 theta 跟踪准确,d 轴分量应为常数 1,q 轴分量应为 0。
为什么会这样?因为三相对称电流在同步旋转坐标系下,就是直流量。这也是 FOC 的核心思想——把交流量变成直流量,方便控制。
注意:如果 d/q 波形不是直线,而是有波动,说明 theta 没对准。检查 Ramp 模块的初始值和斜率,确保 theta 与电流相位同步。
3.3 C 语言坐标变换代码实现
仿真验证通过后,咱们开始写 C 代码。我习惯用结构体封装,这样代码可读性好,也方便移植。
3.3.1 数据结构定义
// 三相电流结构体
typedef struct {
float a;
float b;
float c;
} abc_t;
// 静止坐标系
typedef struct {
float alpha;
float beta;
} alphabeta_t;
// 旋转坐标系
typedef struct {
float d;
float q;
} dq_t;
3.3.2 Clarke 变换实现
alphabeta_t clarke_transform(abc_t in) {
alphabeta_t out;
// 等幅值变换
out.alpha = in.a - 0.5f * in.b - 0.5f * in.c;
out.beta = 0.8660254f * (in.b - in.c); // sqrt(3)/2 ≈ 0.8660254
return out;
}
3.3.3 Park 变换实现
dq_t park_transform(alphabeta_t in, float theta) {
dq_t out;
float sin_theta = sinf(theta);
float cos_theta = cosf(theta);
out.d = in.alpha * cos_theta + in.beta * sin_theta;
out.q = -in.alpha * sin_theta + in.beta * cos_theta;
return out;
}
关于三角函数:在嵌入式 MCU 上,sinf/cosf 可能比较慢。我一般用查表法或者 CORDIC 算法。不过初期调试时直接用 math.h 没问题,先把功能跑通再说。
3.3.4 逆变换(可选)
虽然本章不重点讲逆变换,但调试时经常需要。顺手给出来:
alphabeta_t inv_park_transform(dq_t in, float theta) {
alphabeta_t out;
float sin_theta = sinf(theta);
float cos_theta = cosf(theta);
out.alpha = in.d * cos_theta - in.q * sin_theta;
out.beta = in.d * sin_theta + in.q * cos_theta;
return out;
}
abc_t inv_clarke_transform(alphabeta_t in) {
abc_t out;
out.a = in.alpha;
out.b = -0.5f * in.alpha + 0.8660254f * in.beta;
out.c = -0.5f * in.alpha - 0.8660254f * in.beta;
return out;
}
3.4 变换结果验证
代码写完了,怎么知道对不对?我有一套标准流程:
3.4.1 准备测试数据
从 Simulink 仿真中导出几组典型数据。比如:
| ia | ib | ic | theta (rad) | 期望 d | 期望 q |
|---|---|---|---|---|---|
| 0.0000 | 0.8660 | -0.8660 | 0.0000 | 0.0000 | -1.0000 |
| 0.5000 | 0.5000 | -1.0000 | 0.5236 | 0.5000 | -0.8660 |
| 1.0000 | -0.5000 | -0.5000 | 1.5708 | 1.0000 | 0.0000 |
3.4.2 编写测试代码
#include <stdio.h>
#include <math.h>
// 测试用例结构体
typedef struct {
float ia, ib, ic;
float theta;
float expected_d, expected_q;
} test_case_t;
int main() {
test_case_t tests[] = {
{0.0000f, 0.8660f, -0.8660f, 0.0000f, 0.0000f, -1.0000f},
{0.5000f, 0.5000f, -1.0000f, 0.5236f, 0.5000f, -0.8660f},
{1.0000f, -0.5000f, -0.5000f, 1.5708f, 1.0000f, 0.0000f},
};
int num_tests = sizeof(tests) / sizeof(tests[0]);
float tolerance = 0.001f;
for (int i = 0; i < num_tests; i++) {
abc_t abc = {tests[i].ia, tests[i].ib, tests[i].ic};
alphabeta_t ab = clarke_transform(abc);
dq_t dq = park_transform(ab, tests[i].theta);
float err_d = fabsf(dq.d - tests[i].expected_d);
float err_q = fabsf(dq.q - tests[i].expected_q);
printf("Test %d: d=%f (err=%f), q=%f (err=%f)\n",
i+1, dq.d, err_d, dq.q, err_q);
if (err_d > tolerance || err_q > tolerance) {
printf(" FAILED!\n");
} else {
printf(" PASSED\n");
}
}
return 0;
}
3.4.3 批量验证技巧
我曾经在一个项目里,用 Python 脚本自动生成 1000 组随机测试数据,然后对比 C 代码和 Matlab 的输出。结果发现一个很隐蔽的问题——当 theta 很大时,sinf/cosf 的精度损失导致 d/q 误差累积。
所以我的建议是:
- 定点数 vs 浮点数:如果 MCU 没有 FPU,用 Q15 或 Q31 格式时,误差分析要更严格。
- 角度归一化:把 theta 限制在 [0, 2π) 范围内,避免角度溢出。
- 误差阈值:浮点运算允许 1e-5 到 1e-6 的误差,定点数可以放宽到 1e-3。
避坑指南:我曾经在 STM32F103 上做项目,没有 FPU,用 Q15 格式实现 Park 变换。结果发现 q 轴总是有个固定偏置。查了两天,最后发现是查表时索引计算溢出了。从那以后,我每次做定点数实现都会先跑一遍全范围的边界测试。
3.5 本章知识体系
下面这张图总结了坐标变换从理论到代码验证的完整链路:
嗯,到这里坐标变换的理论、仿真、代码、验证就全串起来了。这套流程我用了很多年,每次做新平台或者换 MCU 都会走一遍。虽然看起来步骤多,但能帮你省掉后面调试时的大量痛苦。