4. 核心算法实现(一):Clark变换与Park变换的C代码实现、定点数与浮点数选择、查表法优化三角函数
好,咱们直接进入正题。
FOC 控制里,Clark 变换和 Park 变换是绕不开的两道坎。说白了,它们就是把电机三相电流这种“物理世界”的量,映射到“控制世界”的旋转坐标系里。你想想看,三相电流是正弦波,不好控;但转到 dq 轴之后,就变成了直流分量,PID 调起来就顺手多了。
我个人习惯,在讲代码之前,先理清楚数学关系。不然你抄完代码,出了问题都不知道怎么查。
4.1 Clark 变换:从三相到两相
Clark 变换,也叫 3s/2s 变换。它的任务是把 Ia、Ib、Ic 这三相电流,投影到 αβ 两相静止坐标系上。
公式其实很简单:
Iα = Ia
Iβ = (Ia + 2*Ib) / √3
注意,这里有个前提:三相电流是平衡的,即 Ia + Ib + Ic = 0。所以 Ic 其实可以不用采样,算出来就行。我在项目中遇到过采样电阻不够的情况,就只采两相,第三相用公式补,省了一个 ADC 通道。
来看 C 代码实现:
typedef struct {
float Ialpha;
float Ibeta;
} Clark_Output;
Clark_Output clark_transform(float Ia, float Ib) {
Clark_Output out;
out.Ialpha = Ia;
out.Ibeta = (Ia + 2.0f * Ib) * 0.577350269f; // 1/√3
return out;
}
嗯,这里要注意:0.577350269f 是 1/√3 的浮点近似。如果你用定点数,就得把这个值放大成 Q 格式。
4.2 Park 变换:从静止到旋转
Clark 变换完了,我们得到的是 αβ 轴上的正弦波。接下来 Park 变换把它转到 dq 旋转坐标系,变成直流。
公式:
Id = Iα * cos(θ) + Iβ * sin(θ)
Iq = -Iα * sin(θ) + Iβ * cos(θ)
这里的 θ 是转子电角度,通常由编码器或霍尔传感器提供。
代码实现:
typedef struct {
float Id;
float Iq;
} Park_Output;
Park_Output park_transform(float Ialpha, float Ibeta, float theta) {
Park_Output out;
float sin_t = sinf(theta);
float cos_t = cosf(theta);
out.Id = Ialpha * cos_t + Ibeta * sin_t;
out.Iq = -Ialpha * sin_t + Ibeta * cos_t;
return out;
}
看到 sinf 和 cosf 了吗?这是最直接的写法,但也是性能瓶颈。为什么?因为三角函数计算太慢了。
4.3 定点数与浮点数:选哪个?
这是个老生常谈的问题。我直接给结论:
| 特性 | 浮点数 | 定点数 |
|---|---|---|
| 精度 | 高,动态范围大 | 受 Q 格式限制,需小心溢出 |
| 速度 | 有 FPU 时快,无 FPU 时极慢 | 整数运算,通常很快 |
| 代码可读性 | 好,直接写数学公式 | 差,需要手动处理缩放 |
| 适用场景 | 带 FPU 的 MCU(如 STM32F4/F7/H7) | 低成本 MCU(如 STM32F1、C2000 部分型号) |
我的建议是:能用浮点就用浮点。现在带 FPU 的 MCU 已经很便宜了,没必要在定点数上折磨自己。但如果你确实要用定点数,记住三点:
- 选好 Q 格式。比如电流用 Q12,角度用 Q15。
- 乘法后记得右移,防止溢出。
- 加法和减法一般没问题,但乘法和除法要小心。
4.4 查表法优化三角函数
好,回到三角函数的问题。不用标准库,那用什么?查表法。
思路很简单:把 0 到 360 度的 sin 值预先算好,存成一个数组。运行时直接查表,或者查表加线性插值。
代码示例:
#define TABLE_SIZE 256
float sin_table[TABLE_SIZE];
void init_sin_table(void) {
for (int i = 0; i < TABLE_SIZE; i++) {
sin_table[i] = sinf(2.0f * 3.1415926f * i / TABLE_SIZE);
}
}
float fast_sin(float theta) {
// theta 范围:0 ~ 2*PI
int index = (int)(theta * TABLE_SIZE / (2.0f * 3.1415926f)) % TABLE_SIZE;
return sin_table[index];
}
这个实现精度一般,但速度极快。如果你需要更高精度,可以加线性插值:
float fast_sin_lerp(float theta) {
float frac = theta * TABLE_SIZE / (2.0f * 3.1415926f);
int index = (int)frac;
float delta = frac - index;
float s0 = sin_table[index % TABLE_SIZE];
float s1 = sin_table[(index + 1) % TABLE_SIZE];
return s0 + delta * (s1 - s0);
}
你看,这样既保留了速度,精度也够用。我在一个 72MHz 的 STM32F103 项目里,用查表法把 Park 变换的耗时从 12μs 降到了 2μs,效果非常明显。
4.5 本章知识体系
为了让你更直观地理解,我画了一张图:
这张图把 Clark 变换、Park 变换以及优化要点串在了一起。你写代码的时候,脑子里要有这个流程。
好了,这一章就到这里。Clark 和 Park 是 FOC 的基石,代码虽然简单,但优化空间很大。下一章我们会继续深入,看看反 Park 变换和 SVPWM 怎么实现。