
零中频架构现在基本是软件无线电接收前端的标配了ADI的AD9361更是把这种架构做到极致的典型代表。很多朋友手里有AD9361的板子但真正去调之前心里没底——寄存器配置那么多发出去信号到底对不对收回来数据能不能解调完全是个黑盒。我建议在动板子之前先用Matlab把零中频收发链路完完整整跑一遍。这篇文章就用Matlab对AD9361的零中频架构做一个端到端仿真从发送端的比特到接收端的解调全部打通并给出完整可运行的代码。适合正在调AD9361驱动、做FPGA基带开发、或者刚接触零中频架构的同学参考。1. 内容整体设计与思路拆解1.1 为什么不直接拿板子调非要先做仿真AD9361是一款高度集成的射频收发器内部集成了完整的零中频接收和发送链路。它的寄存器配置量非常大而且很多参数之间是耦合的。我见过太多人在板子上调了半天最终发现是发送端滤波器的配置和采样率的配置对不上导致信号在频域上歪掉了。用Matlab做仿真最大的价值是把链路逻辑和硬件寄存器解耦。先在仿真环境里把信号从产生、调制、上变频到恢复、解调的完整逻辑跑通再去看AD9361的手册你会清楚每个寄存器到底在控制什么。比如你仿真时会发现零中频架构有直流偏置和I/Q不平衡问题再去查看AD9361手册里的DC Offset Calibration和Quadrature Calibration章节就会觉得非常亲切。1.2 零中频架构的核心特点零中频架构简单说就是本振频率直接等于射频载波频率一次混频就把射频信号变成基带信号。相比超外差架构它省掉了中频滤波器、第二本振等一堆器件硬件成本低、集成度高AD9361能在单颗芯片里集成两个完整的收发通道靠的就是这个架构。但零中频架构天生有三大痛点这也是Matlab仿真时要重点模拟的第一是直流偏置。本振泄露会自己把自己混频成直流这个直流会落在基带信号的中间直接污染信号。第二是I/Q不平衡。受限于模拟器件的一致性I路和Q路的幅度、相位不可能完全相等这个误差会造成镜像干扰直接掉信噪比。第三是闪烁噪声。零中频架构里信号在直流附近而模拟器件的闪烁噪声集中在低频对信号的信噪比影响非常大。在Matlab里我们可以把这三个问题一一建模然后设计相应的补偿算法这样到了板子上你有充足的预判。1.3 Matlab仿真的整体定位与边界这次仿真不是要做一套完整的通信系统级仿真——如果你想连接高阶调制和信道编码那是另一套更复杂的工程。这里重点是射频前端建模基带收发验证核心是搭建一个贴近AD9361实际工作的链路。我用的方法是在Matlab里用基带等效模型来做。射频信号不用真的用几十GHz的采样率去表示而是在基带用复数信号等效。这个等效模型在通信仿真里非常常用前提是系统满足窄带条件。AD9361的工作频率从70MHz到6GHz基带带宽最大支持56MHz完全满足窄带条件。这样做的计算效率极高而且关注点恰好是零中频架构的基带特性比如直流偏置、镜像干扰、I/Q不平衡这些都是基带可以建模出来的。2. 收发链路架构与AD9361参数配置对标2.1 仿真链路整体框图整个仿真链路分成了发送端、信道、接收端三部分。发送端做的是比特生成、BPSK映射、脉冲成型、上采样、插入导频、I/Q混频。这里的混频在基带模型里表现为把复数基带信号搬移到数字中频或者保持基带直接发射。这里要注意一个概念零中频架构在基带数字端的模型。AD9361的基带接口是数字I/Q数据它的DAC输出的是模拟基带I/Q信号然后经过混频器直接上变频到射频。所以在Matlab建模的时候发送端的上变频其实是看我们想仿真到哪个层级。如果只是想验证基带算法直接发复数基带信号即可。如果想要更贴近AD9361的模拟前端就需要建模DAC、低通滤波器、混频器、本振泄露、I/Q不平衡。为了实用我采用了后者因为这样接收端才能模拟出AD9361接收时面临的真实问题。2.2 AD9361关键参数与仿真参数对齐AD9361的配置里有几个关键参数仿真时必须对齐BBPLL频率决定了一切时钟域。AD9361的内部时钟由BBPLL分频得到比如基带采样率、本振频率都源于这个PLL。在实际配置中基带采样率通常由FPGA侧的接口速率和抽取/内插比共同决定。我用一组典型的AD9361配置参数作为仿真基准发射路径内插配置AD9361发射路径有三种内插模式。对应FPGA侧的采样率我们需要计算出DAC实际采样率。接收路径抽取配置接收路径也有对应的抽取模式。在Matlab里我们不用真的去建这些时钟树但要用最终的采样率作为仿真参数基准。比如仿真设定基带符号速率1MHz基带采样率30.72MHz这个采样率和AD9361常用的30.72MSPS接口采样率是对齐的。2.3 为什么选择BPSK做验证信号为什么不用QPSK或者16QAM而是用BPSK原因比较简单第一BPSK对载波同步和定时同步偏差的容忍度最高便于先把链路打通第二BPSK的星座图只有两个点接收端调试时眼图、星座图都很直观第三BPSK配零中频架构刚好能直接暴露直流偏置问题——直流偏置会让BPSK星座图的中心点偏移而这在星座图上肉眼可见。AD9361的应用文档里也经常用BPSK做链路自检因为它的信息熵低容错率高。建议你先跑通BPSK再换成QPSK最后再上高阶QAM。3. 核心细节解析与实操要点完整代码与逐段讲解3.1 初始化参数与信号生成下面是仿真代码的第一部分负责参数初始化和发射比特流生成。代码里写了详细的注释完全可以直接复制运行。%% 零中频收发链路仿真 —— 对标AD9361架构 % 清除工作区 clear; close all; clc; rng(42); %% 链路参数定义 % 符号速率与采样率 Fs 30.72e6; % 基带采样率对标AD9361接口采样率30.72MSPS Rs 1e6; % 符号速率 sps Fs / Rs; % 每个符号的采样点数 % 这里sps 30.72不是整数务必注意后面需要特殊处理 numSymbols 2000; % 发送符号总数 numBits numSymbols; % BPSK: 1个符号 1个比特 % BPSK映射 bits randi([0 1], numBits, 1); symbols 2 * bits - 1; % 0 - -1, 1 - 1 %% RRC脉冲成型滤波器 % 滚降系数AD9361的基带滤波典型配置为0.35 rolloff 0.35; filterSpan 8; % 滤波器截断长度符号数 rrcFilter rcosdesign(rolloff, filterSpan, sps, sqrt);这里有个非常典型的坑sps 30.72 不是整数。rcosdesign要求sps是整数所以这里直接运行会报错。实际的解决方案有两种一是把采样率调整到31MHz使sps变成整数二是使用任意倍插值。但在实际FPGA实现中30.72MHz对应的是12.288MHz的晶振时钟倍频出来的不是随意改的。我建议在仿真中保持30.72MSPS用重采样方式解决但这样代码会复杂。本文为了代码简洁且可复现先强制把Fs调整到31MHz即Fs 31e6sps 31。后面在板级联调时再改成30.72MSPS并配合真实接口时序来验证。这一点请大家注意区分仿真的核心是验证架构和算法思路采样率数值只要和最终硬件配置保持相同的数量级和比例关系即可。3.2 上采样与脉冲成型接下来做上采样把符号序列变成采样点序列再经过RRC成型。%% 上采样 % 在每个符号后插入 (sps-1) 个零 txSamples zeros(length(symbols) * sps, 1); txSamples(1:sps:end) symbols; %% 脉冲成型滤波 txShaped filter(rrcFilter, 1, txSamples);滤波后信号会有滤波器延迟即filterSpan个符号。这个延迟在接收端必须对齐否则定时误差会很严重。后面接收端我会用同步方法来自动对齐这里先不手动补偿。成型滤波的作用是限制信号带宽。在AD9361的发送端这个功能由数字滤波器和模拟基带滤波器共同完成。仿真里用RRC接收端再用一个匹配的RRC这样收发联合起来是升余弦响应没有码间干扰。3.3 零中频发射前端建模这里是核心部分。真正对标AD9361的发射链路不能只是简单地把复基带信号发出去。AD9361发射链路里DAC输出的是模拟I/Q信号然后经过低通滤波器再进入混频器上变频到射频。零中频架构的发射前端有几个非理想特性要在仿真中体现。%% 发射前端建模 % 这里仿真的是DAC之后的模拟基带信号再等效到数字域 % 模拟I/Q增益不平衡 TxIqGainImbalance 0.05; % 5%的增益不平衡 TxIqPhaseImbalance 2; % 2度的相位不平衡 % 本振泄露建模即LO coupling TxLoLeakage 0.02; % 本振泄露幅度 % 发射I路和Q路 txI real(txShaped); txQ imag(txShaped); % 加入增益不平衡 txI_imb txI * (1 TxIqGainImbalance); txQ_imb txQ * (1 - TxIqGainImbalance); % 加入相位不平衡等效于让Q路引入一部分I路的信号 txQ_imb txQ_imb * cosd(TxIqPhaseImbalance) txI_imb * sind(TxIqPhaseImbalance); % 加入本振泄露 leakI TxLoLeakage * ones(size(txI_imb)); leakQ TxLoLeakage * ones(size(txQ_imb)); % 合成复基带信号 txSignal (txI_imb 1j * txQ_imb) (leakI 1j * leakQ);这段代码背后的物理意义要解释清楚。增益不平衡模拟的是AD9361内部I路和Q路两个DAC通道在实际制造中的差异。相位不平衡则是混频器两个正交本振信号不完全是90度引起的。本振泄露是零中频发射机的典型问题。混频器的本振端口到射频输出端口之间存在寄生耦合本振信号会漏到射频输出端。在接收端解调的时候这个漏过去的本振又被接收机自己的本振混频直接变成直流。在实际的AD9361配置中芯片内部有BIST和校准功能可以自动校正这些非理想性。但校准不是完美的总会有残余误差。我们在仿真中加入这些误差就是为了测试接收端算法对这些误差的容忍度。3.4 信道与接收前端建模信号经过发射端后进入信道。这里做一个简单的加性高斯白噪声信道然后进入接收端。接收端的前端同样有非理想性。%% 信道 % 加入高斯白噪声信噪比设定为12dB SNR_dB 12; txPower mean(abs(txSignal).^2); noisePower txPower / (10^(SNR_dB/10)); rxSignalCh txSignal sqrt(noisePower/2) * (randn(size(txSignal)) 1j*randn(size(txSignal))); %% 接收前端建模 % 接收端同样存在I/Q不平衡 RxIqGainImbalance -0.03; RxIqPhaseImbalance -1.5; % 直流偏置接收端混频器自混频 RxDcOffset 0.01 0.005j; % 模拟接收端I/Q不平衡 rxI real(rxSignalCh); rxQ imag(rxSignalCh); rxI_imb rxI * (1 RxIqGainImbalance); rxQ_imb rxQ * (1 - RxIqGainImbalance); rxQ_imb rxQ_imb * cosd(RxIqPhaseImbalance) rxI_imb * sind(RxIqPhaseImbalance); rxSignal (rxI_imb 1j * rxQ_imb) RxDcOffset;接收端的直流偏置比发射端的本振泄露更常见。AD9361接收通道在混频时本振信号从混频器射频端口漏进来看似不可能但事实上在高频布线中本振信号通过基板耦合到射频输入端口的情况很常见。还有一种更隐蔽的机制强干扰信号从射频端口进入经过混频器非线性产物产生直流。在AD9361的实际使用中这些前端不理想因素可以通过寄存器配置内部的直流失调校正、I/Q相位校正、I/Q增益校正来消除一部分。但校正算法依赖初始校准且受温度影响会漂移。所以我们仿真时不能假设这些误差完全不存在。3.5 接收端信号恢复算法接收端的算法是核心。AD9361数字接口出来的是数字I/Q基带信号接下来需要依次完成直流偏置估计与消除、匹配滤波、定时同步、载波同步、解映射、误码率统计。%% 直流偏置估计与消除 % 直流偏置在频域上就是零频处的冲击对信号求平均即可估计 dcEst mean(rxSignal); rxSignalDcRemoved rxSignal - dcEst; %% 匹配滤波 rxFiltered filter(fliplr(rrcFilter), 1, rxSignalDcRemoved); % 匹配滤波器会引入与发送端相同的群延迟需要对齐 % 简单做法截掉信号开头部分的瞬态响应 groupDelay filterSpan * sps; rxFiltered rxFiltered(groupDelay1:end);这里的关键是匹配滤波器的实现。我们用fliplr(rrcFilter)作为匹配滤波器系数其实对于对称滤波器来说fliplr之后和原滤波器是一样的。因为RRC滤波器是线性相位、对称的所以匹配滤波器和成型滤波器本质上相同。但要注意实际数字通信中更常见的做法是发送端和接收端各用一组根升余弦滤波器合起来是升余弦滤波响应。匹配滤波器延迟是两组滤波器的总群延迟所以要截掉的点数是filterSpan×sps×2。我发现很多初学者在这里栽跟头截取的点数不对导致后面定时同步一直锁不住。建议在代码里做延迟估计而不是拍脑袋截取。这里为了控制篇幅我直接用了一个已知的群延迟值。3.6 定时同步算法实现定时同步是需要重点讲的部分。仿真中sps31符号点不一定正好落在采样点上需要恢复出最佳采样时刻。我用的方法是对每个符号周期内的31个采样点分别计算平均功率选择功率最大的采样点作为最佳采样点。这是一种简单的能量检测方法适合在加性高斯白噪声信道下工作。%% 定时同步能量最大法 numRxSymbols floor(length(rxFiltered) / sps); rxSymbolMatrix reshape(rxFiltered(1:numRxSymbols*sps), sps, numRxSymbols); energyPerPhase mean(abs(rxSymbolMatrix).^2, 2); [~, bestPhase] max(energyPerPhase); downsampled rxSymbolMatrix(bestPhase, :).;这种方法的局限是精度只有一个采样间隔。在sps31的情况下定时误差最大是1/31个符号周期对于BPSK解调来说完全可以接受。如果要做更精细的定时同步比如用Gardner算法或者Mueller-Muller算法后续可以在代码中扩展。我用能量最大法的另一个考虑是代码的可读性。Gardner算法在工程上更常用但第一版仿真中我倾向于用直觉化的方法先把链路跑通确认无误后再替换成环路型算法。3.7 载波同步与误码率统计BPSK对载波频偏有一定容忍度但对相位偏置敏感。由于我们的仿真中本振没有引入频偏只会有相位偏置我直接用接收信号实部符号进行判决。%% 载波相位估计与补偿 % 对于BPSK可以通过取实部符号直接判决 % 但先做一次简单的相位估计会提升性能 receivedSymbols downsampled; % 硬判决 decodedBits real(receivedSymbols) 0; % 与原始比特对齐后计算误码率 % 由于滤波延迟已经截掉这里无需额外对齐 decodedBits decodedBits(1:numBits); % 误码率统计 bitErrors sum(decodedBits ~ bits); BER bitErrors / numBits; fprintf(误码数: %d, 误码率: %.2e\n, bitErrors, BER);这里相位补偿省略了。如果仿真中加入本振频偏那接收端就必须做一个科斯塔斯环来恢复载波。AD9361本身的射频本振是锁相环产生的通常频偏很小但在高载频比如5.8GHz下即使有温补晶振也容易产生几kHz的频偏。这时候载波同步环路就是必须的了。等最后一节我会单独给出带载波频偏的仿真扩展思路。3.8 画图分析把关键节点的信号画出来这是仿真最有价值的部分。%% 画图 figure(Name, 零中频收发链路仿真结果); % 发射基带信号与接收基带信号频谱 subplot(2,2,1); [psdTx, f] pwelch(txSignal, [], [], [], Fs, centered); plot(f, 10*log10(psdTx)); title(发射基带信号频谱); xlabel(频率 (Hz)); ylabel(功率谱密度 (dB)); grid on; subplot(2,2,2); [psdRx, f] pwelch(rxSignal, [], [], [], Fs, centered); plot(f, 10*log10(psdRx)); title(接收基带信号频谱); xlabel(频率 (Hz)); ylabel(功率谱密度 (dB)); grid on; subplot(2,2,3); plot(real(txSignal(1:1000)), b); hold on; plot(imag(txSignal(1:1000)), r); legend(I路, Q路); title(发射基带IQ时域波形); grid on; subplot(2,2,4); plot(real(receivedSymbols), imag(receivedSymbols), .); title(接收端解调星座图); xlabel(I); ylabel(Q); grid on;星座图画出来后零中频架构的问题就非常明确了。如果没有直流偏置和I/Q不平衡BPSK星座图会是两个点左右对称分布在单位圆上。加入直流偏置后两个点整体偏移离原点不再对称。加入I/Q不平衡后两个点就会沿某个方向被拉伸。3.9 完整代码整合把上面几段代码拼接起来就是一个完整的脚本。以下是整合后的框架我建议你保存为一个.m文件逐步运行。% 完整的零中频收发仿真脚本 clear; close all; clc; rng(42); % --- 参数 --- Fs 31e6; Rs 1e6; sps Fs/Rs; numSymbols 2000; bits randi([0 1], numSymbols, 1); symbols 2*bits - 1; rolloff 0.35; filterSpan 8; rrcFilter rcosdesign(rolloff, filterSpan, sps, sqrt); % --- 发射 --- txSamples zeros(length(symbols)*sps, 1); txSamples(1:sps:end) symbols; txShaped filter(rrcFilter, 1, txSamples); % 发射前端 txI real(txShaped); txQ imag(txShaped); txI_imb txI*1.05; txQ_imb txQ*0.95; txQ_imb txQ_imb*cosd(2) txI_imb*sind(2); txSignal (txI_imb 1j*txQ_imb) 0.02*(11j); % --- 信道 --- SNR_dB 12; txPower mean(abs(txSignal).^2); noisePower txPower / (10^(SNR_dB/10)); rxSignalCh txSignal sqrt(noisePower/2)*(randn(size(txSignal)) 1j*randn(size(txSignal))); % --- 接收前端 --- rxI real(rxSignalCh); rxQ imag(rxSignalCh); rxI_imb rxI*0.97; rxQ_imb rxQ*1.03; rxQ_imb rxQ_imb*cosd(-1.5) rxI_imb*sind(-1.5); rxSignal (rxI_imb 1j*rxQ_imb) 0.01 0.005j; % --- 接收算法 --- dcEst mean(rxSignal); rxSignalDcRemoved rxSignal - dcEst; rxFiltered filter(rrcFilter, 1, rxSignalDcRemoved); groupDelay filterSpan * sps; rxFiltered rxFiltered(groupDelay1:end); numRxSymbols floor(length(rxFiltered)/sps); rxSymbolMatrix reshape(rxFiltered(1:numRxSymbols*sps), sps, numRxSymbols); energyPerPhase mean(abs(rxSymbolMatrix).^2, 2); [~, bestPhase] max(energyPerPhase); downsampled rxSymbolMatrix(bestPhase, :).; % --- 判决与BER --- decodedBits real(downsampled) 0; decodedBits decodedBits(1:numBits); bitErrors sum(decodedBits ~ bits); BER bitErrors/numBits; fprintf(BER %.2e\n, BER);4. 常见问题与排查技巧实录4.1 直流偏置估计不准导致的误码实际仿真跑下来会发现一个有意思的现象直流偏置的估计不能简单地对整段接收信号求平均。因为基带信号本身不是零均值的BPSK的1和-1即使等概率出现由于滤波器截断、定时偏差等原因帧头位置的均值也可能有偏移。更好的做法是单独留出一段已知的导频序列来估直流偏置。AD9361在接收链路里也有类似的机制校准阶段会关闭接收通路或者发送特定的直流信号来测量。在实际工程中我一般建议在数据帧的头部插入一段全1的序列这样在接收端取这段序列的平均值即可精确估计出直流偏置。4.2 I/Q不平衡补偿什么时候做很多同学在仿真的早期阶段完全不加入I/Q不平衡等链路通了再加入结果到后面发现误码率始终差一点点。这个现象其实说明了一个本质问题数字域的I/Q不平衡补偿算法必须和整个接收链路一起联调。I/Q不平衡补偿有两个手段。一个是在频域上用自适应滤波。还有一个是在时域上用盲估计和补偿。AD9361内部用增益校准和相位校准寄存器来做是盲校准的方法。在Matlab仿真里我用最小均方误差算法做自适应补偿能够明显提升星座图的汇聚质量。如果你的应用是BPSKI/Q不平衡的影响其实有限但换成16QAM或者64QAM就会发现I/Q不平衡直接让星座图的角落位置的点模糊不清。4.3 定时同步的失败模式能量最大法有个隐含假设最佳采样点具有最大平均功率。这在加性高斯白噪声下成立但在有码间干扰的情况下就不一定了。更麻烦的情况是sps非整数。前面提到的30.72MSPS采样率和1MHz符号速率sps 30.72如果我们直接按31个采样点取一符号去reshape会导致相邻符号的采样点位置缓慢滑动。这个问题在仿真中会表现为每隔一定数量的符号星座点就开始发散。实际的解决方案有三个层级第一个是重采样到整数倍采样率比如把30.72MSPS重采样到31MSPS然后在31MSPS域做整数定时同步。第二个是用多相滤波器组每个符号周期输出一个最优采样点这是FPGA里常做的。第三个是用Gardner定时同步环路这个环路天然支持任意插值率也是实际FPGA实现里最常用的方案。MATLAB的Simulink里Communications Toolbox自带了一个定时同步库内置了Gardner算法可以直接拖出来用。我们这版仿真为了保持全代码可见选择了能量最大法是故意的——让用户可以看清每一步在做什么。4.4 Matlab版本与工具箱兼容性问题这里补充一个非常实际的问题。rcosdesign这个函数属于Communications Toolbox。如果没有安装这个工具箱代码会直接报错Undefined function or variable rcosdesign。如果没有这个工具箱可以自己手动实现RRC滤波器核心思路是计算滤波器的频域响应然后做逆傅里叶变换得到时域系数。这段代码我用过很多次。% 手动实现RRC滤波器无工具箱版本 function h rrc_manual(beta, span, sps) N span * sps; t (-N/2:N/2) / sps; h zeros(size(t)); for k 1:length(t) if t(k) 0 h(k) 1 - beta 4*beta/pi; elseif abs(t(k)) 1/(4*beta) h(k) (beta/sqrt(2)) * ((12/pi)*sin(pi/(4*beta)) (1-2/pi)*cos(pi/(4*beta))); else num sin(pi*t(k)*(1-beta)) 4*beta*t(k).*cos(pi*t(k)*(1beta)); den pi*t(k)*(1-(4*beta*t(k)).^2); h(k) num ./ den; end end h h / sqrt(sum(h.^2)); end5. 从仿真到AD9361板级联调的衔接建议5.1 代码移植到FPGA时的资源估算Matlab跑通并不等于FPGA可以量产。仿真里2000个符号的RRC滤波是整段滤波而FPGA里必须用流水线结构做卷积。sps31的滤波器阶数是8×311249阶实际上249个乘累加器在FPGA里并不算多但要注意并行度和时钟速率的关系。AD9361的基带接口时钟和采样率是固定的。以30.72MSPS为例如果FPGA用两个通道各15.36MHz的并行数据总线那么每个时钟周期处理两个采样点。RRC滤波器就需要在一个时钟周期内完成249次乘加运算这会消耗大量DSP Slice。建议的做法是使用多相分解滤波器结构。把249阶滤波器分解成31个子滤波器每个子滤波器7到8阶这样每个采样点只需要7到8次乘法。这是软件无线电里非常经典的做法。5.2 AD9361初始化寄存器的顺序问题AD9361的上电初始化顺序是有讲究的。如果配置顺序不对有些通道状态机会卡住。在实际项目里我习惯按照下面的顺序来做先配置全局使能和时钟分频然后配置射频前端参数本振频率、增益模式再配置校准模式直流失调校准、I/Q相位校准、I/Q增益校准最后配置数据接口格式CMOS或LVDS全双工或半双工。注意校准必须在数据通路稳定之后再做否则校准结果会写入到错误的寄存器。这个顺序问题在ADI官方文档中描述较为分散很多人在第一次调板子时都因为顺序问题吃了亏。Matlab仿真能帮上什么忙呢在仿真里我们可以先明确数据通路的结构知道ADC和DAC的数据顺序是什么然后再去看寄存器配置时才对得上号。比如AD9361支持I/Q数据交换IQ Swap的寄存器位如果配置和基带算法中I/Q的定义不一致解调出来的星座图会发生镜像翻转。这在Matlab里很容易模拟把Q路取反再解调看看星座图变成什么样你就知道IQ Swap配置错误时是什么现象了。5.3 仿真结果和板级实测如何对标这里我建议方法如下在Matlab仿真的发送端生成一段固定的基带I/Q数据存成文本文件通过串口或网口灌给FPGA的发送缓存让AD9361把这个数据发射出去。然后用频谱仪看发射信号频谱。此时Matlab仿真里画的基带频谱和频谱仪上看到的射频频谱应该是高度一致的只是中心频率搬移到了本振频率上。接收端更直观用信号源发射一个单音信号频率设置为本振频率1MHz此时AD9361的基带输出应该是一个1MHz的单音。你可以把这个单音信号采回来用Matlab做FFT观察它的幅度和频率。如果发现存在镜频说明接收端的I/Q不平衡校准还没生效仿真里的现象和实测就能对上了。这种仿真生成激励、硬件实测、回采分析的三步法是我调AD9361时最常用的一套流程。它能快速定位问题到底出在射频前端、数字前端还是基带算法。5.4 从BPSK到QPSK再到高阶QAM的演进路径这版仿真用的是BPSK。在代码基础之上你可以快速扩展到QPSK只需要修改符号映射部分。% QPSK映射只需要把原来的2比特转1个4符号改成2比特转1个4象限符号 % 输入bit 00-1j, 01--1j, 10-1-j, 11--1-j bits_qpsk randi([0 3], numSymbols, 1); symbols_qpsk exp(1j*pi/4) * (1/sqrt(2)) * ... ((1-2*(floor(bits_qpsk/2))) 1j*(1-2*mod(bits_qpsk,2)));然后重新跑一遍链路你会看到误码率性能下降了不少。因为QPSK的相邻星座点距离比BPSK近了一个数量级对I/Q不平衡和直流偏置的容错度更差。这一步做完之后再换成16QAM你就能把零中频架构的整个非理想特性摸得透透的。6. 最后再分享几点实操体会6.1 仿真模型不要一开始做太复杂我见过很多工程师把零中频的仿真模型做得非常精细每个模拟器件都建模结果代码几千行跑出来很多非理想效应已经超出了数字端算法的纠正能力范围反而分不清问题在哪。我的经验是第一版仿真只要三个非理想效应本振泄露引起的直流偏置、I/Q增益不平衡、I/Q相位不平衡。这三个是零中频架构最大的三个问题先把这三个问题建模数字端的补偿算法就已经很有挑战性了。温度漂移、非线性、相位噪声这些可以放在后续版本迭代中再加入。6.2 Matlab代码的版本管理同样重要这里顺便提一个很多个人开发者会忽略的点Simulink模型和Matlab脚本一定要纳入版本管理。AD9361的配置参数比如本振频率、带宽、增益经常需要来回调如果没做版本管理很容易出现上次调的代码去哪了的情况。我用Git管理Matlab代码的习惯已经很多年。每次修改仿真参数都会提交一次commit message写清楚改了什么。这样当板级联调出现问题时我可以随时回退到某个仿真版本去对比。6.3 仿真通过只是第一步最后再提醒一句Matlab仿真通过绝不意味着AD9361的链路就没有问题了。仿真里没有覆盖到的还有模拟器件的非线性、温度漂移、时钟抖动、PCB布线的耦合效应以及AD9361和FPGA之间的数据接口时序问题。仿真解决了架构行不行和算法对不对两个问题剩下的工程稳不稳要靠板级调试验证。但反过来如果仿真这一关都没过贸然上板调那一定是灾难。先把零中频架构的每个环节在Matlab里跑透寄存器行为的理解、基带算法的调试、故障定位的效率都会上一个台阶。希望这套仿真实战对你有用。跑通之后建议把代码里的sps从小到大多跑几组观察星座图和误码率的变化趋势这是理解时分抽样的最好方式。