OFDM通信系统的MATLAB仿真(2)
关于OFDM系统的MATLAB仿真实现的第二篇随笔,在第一篇中,我们讨论的是信号经过AWGN信道的情况,只用添加固定噪声功率的高斯白噪声就好了。但在实际无线信道中,信道干扰常常是加性噪声、多径衰落的结合。今天我们准备再进一步,让信号经过多径瑞利衰落信道。在这种信道条件下,信号具体是怎么怎么变化的呢?下面将讲解系统仿真的各个部分以及实现多径衰落的方法。
注意:为了整个系统的完整性,第一篇随笔中的每个步骤这里也都又写了一遍,但省略了补充知识部分,在第一篇的基础上添加了实现多径衰落的部分。想要看信噪比计算和噪声功率计算的同学可以去看第一篇随笔。
关于OFDM系统我目前参考的是《MIMO-OFDM无线通信技术及MATLAB实现》这本书,这里将将作者实现OFDM系统的思路及其代码重新理顺一遍。注意这篇文章我没有一来就贴公式,巴拉巴拉讲原理,那样不就和老师上课念PPT一样了吗。其实我更喜欢直接学习大佬的仿真代码,先对过程有个个大概思路再去推导细节和公式。这里因为我理解的水平也有限,有不对的地方希望大佬能帮忙指正。如果是没怎么接触过OFDM的萌新,这篇文章可以帮助你对OFDM符号级的仿真有个粗浅的了解XD。
首先画一个我个人认为特别好理解的OFDM符号变化图来帮助理解代码,多径瑞利衰落在步骤4到步骤5之间,在添加AWGN的前面。接下来我会详细的介绍每个步骤在干什么。

