OFDM通信系统的MATLAB仿真(1)
由于是第一篇博客,想先说点废话,其实自己早就想把学到的一些东西总结成文章随笔之类的供自己复习时查看的了。但是一是觉得自己学的的不够深入,总结也写不出什么很深刻的东西;二是觉得网上也有海量的资料了,需要时查一查根本不需要自己写。但是恰恰也是网上的资料过于庞大,良莠不齐,导致每次都如海水一样的知识涌入脑中,最后也如走马观花一般了了看下,知识吸收率低的惊人。现在也准备改变一下观念,尽量把自己学过的东西归纳整理,以随笔的形式发出来,可能有些地方我还不能理解作者的做法,我也会记录出来,懂的地方解释清楚,不懂的地方也标记清楚,同时在之后的不断学习和总结中补上之前挖的坑,强迫症写文章真的是太难了~哭。
不过也这样安慰自己吧,将所学的进行输出整理其实也是很重要的一步,看似时间浪费了好多,不过读书人花的时间怎么能叫浪费呢!整理出来自己看着不爽吗。如果能同时帮助到其他人的话,那就太好了!希望自己能坚持下去。
关于OFDM系统我目前参考的是《MIMO-OFDM无线通信技术及MATLAB实现》这本书,我看到网上用这本书做参考的人还挺多的,这里将将作者实现OFDM系统的思路及其代码重新理顺一遍。因为我理解的也不是很深入,有不对的地方希望大佬能帮忙指正。如果是没怎么接触过OFDM的萌新,这篇文章可以帮助你对OFDM地仿真有个粗浅的了解。
首先画一个我个人认为特别好理解的OFDM符号变化图来帮助理解代码,接下来我会详细的介绍每个步骤在干什么。
步骤0>
先假装前景摘要一下。本OFDM系统仿真用到的技术主要有:16QAM调制解调 IFFT与FFT 添加AWGN噪声
,没用到的有:信道编码 交织 多径瑞利信道 信道估计
等等,哇,没用到的技术还有这么多,惭愧惭愧。这些技术的数学理论推导确实很难,但是在MATLAB仿真中往往用几个自带的函数就能解决问题,所以要实现一个简单的OFDM系统还是很容易的,不要被天花乱坠般恐怖的数学公式吓跑了(所以我最喜欢的就是直接看代码的运行过程,然后有时间再去研究数学推导2333)。
步骤1>
这个仿真好像暂时没有时间的概念,单位是按照采样点来的。假设一帧有三个OFDM符号,每个符号长度为64(刚好在步骤3做IFFT时长度也为64,满足2的幂次方)。我们首先生成数字基带信号,信号长度为192个采样点,由于要进行16QAM调制,我们直接随机生成192个16进制的数作为基带信号X(K),然后再将X(K)经过16QAM星座图映射便完成了调制。
另外在步骤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; % 初始化误码率
X = randi([0,15],1,NFFT * NFRAME); % 生成数字基带信号
X_mod = qammod(X,M,'gray') / (sqrt(10)); % 16QAM调制,格雷码星座图,并归一化
星座图补充:
简单来说,16QAM星座图映射就是将一个16进制的数根据一定的规律变成一个复数。具体的数学推导我们这暂时不多加讨论,只写实现方法。比如下图是一个按格雷码编码的16QAM星座图(为什么是格雷码呢,你看它相邻码的二进制都只有1位不同)。看星座图可知,假设数字信号X为5,经过qammod()函数后X_mod变为-a+bi
;同理假设数字信号X为13,经过qammod()函数后X_mod变为a+bi
,其中a和b都是一个确定的数。注意最后要进行归一化处理除以\(\sqrt[]{10}\),如果是QPSK调制,则要除以\(\sqrt[]{2}\)。
信噪比补充:
S:信号功率
N:噪声功率
W:传输信道带宽
Eb:信号单位比特能量
Es:信号单位符号能量
Rb:信号比特传输速率
Rs:信号符号传输速率
N0:噪声的功率谱密度
K:通信系统的调制率,\(\text{K} = log_2 M\)
EbN0 = Eb / N0 :比特信噪比
EsN0 = Es / N0 :符号信噪比
snr = S / N;信号噪声功率比
其实上面三个是不一样的,但可以相互推导。初学的时候笼统的叫做信噪比,反而越学越混。在MATLAB仿真中,常常是设出比特信噪比EbN0,然后由以下公式计算出snr。
snr = \frac{S}{N} = \frac{Eb \cdot Rb}{N0 \cdot W}\\
Rb = Rs \cdot K
\end{cases}\]
得到:
\]
当滚降系数\(\alpha\)为0时,传输信道带宽W等于信道中的符号速率:
\]
带入snr表达式中,得到:
\]
转换为dB形式,代码里的计算就是按照下面这个公式算的:
\]
步骤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
步骤5>
经过上一步的处理,现在的信号已经能够通过发射机发射出去,本系统只考虑AWGN信道,关于多径瑞利衰落信道下一篇文章再总结。于是考虑仿真生成高斯白噪声。由于snr在程序开头就已经确定好了,所以我们要根据snr计算噪声幅度。
代码:
P_s = xt * xt' / NSYM / NFRAME; % 计算信号功率
delta1 = sqrt(10 .^ (-snr / 10) * P_s / 2); % 计算噪声标准差
yr = xt + delta1 * (randn(size(xt)) + 1i * randn(size(xt)));
计算噪声功率及标准差补充:
知道计算公式后,代码只有三行,但噪声功率和标准差具体是怎么计算出来的呢?下面是简单的推导。
首先计算出离散信号的信号功率P_s:
P & = \lim_{N\to \infty}(\frac{1}{2N + 1} \sum_{-N}^N |x(n)|^2) \\
& = \frac{1}{N} \sum_{0}^N |x(n)|^2
\end{align}\]
再由snr与P和N的关系有:
\]
转换为dB形式:
\]
反解得:
\]
由于产生的是均值为零的高斯白噪声,所以噪声方差\(\delta^2=N\)。又由于此时我们处理的是复数!!在用randn生成复噪声的时候方差会变大一倍,所以我们使用\(\delta1^2 =\frac{\delta^2}{2}\)来生成复噪声,有:
\]
最终推导噪声的标准差如下,代码只是实现了一下下面的公式而已
\]
另外一种加噪方式补充:
也可以直接用官方函数,它会根据输入的信号向量xt和snr,自动计算出要加的噪声功率并且加到信号上输出,信号为实数或复数都可以(我特意去看了它的函数内部实现,发现如果输入信号是复数的话,噪声功率也会特意除以2,也应证了我上面的说法)。
yr = awgn(xt,snr,'measured'); % 官方函数直接加噪
步骤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.0
%%% 16QAM调制(官方函数)
%%% IFFT(官方函数)
%%% 添加循环前缀(单径AWGN中CP好像没啥用??)
%%% 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)
EbN0 = 0:1:20; % 设出比特信噪比(dB)
snr = EbN0 + 10 * log10(K); % 由他俩关系推出(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) % 对于每一种比特信噪比,计算该通信环境下的误码率
%% 16QAM调制(官方函数)
X_mod = qammod(X,M,'gray') / (sqrt(10)); % 16QAM调制,格雷码星座图,并归一化
%% IFFT与循环前缀添加
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
%% 由snr计算噪声幅度并加噪
% P_s = xt * xt' / NSYM / NFRAME; % 计算信号功率
% delta1 = sqrt(10 .^ (-snr(i) / 10) * P_s / 2); % 计算噪声标准差
% yr = delta1 * (randn(size(xt)) + 1i * randn(size(xt)));
yr = awgn(xt,snr(i),'measured'); % 官方函数直接加噪
%% 去除循环前缀并且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; % 每一符号分为左右两边
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
%% 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(i) = Neb / Ntb;
fprintf('EbN0 = %d[dB], BER = %d / %d = %.3f\n', EbN0(i),Neb,Ntb,BER(i))
fprintf(fid, '%d %5.3f\n', EbN0(i),BER(i));
end
%% BER作图分析
fclose(fid);
disp('Simulation is finished');
plot_ber(file_name,K);
几个疑问:
有几个没搞懂的地方还是记录一下
- 这个程序中添加的循环前缀好像并没有什么作用,不知道是不是AWGN信道的原因,在多径瑞利衰落信道中倒是与信道冲激响应有个卷积操作,这里好像添加了CP也没用到就去除了。
- 进行IFFT前为什么要把每个OFDM符号进行左右交换,不是很懂欸。
参考文献:
[1] 张少侃,吕聪敏,甘浩.数字通信系统中Eb/N0与SNR转换方法的研究[J].现代计算机,2019(12):33-36.
[2] Cho Y S, Kim J, Yang W Y, et al. MIMO-OFDM wireless communications with MATLAB[M]. John Wiley & Sons, 2010.