【Python】Python 仿真OFDM发射机、信道和接收机-实现多种调制方式

简介: 文章介绍了如何使用Python和Commpy工具包实现OFDM通信系统的仿真,包括发射机、信道和接收机的过程,并支持BPSK、QPSK、8PSK、16QAM、64QAM等多种调制方式,同时展示了导频插入、信道冲击响应、星座映射的可视化,并计算了系统的误比特率。

1.png

1 引言

OFDM的通信系统仿真,Matlab实现的版本比比皆是,Python版本的底层详细的仿真过程缺少之又少,本人根据Commpy工具包,实现了OFDM的信号发射、经过信道、接收端接收的过程。实现的调制方式有BPSK、QPSK、8PSK、16QAM、64QAM。并可视化了导频的插入方式、信道冲击响应、信号解调前的星座映射和解调后的星座映射,以及计算了仿真系统的误比特率。完整代码的见本人的githubPython实现OFDM仿真

commpy包的官方文档https://commpy.readthedocs.io/en/latest/index.html

commpy包的安装方式

pip install scikit-commpy

2 Python实现

2.1 初始化和定义函数

2.1.1 初始化参数

导入包

import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
import commpy as cpy
K = 64 # OFDM子载波数量
CP = K//4  #25%的循环前缀长度
P = 8  # 导频数
pilotValue = 3+3j  # 导频格式
Modulation_type = 'QAM16' #调制方式,可选BPSK、QPSK、8PSK、QAM16、QAM64
channel_type ='random' # 信道类型,可选awgn
SNRdb = 25  # 接收端的信噪比(dB)
allCarriers = np.arange(K)  # 子载波编号 ([0, 1, ... K-1])
pilotCarrier = allCarriers[::K//P]  # 每间隔P个子载波一个导频
# 为了方便信道估计,将最后一个子载波也作为导频
pilotCarriers = np.hstack([pilotCarrier, np.array([allCarriers[-1]])])
P = P+1 # 导频的数量也需要加1

2.1.2 可视化导频插入的格式

# 可视化数据和导频的插入方式
dataCarriers = np.delete(allCarriers, pilotCarriers)
plt.figure(figsize=(8, 0.8))
plt.plot(pilotCarriers, np.zeros_like(pilotCarriers), 'bo', label='pilot')
plt.plot(dataCarriers, np.zeros_like(dataCarriers), 'ro', label='data')
plt.legend(fontsize=10, ncol=2)
plt.xlim((-1, K))
plt.ylim((-0.1, 0.3))
plt.xlabel('Carrier index')
plt.yticks([])
plt.grid(True)
plt.savefig('carrier.png')

2.png

2.1.3 定义调制和解调方式

m_map = {"BPSK": 1, "QPSK": 2, "8PSK": 3, "QAM16": 4, "QAM64": 6}
mu = m_map[Modulation_type]
payloadBits_per_OFDM = len(dataCarriers)*mu  # 每个 OFDM 符号的有效载荷位数
# 定制调制方式
def Modulation(bits):
    if Modulation_type == "QPSK":
        PSK4 = cpy.PSKModem(4)
        symbol = PSK4.modulate(bits)
        return symbol
    elif Modulation_type == "QAM64":
        QAM64 = cpy.QAMModem(64)
        symbol = QAM64.modulate(bits)
        return symbol
    elif Modulation_type == "QAM16":
        QAM16 = cpy.QAMModem(16)
        symbol = QAM16.modulate(bits)
        return symbol
    elif Modulation_type == "8PSK":
        PSK8 = cpy.PSKModem(8)
        symbol = PSK8.modulate(bits)
        return symbol
    elif Modulation_type == "BPSK":
        BPSK = cpy.PSKModem(2)
        symbol = BPSK.modulate(bits)
        return symbol
# 定义解调方式
def DeModulation(symbol):
    if Modulation_type == "QPSK":
        PSK4 = cpy.PSKModem(4)
        bits = PSK4.demodulate(symbol, demod_type='hard')
        return bits
    elif Modulation_type == "QAM64":
        QAM64 = cpy.QAMModem(64)
        bits = QAM64.demodulate(symbol, demod_type='hard')
        return bits
    elif Modulation_type == "QAM16":
        QAM16 = cpy.QAMModem(16)
        bits = QAM16.demodulate(symbol, demod_type='hard')
        return bits
    elif Modulation_type == "8PSK":
        PSK8 = cpy.PSKModem(8)
        bits = PSK8.demodulate(symbol, demod_type='hard')
        return bits
    elif Modulation_type == "BPSK":
        BPSK = cpy.PSKModem(2)
        bits = BPSK.demodulate(symbol, demod_type='hard')
        return bits

调制方式就是将比特流映射到星座图,得到了复数数值,在信道中以复数数值进行传输。

16QAM的星座图,如下图所示

3.png

2.1.4 定义信道

# 可视化信道冲击响应,仿真信道
# the impulse response of the wireless channel
channelResponse = np.array([1, 0, 0.3+0.3j])
H_exact = np.fft.fft(channelResponse, K)
plt.plot(allCarriers, abs(H_exact))
plt.xlabel('Subcarrier index')
plt.ylabel('$|H(f)|$')
plt.grid(True)
plt.xlim(0, K-1)
# 定义信道
def add_awgn(x_s, snrDB):
    data_pwr = np.mean(abs(x_s**2))
    noise_pwr = data_pwr/(10**(snrDB/10))
    noise = 1/np.sqrt(2) * (np.random.randn(len(x_s)) + 1j *
                            np.random.randn(len(x_s))) * np.sqrt(noise_pwr)
    return x_s + noise, noise_pwr
def channel(in_signal, SNRdb, channel_type="awgn"):
    channelResponse = np.array([1, 0, 0.3+0.3j]) #随意仿真信道冲击响应
    if channel_type == "random":
        convolved = np.convolve(in_signal, channelResponse)
        out_signal, noise_pwr = add_awgn(convolved, SNRdb)
    elif channel_type == "awgn":
        out_signal, noise_pwr = add_awgn(in_signal, SNRdb)
    return out_signal, noise_pwr

可视化冲击响应图,此处的信道衰落,只是举例实现了简单的一个冲击响应波形。复杂的信道模型,根据自己的信道去实现仿真过程。awgn表示加入高斯噪声。

4.png

2.2 OFDM仿真过程

2.2.1 发送端

# 5.1 产生比特流
bits = np.random.binomial(n=1, p=0.5, size=(payloadBits_per_OFDM, ))
# 5.2 比特信号调制
QAM_s = Modulation(bits)
# 5.3 插入导频和数据,生成OFDM符号
def OFDM_symbol(QAM_payload):
    symbol = np.zeros(K, dtype=complex) # 子载波位置
    symbol[pilotCarriers] = pilotValue  # 在导频位置插入导频
    symbol[dataCarriers] = QAM_payload  # 在数据位置插入数据
    return symbol
OFDM_data = OFDM_symbol(QAM_s)
# 5.4 快速傅里叶逆变换
def IDFT(OFDM_data):
    return np.fft.ifft(OFDM_data)
OFDM_time = IDFT(OFDM_data)
# 5.5 添加循环前缀
def addCP(OFDM_time):
    cp = OFDM_time[-CP:]               
    return np.hstack([cp, OFDM_time])  
OFDM_withCP = addCP(OFDM_time)

2.2.2 信道

经过信道,可视化加入冲击响应后的信号和原始信号的波形

# 5.6 经过信道
OFDM_TX = OFDM_withCP
OFDM_RX = channel(OFDM_TX, SNRdb, "random")[0]
plt.figure(figsize=(8,2))
plt.plot(abs(OFDM_TX), label='TX signal')
plt.plot(abs(OFDM_RX), label='RX signal')
plt.legend(fontsize=10)
plt.xlabel('Time'); plt.ylabel('$|x(t)|$');
plt.grid(True);
# plt.savefig('tran-receiver.png')

5.png

2.2.3 接收端

接收端,首先去循环前缀、再快速傅里叶变换、再进行信道估计,再以信道估计的冲击响应去均衡信号,

# 5.7 接收端,去除循环前缀
def removeCP(signal):
    return signal[CP:(CP+K)]
OFDM_RX_noCP = removeCP(OFDM_RX)
# 5.8 快速傅里叶变换
def DFT(OFDM_RX):
    return np.fft.fft(OFDM_RX)
OFDM_demod = DFT(OFDM_RX_noCP)
# 5.9 信道估计
def channelEstimate(OFDM_demod):
    pilots = OFDM_demod[pilotCarriers]  # 取导频处的数据
    Hest_at_pilots = pilots / pilotValue  # LS信道估计

    # 在导频载波之间进行插值以获得估计,然后利用插值估计得到数据下标处的信道响应
    Hest_abs = interpolate.interp1d(pilotCarriers, abs(Hest_at_pilots), kind='linear')(allCarriers)
    Hest_phase = interpolate.interp1d(pilotCarriers, np.angle(Hest_at_pilots), kind='linear')(allCarriers)
    Hest = Hest_abs * np.exp(1j*Hest_phase)

    plt.plot(allCarriers, abs(H_exact), label='Correct Channel')
    plt.scatter(pilotCarriers, abs(Hest_at_pilots), label='Pilot estimates')
    plt.plot(allCarriers, abs(Hest), label='Estimated channel via interpolation')
    plt.grid(True); plt.xlabel('Carrier index'); plt.ylabel('$|H(f)|$'); plt.legend(fontsize=10)
    plt.ylim(0,2)
    plt.savefig('信道响应估计.png')
    return Hest
Hest = channelEstimate(OFDM_demod)

图中所示蓝色点表示信道估计点,黄色的连接线表示估计的冲击响应波形。

6.png

# 5.10 均衡
def equalize(OFDM_demod, Hest):
    return OFDM_demod / Hest
equalized_Hest = equalize(OFDM_demod, Hest)
def get_payload(equalized):
    return equalized[dataCarriers]
QAM_est = get_payload(equalized_Hest)
# 5.10 获取数据位置的数据
def get_payload(equalized):
    return equalized[dataCarriers]
QAM_est = get_payload(equalized_Hest)
# 可视化均衡后的星座图
plt.plot(QAM_est.real, QAM_est.imag, 'bo')
plt.plot(QAM_s.real, QAM_s.imag, 'ro')

plt.grid(True)
plt.xlabel('Real part')
plt.ylabel('Imaginary Part')
plt.title("Received constellation")
plt.savefig('map.png')

7.png

# 5.11 反映射,解调
bits_est = DeModulation(QAM_est)
# 5.12 计算误比特率
print ("误比特率BER: ", np.sum(abs(bits-bits_est))/len(bits))
目录
相关文章
|
1月前
|
数据可视化 算法 Python
【数字通信革命】深入剖析Python实现BPSK、QPSK到QAM信号调制的奥秘,解锁高速数据传输的密钥!
【8月更文挑战第2天】在通信系统中,信号调制至关重要,它将信息嵌入载波信号中以便传输。本文通过Python实现三种基本调制技术:BPSK、QPSK和16-QAM,并提供示例代码。首先需安装NumPy、SciPy和Matplotlib库。BPSK是最简单的相位调制,每个符号携带一位信息;QPSK则每个符号携带两位信息,通过四种相位表示;16-QAM结合幅度和相位调制,每个符号携带更多比特信息。本文提供的代码演示了这些调制方式的实现过程,并利用Matplotlib可视化结果。了解这些调制技术有助于深入探索信号处理领域。
72 18
|
1月前
|
Python
【信号处理】python按原理实现BPSK、QPSK、QAM信号调制
本文提供了两种不同的方法来实现16-QAM(正交幅度调制)的调制和解调过程,一种是使用commpy库,另一种是通过手动定义映射字典来实现。
45 8
|
1月前
|
算法 Python
【Python】Python 实现破零(ZF)和最小均方误差(MMSE)信道均衡
无线通信中用于减少信号失真和噪声影响的两种常见信道均衡技术:Zero Forcing (ZF) 和 Minimum Mean Square Error (MMSE),并给出了ZF均衡器的数学表达式及其实现方法。
36 0
|
1月前
|
Python
【信号处理】Python实现BPSK、QPSK、8PSK、8QAM、16QAM、64QAM的调制和解调
使用Commpy开源包在Python中实现BPSK、QPSK、8PSK、8QAM、16QAM、64QAM等调制和解调方法的具体代码示例,但不包括8QAM的Commpy实现,以及一个完整的编码和解码示例。
105 0
|
4天前
|
数据采集 机器学习/深度学习 数据挖掘
探索Python编程之美:从基础到进阶
【9月更文挑战第4天】在数字时代的浪潮中,编程已成为一种新兴的“超能力”。Python,作为一门易于上手且功能强大的编程语言,正吸引着越来越多的学习者。本文将带领读者走进Python的世界,从零基础出发,逐步深入,探索这门语言的独特魅力和广泛应用。通过具体代码示例,我们将一起解锁编程的乐趣,并理解如何利用Python解决实际问题。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
4天前
|
数据采集 机器学习/深度学习 数据挖掘
探索Python编程之美:从基础到实战
【9月更文挑战第3天】本文旨在通过深入浅出的方式,带领读者领略Python编程语言的魅力。我们将从基本语法入手,逐步深入至高级特性,最终通过实战案例将理论知识与实践操作相结合。无论你是编程新手还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
3天前
|
存储 开发者 Python
探索Python编程之美
【9月更文挑战第5天】在这篇文章中,我们将一起踏上一场Python编程的奇妙之旅。从基础语法到高级特性,我们将一步步揭开Python语言的神秘面纱。你将学习如何编写清晰、高效的代码,掌握函数、类和模块的使用,以及理解面向对象编程的核心概念。此外,我们还将探讨异常处理、文件操作等实用技能。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供宝贵的知识和技巧,让你在编程的道路上更加从容自信。
|
1天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【9月更文挑战第8天】在系统编程中,进程间通信(IPC)是实现多进程协作的关键技术。IPC机制如管道、队列、共享内存和套接字,使进程能在独立内存空间中共享信息,提升系统并发性和灵活性。Python提供了丰富的IPC工具,如`multiprocessing.Pipe()`和`multiprocessing.Queue()`,简化了进程间通信的实现。本文将从理论到实践,详细介绍各种IPC机制的特点和应用场景,帮助开发者构建高效、可靠的多进程应用。掌握Python IPC,让系统编程更加得心应手。
11 4