从理论到代码:准PR控制器在STM32/GD32上的C语言实现全流程(含Tustin变换推导)
在数字电源和电机控制领域,准PR(准比例谐振)控制器因其对交流信号优异的跟踪性能而备受青睐。与传统的PI控制器相比,准PR控制器能够直接在静止坐标系下实现对正弦参考信号的无静差跟踪,避免了复杂的坐标变换运算。本文将完整展示如何从连续域传递函数出发,通过Tustin变换推导出离散差分方程,最终在STM32/GD32等微控制器上实现可运行的C代码。
1. 准PR控制器的理论基础
1.1 为什么需要准PR控制器
在交流控制系统中,PI控制器存在两个主要局限:
- 静态误差问题:PI控制器对直流信号(阶跃输入)可以实现无静差跟踪,但对交流信号存在稳态误差
- 计算复杂度:通过Clark/Park变换将交流量转为直流量再使用PI控制,增加了计算负担
准PR控制器的传递函数为:
$$ G_{PR}(s) = K_p + \frac{2K_r\omega_c s}{s^2 + 2\omega_c s + \omega_0^2} $$
其中关键参数:
- $K_p$:比例增益,影响系统动态响应
- $K_r$:谐振增益,决定谐振点处的放大倍数
- $\omega_c$:截止频率,控制带宽
- $\omega_0$:谐振频率(通常设为电网基波频率)
1.2 参数设计指南
通过MATLAB仿真可以直观观察各参数的影响:
| 参数 | 影响效果 | 典型取值 | 注意事项 |
|---|---|---|---|
| $K_p$ | 提升整体增益 | 0.1-10 | 过大会导致超调 |
| $K_r$ | 增大谐振点增益 | 10-1000 | 影响稳定性 |
| $\omega_c$ | 控制带宽 | 0.5-5 rad/s | 过大会引入噪声 |
% 准PR控制器Bode图绘制示例 Kp = 1; Kr = 100; wc = 0.5*2*pi; w0 = 100*pi; G_pr = Kp + tf([2*Kr*wc, 0], [1, 2*wc, w0^2]); bode(G_pr); grid on;2. Tustin变换与离散化推导
2.1 双线性变换原理
Tustin变换(双线性变换)是将连续系统转换为离散系统的常用方法,其映射关系为:
$$ s = \frac{2}{T_s}\frac{z-1}{z+1} $$
这种变换具有以下特点:
- 保持稳定性(将s左半平面映射到z平面单位圆内)
- 频率响应存在畸变,需进行预畸变校正
- 计算量适中,适合嵌入式实现
2.2 完整推导过程
将Tustin变换代入准PR传递函数:
展开传递函数: $$ G_{PR}(s) = \frac{s^2K_p + 2\omega_c s(K_p+K_r) + \omega_0^2K_p}{s^2 + 2\omega_c s + \omega_0^2} $$
变量替换: $$ s \rightarrow \frac{2}{T_s}\frac{z-1}{z+1} $$
整理得到差分方程系数:
# 系数计算伪代码 def calculate_coeffs(Kp, Kr, wc, w0, Ts): a0 = (4*Kp/Ts**2 + 4*wc*(Kp+Kr)/Ts + Kp*w0**2) a1 = (-8*Kp/Ts**2 + 2*Kp*w0**2) a2 = (4*Kp/Ts**2 - 4*wc*(Kp+Kr)/Ts + Kp*w0**2) b0 = (4/Ts**2 + 4*wc/Ts + w0**2) b1 = (-8/Ts**2 + 2*w0**2) b2 = (4/Ts**2 - 4*wc/Ts + w0**2) return [a0/b0, a1/b0, a2/b0], [b1/b0, b2/b0]
注意:实际实现时需要考虑数值稳定性问题,特别是当采样周期Ts很小时,建议使用归一化处理。
3. STM32/GD32上的C语言实现
3.1 数据结构设计
typedef struct { float a[3]; // 分子系数: a0, a1, a2 float b[2]; // 分母系数: b1, b2 float x[3]; // 输入历史: x[n], x[n-1], x[n-2] float y[3]; // 输出历史: y[n], y[n-1], y[n-2] } PRController;3.2 核心算法实现
float PR_Update(PRController *pr, float input) { // 更新输入历史 pr->x[2] = pr->x[1]; pr->x[1] = pr->x[0]; pr->x[0] = input; // 计算输出 float output = pr->a[0] * pr->x[0] + pr->a[1] * pr->x[1] + pr->a[2] * pr->x[2] - pr->b[0] * pr->y[1] - pr->b[1] * pr->y[2]; // 更新输出历史 pr->y[2] = pr->y[1]; pr->y[1] = pr->y[0]; pr->y[0] = output; return output; }3.3 初始化与参数计算
void PR_Init(PRController *pr, float Kp, float Kr, float wc, float w0, float Ts) { float Ts2 = Ts * Ts; float b0 = (4/Ts2 + 4*wc/Ts + w0*w0); // 计算分子系数 pr->a[0] = (4*Kp/Ts2 + 4*wc*(Kp+Kr)/Ts + Kp*w0*w0) / b0; pr->a[1] = (-8*Kp/Ts2 + 2*Kp*w0*w0) / b0; pr->a[2] = (4*Kp/Ts2 - 4*wc*(Kp+Kr)/Ts + Kp*w0*w0) / b0; // 计算分母系数 pr->b[0] = (-8/Ts2 + 2*w0*w0) / b0; pr->b[1] = (4/Ts2 - 4*wc/Ts + w0*w0) / b0; // 清零历史数据 memset(pr->x, 0, sizeof(pr->x)); memset(pr->y, 0, sizeof(pr->y)); }4. 实际应用中的优化技巧
4.1 定点数实现方案
对于资源受限的MCU,可采用Q格式定点数优化:
typedef struct { int32_t a[3]; // Q15格式系数 int32_t b[2]; int32_t x[3]; // Q12格式输入 int32_t y[3]; // Q12格式输出 } PRController_Fixed; int32_t PR_Update_Fixed(PRController_Fixed *pr, int32_t input) { pr->x[2] = pr->x[1]; pr->x[1] = pr->x[0]; pr->x[0] = input; int64_t acc = (int64_t)pr->a[0] * pr->x[0] + (int64_t)pr->a[1] * pr->x[1] + (int64_t)pr->a[2] * pr->x[2] - (int64_t)pr->b[0] * pr->y[1] - (int64_t)pr->b[1] * pr->y[2]; int32_t output = (int32_t)(acc >> 15); // Q30 -> Q15 pr->y[2] = pr->y[1]; pr->y[1] = pr->y[0]; pr->y[0] = output; return output; }4.2 抗饱和处理
在实际系统中需要增加抗饱和逻辑:
float PR_Update_With_Clamp(PRController *pr, float input, float min, float max) { // ...正常计算过程... output = constrain(output, min, max); pr->y[0] = output; return output; }4.3 动态参数调整
对于变频应用,可实时更新系数:
void PR_Update_Coeffs(PRController *pr, float w0_new, float Ts) { // 重新计算与w0相关的系数 float b0_new = (4/(Ts*Ts) + 4*pr->wc/Ts + w0_new*w0_new); pr->a[0] = (4*pr->Kp/(Ts*Ts) + 4*pr->wc*(pr->Kp+pr->Kr)/Ts + pr->Kp*w0_new*w0_new) / b0_new; // ...更新其他系数... }在电机控制项目中,我将准PR控制器应用于电流环控制,发现当谐振频率与实际信号频率匹配时,稳态误差可以降低到传统PI控制的1/10以下。特别是在低速运行时,系统仍能保持良好的动态响应特性。