
通信技术发展至今已经超过一百年。从最早的有线电报到后来的无线电广播再到今天的 5G 移动通信和卫星互联网设备形态和协议栈换了无数代但通信系统要解决的核心问题始终只有一个让信息可靠、高效地穿越信道从发送端到达接收端。理解这条技术主线是通信工程师区别于只会调用接口的开发者的关键能力。通信工程这门学科积累下来的技术遗产也正是通过一代代工程师的教学、实验和文档传承下来的。本文围绕“调制与解调”这个通信系统的核心环节展开。你会先看到通信系统的基本模型理解模拟通信和数字通信的分界然后我会用 Python 和 NumPy 搭建一个可复现的仿真环境分别实现 AM、FM 和 BPSK 三种典型调制方式最后通过在 AWGN 信道下的误码率实验把“信噪比”和“误码率”这两个抽象概念变成看得见的曲线。文章还会整理仿真中常见的坑和排查路径以及从仿真走向工程实践时需要注意的差异。这套内容适合两类读者一类是刚接触通信原理、想通过代码加深理解的在校学生另一类是长期做上层应用开发、想补一补信号与通信基础的后端或嵌入式工程师。文中的仿真代码可以直接复制运行关键参数和背后的公式都会逐一说明。1. 通信系统的本质信息如何穿越距离1.1 先从最基础的通信模型说起任何一套通信系统无论简单还是复杂都可以抽象成五个模块信源、发送端、信道、接收端、信宿。信源产生原始信息可能是语音、文字、图片也可以是传感器采集到的数据。发送端负责把原始信息转换成适合在信道上传输的形式这个过程的核心就是编码和调制。信道是信息传输的物理媒介可以是双绞线、同轴电缆、光纤也可以是空气。接收端从受干扰的信号中恢复出原始信息完成解调和解码。信宿是信息的最终消费方比如人的耳朵或者应用服务器上的处理程序。实际工程里前一块和后一块之间还会叠加编码、交织、加扰、组帧等大量处理但主线始终是这条链路。把问题简化为这五个模块之后通信系统面临的核心挑战就很清楚了信道会带来衰减、噪声、失真和干扰接收端看到的信息往往已经和发送端面目全非。通信工程师的日常工作本质上就是在对抗信道的不完美。信道的不完美主要体现在三个方面噪声热噪声、器件噪声、环境干扰叠加在信号上导致接收波形抖动。衰减信号在介质中传播会损失能量距离越远衰减越大。失真信道带宽有限、频率响应不平坦导致不同频率成分被不同程度地削弱或延迟。所以通信系统设计本质上是一个工程权衡在有限的带宽、有限的发射功率和给定的信道质量约束下尽量提高传输速率并降低错误概率。1.2 模拟通信与数字通信的分界按照信号在信道上的表现形式通信系统分为模拟通信和数字通信两大类。模拟通信直接让电参量模仿原始消息的变化例如声音的强弱驱动电流大小变化。数字通信则先把原始信息离散化成比特再用有限个离散状态去表示比特。两者对比可以看下表对比项模拟通信数字通信信号形式连续变化的波形有限个离散电平或相位状态抗噪声能力噪声叠加后难以彻底去除可通过判决、纠错编码恢复频谱效率中低频段效率尚可借助高阶调制可以做得更高设备复杂度相对简单但调试依赖经验需要 AD/DA、编解码、同步复杂度高典型应用老式 AM/FM 广播、模拟对讲机移动通信、Wi-Fi、卫星通信、光纤通信再生能力中继放大时噪声也被放大中继可以判决后重新生成干净波形数字通信能够成为今天的主流并不是因为它天生更优秀而是因为在噪声环境中它可以通过“判决”和“纠错”把信号拉回有限状态集合从而避免噪声的持续累积。模拟通信在信号质量足够好、设备成本敏感的短距离场景中仍然有应用例如部分广播和模拟音频链路。1.3 为什么今天还要理解这些基础原理一个常年写 HTTP 接口的开发者可能觉得调制解调离自己很远。但一旦工作内容涉及这些方面基础是否扎实会立刻体现出来排查射频信号质量问题需要理解采样率、带宽、信噪比之间的关系。优化无线模块的功耗与速率需要理解调制阶数和误码率之间的取舍。阅读蓝牙、Wi-Fi、4G/5G 协议中的物理层参数本质还是在看调制、编码和信道。我见过不少工程师能熟练配置无线模块的寄存器但遇到“为什么同一批设备在某个位置误码率特别高”的问题时毫无头绪。原因往往不在寄存器而在信号电平、带宽匹配和噪声基底这些基础概念上。所以这篇博客选择用仿真把这些概念落地让知识不是停留在公式推导而是变成可以运行、可以观察的代码。2. 环境准备搭建可复现的通信仿真环境2.1 Python 环境与依赖本文所有仿真只依赖三个库NumPy 负责数值计算SciPy 负责滤波和特殊函数Matplotlib 负责画图。建议先创建独立虚拟环境避免污染其他项目。python -m venv comm_env # macOS / Linux source comm_env/bin/activate # Windows comm_env\Scripts\activate pip install numpy matplotlib scipy安装完成后可以用下面的代码确认版本和基本可用性import numpy as np import scipy import matplotlib import scipy.signal as signal from scipy.special import erfc print(numpy:, np.__version__) print(scipy:, scipy.__version__) print(matplotlib:, matplotlib.__version__)如果本机 Python 版本较旧建议先在 Python 3.10 以上环境中测试。不同版本的 SciPy 对butter、filtfilt的函数签名保持一致本文代码在较新的 NumPy 2.x 和 SciPy 1.x 下都能运行。2.2 仿真的总体设计为了不把概念混在一起实验分成四步生成一个已知消息信号作为所有调制的输入。分别做 AM、FM 调制再用对应方法解调观察信号在时域上的变化。做 BPSK 数字调制先在无噪声条件下解调确保流程正确。加入 AWGN 噪声扫不同的 Eb/N0统计误码率并与理论曲线对比。代码组织上建议把公共参数放在文件顶部把调制、解调、加噪、误码统计拆成独立函数。这样后续调整参数、扩展新调制方式时不需要改动主流程。comm_sim/ ├─ comm_sim.py # 全部实验代码 ├─ requirements.txt # numpy matplotlib scipy └─ figures/ # 保存输出图片在单一文件里组织实验可以加快上手速度如果是多人协作或长期维护则建议按模块拆分。本文为突出可读性采用单文件示例。2.3 仿真参数速查表后续所有实验共用一套参数先列出来后面不再重复解释参数数值含义fs200 kHz采样率fc20 kHz载波频率fm1 kHz消息信号频率ma0.6AM 调制指数kf2000FM 频偏系数Rb10 kbpsBPSK 比特率spb20每个比特的采样点数采样率的选择要满足奈奎斯特条件同时保证一个载波周期内有足够采样点。这里 fs 是 fc 的 10 倍一个载波周期采 10 个点画图和后续滤波都足够。注意仿真参数在实验室环境可以随意调整但一旦要对比真实系统或现场测试数据必须以实际硬件的采样率、带宽和发射功率为准不能照搬仿真默认值。3. 用代码理解三种调制方式AM、FM 与 BPSK3.1 先准备一个已知的消息信号为了让后续解调结果可以和原始信号对比我们使用一个幅度为 0.6、频率为 1 kHz 的正弦波作为消息信号。这样解调失败时波形差异一眼就能看出来。import numpy as np import matplotlib.pyplot as plt import scipy.signal as signal # 公共参数 fs 200_000 # 采样率 fc 20_000 # 载波频率 fm 1_000 # 消息频率 T 0.01 # 信号时长 t np.arange(0, T, 1 / fs) # 消息信号幅度 0.6 的 1 kHz 正弦波 m 0.6 * np.sin(2 * np.pi * fm * t)直接打印m.shape结果是(2000,)也就是 10 ms 内采了 2000 个点。这个数组就是后面所有调制电路的“输入音频”。3.2 AM 调制与包络检波AM 调制的基本表达式是s_am(t) (1 ma * m(t)) * cos(2 * pi * fc * t)也就是让消息信号改变载波幅度。调制指数 ma 控制幅度变化的深度ma 越大消息信号对载波幅度的影响越明显。代码实现非常直接# AM 调制ma 为调制指数 ma 0.6 s_am (1 m) * np.cos(2 * np.pi * fc * t) # 包络检波取绝对值后低通滤波 env np.abs(s_am) b, a signal.butter(4, 2 * fm / (fs / 2), btypelow) m_hat signal.filtfilt(b, a, env)AM 解调最常见的方式是包络检波先对已调信号取绝对值让负半周翻到正半周再通过低通滤波器把载波频率成分滤掉剩下的就是消息信号的包络。这里的低通截止频率取 2 kHz因为消息信号最高 1 kHz截止频率必须高于 1 kHz 但远低于载波 20 kHz。画出s_am和m_hat后可以看到 AM 信号的外包络正好随m变化解调后的波形与原始m基本重合。这里有一个典型坑如果用普通scipy.signal.lfilter代替filtfilt滤波输出会在开头出现明显的瞬态时延。filtfilt做双向滤波没有相位偏移更适合观察波形但代价是实时系统没法用它因为它是非因果的。学习仿真的用户不建议在离线数据分析时用lfilter去对比波形。3.3 FM 调制与鉴频解调FM 调制不再改变载波幅度而是让载波瞬时频率随消息信号变化。数学上写作s_fm(t) cos(2 * pi * fc * t 2 * pi * kf * integral(m(t) dt))代码里用np.cumsum(m) / fs对消息信号做数值积分# FM 调制 kf 2_000 phase 2 * np.pi * fc * t 2 * np.pi * kf * np.cumsum(m) / fs s_fm np.cos(phase) # 简易正交鉴频解调 baseband s_fm * np.exp(-1j * 2 * np.pi * fc * t) inst_phase np.unwrap(np.angle(baseband)) inst_freq np.diff(inst_phase) * fs / (2 * np.pi) m_hat_fm inst_freq / kf解调的核心思想是把 FM 信号搬移到基带后信号的相位就是瞬时相位它对时间求导就是瞬时频率。瞬时频率减去中心频率再除以频偏系数 kf就能恢复出消息信号。这里的np.diff会少一个点画图时注意对齐t[1:]。FM 比 AM 抗幅度噪声能力强因为幅度方向的干扰不会改变频率但 FM 占用带宽通常更大。这个特性决定了 FM 在广播音质和抗干扰要求高的场景中仍在使用。3.4 BPSK 调制与相干解调数字调制与外层数字通信直接对应。这里选择 BPSK因为它的原理最直观比特 0 映射为幅度 -1比特 1 映射为幅度 1然后乘以载波。载波相位 0 表示 1相位 π 表示 0。# BPSK 参数 Rb 10_000 # 比特率 spb int(fs / Rb) # 每个比特采样点数 rng np.random.default_rng(42) bits rng.integers(0, 2, 200) nrZ 2 * bits - 1 # 0 - -1, 1 - 1 baseband np.repeat(nrZ, spb) t_bpsk np.arange(len(baseband)) / fs carrier np.cos(2 * np.pi * fc * t_bpsk) bpsk_tx baseband * carrier接收端采用相干解调也就是用一个与发送端同频同相的本地载波来混频再积分判决def demod_bpsk(rx, fs, fc, spb): t_rx np.arange(len(rx)) / fs lo np.cos(2 * np.pi * fc * t_rx) mixed rx * lo # 混频到基带 n_sym len(rx) // spb mf mixed[:n_sym * spb].reshape(n_sym, spb).mean(axis1) bits_hat (mf 0).astype(int) return bits_hat bits_hat demod_bpsk(bpsk_tx, fs, fc, spb) print(恢复正确:, np.array_equal(bits_hat[: len(bits)], bits))混频后每个比特区间内有spb20个采样点直接求平均等效于一个积分器。判决规则是平均值大于 0 判为比特 1否则判为比特 0。在无噪声条件下这段代码应该打印恢复正确: True。三种调制方式的对比整理如下调制方式承载信息的物理量解调复杂度抗噪声能力典型场景AM载波幅度包络检波即可最低弱中短波广播FM载波频率鉴频或锁相环中等中调频广播、对讲机BPSK载波相位相干解调需要载波同步强卫星通信、Wi-Fi 早期物理层BPSK 看起来“最强”但它的代价是接收端必须恢复出与发送端同频同相的载波。这个载波同步过程在实际系统中非常复杂也是很多初学仿真的人容易忽略的环节。本文为了聚焦原理假设本地载波完全同步。4. 加入 AWGN 信道从理想环境走向真实环境4.1 高斯白噪声与 Eb/N0 的含义真实信道必然存在噪声。通信原理中最常用的信道模型是 AWGN即加性高斯白噪声。它有三个关键词加性噪声以相加方式叠加到信号上。高斯噪声幅值服从正态分布。白噪声功率谱在频域内均匀分布。衡量噪声强弱不能只看噪声功率而要和信号功率放在一起看。这里有两个常用指标SNR信噪比信号功率与噪声功率之比。Eb/N0比特能量与噪声功率谱密度之比每个比特携带的能量与单位带宽噪声功率的比值。Eb/N0 在数字调制性能分析中更常用因为它剔除了比特率和带宽的影响能直接比较不同调制方式的理论性能。仿真中给基带 BPSK 符号叠加噪声时噪声标准差按下面的公式计算sigma sqrt(1 / (2 * EbN0))这个公式成立的前提是符号能量归一化为 1。推导思路是符号能量 Es 1噪声方差 σ² N0 / 2而 Es/N0 Eb/N0 gamma所以 σ² 1 / (2 gamma)。初学阶段不需要背公式但要理解“归一化能量”这个前提条件否则换成高阶调制时容易出错。4.2 用蒙特卡洛方法统计 BPSK 误码率误码率BER的仿真思路是随机生成大量比特经过调制、加噪声、解调然后统计恢复错误的比特比例。这种反复实验取统计结果的思路叫蒙特卡洛仿真。from scipy.special import erfc def ber_bpsk(eb_n0_db, n_bits200_000, seed0): rng np.random.default_rng(seed) eb_n0 10 ** (eb_n0_db / 10) sigma np.sqrt(1 / (2 * eb_n0)) bits rng.integers(0, 2, n_bits) symbols 2 * bits - 1 rx symbols sigma * rng.standard_normal(n_bits) bits_hat (rx 0).astype(int) return np.mean(bits_hat ! bits) def ber_theory(eb_n0_db): eb_n0 10 ** (eb_n0_db / 10) return 0.5 * erfc(np.sqrt(eb_n0)) eb_n0_db_list np.arange(0, 11, 1) ber_sim [ber_bpsk(x) for x in eb_n0_db_list] ber_ref [ber_theory(x) for x in eb_n0_db_list] for x, s, r in zip(eb_n0_db_list, ber_sim, ber_ref): print(fEb/N0{x:2d} dB sim{s:.6f} theory{r:.6f})代码里有两个细节值得注意每次实验用固定种子seed保证结果可复现。比特数n_bits取 20 万是为了在高信噪比下也能统计到足够数量的错误比特。如果只用几千比特当真实误码率是 10 的负 5 次方时一次实验可能一个错误都没有统计结果会严重失真。4.3 仿真结果与理论曲线的对比BPSK 在 AWGN 信道下的理论误码率是BER Q(sqrt(2 * EbN0))等价于0.5 * erfc(sqrt(EbN0))。仿真运行完成后对比表大致如下Eb/N0 (dB)仿真 BER理论 BER00.07860.078620.03750.037540.01250.012560.00240.002480.000190.00019100.00001 以下0.0000039由于是随机实验仿真值和理论值不会完全相等但在 20 万比特样本下误差一般会控制在可接受范围内。如果固定种子每次结果完全一致换不同的种子结果会在理论值附近小幅波动。画出 BER 曲线时会看到Eb/N0 从 0 dB 提升到 8 dB误码率下降约三个数量级。这说明信噪比在通信系统中是决定性的因素。工程上常见的“灵敏度”指标本质上就是达到某个目标误码率比如 0.001所需的最低接收信号电平。注意不要只验证程序能跑通。仿真实验的验证标准应该是“仿真结果与理论值是否吻合”。BER 曲线与理论曲线偏离比较大时首先要检查实现而不是怀疑理论。5. 仿真中常见的坑与排查路径5.1 解调后波形和原始信号对不上现象AM 解调波形幅度正确但频率成分明显错误或者 FM 解调波形出现不合理的尖峰。排查顺序检查采样率 fs 是否至少大于最高频率成分的 2 倍。实际仿真中建议留 5 到 10 倍余量。检查低通滤波器截止频率。AM 包络检波的低通截止频率必须高于消息最高频率同时远低于载波频率。截止频率取错是常见原因。检查时间轴对齐。np.diff、filtfilt、滤波操作都可能造成向量长度变化或相位偏移画图前先打印几个数组的shape。FM 解调出现边界尖峰通常是因为发生在np.unwrap和np.diff的数据起点。建议去掉每个序列首尾 1% 的数据再观察波形。5.2 BER 曲线和理论值偏差很大现象低 Eb/N0 时仿真 BER 勉强接近理论值但高 Eb/N0 时仿真 BER 不再下降出现平台。这个现象通常有四个原因噪声功率算错。最常见的是直接用一个固定的信号功率值计算噪声而不是用实际信号数组的np.mean(x ** 2)。符号能量没有归一化。BPSK 的符号映射如果用了0和1而不是-1和1平均能量就只剩一半导致等效 Eb/N0 差 3 dB。判决门限不是 0。BPSK 最优门限是 0加了直流偏置后判决自然错误。随机种子复用导致样本相关性。建议每次实验独立生成数据或者使用并行的不同子流。5.3 滤波器引入时延和相位问题scipy.signal.lfilter是因果滤波输出会有群时延filtfilt是非因果的波形对齐好但实时系统不能使用。仿真中如果要对齐波形直接用filtfilt如果模拟真实接收机则必须接受因果滤波带来的时延并在定时同步中处理。问题现象常见原因检查方式处理建议AM 解调波形首尾变形滤波器瞬态影响画图观察首尾 200 点丢弃边界样本或使用 filtfiltFM 解调出现尖峰相位 unwrap 失败或频偏系数不匹配观察 inst_freq 是否出现毛刺检查数据边界确认 kf 与调制端一致BPSK BER 高信噪比平台噪声计算或能量归一化错误验证 sigma 公式输入用符号能量 1 归一化打印信号功率核对输出波形整体滞后lfilter 群时延对比输入输出峰值位置离线分析用 filtfilt实时系统另外做时延补偿排查这类问题有一个通用思路先去掉信道在无噪声条件下让调制解调链路跑通再单独加入噪声确认 BER 走向符合理论最后再引入滤波器、同步等硬件因素。每加一层复杂度验证一次能大幅缩短定位时间。6. 从仿真到工程最佳实践与学习路线6.1 学习环境与生产环境的差异仿真环境能帮我们把原理看得很清楚但进入真实设备之后复杂度会成倍上升。两者差异需要从一开始就建立认知维度学习仿真环境真实生产环境信道模型理想 AWGN参数完全可控多径衰落、干扰、频偏、相噪同时存在载波同步默认完全同步需要专门的同步环路和训练序列定时同步采样点对齐符号定时偏移、时钟频偏都需要估计滤波实现filtfilt 双向滤波只能用因果滤波器有延迟性能验证与理论曲线对比以协议一致性、误块率、时延、功耗为指标代码要求可读性优先需要考虑定点、实时性、资源占用、异常处理初学通信仿真时知道这一点能避免一个误区不要把仿真结果直接当作硬件性能结论。仿真只是帮助理解原理和验证算法的工具。6.2 通信工程的可复用学习路线如果你从零开始补通信基础推荐按下面这个顺序不要跳步信号与系统傅里叶变换、卷积、采样定理。这是所有后续内容的数学工具。通信原理调制解调、AWGN 信道、BER 分析。本文覆盖的就是这批概念。数字信号处理滤波器设计、FFT、多速率信号处理。无线通信衰落信道、OFDM、MIMO、均衡和同步。协议与系统从物理层向上看 MAC、网络层和应用层。每条路线都配上动手实验。比如学完调制解调可以做一个小项目实现一个简单的 BPSK 收发链路加入频偏补偿再对比不同 Eb/N0 下的性能。做完之后你对物理层的理解会比只看书深刻得多。6.3 落地项目前请先做这三件事仿真代码要迁移到真实工程或者写成长期维护的项目建议先完成三件事把随机种子全部移除或抽成可配置参数生产环境的数据生成必须可重复但默认不可固定。把公共参数集中到配置文件或常量类。采样率、载波频率、符号率这类参数散落在代码里是后期维护最大的隐患。为每个关键函数补测试用例。至少包含无噪声解调正确性、加噪后 BER 在合理范围、滤波器边界长度一致性这三类用例。最后回到最初的问题为什么通信工程师还要花时间追溯基础原理。因为无论是模拟时代的调幅调频还是数字时代的 BPSK、QAM、OFDM底层的设计逻辑始终是带宽、功率、复杂度和误码率之间的权衡。把这些权衡关系吃透再去看新协议和新技术就只是换了一套参数和实现方式而不是重新学一门学科。这也是通信工程知识能够一代代传承下去的原因技术载体在变核心约束没有变。