3. 开环扫频法原理:扫频信号生成、幅频/相频特性测量、伯德图绘制

做电流环调试,最怕什么?

怕调了半天,带宽上不去,电机还嗡嗡叫。

我个人习惯,拿到一个新平台,第一件事不是调PI参数,而是先做一次开环扫频。说白了,就是给系统做个“体检”。看看它到底能跑多快,相位裕度还剩多少。这比瞎调参数靠谱多了。

3.1 扫频信号怎么生成?

扫频,就是给系统输入一个频率随时间变化的正弦波。我常用的有两种方式:

  • 线性扫频(Linear Sweep):频率随时间线性增加。适合快速看个大概。
  • 对数扫频(Logarithmic Sweep):频率按对数增加。低频段停留时间长,高频段短。我更喜欢这个,因为伯德图横轴就是对数坐标,看起来更直观。

生成扫频信号的数学表达式很简单:

// 离散时间域的对数扫频信号生成
// f_start: 起始频率 (Hz)
// f_end:   终止频率 (Hz)
// T:       扫频总时长 (s)
// fs:      采样率 (Hz)

float generate_chirp(float t, float f_start, float f_end, float T) {
    float k = (f_end - f_start) / T;  // 频率变化率
    float instantaneous_freq = f_start + k * t;
    float phase = 2 * PI * (f_start * t + 0.5f * k * t * t);
    return sinf(phase);
}
我的经验:扫频时长T不要设太短。我一般设1~2秒。太短了,低频段信息不够;太长了,电机可能发热或者跑飞。另外,扫频信号的幅值要控制好,我通常取额定电流的5%~10%,别把电机扫坏了。

3.2 幅频/相频特性怎么测量?

信号发出去了,怎么知道系统响应呢?

我们需要同时采集输入信号(电流指令)和输出信号(实际电流反馈)。然后做傅里叶分析。

具体步骤是这样的:

  1. 同步采集:输入和输出必须严格同步。我遇到过采样不同步导致相位计算全错的情况,排查了好久。
  2. 加窗处理:直接做FFT会有频谱泄露。我习惯用汉宁窗(Hanning Window),效果不错。
  3. 提取基波分量:在每一个频率点上,提取输入和输出的幅值和相位。

幅频特性计算:

// 假设已经通过FFT得到了输入和输出的复数频谱
// f_idx: 当前扫频频率对应的FFT索引

float amplitude_in  = sqrtf(real_in[f_idx] * real_in[f_idx] + imag_in[f_idx] * imag_in[f_idx]);
float amplitude_out = sqrtf(real_out[f_idx] * real_out[f_idx] + imag_out[f_idx] * imag_out[f_idx]);

float gain_dB = 20.0f * log10f(amplitude_out / amplitude_in);

相频特性计算:

float phase_in  = atan2f(imag_in[f_idx],  real_in[f_idx]);
float phase_out = atan2f(imag_out[f_idx], real_out[f_idx]);

float phase_deg = (phase_out - phase_in) * 180.0f / PI;
// 注意:需要做相位解缠绕(unwrap),否则相位会跳变
注意:相位计算时,一定要做相位解缠绕(unwrap)。我曾经因为没做这一步,画出来的伯德图相位曲线像锯齿一样,根本没法看。解缠绕说白了就是把跳变超过180°的相位,加上或减去360°的整数倍,让它连续起来。

3.3 伯德图怎么绘制?

有了幅值和相位数据,画伯德图就简单了。横轴是频率(对数坐标),纵轴分别是增益(dB)和相位(度)。

我一般用Python的matplotlib库来画,在MCU上也可以把数据通过串口发到上位机处理。

下面是一个简单的绘制流程:

import numpy as np
import matplotlib.pyplot as plt

# 假设已经测量得到以下数据
freq = np.array([10, 20, 50, 100, 200, 500, 1000])  # Hz
gain = np.array([0, -0.5, -1, -3, -6, -12, -20])    # dB
phase = np.array([0, -5, -10, -45, -90, -135, -160]) # 度

plt.figure(figsize=(10, 6))

# 幅频图
plt.subplot(2, 1, 1)
plt.semilogx(freq, gain, 'b-o')
plt.grid(True, which='both', linestyle='--')
plt.ylabel('增益 (dB)')
plt.title('开环扫频伯德图')

# 相频图
plt.subplot(2, 1, 2)
plt.semilogx(freq, phase, 'r-o')
plt.grid(True, which='both', linestyle='--')
plt.xlabel('频率 (Hz)')
plt.ylabel('相位 (度)')

plt.tight_layout()
plt.show()

画出来之后,怎么看?

  • 穿越频率(0dB线):增益降到0dB时的频率。这就是系统的带宽。我一般希望电流环带宽在1kHz~3kHz之间。
  • 相位裕度:在穿越频率处,相位距离-180°还有多少度。低于30°系统就容易振荡。我习惯留45°以上。
核心结论:开环扫频法,就是用已知信号去激励系统,然后通过测量响应来反推系统的传递函数。它不需要知道电机参数,完全基于实测数据,所以非常准确。我每次调完PI参数,都会用扫频法验证一下,看看实际带宽和设计值是否一致。

3.4 知识体系总览

下面这张图,是我自己总结的开环扫频法知识结构。你可以把它当作一个检查清单:

开环扫频法知识体系 扫频信号生成 线性扫频 / 对数扫频 幅频/相频测量 FFT + 加窗 + 解缠绕 伯德图绘制 幅频图 + 相频图 关键参数:扫频时长T、幅值A、采样率fs、FFT点数N 输出结果 穿越频率 → 带宽 | 相位裕度 → 稳定性 实际调试时,建议先做一次快速扫频(1秒),找到大概的穿越频率,再做精细扫频

嗯,到这里,开环扫频法的原理就讲完了。说白了,就是三步走:生成信号、测量响应、画图分析。每一步都有坑,但踩过一次就记住了。

避坑指南:我曾经在测量相位时,忘记考虑ADC采样延时和PWM更新延时,导致相位测量结果偏大。后来我在代码里加了一个固定的相位补偿值,才把问题解决。如果你发现扫频结果和理论值对不上,先检查一下采样和控制的时序对齐。

公众号:蓝海资料掘金营,微信deep3321