简介:这套Matlab代码面向通信专业学生、信号处理工程师与算法测试人员,实现了无线电信号中的AGC(自动增益控制)算法。AGC能够根据接收信号强弱自动调整前级增益,输入信号幅度增大时降低增益防过载,信号较弱时提升增益改善信噪比,从而保证输出幅度稳定。为便于理解算法工作过程,包内源码完整还原了信号生成、功率测量、增益计算、控制更新和结果绘制等环节:信号生成模块可模拟不同调制方式与强度,功率测量模块通过窗口化平均估计瞬时功率,增益控制模块根据功率与参考门限动态调节步长,并通过循环迭代实现连续跟踪,同时绘制仿真前后对比图。压缩包共15个文件,含9个m脚本/函数和6个mat信号样本文件,整体大小8.53MB,模块划分清晰,可读性强;此外还可通过调整参考功率、环路带宽等参数控制响应速度,抑制振荡与过冲,为实际硬件实现提供参考。已有458人学习下载,适合希望快速掌握AGC原理、进行通信算法仿真或二次开发的读者使用。
1. 无线电信号里的 AGC 是一台自动变增益的机器
第一次拿到老式的短波接收机,拧音量旋钮时会发现一个奇怪现象:同一个频率上,话音忽大忽小,噪声却始终稳定在一个水平上。这不是扬声器的问题,而是前端电路里有一级自动增益控制(AGC)在起作用——它不停测量进入中频的信号强度,再把可变增益放大器的倍数反过来调整,让输出功率基本恒定。到了软件无线电时代,这个环路被搬进了 Matlab 代码里,成了接收机信号处理链上最容易被忽略、却又直接决定解调质量的环节。
AGC 要解决的麻烦很具体:天线口的信号动态范围可以超过 100 dB,而 A/D 转换器和后面的解调算法只能舒服地工作在某个窄带范围内。信号太弱,量化噪声把符号吞掉;信号太强,削顶失真引入一堆杂散。自动增益控制就是拿一个闭环反馈回路,在时域上把信号的 RMS 电平钉在参考值附近。它不关心波形内容,只关心“这段信号有多响”。
做数字接收机的工程师、做雷达回波预处理的学生、以及刚把 Matlab 装好准备跑通信仿真的初学者,都会遇到这个需求。本文从 AGC 的数学原理说起,给出一份可以直接保存为 .m 文件的基线代码,再逐步加上攻击释放、双环结构这类工程改进,最后用 Matlab 的仿真工具验证效果。读完这套内容,你能自己写出一个能应对衰落和突发的 AGC 模块,也明白参数到底该怎么调。
2. 用滑动窗口把 AGC 的数学基础立住
2.1 AGC 闭环的三个动作:估计功率、求增益、回送相乘
数字域 AGC 本质上是一个离散时间反馈系统。它每时每刻都在做三件事:第一,从输入信号里估计当前的功率水平;第二,拿这个估计值和参考电平做比较,算出需要补多少增益;第三,把增益作用到信号上,输出给后面的模块。
功率估计最常用的是 RMS(均方根)方法。对一段长度为 N 的采样序列,RMS 定义如下:
% 对最近 N 个采样求 RMS window = signal(idx-N+1 : idx); rms_now = sqrt(mean(window .* conj(window)));conj是取共轭,复数信号用window .* conj(window)得到瞬时功率,再取平均。这个做法的好处是它和信号调制方式无关,QPSK 也好、OFDM 也好,RMS 都能反映“总体响度”。用峰值来做估计则不稳定,峰均比(PAPR)高的信号会让增益永远满足不了要求。
求得 RMS 之后,增益的计算就是一个除法:
gain = ref_level / rms_now;这里ref_level是目标 RMS 电平。如果输入信号 RMS 是 0.01,目标电平是 0.1,那增益就是 10。这个增益直接乘到当前采样上,输出信号就“听起来”更响了。
但工程上不能用这种瞬时方式直接更新增益。原因在于:真实信号每一帧的 RMS 都在抖动,直接相除会让增益跟着毛刺剧烈跳动,输出波形反而被调制出额外包络。所以 AGC 环路里必须加一个低通滤波,把增益的变化速度限制住。这个限制环节决定了 AGC 的响应速度,也就是后续要说的攻击时间和释放时间。
2.2 基线实现:把 RMS 估计和增益回送写成 Matlab 循环
把上面的数学式子拼起来,就是一段能在 Matlab 里直接运行的基线 AGC。这段代码不考虑效率,先把环路结构写清楚:
function [y, gain_log] = agc_baseline(x, fs, ref_level, win_len, alpha) % AGC 基线实现:滑动窗口 RMS 估计 + 一阶低通增益更新 % 输入: % x 输入信号,列向量 % fs 采样率(Hz),用于换算时间常数 % ref_level 期望输出的 RMS 电平 % win_len RMS 估计窗口长度(采样点) % alpha 增益平滑系数,0~1,越小变化越慢 % 输出: % y 经过 AGC 后的信号 % gain_log 每一时刻的增益记录,用于画图和分析 x = x(:); N = length(x); y = zeros(size(x)); gain_log = zeros(size(x)); gain_prev = 1.0; % 初始增益设为 1,也可以设成上电默认值 for n = 1:N % 1. 用滑动窗口估计输入 RMS idx_start = max(1, n - win_len + 1); window = x(idx_start : n); rms_now = sqrt(mean(window .* conj(window))); % 2. 防止除零 if rms_now < 1e-12 rms_now = 1e-12; end % 3. 计算理想增益 gain_target = ref_level / rms_now; % 4. 一阶 IIR 平滑:攻击和释放用同一个系数 gain_prev = (1 - alpha) * gain_prev + alpha * gain_target; % 5. 施加增益 y(n) = x(n) * gain_prev; gain_log(n) = gain_prev; end end逻辑上这个循环做了五步:滑窗求 RMS、防止除零、计算目标增益、用一阶低通平滑、把增益乘到信号上。alpha是核心参数,它表示“每次采样向目标增益靠近多少”。alpha = 1时环路完全无记忆,增益变成瞬时值,输出波形被严重调制;alpha = 0.01时增益变化很慢,输出比较平滑但对突发信号反应迟钝。
| 参数 | 推荐范围 | 作用 | 过大 | 过小 |
|---|---|---|---|---|
ref_level | 0.01 ~ 0.3 | 设定输出信号的 RMS 电平 | 输出削顶风险高 | 输出信噪比浪费 |
win_len | 50 ~ 500 | 功率统计时长 | 响应慢 | 瞬时功率跳动大 |
alpha | 0.005 ~ 0.1 | 增益平滑速度 | 增益抖动明显 | 跟不上信号起伏 |
以fs = 1e6、窗口长度 100 为例,功率估计窗口正好是 100 微秒。对大多数窄带通信信号,这个窗口能把载波周期都平均掉,又不至于太慢。ref_level的取值则要看后面接的是什么模块:如果是 QPSK 解调器,ref_level通常取 0.1 到 0.2,这样在满幅 ±1 的浮点信号下留足峰值余量。
2.3 环路稳定性:alpha 系数就是 AGC 的时间常数
上面代码里最值得展开的是alpha。一阶低通滤波器的传输函数是:
H(z) = alpha / (1 - (1-alpha) * z^-1)它的时间常数 τ 和alpha的关系是:
tau = -1 / (fs * log(1 - alpha)); % 单位:秒如果fs = 1e6、alpha = 0.01,τ 约等于 100 微秒。这意味着 AGC 大概用 3 到 5 个 τ(0.3~0.5 毫秒)才能把增益调整到位。反过来,如果想指定时间常数,用下面的式子反推:
alpha = 1 - exp(-1 / (fs * tau));这里有个初学者常犯的错:直接用1/(fs*tau)当alpha。当fs * tau数值很大时两者差不多,但在高速采样率下误差会被放大,导致实际收敛速度和预期偏差很大。使用指数形式的换算才是自洽的。
稳定性方面:alpha在 0 到 2 之间环路都是稳定的,超过 2 就振荡。实际应用当然不会取那么大,但在做定点化或浮点转固定点时要注意,alpha被量化后如果偏大,环路会出现极限环。这个细节在纯浮点 Matlab 仿真里看不见,等转到 C 或者 FPGA 上才暴露。
3. 从基线版到工程级 Matlab AGC:三处关键改动
3.1 改动一:增益限幅和归一化,防止无限放大
基线代码里有个隐患:如果输入信号长时间为零,rms_now被钳位到1e-12,理想增益就会飙到1e13量级。虽然在下一个非零采样到来时增益会被拉回来,但拉回来的过程本身会产生一个巨幅瞬态,把后面的滤波器打懵。限制增益范围是最直接的防护手段,同时也要考虑实际系统里可变增益放大器(VGA)的动态范围。模拟前端的增益调节范围有限,数字 AGC 计算出的增益如果超出该范围,只是画饼。
gain_max = 10^(60/20); % 60 dB 增益上限 gain_min = 10^(-40/20); % 40 dB 衰减下限 gain_target = min(max(gain_target, gain_min), gain_max);dB 和线性倍数的换算关系:gain_linear = 10^(gain_db/20)。写代码时统一用线性倍数计算,只在分析时转成 dB 表示。增益限幅放在平滑之前还是之后都行,放在之前可以让平滑过程不被超范围的突变拉走,收敛更干净。
3.2 改动二:攻击和释放采用非对称系数
真实世界的信号波动不是对称的。信号突然变强是坏事——它会引起削顶,必须快压;信号突然变弱则没那么紧急——只是信噪比变差,可以慢放。这就是 AGC 里经典的攻击/释放(Attack/Release)非对称处理思想。
% 攻击快,释放慢 if gain_target > gain_prev alpha_used = alpha_attack; % 比如 0.3 else alpha_used = alpha_release; % 比如 0.005 end gain_prev = (1 - alpha_used) * gain_prev + alpha_used * gain_target;攻击系数取 0.1~0.3,对应微秒级响应;释放系数取 0.001~0.01,对应毫秒级响应。这样设计后,遇到脉冲干扰时 AGC 能快速压住幅度,干扰过去后再慢慢恢复增益,避免输出信号出现明显的“喘息”效应。
用表格对比一下对称与非对称的效果:
| 场景 | 对称系数 | 非对称系数 |
|---|---|---|
| 突发强干扰 | 增益下压速度慢,有削顶 | 增益快速下压,保护后级 |
| 干扰消失 | 增益恢复快,包络起伏大 | 增益缓慢恢复,输出平稳 |
| 语音/数据混合信号 | 两段都不合适 | 攻击快、释放慢,动态范围控制好 |
攻击释放参数的选择还和调制方式有关。OFDM 信号峰均比高,攻击要更慢一些,避免 AGC 跟着单个 OFDM 符号的峰值抖动;单载波 QAM 则可以适当加快攻击。
3.3 改动三:双环 AGC,慢环路锁电平、快环路抓突发
单环 AGC 有个矛盾:环路带宽决定了对功率变化的反应速度,但带宽过大又会把信号本身的幅度调制(AM 分量)也压掉,这对 AM 解调或者带包络信息的调制方式是致命的。工程上常见的折中方案是用双环结构:一个极慢的环路负责把长时间平均功率拉到参考点,另一个快环路只在检测到功率突变时介入。
function [y, g_slow, g_fast] = agc_dual_loop(x, fs) % 双环 AGC 简化实现 % 慢环:持续跟踪平均功率,时间常数约 200 ms % 快环:当信号强度突变超过 10 dB 时介入,时间常数约 1 ms N = length(x); y = zeros(N, 1); g_slow = ones(N, 1); g_fast = ones(N, 1); tau_slow = 0.2; % 慢环时间常数 200 ms tau_fast = 0.001; % 快环时间常数 1 ms a_slow = 1 - exp(-1/(fs*tau_slow)); a_fast = 1 - exp(-1/(fs*tau_fast)); ref = 0.1; % 目标 RMS gain_s = 1.0; gain_f = 1.0; rms_avg = 0; threshold_db = 10; % 快环介入门限 for n = 1:N % 慢环功率估计,使用极长窗口 rms_avg = (1 - a_slow) * rms_avg + a_slow * abs(x(n)); if rms_avg < 1e-12, rms_avg = 1e-12; end gain_s = (1 - a_slow) * gain_s + a_slow * (ref / rms_avg); % 快环:检测瞬时功率偏离慢环的程度 rms_inst = abs(x(n)); if rms_inst > 0 dev_db = 20 * log10(rms_inst / max(rms_avg, 1e-12)); else dev_db = -inf; end if dev_db > threshold_db gain_f = (1 - a_fast) * gain_f + a_fast * (ref / rms_inst); else gain_f = 1.0; % 不干预 end g_slow(n) = gain_s; g_fast(n) = gain_f; y(n) = x(n) * gain_s * gain_f; end end参数说明:tau_slow = 0.2秒意味着慢环在 1 秒内只完成几次有效调整,它只响应信道的大尺度衰落;threshold_db = 10表示瞬时功率高出平均水平 10 倍时才触发快环,这能识别出脉冲干扰这样的瞬态事件。快环的增益是独立叠加在慢环之上的,事件结束后gain_f会自动回到 1,不影响慢环的长期跟踪。
双环结构的代价是多了一组参数要调,而且快环的触发电平不能设得太低,否则正常的信号峰均比波动也会让它频繁介入,反而引入谐波失真。一般从 10 dB 起步,根据实际信号的峰均比往上加。
4. 在 Matlab 里搭建信道场景验证 AGC 性能
4.1 先构造一个带衰落和突发的测试信号
验证 AGC 不能只用恒定幅度信号,那测不出环路的动态行为。常见的做法是生成一个单音或 QPSK 信号,叠加瑞利衰落包络,再在中间插入一段突发干扰。
fs = 2e6; % 采样率 2 MHz t = (0:fs*0.1-1).'/fs; % 100 ms 数据 fc = 50e3; % 载波 50 kHz x = cos(2*pi*fc*t); % 单音测试信号 % 模拟慢衰落:1/Ts 为衰落带宽 fade_freq = 20; % 20 Hz 正弦衰落 fade_env = 0.3 + 0.7 * abs(sin(2*pi*fade_freq*t)); % 0.3~1.0 波动 x_fade = x .* fade_env; % 在 40~45 ms 处插入 4 倍幅度的突发 burst_region = (t > 0.04) & (t < 0.045); x_burst = x_fade .* (1 + 3 * burst_region);fade_env用正弦取绝对值来模拟一个 20 Hz 的时变包络,动态范围约 10 dB;突发段把信号幅值放大到原来的 4 倍(约 12 dB),考验 AGC 的攻击速度。
4.2 画功率曲线和增益曲线,直接观察收敛行为
运行 AGC 之后,用movmean或者直接画20*log10(abs(y))看输出包络是否平整。下面这段代码把输入输出和增益画在同一张图上:
[y, gain_log] = agc_baseline(x_burst, fs, 0.1, 100, 0.02); figure('Color', 'w', 'Position', [100 100 800 600]); subplot(3,1,1); plot(t*1e3, 20*log10(abs(x_burst) + 1e-9)); ylabel('输入功率 (dB)'); title('输入信号包络:衰落 + 突发'); grid on; ylim([-80 0]); subplot(3,1,2); plot(t*1e3, 20*log10(gain_log + 1e-9)); ylabel('增益 (dB)'); title('AGC 增益变化过程'); grid on; subplot(3,1,3); plot(t*1e3, 20*log10(abs(y) + 1e-9)); ylabel('输出功率 (dB)'); xlabel('时间 (ms)'); title('AGC 输出包络'); grid on; ylim([-80 0]);20*log10(abs(x) + 1e-9)里的+1e-9是为了防止log10(0)产生-Inf,画图时出现断线。看输出包络的平坦程度能快速判断 AGC 是否工作正常:理想情况下输出包络应该是一条平滑的水平线,只在突发起始处有小的凹坑——那是攻击过程的痕迹。
4.3 用 EVM 和幅度波动评价 AGC 质量
对调制的信号,功率包络平坦还不够,还要看解调星座图的收敛情况。误差矢量幅度(EVM)是衡量信号质量的通用指标,计算公式如下:
% 假设有理想参考符号 ref_sym 和接收符号 rx_sym err = rx_sym - ref_sym; evm_rms = sqrt(mean(abs(err).^2) / mean(abs(ref_sym).^2)) * 100; % 单位 %EVM 越低说明 AGC 引入的失真越小。基线 AGC 在alpha = 0.02时,信号包络波动可能控制在 ±0.5 dB 以内,但对于 64-QAM 信号,EVM 要求至少低于 8%,这时攻击系数就不能太大,否则每个符号的能量差异会被瞬时增益调制放进去。
| 指标 | 计算公式 | 好的范围 | 说明 |
|---|---|---|---|
| 输出包络波动 | max(20log10(abs(y))) - min(...) | < 1 dB | 观察平稳段的波动 |
| 收敛时间 | 从阶跃开始到误差 < 1 dB | 攻击 < 1 ms,释放 < 50 ms | 用阶跃信号测量 |
| EVM | 见上式 | QPSK: < 10%,64QAM: < 8% | 需要星座图对齐 |
测量收敛时间可以用一个幅度阶跃信号:前 10 ms 输出幅度 0.1,后 10 ms 突跳到 1,然后看 AGC 增益曲线从突变点到稳定值的时间间隔。这就是后面章节要讲的“用阶跃响应定标”。
5. 参数调优技巧:用阶跃响应定标,注意三个工程坑
5.1 用阶跃信号给 AGC 定标,不再瞎试参数
AGC 的参数互相牵扯,逐个试会浪费一晚上。我一般会先用一个理想阶跃信号把环路的基本响应标定出来,再放到真实信道信号里微调。操作方法是:生成一段信号,前半段幅度 0.1,后半段幅度 1.0,在交界处加一个陡峭的跳变。运行 AGC 后看增益对数曲线,从跳变点到增益第一次进入目标值 ±1 dB 的时间差,就是攻击时间;反过来从高幅度跳回低幅度测的就是释放时间。用这种方式标定后的参数有一个明确物理含义,到了和硬件联调阶段,直接把时间常数换算成寄存器值即可,不用再猜。
5.2 坑一:浮点仿真通过,定点就不行
Matlab 默认跑双精度浮点,AGC 的乘法永远不溢出,但真实系统早就定点化了。定点化的关键是把增益的更新公式改成定点格式:gain用 Q1.15 或 Q1.31,alpha用 0~1 的定点小数。最容易出问题的是gain_prev的更新:(1-alpha) * gain_prev + alpha * gain_target在浮点里没问题,定点化后1-alpha的舍入误差会累积,长时间运行增益会漂移。解决方法是每 N 个采样做一次溢出检查,或者在环路里定期把增益强制拉回 1 附近。验证定点行为时,用 MATLAB 的 Fixed-Point Designer 工具箱把同一个信号分别跑浮点和定点版本,对比输出之差,误差超过 0.1 dB 就要检查位宽。
5.3 坑二:增益跳变带来频谱泄漏
即使 AGC 的输出幅度平稳,增益突变本身也会在频谱上产生毛刺。当一个突发信号到来,攻击环路迅速把增益从 20 dB 压到 0 dB,增益不是一个直流电平的衰减,而是一个快速变化的调制函数,它会和信号相乘产生新的频谱分量。这是很多接收机 AGC 后出现带外杂散的根源。要抑制它,除了用更平滑的增益变化曲线,还可以把增益变化率做一个斜率限制(slew rate limit)。在 Matlab 里实现就是在每步更新后检查增益变化量是否超过某个阈值,超过就截断。
max_step_db = 0.5; % 每次采样最多变化 0.5 dB gain_step_db = (20*log10(gain_new) - 20*log10(gain_old)); if abs(gain_step_db) > max_step_db gain_new = gain_old * 10^(sign(gain_step_db)*max_step_db/20); end这个限制会稍稍延长攻击时间,换来的是干净的频谱。采样率越高,每步允许的 dB 数可以越大,最终的时间分辨率还是由环路的时间常数决定。
5.4 坑三:AGC 状态复位和初始化顺序
最后一个坑藏在系统集成时:AGC 不是独立模块,它和 A/D 前端的直流偏置校正、NCO 混频、降采样滤波器一起工作。在很多软件无线电设备里,AGC 的状态(gain_prev、rms_avg)需要在接收机启动时清零,否则旧参数会直接作用在新信号上,造成起始端的剧烈瞬态。另外,建议在 AGC 模块里加一个reset输入端口,当信道切换、频率跳变时由上位机触发复位,比让 AGC 自己慢慢收敛要省时间。尤其是 TDMA 这类突发通信体制,每次突发到来都重新初始化 AGC 状态,往往比连续跟踪得到更好的误码率——突发很短,环路根本来不及收敛,倒不如用一个开环增益表直接查表,这才是突发通信场景里真正高效的做法。
本文还有配套的精品资源,点击获取