目录
二、Pkt detect + Freq Sync + Time Sync + ChaEst
前言
光阴似箭,岁月如梭,还记得当时上完 OFDM 就未再作理会了。如今临界毕业,整理学习资料时发现了 OFDM 相关内容,遂加以整理,并在 CSDN 纪念一下。当时 OFDM 是英文授课的,学习资料同理也都是英文的,加之 OFDM 中有许多专业名词术语、网上资料层次不齐,以致学来一头雾水,不得要领。好在后来自己整理了不少中文学习资料和代码,很好地加深了对 OFDM 的理解,遂感叹“实践出真知”。而此处将分享某次基于 802.11a 的 OFDM 实验报告稿件 (因此没失踪的原件详细),后续有机会还会上传更多资料。(更新:部分资料链接)
本文的内容结构将分三部分简要介绍,同时给出呕心沥血的 MATLAB (2016a) 实现 (含注释)。
第一部分,实现了一个 OFDM 基本系统,关键词:前导训练序列(Preamble training symbol)、长/短训练序列(Long/Short training symbol)、卷积码、4QAM调制、导频(Pilot)、映射(Mapping)与逆/解映射(Demapping)、循环前缀(CP)等。
第二部分,实现了对 OFDM 系统进行功能拓展,关键词:包检测(Packet detection)、频率同步、时间精同步、信道估计等。
第三部分,将各功能集成到统一的系统之中,并给出实现过程及注释。
事实上,学习的原理主要来自《宽带无线通信OFDM技术 - 第二版》和《OFDM移动通信技术原理与应用 》这两本书 (见文末) 以及课件,虽然它们有一定年代了,但基本原理和经典内容等都是通用的、不过时的。
最后,原本是一字一句敲,可惜效率低下,遂用截图代之 T T。不论如何,如果能一字一句读懂,必能豁然开朗,希望对读者有帮助 ^ ^ 。
一、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 代码实现
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));
2.1.3 仿真结果
2.2 频率同步 —— 基于自相关的方法
2.2.1 实现过程
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;
2.2.3 仿真结果
2.3 时间精同步
2.3.1 实现原理
2.3.2 实现代码
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;
2.3.3 仿真结果
2.4 信道估计
2.4.1 实现原理
2.4.2 实现代码(关键语句)
channel_est = mean(freq_tr_syms,2).*conj(LongTrain);
2.4.3 仿真结果
三、Complete OFDM System
3.1 仿真步骤
3.2 仿真结果
3.3 实现代码
%% ************************** 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')
参考文献
《OFDM移动通信技术原理与应用 》人民邮电出版社,佟学俭、罗涛,2003
《宽带无线通信OFDM技术 - 第二版》人民邮电出版社,王文博、郑侃,2007