步骤0>
照例假装前景摘要一下。本OFDM系统仿真用到的技术主要有:16QAM调制解调 IFFT与FFT 多径瑞利信道 添加AWGN噪声,没用到的有:信道编码 扩频 交织 信道估计等等,哇,越难的技术越不想学(主要是学不懂)。这些技术的数学理论推导确实很难,但是在MATLAB仿真中往往用几个自带的函数就能解决问题,所以要实现一个简单的OFDM系统还是很容易的,不要被天花乱坠般恐怖的数学公式吓跑了(所以我最喜欢的就是直接看代码的运行过程,然后有时间再去研究数学推导23333)。
步骤1>
这个仿真好像暂时没有时间的概念,单位是按照采样点来的。假设一帧有三个OFDM符号,每个符号长度为64(刚好在步骤3做IFFT时长度也为64,满足2的幂次方)。我们首先生成数字基带信号,信号长度为192个采样点,由于要进行16QAM调制,我们直接随机生成192个16进制的数作为基带信号X(K),然后再将X(K)经过16QAM星座图映射便完成了调制。注意调制完输出的X_mod是复信号。
另外在步骤1我们还要进行信噪比的一些初始化,便于计算噪声幅度和最后的计算比特误码率。
增加部分:
在步骤1中,我们增加对信道特征的初始化工作。主要是假设多径信道个数和信道功率,以及各信道的时延,为之后信号通过多径信道的计算做准备。
代码:
clc; clear all; clode all
NFRAME = 3; % 每一帧的OFDM符号数
NFFT = 64; % 每帧FFT长度
NCP = 16; % 循环前缀长度
NSYM = NFFT + NCP; % OFDM符号长度
M = 16; K = 4; % M:调制阶数,K:log2(M)
EbN0 = 0; % 设出比特信噪比(dB)
snr = EbN0 + 10 * log10(K); % 由公式推出snr(dB)表达式
BER(1 : length(EbN0)) = 0; % 初始化误码率
P_hdB = [0 -8 -17 -21 -25]; % 各信道功率特性(dB)
D_h = [0 3 5 6 8]; % 各信道延迟(采样点)
P_h = 10 .^ (P_hdB / 10); % 各信道功率特性
NH = length(P_hdB); % 多径信道个数
LH = D_h(end)+1; % 信道长度(延迟过后)
X = randi([0,15],1,NFFT * NFRAME); % 生成数字基带信号
X_mod = qammod(X,M,'gray') / (sqrt(10)); % 16QAM调制,格雷码星座图,并归一化
步骤2、3、4>
接下来的三个步骤分别如下,注意都是一个符号一个符号处理的,可回去看最开始的符号变化图:
- 将每个OFDM符号的前一半和后一半交换,至于为什么要做交换,我仍然不是很懂。有大佬知道的话希望能在评论区指导一下,感激不尽!
- 对交换过后的每个OFDM符号做IFFT,记录输出为x1(n)。
- 对每个OFDM符号添加循环前缀CP,实际操作很简单,因为这里设的CP的长度NCP为16。就是把每个符号的后16个采样点添加到当前符号的最前面来,每个符号因此就变成了64+16=80个采样点。
由于这三个步骤都是在一个循环里处理的,所以我也就把步骤2、3、4写到一起了。
代码:
x(1 : NFFT * NFRAME) = 0; % 预分配x数组
xt(1 : (NFFT + NCP) * NFRAME) = 0; % 预分配x_t数组
len_a = 1 : NFFT; % 处理的X位置
len_b = 1 : (NFFT + NCP); % 处理的X位置(加上CP的长度)
len_c = 1 : NCP;
len_left = 1 : (NFFT / 2); len_right = (NFFT / 2 + 1) : NFFT; % 每一符号分为左右两边
for frame = 1 : NFRAME % 对于每个OFDM符号都要翻转和IFFT
x(len_a) = ifft([X_mod(len_right), X_mod(len_left)]); % 左右翻转再ifft
xt(len_b) = [x(len_c + NFFT - NCP), x(len_a)]; % 添加CP后的信号数组
len_a = len_a + NFFT; % 更新找到下一个符号起点位置
len_b = len_b + NFFT + NCP; % 更新找到下一个符号起点位置(CP开头)
len_c = len_c + NFFT;
len_left = len_left + NFFT; len_right = len_right + NFFT;
end
增加步骤:
如前面所说的,我们在步骤4和步骤5之间仿真信号xt经过多径衰落信道。听起来一头雾水,说那么多有的没的,其实就是做个卷积啦,就是拿信号xt与信道冲激响应h做卷积运算就OK了(终于有数字信号处理内味儿了~)。如何求信道冲激响应呢?这需要小小推导一下。
离散多径衰落信道的一个简单数学模型如下:
y(n) & = a_1(n)\cdot x(n-\tau_1(n)) + a_2(n)\cdot x(n-\tau_2(n)) + ... + a_N(n)\cdot x(n-\tau_N(n))\notag\\
& = \sum_{i = 1}^{N} a_i(n)x(n-\tau_i(n))\tag{1}\\
\end{align}\]
其中\(x(n)\)表示输入信号,\(a_i(n)\)表示第i条路径上的衰减系数,\(\tau_i(n)\)为第i条路经上的传播时延。
由于表示的信道是线性信道,故可以用在\(n\)时刻对\(n-\tau\)时刻发射的冲激的响应\(h(\tau,n)\)来表示。我们已知用\(h(\tau,n)\)表示的经过信道的输入\输出为卷积关系:
\]
于是由上述两个公式我们可以推得多径衰落信道冲激响应的数学表达式为:
\]
瑞利随机变量产生补充:
在一般的衰落环境中,无线衰落信道可以由复高斯随机变量\(W1 + jW2\)表示,其中\(W1\)和\(W2\)都是均值为0,方差为\(\delta^2\)的独立同分布(i.i.d.)高斯随机变量。
如何产生瑞利随机变量呢?首先通过MATLAB内置函数randn()产生均值为0,方差为1的两个i.i.d.高斯随机变量\(W1\)和\(W2\)。瑞利随机变量X为:
\]
所以一旦通过内置函数randn()生成好了\(W1\)和\(W2\),就可以由公式(4)生成平均功率为\(E(X^2) = 2\delta^2\)的瑞利随机变量。
在仿真中我们已经提前给出了瑞利信道平均功率\(P_h\),所以有\(2\delta^2 = p_h\),推出:
\]
代码:
A_h = (randn(1,NH) + 1i * randn(1,NH)) .* sqrt(P_h / 2); % 由公式(4)(5)生成瑞利随机变量
h = zeros(1,LH); % 初始化信道冲激响应模型
h(D_h + 1) = A_h; % 信道冲激响应(同时体现出衰减系数和信道时延),公式(3)的代码体现
xt1 = conv(xt,h); % 卷积,输出通过该信道的信号,公式(2)的代码体现
步骤5>
经过上一步的处理,现在考虑仿真添加高斯白噪声。由于snr在程序开头就已经确定好了,所以我们要根据snr计算噪声功率(噪声方差)从而添加噪声。注意由于卷积过后输出信号长度会变长,计算信号功率时记得只取原本的长度。
代码:
xt2 = xt1(1 : NSYM * NFRAME); % 只取卷积过后属于OFDM符号的部分
P_s = xt2 * xt2' ./ NSYM ./ NFRAME; % 计算信号功率
A_n = sqrt(10 .^ (-snr(i) / 10) * P_s / 2); % 计算噪声标准差
yr = xt1 + A_n * (randn(size(xt1)) + 1i * randn(size(xt1))); % 根据噪声标准差添加噪声
步骤6、7>
现在的信号已经是经过多径瑞利衰落并且添加了高斯白噪声的信号,不容易啊!我们的仿真已经完成了一半。接下来的两个步骤与步骤2、3、4是呈镜像,倒着实现一遍就行了。步骤分别如下,注意都是一个符号一个符号处理的,可回去看最开始的符号变化图:
- 对每个OFDM符号去除循环前缀CP,就是把每个符号的前16个采样点去掉就好。
- 对每个OFDM符号做FFT,然后将将每个OFDM符号的前一半和后一半交换,记录输出为Y(K)。
代码:
y(1 : NFFT * NFRAME) = 0; % 预分配y数组
Y(1 : NFFT * NFRAME) = 0; % 预分配Y数组
len_a = 1 : NFFT; % 处理的y位置
len_b = 1 : NFFT; % 处理的y位置
len_left = 1 : (NFFT / 2); len_right = (NFFT / 2 + 1) : NFFT; % 每一符号分为左右两边
for frame = 1 : NFRAME % 对于每个OFDM符号先去CP,再FFT再翻转
y(len_a) = yr(len_b + NCP); % 去掉CP
Y(len_a) = fft(y(len_a)); % 先fft再翻转
Y(len_a) = [Y(len_right), Y(len_left)];
len_a = len_a + NFFT;
len_b = len_b + NFFT + NCP;
len_left = len_left + NFFT; len_right = len_right + NFFT;
end
步骤8>
16QAM解调,这里是直接用的官方自带函数
代码:
Yr = qamdemod(Y * sqrt(10),M,'gray');
步骤9>
16QAM解调完毕后,其实我们已经可以自己在工作区里对比解调得到的信号Yr和我们的基带数字信号X了。但作为严谨的打工仔,怎么能不进行误码率分析呢?于是当前步骤我们研究一下怎么分析误码率。其实也很简单,计算一下Yr和X有几比特不相同,再计算一下总共有几比特,把它们相除就得到了我们的比特误码率(BER)。
需要注意的一点是,既然是误比特率,就要把16进制的信号转换成2进制,以比特为单位计算错误数
代码:
Neb = sum(sum(de2bi(Yr,K) ~= de2bi(X,K))); % 转为2进制,计算具体有几bit错误
Ntb = NFFT * NFRAME * K; % 仿真的总比特数
BER = Neb / Ntb;
完整代码:
最后贴一个完整代码,代码是参考的《MIMO-OFDM无线通信技术及MATLAB实现》这本书。我是一行一行自己重新实现了一遍并且加上了详细的中文注释,希望能对像我这样的刚入门的萌新有所启发。对了,后面有个与理论值相比较的作图函数有点占位置,我就暂时不放到这篇文章中了XD。注意在包含多径衰落信道的仿真的时候,如果想要仿真不同信噪比时的误码率,务必要生成一个状态种子,保持衰落信道参数在每一次仿真中都不变。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Version 3.1
%%% 16QAM调制(官方函数)
%%% IFFT(官方函数)
%%% 添加循环前缀
%%% 经过多径瑞利衰减信道
%%% 添加AWGN
%%% 去除循环前缀
%%% FFT(官方函数)
%%% 16QAM解调(官方函数)
%%% BER分析
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clear all;close all;clc;
%% 基带数字信号及一些初始化
NFRAME = 3; % 每一帧的OFDM符号数
NFFT = 64; % 每帧FFT长度
NCP = 16; % 循环前缀长度
NSYM = NFFT + NCP; % OFDM符号长度
M = 16; K = 4; % M:调制阶数,K:log2(M)
P_hdB = [0 -8 -17 -21 -25]; % 各信道功率特性(dB)
D_h = [0 3 5 6 8]; % 各信道延迟(采样点)
P_h = 10 .^ (P_hdB / 10); % 各信道功率特性
NH = length(P_hdB); % 多径信道个数
LH = D_h(end)+1; % 信道长度(延迟过后)
EbN0 = 0:1:20; % 设出比特信噪比(dB)
snr = EbN0 + 10 * log10(K); % 由比特信噪比计算出snr(dB)
BER(1 : length(EbN0)) = 0; % 初始化误码率
file_name=['OFDM_BER_NCP' num2str(NCP) '.dat'];
fid=fopen(file_name, 'w+');
X = randi([0,15],1,NFFT * NFRAME); % 生成基带数字信号
%%
for i = 1 : length(EbN0) % 对于每一种比特信噪比,计算该通信环境下的误码率
randn('state',0); % 很重要!!保持信道参数在每一次仿真中都不变
rand('state',0);
%% 16QAM调制(官方函数)
X_mod = qammod(X,M,'gray') / (sqrt(10)); % 16QAM调制,格雷码星座图,并归一化
%% IFFT与循环前缀添加
x(1 : NFFT * NFRAME) = 0; % 预分配x数组
xt(1 : (NFFT + NCP) * NFRAME) = 0; % 预分配xt数组
len_a = 1 : NFFT; % 处理的X位置
len_b = 1 : (NFFT + NCP); % 处理的X位置(加上CP的长度)
len_c = 1 : NCP;
len_left = 1 : (NFFT / 2); len_right = (NFFT / 2 + 1) : NFFT; % 每一符号分为左右两边??
for frame = 1 : NFRAME % 对于每个OFDM符号都要翻转和IFFT
x(len_a) = ifft([X_mod(len_right), X_mod(len_left)]); % 左右翻转再ifft
xt(len_b) = [x(len_c + NFFT - NCP), x(len_a)]; % 添加CP后的信号数组
len_a = len_a + NFFT; % 更新找到下一个符号起点位置
len_b = len_b + NFFT + NCP; % 更新找到下一个符号起点位置(CP开头)
len_c = len_c + NFFT;
len_left = len_left + NFFT; len_right = len_right + NFFT;
end
%% 经过多径瑞利衰减信道
A_h = (randn(1,NH) + 1i * randn(1,NH)) .* sqrt(P_h / 2); % 生成瑞利随机变量
h = zeros(1,LH); % 初始化信道冲激响应模型
h(D_h + 1) = A_h; % 信道冲激响应(同时体现出衰减系数和信道时延)
xt1 = conv(xt,h); % 卷积,输出通过该信道的信号
%% 由snr计算噪声幅度并加噪
xt2 = xt1(1 : NSYM * NFRAME); % 只取卷积过后属于OFDM符号的部分
P_s = xt2 * xt2' ./ NSYM ./ NFRAME; % 计算信号功率
A_n = sqrt(10 .^ (-snr(i) / 10) * P_s / 2); % 计算噪声标准差
yr = xt1 + A_n * (randn(size(xt1)) + 1i * randn(size(xt1))); % 根据噪声标准差添加噪声
%% 去除循环前缀并且FFT
y(1 : NFFT * NFRAME) = 0; % 预分配y数组
Y(1 : NFFT * NFRAME) = 0; % 预分配Y数组
len_a = 1 : NFFT; % 处理的y位置
len_b = 1 : NFFT; % 处理的y位置
len_left = 1 : (NFFT / 2); len_right = (NFFT / 2 + 1) : NFFT; % 每一符号分为左右两边
H= fft([h zeros(1,NFFT-LH)]); % 信道频率响应
H_shift(len_a)= [H(len_right) H(len_left)];
for frame = 1 : NFRAME % 对于每个OFDM符号先去CP,再FFT再翻转
y(len_a) = yr(len_b + NCP); % 去掉CP
Y(len_a) = fft(y(len_a)); % 先fft再翻转
Y(len_a) = [Y(len_right), Y(len_left)] ./ H_shift; % //
len_a = len_a + NFFT;
len_b = len_b + NFFT + NCP;
len_left = len_left + NFFT; len_right = len_right + NFFT;
end
%% 16QAM解调(官方函数)
Yr = qamdemod(Y * sqrt(10),M,'gray');
%% BER计算(多次迭代算均值会更准确)
Neb = sum(sum(de2bi(Yr,K) ~= de2bi(X,K))); % 转为2进制,计算具体有几bit错误
Ntb = NFFT * NFRAME * K; %[Ber,Neb,Ntb]=ber(bit_Rx,bit,Nbps);
BER(i) = Neb / Ntb;
fprintf('EbN0 = %3d[dB], BER = %4d / %8d = %11.3e\n', EbN0(i),Neb,Ntb,BER(i))
fprintf(fid, '%d %11.3e\n', EbN0(i),BER(i));
end
%% BER作图分析
fclose(fid);
disp('Simulation is finished');
plot_ber(file_name,K);
参考文献:
[1] Tse D, Viswanath P. Fundamentals of wireless communication[M]. Cambridge university press, 2005.
[2] Cho Y S, Kim J, Yang W Y, et al. MIMO-OFDM wireless communications with MATLAB[M]. John Wiley & Sons, 2010.
[3] Goldsmith A. Wireless communications[M]. Cambridge university press, 2005.
OFDM通信系统的MATLAB仿真(2)的更多相关文章
- OFDM通信系统的MATLAB仿真(1)
由于是第一篇博客,想先说点废话,其实自己早就想把学到的一些东西总结成文章随笔之类的供自己复习时查看的了.但是一是觉得自己学的的不够深入,总结也写不出什么很深刻的东西:二是觉得网上也有海量的资料了,需要 ...
- 经典功率谱估计及Matlab仿真
原文出自:http://www.cnblogs.com/jacklu/p/5140913.html 功率谱估计在分析平稳各态遍历随机信号频率成分领域被广泛使用,并且已被成功应用到雷达信号处理.故障诊断 ...
- MATLAB仿真总结
MATLAB仿真过程中,编写MATLAB代码的时候犯了很多错误,做了很多蠢事.记录下自己犯错的点点滴滴,并引以为戒.使用MATLAB版本为2014a,以下内容如有不当还请指正. 1. 仿真开始前清理工 ...
- 极化码的matlab仿真(1)——参数设置
根据老师的安排,对于极化码的了解从仿真开始. 仿真的手段有很多种.可以利用C,C++,matlab等进行仿真的实现.其中matlab由于具有强大的函数库,和壮观的矩阵运算能力,被(我们老师课题组)看中 ...
- (转) 经典功率谱估计及Matlab仿真
原文出自:http://www.cnblogs.com/jacklu/p/5140913.html 功率谱估计在分析平稳各态遍历随机信号频率成分领域被广泛使用,并且已被成功应用到雷达信号处理.故障诊断 ...
- UVW平台运动控制算法以及matlab仿真
UVW平台运动控制算法以及matlab仿真 最近公司同事因为对某视觉对位平台的运动控制算法有疑问,所以来请教我.由于我也是第一次接触到UVW自动对位平台(也可以叫XXY自动对位平台),于是找了一些 ...
- Matlab 仿真实现TI Instaspin 的Foc 逆Clarke变换和SVPWM
一直没搞明白TI 的Instaspin的SVPWM实现原理,最后只能在Matlab里仿真看看输出波形是不是和普通的SVPWM实现输出的波形一样,用M文件实现,下面是代码: clear all; the ...
- MATLAB仿真中连续和离散的控制器有何区别?
matlab系统同时提供连续和离散的控制器和对象的目的是:在降低用户使用复杂程度的同时提高仿真精度.仿真速度和应用的广泛性. 仿真步长和求解精度的概念对于理解这个问题至关重要. 首先是步长,步长和求解 ...
- 极化码的matlab仿真(4)——SC译码(2)
================================================ 首先自作多情的说一句--"抱歉!" 古语"有志者.事竟成",是 ...
随机推荐
- 入门大数据---Sqoop基本使用
一.Sqoop 基本命令 1. 查看所有命令 # sqoop help 2. 查看某条命令的具体使用方法 # sqoop help 命令名 二.Sqoop 与 MySQL 1. 查询MySQL所有数据 ...
- .net Core中如何读取Appsetting配置文件
现在APPSetting下面配置以下节点 { "Logging": { "IncludeScopes": false, "LogLevel" ...
- JavaScript基础JavaScript的常用编码惯例(007)
采用一定的编码惯例,可以使得项目中的代码提到较高的一致性,可读性和可预测性. 1.缩进缩 进可以提高代码的可读性.不过错误的缩进也可能导致代码的误读.有人认为缩进应该使用tab,另外的一些人主张采用4 ...
- 循环&&数组&&方法&&面向对象
day03 数值的默认值 类型 初始化的值 byte,short,int,long 0 float,double 0.0 char 空格 boolean false 引用类型 null JVM的内存 ...
- java简介&&变量
Day01 简介 数据 1.数据大体分为两类:基本类型和引用类型 2.基本类型的数据分为四类八种,四类为整型,浮点,布尔,字符 3.Long类型的数据超过int范围要在之后面加个L,不加L是整型会进行 ...
- 创建windows窗口
from tkinter import * win=Tk() #创建窗口对象 win.title("我的第一个gu ...
- HBase2.0 meta信息丢失的修复方法
在HBase入库日志中发现有一个表入库失败,检查HBase服务端后发现该表的meta信息丢失了: 而HDFS上的region还在: 而HBCK工具不支持HBase2.0版本,只好自己写一个修复工具.网 ...
- 「疫期集训day11」沙漠
可恶的英格兰人,为了石油而攻打我们----岂能让他们得逞?----鄂斯曼帝国的士兵 今天整理日,不错不错 写了一天的DP,截一些较好的题: 收获: \(1.\) 对拍更熟练了,主要是线段和合并饭团两题 ...
- VS2019阅读源码 翻译注释插件
VS翻译插件: Comment Translator China https://marketplace.visualstudio.com/items?itemName=netcorevip.Comm ...
- python中常见的数据类型
str 常用方法 1. 索引(下标) s = 'ABCDEFGHIJKLMN's1 = s[0]print('s[0] = ' + s1) #s[0] = A 2. 切片:顾头不顾尾 s = 'A ...