简介:这份资源是2024年全国大学生电子设计竞赛C题「无线传输信号模拟系统」的江苏省第一名完整方案,面向备赛电赛的本科生、研究生及电子设计爱好者,尤其适合需要攻克DDS信号生成、调幅波合成与多径传输模拟等难点的中高级选手。方案以STM32F407ZGT6为核心,通过DDS产生四路频率与相位相对独立的信号,分别作为直达与多径AM信号的载波和调制信号,并独立控制幅值与相移;利用DAC提供偏置,经加法器与AD835乘法器得到调幅波,再通过运放放大至目标幅值范围,多径信号额外经PE4302程控增益器调节增益后合路输出。压缩包共约2000个文件,以1096个C源文件、831个头文件为主,辅以txt说明、md笔记及少量Python、JSON、CPP脚本,整体约83.75MB,目录结构便于按模块查阅。目前已有2279人学习下载,读者可从中获取完整赛题实现思路、调幅波生成与增益控制细节,以及可复用的嵌入式工程代码,适合对照复盘与二次开发。
1. 从一道电赛题说起:无线传输信号模拟系统到底要做什么
每年电赛季,总有一道题让控制类和通信类的同学同时头疼——无线传输信号模拟系统。2024年C题就是这样一个典型:要求模拟无线信道中的信号传输过程,在发射端产生特定调制信号,经过模拟信道后由接收端解调还原,并且要能定量控制信噪比、衰减、频偏这些信道参数。听起来像是通信原理实验课的升级版,但真正动手做起来,你会发现它横跨了信号生成、信道建模、实时处理和参数测量四个领域,任何一个环节掉链子,最后解调出来的星座图就是一锅粥。
我带过两届电赛队伍,也看过不少拿省一甚至国奖的方案。这类题目的核心难点不在于某个单点技术有多深,而在于系统级联后的信噪比预算和实时性约束。发射端DAC出来的信号经过信道模拟器,再进入接收端ADC,中间任何一级的噪声、杂散、量化误差都会累积。很多队伍在单独测试每个模块时指标都漂亮,一联调就翻车,星座点糊成一片,误码率下不去。这篇方案拆解就是围绕这个痛点展开的,适合正在准备电赛或者做类似通信系统课程设计的同学参考。
2. 系统总体方案:零中频架构与四大模块的职责划分
2.1 为什么选零中频而不是超外差
无线传输信号模拟系统的架构选择,本质上是在复杂度、成本和性能之间找平衡。常见的方案有超外差和零中频两种。超外差架构把射频信号先搬到一个中频再处理,镜像抑制和邻道选择性好,但需要本振、混频器、中频滤波器这一整套链路,对电赛四天三夜的周期来说,调试量太大。零中频架构直接把基带信号搬到射频,省掉了中频级,链路短、元件少,缺点是直流偏移和I/Q不平衡会直接影响EVM。
我一般会建议队伍选零中频,原因很实际:电赛的评分重点在系统功能和参数可控性,不是射频前端的极限性能。零中频架构下,用一片AD9361或者类似的射频捷变收发器就能覆盖70MHz到6GHz的频率范围,通道带宽可以软件配置,省去了大量硬件调试时间。把精力集中在基带信号处理和信道模拟算法上,性价比更高。
2.2 四大模块的职责与接口定义
整个系统拆成四个模块:发射端基带处理、信道模拟、接收端基带处理、人机交互与参数测量。每个模块的职责和接口必须提前定死,不然后期联调就是灾难。
发射端基带处理负责产生调制信号。以QPSK为例,需要完成比特流生成、串并转换、星座映射、脉冲成型滤波、上变频到中频。输出给DAC的数据是I/Q两路,采样率通常选在10MSPS到30MSPS之间。这里有个血泪经验:脉冲成型滤波器的滚降系数不要设得太小,0.35是个稳妥的选择,太小会导致峰均比升高,DAC后面的放大器容易进入非线性区。
信道模拟模块是整道题的核心。它要模拟三样东西:加性高斯白噪声、多径衰落、频偏。AWGN用Box-Muller算法生成高斯随机数,按目标信噪比叠加到信号上。多径用抽头延迟线模型,抽头系数可以预设也可以实时计算。频偏就是在时域乘一个复指数,频率偏移量由用户设定。这个模块的输入是发射端送来的I/Q数据,输出是叠加了信道损伤的信号。
接收端基带处理做相反的事:下变频、匹配滤波、符号同步、载波恢复、判决解调。符号同步用Gardner算法,载波恢复用Costas环。这两个算法的环路带宽需要仔细调,太宽了噪声进来,太窄了锁不住。
人机交互模块用OLED或者串口屏显示星座图、误码率、信噪比这些关键指标,同时提供按键或者触摸来调整信道参数。
3. 信道模拟的工程实现:从AWGN到多径衰落的代码落地
3.1 AWGN生成与信噪比精确控制
高斯白噪声的生成看起来简单,但要精确控制信噪比,有几个细节容易踩坑。首先是噪声功率的计算基准,必须和信号功率在同一个观测窗口内统计。其次是高斯随机数的生成方法,Box-Muller是最常用的,但要注意它一次生成两个独立的高斯变量,别浪费了。
import numpy as np def awgn_channel(signal, snr_db): """ 对输入信号添加高斯白噪声 signal: 复数基带信号,形状为(N,) snr_db: 目标信噪比,单位dB 返回: 添加噪声后的信号 """ # 计算信号平均功率 signal_power = np.mean(np.abs(signal) ** 2) # 由信噪比反推噪声功率 snr_linear = 10 ** (snr_db / 10.0) noise_power = signal_power / snr_linear # 生成复高斯噪声,实部和虚部各占一半功率 noise = np.sqrt(noise_power / 2) * ( np.random.randn(len(signal)) + 1j * np.random.randn(len(signal)) ) return signal + noise这段代码的关键在noise_power / 2这个系数。复高斯噪声的总功率是实部功率加虚部功率,如果直接对实部和虚部都用sqrt(noise_power),总功率就翻倍了,信噪比会差3dB。这个坑我在第一次做信道模拟时踩过,星座图看起来只是稍微散了一点,但误码率测试怎么都过不了,后来用功率计一测才发现噪声功率高了整整一倍。
3.2 多径衰落模型的抽头延迟线实现
多径衰落的本质是信号经过不同路径到达接收端,各路径的时延和衰减不同。抽头延迟线模型把这种物理过程抽象成有限个抽头,每个抽头对应一条路径的复增益和时延。
class MultipathChannel: def __init__(self, delays, gains): """ delays: 各路径时延,单位采样点数,列表 gains: 各路径复增益,列表 """ self.delays = delays self.gains = gains self.buffer = np.zeros(max(delays) + 1, dtype=complex) def process(self, sample): """逐样本处理,适合实时流式数据""" # 新样本移入缓冲区头部 self.buffer = np.roll(self.buffer, 1) self.buffer[0] = sample # 各路径加权求和 output = 0j for delay, gain in zip(self.delays, self.gains): output += gain * self.buffer[delay] return output这个实现是逐样本处理的,适合在FPGA或者DSP上做实时流式处理。如果是在PC上做离线仿真,可以用卷积一次性算完,效率更高。抽头系数可以固定,也可以根据多普勒频移实时更新。电赛题目里通常要求模拟静态多径和动态多径两种场景,静态的用固定抽头就行,动态的需要用Jakes模型或者简单的正弦波叠加来生成时变抽头。
3.3 频偏引入与补偿的对称设计
频偏在时域上就是乘一个复指数,实现很简单,但补偿端的对称设计才是关键。发射端引入的频偏是exp(j*2*pi*f_offset*t),接收端补偿时要用exp(-j*2*pi*f_offset_est*t),其中f_offset_est是估计出来的频偏。如果估计不准,星座图会旋转,QPSK的四个点会变成四个圆弧。
def apply_frequency_offset(signal, fs, freq_offset): """ 对信号施加频偏 signal: 输入复数信号 fs: 采样率 freq_offset: 频偏值,单位Hz """ t = np.arange(len(signal)) / fs return signal * np.exp(1j * 2 * np.pi * freq_offset * t)频偏的估计用Costas环或者基于FFT的粗估计加细估计。Costas环的环路带宽一般取符号率的1%到5%,具体值要看频偏范围和噪声水平。频偏大的时候环路带宽要宽一点,否则锁不住;噪声大的时候要窄一点,否则抖动太大。这个参数没有标准答案,需要根据实际场景调。
4. 接收端同步与解调:符号定时和载波恢复的联调技巧
4.1 Gardner符号同步算法的参数整定
Gardner算法是一种非数据辅助的符号定时同步方法,每个符号只需要两个采样点,计算量小,适合实时实现。它的核心思想是利用相邻符号的过零点来提取定时误差。
class GardnerTimingRecovery: def __init__(self, samples_per_symbol, loop_bw): self.sps = samples_per_symbol self.mu = 0.0 # 插值相位 self.integral = 0.0 # 环路滤波器积分项 self.kp = loop_bw * 0.1 # 比例增益,经验值 self.ki = loop_bw * 0.01 # 积分增益,经验值 self.prev_sample = 0j self.prev_symbol = 0j def update(self, sample): """输入一个采样点,输出同步后的符号""" # 定时误差检测 error = (sample - self.prev_symbol).real * self.prev_sample.real # 环路滤波 self.integral += self.ki * error correction = self.kp * error + self.integral # 更新插值相位 self.mu += correction # 这里省略了具体的插值计算,实际实现需要用Farrow结构 self.prev_sample = sample return self.mu环路带宽loop_bw的选取是个玄学。太宽了,定时抖动大,星座点发散;太窄了,收敛慢,前几百个符号可能都锁不住。我一般会从0.01开始试,观察星座图的收敛速度,如果1000个符号内还锁不住就加大到0.02,如果锁住了但星座点很散就减到0.005。这个调试过程没有捷径,只能反复试。
4.2 Costas环载波恢复的锁定检测
Costas环用于载波相位恢复,对QPSK信号,鉴相器的输出是I路和Q路的乘积。锁定的判断可以通过观察鉴相器输出的均值是否趋近于零来实现。
class CostasLoop: def __init__(self, loop_bw, fs, symbol_rate): self.phase = 0.0 self.freq = 0.0 self.alpha = loop_bw * 0.1 self.beta = loop_bw * 0.01 self.lock_counter = 0 self.locked = False def update(self, sample): # 相位旋转 rotated = sample * np.exp(-1j * self.phase) # QPSK鉴相器 error = np.sign(rotated.real) * rotated.imag - \ np.sign(rotated.imag) * rotated.real # 环路滤波 self.freq += self.beta * error self.phase += self.freq + self.alpha * error # 锁定检测:连续N个符号误差均值小于阈值 if abs(error) < 0.1: self.lock_counter += 1 else: self.lock_counter = 0 if self.lock_counter > 100: self.locked = True return rotated锁定检测很重要,因为电赛评分时会看系统能不能自动判断同步状态。如果Costas环没锁上,解调出来的比特全是错的,但系统如果不知道,就会一直输出错误结果。加一个锁定指示,没锁的时候输出全零或者特定的同步头,反而能体现系统的完整性。
4.3 联调时的信噪比预算分配
接收端联调最容易出现的问题是:单独测发射端,EVM很好;单独测信道模拟,噪声功率准确;单独测接收端,环路的锁定范围够宽。但一联调,误码率就是下不去。这时候要做一个信噪比预算表,把每一级的噪声系数和增益都列出来,看看哪一级吃掉了最多的裕量。
| 环节 | 噪声来源 | 典型噪声系数 | 对信噪比的贡献 |
|---|---|---|---|
| DAC输出 | 量化噪声 | 理论SNR=6.02N+1.76 | N=14时约86dB |
| 发射端滤波器 | 截断误差 | 可忽略 | 取决于字长 |
| 信道模拟 | 算法噪声 | 由目标SNR决定 | 主要可控项 |
| ADC输入 | 量化噪声 | 理论SNR=6.02N+1.76 | N=12时约74dB |
| 接收端处理 | 算法损耗 | 1-2dB | 环路失锁时急剧恶化 |
这张表的作用是帮你定位瓶颈。如果目标信噪比是20dB,而ADC的量化信噪比只有74dB,那ADC不是瓶颈。但如果接收端算法的实现损耗有3dB,那实际到达解调器的信噪比就只有17dB,误码率会比理论值差一个数量级。
5. 避坑与常见问题:联调阶段最容易翻车的五个点
5.1 星座图糊成一团但误码率正常
现象:星座图上四个点明显发散,但误码率测试结果却很好。原因通常是显示刷新率太低,星座图是多个符号周期叠加的结果,如果符号同步没完全锁定,每个符号的采样点位置在漂移,叠加出来就是糊的。但误码率统计的是判决后的比特,只要采样点还在判决区域内,比特就是对的。解决方法是降低星座图的显示刷新率,或者只显示锁定后的符号。
5.2 信噪比设置值和实测值差3dB
现象:软件里设置信噪比20dB,但用频谱仪测出来只有17dB。原因在复高斯噪声的功率分配上,实部和虚部各分了一半功率,如果代码里没有除以2,噪声总功率就翻倍了。这个坑前面提过,但每年都有队伍踩。解决方法是写一个功率检测函数,在加噪声前后分别统计信号功率,确认信噪比计算无误。
5.3 频偏补偿后星座图旋转
现象:加了频偏补偿,但星座图还是慢慢旋转。原因是频偏估计有残差,或者Costas环的积分项没有正确累积。频偏补偿是乘法操作,如果估计值有1Hz的误差,1秒后相位就差了360度。解决方法是先用FFT做粗估计,把频偏降到1Hz以内,再用Costas环做细跟踪。粗估计的精度取决于FFT点数,1024点FFT在1MSPS采样率下的频率分辨率约1kHz,不够的话可以补零到4096点。
5.4 多径信道下符号同步失锁
现象:AWGN信道下同步正常,切到多径信道就失锁。原因是多径导致符号间干扰,Gardner算法的过零点检测被破坏了。解决方法是在符号同步之前加一个自适应均衡器,用LMS算法消除多径影响。均衡器的阶数取多径时延扩展的2到3倍,步长因子取0.01左右。如果多径时延不大,也可以把Gardner的环路带宽调宽一点,牺牲一些抖动性能换取锁定能力。
5.5 实时处理时数据吞吐量不够
现象:离线仿真一切正常,改成实时流式处理后误码率飙升。原因是处理一帧数据的时间超过了帧周期,缓冲区溢出导致丢帧。解决方法是先做时间预算:统计每个模块处理一个样本需要的时钟周期数,乘以采样率,看是否超过处理器的主频。如果超了,要么降低采样率,要么把计算量大的模块放到FPGA里做硬件加速。我一般会在设计初期就用示波器测每个模块的耗时,留出至少30%的裕量。
6. 验收与答辩:怎么用一套测试流程证明系统达标
验收和答辩是两回事。验收是证明系统能跑,答辩是证明你懂为什么能跑。很多队伍系统做得不错,但答辩时被问几个参数就露馅了,问题出在没有一套完整的测试记录。
我一般会要求队伍准备三样东西:一张系统框图、一张测试数据表、一段演示视频。系统框图不用太花哨,把四个模块和关键接口标清楚就行。测试数据表要覆盖所有要求的指标,每个指标至少测三个点,比如信噪比从0dB到30dB,每隔5dB测一次误码率,画成曲线。演示视频要包含参数调整的过程,比如从AWGN切换到多径,从无频偏切换到有频偏,展示系统能实时响应。
答辩时最容易被问到的几个问题:信噪比是怎么定义的,噪声功率怎么校准的;符号同步的环路带宽怎么选的,为什么选这个值;多径模型的抽头系数怎么来的,能不能模拟时变多径;系统的实时性怎么保证的,最坏情况下的处理延迟是多少。这些问题没有标准答案,但如果你能说出自己的取舍逻辑,比如“环路带宽选0.01是因为在1000个符号内能锁定,同时抖动在可接受范围内”,评委就会认可。
还有一个技巧:准备一个“故障演示”。故意把信噪比调到很低,让星座图完全散开,然后展示系统能自动检测到失锁并报警。这比只展示成功案例更能体现系统的鲁棒性。评委看了一天的成功演示,突然看到一个能处理失败的,印象分反而更高。
从那以后我每次带队伍,都强制要求他们在答辩前走一遍完整的测试流程,把每个指标的测试方法和数据都记录在案。希望这些经验能帮到正在准备电赛或者类似通信系统设计的你。
本文还有配套的精品资源,点击获取