目录
前言
一、Basic OFDM
1.1 系统流程
1.2 基本步骤
1.2.1 系统参数
1.2.2 训练序列
1.2.3 信道编码与信道译码
1.2.4 调制与解调
1.2.5 插入导频
1.2.6 映射与解映射
1.2.7 循环前缀
二、Pkt detect + Freq Sync + Time Sync + ChaEst
2.1 包检测 (Packet Detection)
2.1.1 实现原理
2.1.2 代码实现
2.1.3 仿真结果
2.2 频率同步 —— 基于自相关的方法
2.2.1 实现过程
2.2.3 仿真结果
2.3 时间精同步
2.3.1 实现原理
2.3.2 实现代码
2.3.3 仿真结果
2.4 信道估计
2.4.1 实现原理
2.4.2 实现代码(关键语句)
2.4.3 仿真结果
三、Complete OFDM System
3.1 仿真步骤
3.2 仿真结果
3.3 实现代码
四、补充材料
4.1 rx_timed_to_freqd.m
参考文献
光阴似箭,岁月如梭,还记得当时上完 OFDM 就未再作理会了。如今临界毕业,整理学习资料时发现了 OFDM 相关内容,遂加以整理,并在 CSDN 纪念一下。当时 OFDM 是英文授课的,学习资料同理也都是英文的,加之 OFDM 中有许多专业名词术语、网上资料层次不齐,以致学来一头雾水,不得要领。好在后来自己整理了不少中文学习资料和代码,很好地加深了对 OFDM 的理解,遂感叹“实践出真知”。而此处将分享某次基于 802.11a 的 OFDM 实验报告稿件 (因此没失踪的原件详细),后续有机会还会上传更多资料。(更新:部分资料链接) (更新:有些遗漏的内容,会根据读者反馈补充到文末的补充材料中)
本文的内容结构将分三部分简要介绍,同时给出呕心沥血的 MATLAB (2016a) 实现 (含注释)。(注:放心,通常几个代差的 MATLAB 版本都不会有太大的兼容性问题)
- 第一部分,实现了一个 OFDM 基本系统,关键词:前导训练序列(Preamble training symbol)、长/短训练序列(Long/Short training symbol)、卷积码、4QAM调制、导频(Pilot)、映射(Mapping)与逆/解映射(Demapping)、循环前缀(CP)等。
- 第二部分,实现了对 OFDM 系统进行功能拓展,关键词:包检测(Packet detection)、频率同步、时间精同步、信道估计等。
- 第三部分,将各功能集成到统一的系统之中,并给出实现过程及注释。
事实上,学习的原理主要来自《宽带无线通信OFDM技术 - 第二版》和《OFDM移动通信技术原理与应用》这两本书及课件,虽然它们有一定年代了,但基本原理和经典内容等都是通用的、不过时的。
最后,原本是一字一句敲,可惜效率低下,遂用截图代之 T T。不论如何,如果能一字一句读懂,必能豁然开朗,希望对读者有帮助 ^ ^ 。
function detected_packet = FindPacket(rx_signal)
win_size = 700; % 搜索窗大小 (理论上大于噪声长度500就行)
LTB = 16; % 每个短训练序列符号长度LTB
% 一次性计算所有相邻符号相关性
xcorr = rx_signal(1:win_size+2*LTB).* ...
conj(rx_signal(1*LTB+1:win_size+3*LTB)); %(732,1)
% (1:700+2*16) .* (1*16+1:700+3*16 = 1;732 .* 17:748
%-------------------------------------------------------------------------
% 逐对计算所有相邻符号相关性mn(的分子|Cn|)
Cn_xcorr = zeros(700,1);
for i = 1:700
recorder = 0;
for j = i : (i+2*LTB- 1)
recorder = recorder + (xcorr(j+1));
end
Cn_xcorr(i) = abs(recorder);
end
%-------------------------------------------------------------------------
% 逐对计算所有相邻符号相关性mn(的分母Pn)
rx_pwr = abs(rx_signal(1*LTB+1 : win_size+3*LTB)).^2 ; %17-748
Pn_xcorr = zeros(700,1);
for i = 1:700
recorder = 0;
for j = i : (i+2*LTB-1)
recorder = recorder + rx_pwr(j+1);
end
Pn_xcorr(i) = recorder;
end
%-------------------------------------------------------------------------
% 一次性计算所有相邻符号相关性mn=|Cn|/Pn
x_len = length(Cn_xcorr); % 700*1
mn = Cn_xcorr(1:x_len)./Pn_xcorr(1:x_len);
plot(mn); % 绘图
% 判断有无检测到包
threshold = 0.75; % 判决门限值
thres_idx = find(mn > threshold); % 查询满足条件(相关性大于门限值)元素的id
if isempty(thres_idx)
thres_idx = 1;
else
thres_idx = thres_idx(1); % 若有多个id满足条件,则取首个作为检测到包的起点
end
% 根据id取出数据包
detected_packet = rx_signal(thres_idx:length(rx_signal));
clc;
fs = 20e6;
gi = 1/4;
fftlen = 64;
gilen = gi*fftlen;
ShortTrain = sqrt(13/6) * [0 0 1+j 0 0 0 -1-j 0 0 0 1+j 0 0 0 -1-j ...
0 0 0 -1-j 0 0 0 1+j 0 0 0 0 0 0 -1-j 0 0 0 ...
-1-j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0].';
short_demap = zeros(64, 1);
short_demap([7:32 34:59],:) = ShortTrain;
short_demap([33:64 1:32],:) = short_demap;
% 将频域的短训练序列转化到时域
ShortTrain=sqrt(64)*ifft(sqrt(64/52)*short_demap);
ShortTrain =ShortTrain(1:16);
transmit=[ShortTrain;ShortTrain;ShortTrain;ShortTrain;ShortTrain;
ShortTrain;ShortTrain;ShortTrain;ShortTrain;ShortTrain];
phase=zeros(1,1);
mse=zeros(1,1);
error = zeros(1,500);
snr = 0:1:20;
for snr_idx = 1:length(snr)
for n = 1:500
len = length(transmit); % 计算传输信号长度
noise =sqrt(1/(10^(snr(snr_idx)/10))/2)*( randn(len,1)+j*randn(len,1));
% 加噪声
transmit1 = transmit + noise;
% 加频偏 [0:total_length-1]/fs=nTs ▲f=0.2*fs/fftlen
cfo = 0.2*fs/fftlen/fs*[0:len-1];
phase_shift = exp(j*2*pi*cfo).';
transmit2 = transmit1.*phase_shift; % 将频偏加到传输的信号上
LTE = 16; %长度为16的窗口
phase=0;
for i=1:(len-LTE)
%每一个数据与d个数据后的数据共轭相乘,求总和
phase=phase+transmit2(i).*conj(transmit2(i+LTE));
end
%求估计出的频偏
cfo_est = -angle(phase) / (2*LTE*pi/fs);
%求频偏估计误差
error(n) = (cfo_est - (0.2*fs/fftlen))/(0.2*fs/fftlen);
end
mse(snr_idx) = mean(abs(error).^2);
end
semilogy(snr,mse,'-o');
xlabel('SNR/dB');
ylabel('MSE');
grid on;
clear all; clc;
gi = 1/4;
fftlen = 64;
gilen = gi*fftlen;
ShortTrain = sqrt(13/6) * [0 0 1+j 0 0 0 -1-j 0 0 0 1+j 0 0 0 -1-j ...
0 0 0 -1-j 0 0 0 1+j 0 0 0 0 0 0 -1-j 0 0 0 ...
-1-j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0].';
short_demap = zeros(64, 1);
short_demap([7:32 34:59],:) = ShortTrain;
short_demap([33:64 1:32],:) = short_demap;
% 将频域的短训练序列转化到时域并进行功率归一化
ShortTrain=sqrt(64)*ifft(sqrt(64/52)*short_demap);
ShortTrain =ShortTrain(1:16);
short_train_blks=[ShortTrain;ShortTrain;ShortTrain;ShortTrain;ShortTrain;
ShortTrain;ShortTrain;ShortTrain;ShortTrain;ShortTrain];
longTrain = [1 1 -1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 -1 1 1 -1 1 -1 1 1 1 ...
1 1 -1 -1 1 1 -1 1 -1 1 -1 -1 -1 -1 -1 1 1 -1 -1 1 -1 1 -1 1 1 1 1].';
long_demap = zeros(64, 1);
long_demap([7:32 34:59],:) = longTrain;
long_demap([33:64 1:32],:) = long_demap;
% 将频域的长训练序列转化到时域并进行功率归一化
longTrain=sqrt(64)*ifft(sqrt(64/52)*long_demap);
% 取长训练序列的后32位作为cp前缀
long_train_syms = [longTrain(33:64,:); longTrain; longTrain];
% 构成发送序列
transmit = [short_train_blks; long_train_syms];
len = length(transmit);
error = zeros(500,1);
time_est = zeros(500,1);
snr = 0:1:10;
for snr_idx = 1:length(snr)
for n = 1:500
noise = sqrt(1/(10^(snr(snr_idx)/10))/2)* ...
(randn(len,1)+j*randn(len,1));
transmit1 = transmit + noise; % 加噪声
i_matrix=zeros(64,1);
j_matrix=zeros(51,1);
for j=150:200 % 正确的同步位置在160+32+1处,选择范围包括193
for i=1:64 % 长训练序列的64位
% 接受序列与长训练序列共轭相乘
i_matrix(i)=transmit1(j-1+i).*conj(longTrain(i));
% 以每一个bit为起始计算出一个和
j_matrix(j-149)=j_matrix(j-149)+i_matrix(i);
end
end
[a,b] = max(abs(j_matrix)); % 求和最大的,相关程度最高
time_est(n) = 149 + b; % 求计算出的同步位置
error(n) = time_est(n) - 193; % 估计位置偏差
end
end
mse(snr_idx)= mean(abs(error).^2); % 求mse
semilogy(snr,mse);
xlabel('SNR/dB');
ylabel('MSE');
grid on;
channel_est = mean(freq_tr_syms,2).*conj(LongTrain);
%% ************************** Preparation part **************************
clear all; clc;
% 系统参数
fs = 8e6; % 抽样频率
ml = 2; % 调制阶数 = 2 —— QPSK调制
NormFactor = sqrt(2/3*(ml.^2-1));
gi = 1/4; % 保护间隔比例 = 1/4
fftlen = 64; % FFT长度 = 64 points/chips
gilen = gi*fftlen; % 保护间隔/循环前缀长度 = 16 points/chips
blocklen = fftlen + gilen; % OFDM符号长度 = 80 points/chips
% 子载波标号
DataSubcPatt = [1:5 7:19 21:26 27:32 34:46 48:52]'; % 数据子载波位置标号
PilotSubcPatt = [6 20 33 47]; % 导频子载波位置标号
UsedSubcIdx = [7:32 34:59]; % 共用52个子载波
% 信道编码参数
trellis = poly2trellis(7,[133 171]); % 卷积码
tb = 7*5;
ConvCodeRate = 1/2; % 码率 = 1/2
% 训练序列
% 短训练序列 (NumSymbols = 52)
ShortTrain = sqrt(13/6) * [0 0 1+j 0 0 0 -1-j 0 0 0 1+j 0 0 0 -1-j ...
0 0 0 -1-j 0 0 0 1+j 0 0 0 0 0 0 -1-j 0 0 0 ...
-1-j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0 0 1+j 0 0].';
NumShortTrainBlks = 10; % 短训练序列符号数 = 10
NumShortComBlks = 16*NumShortTrainBlks/blocklen; % 160/80=2
% 长训练序列 (NumSymbols = 52)
LongTrain = [1 1 -1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 -1 1 1 -1 1 -1 1 1 1 ...
1 1 -1 -1 1 1 -1 1 -1 1 -1 -1 -1 -1 -1 1 1 -1 -1 1 -1 1 -1 1 1 1 1].';
NumLongTrainBlks = 2; % 长训练序列符号数 = 2
%短训练序列加长训练序列共相当于4个OFDM符号
NumTrainBlks = NumShortComBlks + NumLongTrainBlks;
short_train = tx_freqd_to_timed(ShortTrain); % 把短训练序列从频域转换到时域
%plot(abs(short_train));
short_train_blk = short_train(1:16); % 每个短训练序列长度16
% 共10个短训练序列 -- 总长度为10*16=160
short_train_blks = repmat(short_train_blk,NumShortTrainBlks,1);
long_train = tx_freqd_to_timed(LongTrain); % 把长训练序列从频域转换到时域
long_train_syms = [long_train(fftlen-2*gilen+1:fftlen,:); % 加循环前缀
long_train; long_train];
% 构成前导训练序列
preamble = [short_train_blks; long_train_syms];
% 包信息
NumBitsPerBlk = 48*ml*ConvCodeRate;
% 每个OFDM符号信息量=48个*2(调制阶数,每个数2bit信息)*卷积码效率
NumBlksPerPkt = 50; % 每个包符号数50
NumBitsPerPkt = NumBitsPerBlk*NumBlksPerPkt; % 每个包信息量位50*48
NumPkts = 250; % 总包数250
% 信道与频偏参数
h = zeros(gilen,1); % 定义多径信道
h(1) = 1; h(3) = 0.5; h(5) = 0.2; % 3径
h = h/norm(h);
CFO = 0.1*fs/fftlen; % 频偏
% 定时参数
ExtraNoiseSamples = 500; % 包前加额外500长度的噪声
%% ************************** Loop start *************************************
snr = 0:1:20; % 用于检测的信噪比值
ber = zeros(1,length(snr)); % 0值初始化误码率
per = zeros(1,length(snr)); % 0值初始化误包率
for snr_index = 1:length(snr)
num_err = 0;
err = zeros(1,NumPkts);
for pkt_index = 1:NumPkts % 250个包
%% ***************************** Transmitter ********************************
% 生成信息序列
inf_bits = randn(1,NumBitsPerPkt)>0; % 生成48*50个信息比特
CodedSeq = convenc(inf_bits,trellis); % 卷积编码
% 调制
paradata = reshape(CodedSeq,length(CodedSeq)/ml,ml); % 分为两路:
ModedSeq = qammod(bi2de(paradata),2^ml)/NormFactor; % 4QAM调制
mod_ofdm_syms = zeros(52, NumBlksPerPkt);
mod_ofdm_syms(DataSubcPatt,:) = reshape(ModedSeq,48,NumBlksPerPkt);
%调制后信号48行50列对应子载波id [1:5 7:19 21:26 27:32 34:46 48:52]';
mod_ofdm_syms(PilotSubcPatt,:) = 1; % 加导频
% 对OFDM符号做Mapping及IFFT(输出64行50列)
tx_blks = tx_freqd_to_timed(mod_ofdm_syms);
% 加循环前缀
% 每个OFDM符号后16位重复放在前面做cp
tx_frames = [tx_blks(fftlen-gilen+1:fftlen,:); tx_blks];
% 并串转换
tx_seq = reshape(tx_frames,NumBlksPerPkt*blocklen,1); % 50*80
tx = [preamble;tx_seq]; % 在50个OFDM符号前加前导序列,构成一个包
%% ****************************** Channel *********************************
FadedSignal = filter(h,1,tx); % 包通过多径信道
len = length(FadedSignal);
noise_var = 1/(10^(snr(snr_index)/10))/2;
noise = sqrt(noise_var) * (randn(len,1) + j*randn(len,1));
% 加噪声
rx_signal = FadedSignal + noise;
%包前侧加500长度的噪声
extra_noise = sqrt(noise_var) * (randn(ExtraNoiseSamples,1) + ...
j*randn(ExtraNoiseSamples,1));
%包后侧加170长度的噪声
end_noise = sqrt(noise_var) * (randn(170,1) + j*randn(170,1));
% 接收信号
rx = [extra_noise; rx_signal; end_noise];
% 引入频偏
total_length = length(rx); % 计算接收信号长度
t = [0:total_length-1]/fs;
phase_shift = exp(j*2*pi*CFO*t).'; % 加载波频率偏移
rx = rx.*phase_shift;
%% ******************************* Receiver *****************************
% 包检测
%rx_signal去掉包前噪声的接收信号,pkt_offset包前噪声的偏移量
rx_signal = FindPacket(rx);
% 频偏估计与纠正
%rx_signal补偿频率偏移后的接收信号,cfo_est频率偏移量
rx_signal = FrequencySync(rx_signal,fs);
% 时间精同步
% 时间同步位置
fine_time_est = FineTimeSync(rx_signal, long_train);
% Time synchronized signal
% 期望去掉短训练序列及长序列前cp后,
% 得到长度即长训练序列64*2个+80*50个OFDM符号
expected_length = 64*2+80*NumBlksPerPkt;
% 去掉短训练序列以及长序列前cp
fine_time_est_end = fine_time_est+expected_length-1;
sync_time_signal = rx_signal(fine_time_est:fine_time_est_end);
[freq_tr_syms, freq_data_syms, freq_pilot_syms] = ...
rx_timed_to_freqd(sync_time_signal);
% freq_tr_syms取出长训练序列48行n_data_syms列
% freq_data_syms取出信息48行n_data_syms列
% freq_pilot_syms取出导频4行n_data_syms列
% 信道估计
% 接收longtrain freq_tr_syms取行!
% 平均 H(k) = freq_tr_syms * conj(LongTrainingSyms)
channel_est = mean(freq_tr_syms,2).*conj(LongTrain);
% Data symbols channel correction
% 扩展信息序列对应的H(k),同接收OFDM符号个数相同
chan_corr_mat = repmat(channel_est(DataSubcPatt), ...
1, size(freq_data_syms,2));
% 用估计的H(k)共轭乘接受信息序列,得到估计的发送信息序列
freq_data_syms = freq_data_syms.*conj(chan_corr_mat);
% 对导频部分做同样的处理
chan_corr_mat = repmat(channel_est(PilotSubcPatt), ...
1, size(freq_pilot_syms,2));
freq_pilot_syms = freq_pilot_syms.*conj(chan_corr_mat);
% 幅度归一化
% 信息序列对应的H(k)绝对值平方行求和
chan_sq_amplitude = sum(abs(channel_est(DataSubcPatt,:)).^2, 2);
%扩展至估计的发送信息序列列数
chan_sq_amplitude_mtx = repmat(chan_sq_amplitude, ...
1, size(freq_data_syms,2));
data_syms_out = freq_data_syms./chan_sq_amplitude_mtx; % 幅度归一化
% 对导频序列做同样处理
chan_sq_amplitude = sum(abs(channel_est(PilotSubcPatt,:)).^2, 2);
chan_sq_amplitude_mtx = repmat(chan_sq_amplitude, ...
1, size(freq_pilot_syms,2));
pilot_syms_out = freq_pilot_syms./chan_sq_amplitude_mtx;
phase_est = angle(sum(pilot_syms_out)); % 计算导频
phase_comp = exp(-j*phase_est);
data_syms_out = data_syms_out.*repmat(phase_comp, 48, 1);
Data_seq = reshape(data_syms_out,48*NumBlksPerPkt,1); % 48*50
% 解调
DemodSeq = de2bi(qamdemod(Data_seq*NormFactor,2^ml),ml);
deint_bits = reshape(DemodSeq,size(DemodSeq,1)*ml,1).';
% 卷积码译码
DecodedBits = vitdec(deint_bits(1:length(CodedSeq)), ...
trellis,tb,'trunc','hard'); % 维特比译码
% 误差计算
err(pkt_index) = sum(abs(DecodedBits-inf_bits)); % 计算误码个数
num_err = num_err + err(pkt_index);
end
ber(snr_index) = num_err/(NumPkts*NumBitsPerPkt); % 误码率
per(snr_index) = length(find(err~=0))/NumPkts; % 误包率
end
%% 绘制 SNR-BER 和 SNR-PER曲线
semilogy(snr,ber,'-b.');
hold on;
semilogy(snr,per,'-re');
xlabel('SNR (dB)');
ylabel('ERROE');
grid on;
legend('BER','PER')
function [freq_tr_syms, freq_data_syms, freq_pilot_syms] = rx_timed_to_freqd(time_signal)
UsedSubcIdx = [7:32 34:59];
DataSubcIdx = [7:11 13:25 27:32 34:39 41:53 55:59];
PilotSubcIdx = [12 26 40 54];
% Long Training symbols
long_tr_syms = time_signal(1:2*64);
long_tr_syms = reshape(long_tr_syms, 64, 2);
% to frequency domain
freq_long_tr = fft(long_tr_syms)/(64/sqrt(52));
reorder = [33:64 1:32];
freq_long_tr(reorder,:) = freq_long_tr;
% Select training carriers
freq_tr_syms = freq_long_tr(UsedSubcIdx,:);
% Take data symbols
data_syms = time_signal(129:length(time_signal));
data_sig_len = length(data_syms);
n_data_syms = floor(data_sig_len/80);
% Cut to multiple of symbol period
data_syms = data_syms(1:n_data_syms*80);
data_syms = reshape(data_syms, 80, n_data_syms);
% remove guard intervals
data_syms(1:16,:) = [];
% perform fft
freq_data = fft(data_syms)/(64/sqrt(52));
%Reorder pattern is [33:64 1:32]
freq_data(reorder,:) = freq_data;
%Select data carriers
freq_data_syms = freq_data(DataSubcIdx,:);
%Select the pilot carriers
freq_pilot_syms = freq_data(PilotSubcIdx,:);
赠人玫瑰,手有余香~ 如果觉得有用,请为我点个赞吧~ ^ ^
《OFDM移动通信技术原理与应用 》人民邮电出版社,佟学俭、罗涛,2003
《宽带无线通信OFDM技术 - 第二版》人民邮电出版社,王文博、郑侃,2007