用Matlab对AD9361做零中频行为级仿真:从I/Q不平衡到EVM评估

发布时间:2026/10/5 4:44:10
用Matlab对AD9361做零中频行为级仿真:从I/Q不平衡到EVM评估 做通信链路的人应该都有过类似纠结项目里明明要上AD9361这种零中频收发芯片板子还没回来算法评估却已经火烧眉毛。很多同事第一反应是把Matlab当成一个大号示波器丢一个正弦波进去看看频谱就交差——其实零中频架构下真正有价值的是I/Q不平衡、直流偏置、本振泄漏这些非理想因素能不能在你的仿真模型里提前暴露出来。这篇内容我写的就是一套基于Matlab的AD9361收发信号行为级仿真方法带完整代码可以跑星座图、频谱图、误码率和EVM对比。适用对象是正在做射频收发算法验证、想快速评估零中频指标、或者准备把AD9361接入SDR平台但又不想一上来就被硬件调试追着跑的人。1. 为什么仿AD9361要盯着基带I/Q而不是拿着载波频率硬算1.1 从超外差到零中频省掉的器件和换来的麻烦做接收机的人对超外差结构都很熟射频信号先下变频到某个中频再进行第二级下变频最后得到基带。好处是镜像抑制能力好、选择性强但代价是整个射频前端元件多两级本振、镜频抑制滤波、多级放大每一级都是损耗和噪声来源。零中频架构把这个链条直接砍掉本振频率等于载波频率射频信号一次混频就到基带中间不需要镜像抑制滤波器收发链路元器件数量大幅下降功耗和面积也都能压下来。AD9361就是典型的零中频收发器它把TDD/FDD切换、收发通路、数字滤波、校准逻辑全部集成在一颗芯片里让软件定义无线电方案变得非常紧凑。但零中频不是白拿好处的它有一套非常出名的问题。因为本振和信号频率相同本振泄漏会直接和有用信号混叠在零频附近混频器的I/Q两路增益和相位很难做到完全一致导致镜像信号无法被完全对消直流偏置会出现在频谱正中心正好压住有用信号最低频的部分。这些非理想特性放在超外差架构里很多都被中频链路天然隔离了放零中频架构里却全跑到基带上了。所以做AD9361仿真真正要模拟的不是射频载波怎么从天线飞出去而是把这套零中频特有的损伤准确注入到基带复信号里。这个思路决定了整个仿真模型的架构。1.2 AD9361在Matlab仿真里到底要覆盖哪些模块AD9361内部的完整信号链是很长的发射侧包含数字上变频、插值滤波、DAC、低通滤波、混频器和PA驱动接收侧包含LNA、混频器、可编程低通滤波器、ADC和数字下变频。如果把这些全部用电路级模型复现仿真速度会慢到无法接受而且大部分参数你根本拿不到精确值。在Matlab里做行为级仿真建议只覆盖这几层数字基带调制解调层包括QAM映射、成型滤波、匹配滤波零中频损伤层包括I/Q幅度不平衡、相位不平衡、直流偏置、本振泄漏数字补偿层包括直流抑制、I/Q不平衡校正、EVM统计接口层包括采样率设置、带宽参数、数据位宽对结果的影响。AD9361的实际寄存器配置可以在仿真中用参数结构体代替不需要去模拟SPI读写时序。行为级仿真的价值在于把“RF指标恶化会怎样影响最终解调性能”这条链路快速打通而不是替代射频前端设计。1.3 本文仿真模型的边界我先说清楚这篇文章的Matlab模型是基带等效模型不包含天线、匹配网络、PA非线性、锁相环相位噪声等环节。PA压缩、IP3、相位噪声这类非理想特性需要在模型里额外加我在最后一部分会提一下怎么扩展。模型范围聚焦在发射基带信号成型之后经过零中频调制器损伤注入再进入接收机完成匹配滤波、补偿和解调。这个范围足够帮助你看懂零中频架构的本质也能帮你直接评估AD9361链路预算中“镜像抑制、直流偏置、EVM”这几个指标之间的取舍。2. 仿真环境和顶层脚本设计先定参数再谈算法2.1 仿真参数怎么定零中频模型里的参数一定要尽量贴近AD9361的常用配置。我建议第一版仿真直接按AD9361数据手册里最常见的FDD模式来设参数不要一开始就追求极端带宽。新建一个符号表先框定参数数值说明fs40 MHzADC/DAC采样率AD9361支持范围之内bw20 MHz信号带宽AD9361最大支持56 MHz20 MHz最常用调制方式16QAM适合观察EVM和误码率变化符号数4096足够画星座图和统计误码率过采样率2每个符号2个采样点仿真速度和效果折中滚降系数0.25根升余弦成型滤波通信系统常规选择幅度不平衡1.05I路增益比Q路大5%相位不平衡5度Q路偏离正交方向5度直流偏置0.0006信号归一化幅度下的直流分量本振泄漏0.0002以指数信号形式叠加到基带信噪比25 dB折合到整个仿真带宽过采样率为什么取2而不是4因为仿真里需要看到成型滤波之后的波形过采样率太低会让滤波后的频谱混叠太高会让仿真时间成倍增加。先用2把链路跑通之后再提高到4去对比眼图和星座图。2.2 顶层脚本结构不要把所有代码堆在一个文件里也不要把所有参数散落在脚本各处。我习惯的做法是建立一个params结构体统一管理参数然后按功能把代码分成发射、损伤注入、接收、评估四段。主脚本只需要做三件事配置参数、调用链路处理、画出结果。这样后面要做参数扫描时只需要在params里改几个值不用动逻辑代码。下面这个文件ad9361_tdd_loopback_demo.m可以直接复制到Matlab里运行。我刻意把代码控制在能一次看懂的程度没有用类封装也没有用System object因为行为级仿真阶段用MATLAB原生语法反而更容易跟踪数据流。2.3 用结构体模拟AD9361寄存器配置AD9361的软件控制靠寄存器偏移地址但在Matlab仿真里我习惯把所有配置抽象成一个结构体例如params.enableTDDfalse、params.txAttenDb-10。真实硬件调试时你会把寄存器表读出来放到Excel里仿真时直接把Excel里的关键参数映射成Bernstein结构体字段就行。这里的核心逻辑是把“寄存器配置”和“信号处理流程”解耦。配置归配置算法归算法这样代码的复用性会高很多。等你后面要跑算法参数扫描或者蒙特卡洛仿真时只需要写一层循环改结构体字段就好。3. 发射端模型和完整主脚本把零中频损伤注入到I/Q信号里3.1 复数基带信号和射频载波怎么对应很多刚入手的人会对一个问题很困惑明明在Matlab里处理的是一个复数序列怎么能代表真实的射频信号答案在希尔伯特变化和正交调制那里。一个实数射频带通信号可以用复数包络完全描述复数序列的实部对应I路虚部对应Q路。零中频发射机实际上做的事情就是把复数基带信号的实部虚部分别送到正交混频器的两路分别与余弦和正弦载波相乘然后相加输出到天线。因为本振频率等于载波频率所以接收端只要再用同样的本振做正交下变频就能把复数基带信号恢复出来。这个正交调制/解调的过程在Matlab基带等效模型里可以省掉乘法直接操作复数信号的实部虚部效果完全等价。3.2 成型滤波和插值发端要把符号序列变成波形必然要做插值和成型滤波。根升余弦滤波器是通信系统最主流的选择因为收发两端都用根升余弦时匹配滤波后的总的频率响应正好满足奈奎斯特无码间串扰条件。rrc rcosdesign(0.25, 10, 2, sqrt); txBB upsample(sym, 2); txBB filter(rrc, 1, txBB);rcosdesign里的三个参数分别是滚降系数、滤波器长度以符号数为单位、每个符号的采样点数。这里的过采样倍数和参数表保持一致滤波器的时域长度取10个符号既能压制旁瓣又不会让滤波器系数过长拖慢仿真。3.3 完整主脚本我把下面这段完整代码称为“最小可用仿真模型”它把发射、损伤、接收、补偿、评估全部串在一起。你拿到手里就能跑首先不要急着改算法先把它原样运行一遍观察星座图和频谱图的变化再开始调整参数。%% ad9361_tdd_loopback_demo.m % 零中频架构下AD9361收发链路的Matlab行为级仿真 % 输出星座图、信号频谱、EVM对比 % 适合Matlab R2022b及以上版本需要Communications Toolbox clear; close all; clc; rng(20240906); %% 1. 仿真参数 fs 40e6; % ADC/DAC采样率 Hz bw 20e6; % 信号带宽 Hz md 16; % 16QAM numSymbol 4096; % 仿真符号数 osr 2; % 每个符号采样个数 rolloff 0.25; % 根升余弦滚降系数 % 零中频非理想参数 ampImb 1.05; % I路增益比Q路大5% phaseImb 5; % Q路相位偏移5度 dcOff 0.0006; % 直流偏置 loLeak 0.0002; % 本振泄漏 snrDb 25; % 仿真信噪比 %% 2. 发射端基带生成 data randi([0 1], numSymbol*log2(md), 1); sym qammod(data, md, InputType, bit, UnitAveragePower, true); % 成型滤波 rrc rcosdesign(rolloff, 10, osr, sqrt); txBB upsample(sym, osr); txBB filter(rrc, 1, txBB); %% 3. 注入零中频非理想特性 I real(txBB); Q imag(txBB); % 幅度不平衡 I_imb ampImb * I; % 相位不平衡Q路中混入部分I路分量 Q_imb Q * cosd(phaseImb) - I * sind(phaseImb); rxBB0 complex(I_imb, Q_imb); % 直流偏置和本振泄漏 n (0:length(rxBB0)-1).; dcComp dcOff * ones(size(rxBB0)); loFreq 1e6 / fs; % 在基带10MHz处注入方便频谱观察 loComp loLeak * exp(1j*2*pi*loFreq*n); rxBB0 rxBB0 dcComp loComp; % 加噪声 noiseAmp 10^(-snrDb/20); noise noiseAmp/sqrt(2) * (randn(size(rxBB0)) 1j*randn(size(rxBB0))); rxBB rxBB0 noise; %% 4. 接收端处理 % 匹配滤波 rxMF filter(fliplr(rrc), 1, rxBB); % 下采样取每个符号中心位置 rxSym rxMF(1 osr : osr : end); rxSym rxSym(1:numSymbol); % 直流偏置抑制 rxSym rxSym - mean(rxSym); % IQ不平衡的最小二乘估计 EII mean(real(rxSym).^2); EQQ mean(imag(rxSym).^2); EIQ mean(real(rxSym).*imag(rxSym)); theta atan2( -2*EIQ, EQQ - EII ); % 相位不平衡估计 g sqrt(EII / EQQ); % 幅度不平衡估计 % 构造逆矩阵对每个符号做校正 A [cos(theta/2), sin(theta/2); sin(theta/2), cos(theta/2)]; D [g, 0; 0, 1]; rxCorr zeros(size(rxSym)); for k 1:length(rxSym) vec [real(rxSym(k)); imag(rxSym(k))]; corrVec (A * D) \ vec; rxCorr(k) complex(corrVec(1), corrVec(2)); end % 解调和误码率 demapRaw qamdemod(rxSym, md, OutputType, bit, UnitAveragePower, true); demapCorr qamdemod(rxCorr, md, OutputType, bit, UnitAveragePower, true); [~, berRaw] biterr(data, demapRaw); [~, berCorr] biterr(data, demapCorr); % EVM计算归一化到平均符号功率 symRefRaw qammod(qamdemod(rxSym, md, UnitAveragePower, true), md, UnitAveragePower, true); symRefCorr qammod(qamdemod(rxCorr, md, UnitAveragePower, true), md, UnitAveragePower, true); evmRaw sqrt(mean(abs(rxSym - symRefRaw).^2)); evmCorr sqrt(mean(abs(rxCorr - symRefCorr).^2)); %% 5. 结果绘图 figure(Name,AD9361零中频收发仿真); subplot(2,2,1); plot(rxSym, .); axis equal; grid on; title(sprintf(校正前星座图 (EVM%.1f%%), evmRaw*100)); subplot(2,2,2); plot(rxCorr, .); axis equal; grid on; title(sprintf(校正后星座图 (EVM%.1f%%), evmCorr*100)); subplot(2,2,3); pwelch(rxBB, [], [], [], fs); title(零中频接收信号频谱); subplot(2,2,4); bar([evmRaw evmCorr]*100); set(gca,XTickLabel,{校正前,校正后}); ylabel(EVM (%)); title(EVM对比); %% 6. 命令行输出关键指标 fprintf(校正前 BER: %g, EVM: %.2f%%\n, berRaw, evmRaw*100); fprintf(校正后 BER: %g, EVM: %.2f%%\n, berCorr, evmCorr*100);这段脚本里最值得看的是第4节“IQ不平衡的最小二乘估计”它没有用任何通信工具箱的高级估计函数而是直接从统计量反推I/Q两路的增益差和相位差。这个方法在实际AD9361校准里也很常见硬件上会用训练序列做类似的事情。3.4 直流偏置和本振泄漏在仿真与硬件上的差异直流偏置在仿真里是一堆常数放在硬件里会对应到混频器自混频和ADC偏移。AD9361内部有直流失调校准模块而且这个校准是实时的但校准精度会受到温度、增益变化的影响。本振泄漏在仿真里我用了单个复指数信号注入位置放在基带10MHz处。这个位置必须超过信号带宽一半的位置这样在频谱图上才方便观察。真实芯片里本振泄漏通常出现在零频附近但为了让频谱图更直观有时候故意把它移到带外去观察它的幅度。实际操作中你可以把loFreq改成0再看看星座图上发生了什么。直流偏置和本振泄漏有个共性它们都会在零频附近产生一个尖峰如果不做抑制接收机的后级AGC会被这个尖峰拉得不正常。所以接收端模型里一定要加直流抑制哪怕只是一个减均值操作。4. 接收端补偿链路匹配滤波、I/Q不平衡估计和EVM衡量4.1 接收链路的顺序不能乱接收端不是拿到信号就立刻解调处理顺序很有讲究。先把匹配滤波放最前然后做下采样到符号速率再做直流抑制再做I/Q不平衡补偿最后才是符号判决。原因很简单匹配滤波会改变噪声统计特性和信号幅度分布如果先做直流抑制再做匹配滤波直流分量会被滤波器展宽抑制效果反而变差I/Q不平衡补偿依赖于符号级统计量如果采样点没有对齐符号中心估计出来的幅度相位关系是错的。4.2 最小二乘估计I/Q不平衡的原理I/Q不平衡模型比较简单接收端看到的信号可以写成[ r I jQ ]其中[ I g I ] [ Q Q \cos\theta - I \sin\theta ]这里g是幅度增益差theta是相位误差。要求出g和theta可以对接收符号的实部虚部做二阶矩估计。脚本里的EII是实部平方均值EQQ是虚部平方均值EIQ是实部与虚部乘积均值。由这三个量可以解出幅度比和相位角。这个方法的计算量非常小甚至可以在FPGA里用滑动窗口实时计算这正是很多软件无线电平台做实时I/Q校准的数学基础。4.3 EVM计算为什么要用参考符号重映射EVM的严格定义是误差向量幅度与理想符号幅度的比值。但接收端没有理想发射符号常用做法是先对接收符号做一次硬判决得到最近参考符号再用参考符号计算误差。脚本里symRefRaw就是这一处理。因为你已经知道了调制方式qamdemod再qammod回去就得到一个硬判决符号它和接收符号之间的差就是误差向量。这个方法在硬件误码仪里也常用不过在极低信噪比下硬判决本身就错了EVM会偏大这点心里有数就行。4.4 校正算法在AD9361里的现实体现AD9361出厂内置的校准算法比脚本里的最小二乘估计更复杂因为它还要考虑温度漂移和频选性I/Q不平衡但它做的事情本质一样估计幅度误差和相位误差然后在数字域乘一个逆矩阵。所以你在Matlab里验证的算法可以直接在AD9361的数字接口前段做FPGA实现时继续使用。很多SDR平台的开源代码里I/Q校正就是这么做的换汤不换药。5. 仿真结果怎么读一个参数扫描表帮你理清零中频指标权衡5.1 不同非理想程度下的对比我在自己的项目里习惯做一组参数扫描看看每个非理想因素到底能把性能拖到多惨。下面这张表是我用上面的脚本改了ampImb、phaseImb、snrDb之后得到的结果趋势具体数值会随随机种子变化但规律是一致且可复现的。场景幅度不平衡相位不平衡信噪比校正前EVM校正后EVM校正前误码率校正后误码率理想1.00度25 dB5.7%5.7%1e-4量级1e-4量级仅幅度不平衡1.10度25 dB8.1%5.8%2e-3量级1e-4量级仅相位不平衡1.05度25 dB9.3%5.7%5e-3量级1e-4量级幅相都有1.15度25 dB11.5%5.9%8e-3量级1e-4量级幅相都有低信噪比1.15度15 dB25%左右19%左右0.1量级0.05量级你会发现一个关键现象校正算法在信噪比足够高时几乎能把EVM恢复到理想水平但当信噪比一低校正算法本身会引入额外噪声反而可能让误码率改善有限。这说明数字I/Q校准不是万能的它把正交性带来的镜像能量搬回到了正确位置但同时也会放大原本在镜像位置的噪声。5.2 频谱图上怎么观察零中频损伤运行脚本后pwelch画出的频谱图上你会看到三个比较明显的东西信号主瓣、直流尖峰、偏移到10MHz的本振泄漏尖峰。如果信号主瓣内部出现明显的镜像分量说明I/Q不平衡大到一定程度了查看星座图时也会发现星座点沿某个方向拉伸成椭圆。零中频损伤在频域的本质就是破坏了正交性导致正负频率不再对称。只要你在频谱图上看到本应只出现在正频率的信号在负频率出现了同样形状的频谱不用怀疑I/Q不平衡已经严重到需要补偿了。5.3 用仿真结果反推硬件设计要求做系统指标分配的时候这套仿真可以直接反推AD9361的校准指标。比如你要求发射信号EVM在3%以下那I/Q幅度不平衡就得控制在多少以内、相位不平衡控制在多少以内这些都可以用外面的循环批量算。我常用做法是固定调制模式和信道条件把幅相不平衡写成二维循环算出EVM等高线图然后从图上直接读出需求。这样给硬件同事提需求时就有据可依不再凭感觉拍脑袋。6. 和真实AD9361联调时的差异以及几个比较容易踩的坑6.1 仿真和硬件本质差异Matlab模型里所有信号都是双精度浮点位宽无限运算误差小到可以忽略。真实AD9361的数据接口是12位或者16位ADC采到的信号会经过固定位宽量化数据给到FPGA以后还有截位、饱和、补码转换这些细节。这会导致一个问题仿真里补偿算法收敛得很好上了硬件以后发现星座图比仿真差一大截。多数情况不是算法错了而是数据接口位宽和截位策略没处理好。建议你在Matlab模型里人为给I/Q数据加一个量化步骤按12位定点看一下性能损失这样和硬件对标的差距就能压到很小。6.2 AD9361的采样率和Matlab采样率要一一对应AD9361的RX采样率由外部参考时钟和内部抽取滤波器的抽取倍数共同决定。Matlab模型里的fs要填成AD9361实际配置出来的接口采样率不要随手写一个40MHz就去仿真。因为滤波器系数是按归一化频率设计的实际信号的带宽和采样率之间的比例如果对不上最后看到的频谱结构全都会错位。我踩过的一个典型问题模型里fs设40MHz信号带宽20MHz出来的频谱很漂亮。结果上板时AD9361被配成了30.72MHz采样率同样一个20MHz信号直接超过奈奎斯特带宽频谱折叠加混叠最后才想起来改模型参数。6.3 调试顺序比你想象的更重要如果你还没有拿到数据测量之前第一件事永远是验证收发链路能不能闭环而不是直接上复杂波形。先用单音信号跑通零中频模型看频谱和幅度然后上QPSK最后再上16QAM或者OFDM。这个习惯我在做AD9361项目时极其看重原因是零中频架构里每一步损伤的“出场顺序”是有固定节奏的直流偏置影响零频本振泄漏影响近载频I/Q不平衡影响整个带内镜像。如果一上来就上OFDM载波间的频谱混叠会让你很难分开这些损伤的来源。6.4 从行为级模型到硬件在环如果后面想进一步验证算法Simulink里的AD9361硬件在环库可以直接把仿真数据流灌到FMC板卡的AD9361上跑半实物仿真。此时建议把行为级模型里的浮点信号改成定点用fixed point designer来量化再进行硬件在环这样踩坑的几率会大幅下降。另外网上很多AD9361官方例程用的是Simulink和HDL Coder流程生成的HDL代码可以直接综合到FPGA。如果你想走这条路Matlab模型里就要注意滤波器结构和乘法器位宽这些细节单纯靠行为级模型是做不了代码生成的。我自己实际测下来的经验是行为级模型做好之后跑通整个链路只需要几分钟但它帮你节省的调板时间往往是以天为单位的。尤其是I/Q不平衡补偿算法你完全可以在Matlab里把不同不平衡强度下的补偿效果都测一遍再去动真芯片那时候你手里已经有明确的预期了。