Communications Toolbox 学习记录一:仿真框架
说明:这个系列主要用来记录对matlab的communication toolbox(通信工具箱)的学习,并非教程!
简介
作为一个工科生、特别是通信专业的学生如果不知道matlab 那就太让人匪夷所思了
matlab是美国mathworks公司旗下的一款产品,主要启用于工业仿真(据说最早是从美国一位大学教授为了授课而研发出来的,tql),其产品的核心竞争力就在于软件已经集成了大量的工具箱,就正如现在很多编程语言其强大之处不在于他的语言本身,而在于其强大的工具库,如python在数据处理、机器学习方面的、java在后端开发当中的…… 这个系列博文主要针对matlab在通信仿真当中常用到的Communications Toolbox工具箱 进行学习与记录。
案例一:从QPSK&OFDM仿真开始
通过一个案例讲解一般的仿真基本框架
教程地址
这个教程,展示了怎样构建一个最基本的通信系统,
这个系统采用了QPSK&OFDM调制方式
简单来说,整体上是OFDM,但是每一个OFDM的子载波采用QPSK进行“预先”调制,具体理解的话可以试试看看北邮通信原理第四版P368-371,上面讲了两个OFDM的例子,子载波分别采用BPSK与MQAM调制
参数配置
主要参数
一个通信仿真程序除了最基本的参数之外,就应该开门见山的写配置信息,
% QPSK参数
M = 4; % Modulation alphabet :调制阶数
k = log2(M); % Bits/symbol: 每符号对应的比特数
% OFDM参数
numSC = 128; % Number of OFDM subcarriers :OFDM子载波数量
cpLen = 32; % OFDM cyclic prefix length:循环前缀长度
%
maxBitErrors = 100; % Maximum number of bit errors:最大比特错误
maxNumBits = 1e7; % Maximum number of bits transmitted :最大传输比特数目
仿真”对象”
matlab自带了的很多类似的对象函数,目的是减少你重新建模的麻烦,这里更改一些对象的参数就能使用matlab自带的仿真对象
下面是一些初始化对象的操作,对象的具体参数要在下一节调节
QPSK 调制解调器对象
设成直接输入输出二进制 数据的 调制模式
%
qpskMod = comm.QPSKModulator('BitInput',true);
qpskDemod = comm.QPSKDemodulator('BitOutput',true);
OFDM 调制解调器
ofdmMod = comm.OFDMModulator('FFTLength',numSC,'CyclicPrefixLength',cpLen);
ofdmDemod = comm.OFDMDemodulator('FFTLength',numSC,'CyclicPrefixLength',cpLen);
信道
将NoiseMethodAWGN 通道对象的属性设置为Variance并定义该VarianceSource属性,以便可以从输入端口设置噪声功率。
channel = comm.AWGNChannel('NoiseMethod','Variance', ...
'VarianceSource','Input port');
错误率计算器
将ResetInputPort属性设置为true以使错误率计算器能够在模拟过程中被重置。
errorRate = comm.ErrorRate( 'ResetInputPort' ,true);
仿真对象的详细参数解释 或者配置
OFDM 调制解调器对象
用info 函数可以查看ofdmMod 对象的输入输出维度
ofdmDims = info(ofdmMod)
显示
ofdmDims = struct with fields:
DataInputSize: [117 1]
OutputSize: [160 1]
表明输入一个117*1的二进制数据,得到一组160*1的数据
换句话说,128个子载波中有117个子载波是数据子载波,11个是其他的子载波如空子载波、训练用的?
总共得到160个输出,是因为还有32个循环前缀 :$128+32=160$
%数据子载波数量 number of data subcarriers
numDC = ofdmDims.DataInputSize(1)
显示
numDC = 117
那么一帧OFDM数据(一个OFDM符号)所需要的比特数为:
frameSize = [k*numDC 1];
现在理一下帧结构,OFDM帧包括数据帧与循环前缀部分(时域上的划分),在频域上,分为各种子载波,每一个数据子载波“承载”了一个QPSK映射的符号。
信道(高斯白噪声)对象
设置信噪比向量(仿真多个信噪比)
EbNoVec = (0:10) ';
snrVec = EbNoVec + 10 * log10 (k) + 10 * log10 (numDC / numSC);
这里不是特别理解?
按照最常用基本的理论
这个实验应该默认sps=1? k是每个符号对应的比特数 一般$k=R·log_2M$, $R$是编码码率
将numDC / numSC视为编码码率可以好理解一些,就是sps=1 不是很理解,因为sps原本是每个符号的采样点数,OFDM 应该是整体一帧作为一个符号,不可能只采样一个点。如果把sps 理解为:采样增加倍数,这样似乎好理解一些。
后面我再看看相关的书,如果有满意的解释我再修改
错误率计算器对象
初始化BER向量(每一个信噪比一个BER)
初始化错误数据矩阵(暂时不知道,后面看看)
berVec = zeros(length(EbNoVec),3);
errorStats = zeros(1,3);
仿真主体
主体循环采用”蒙特卡洛仿真”:再简单BER场景下就是达到一定误码比特数量maxBitErrors,一般设置为100,或者,传输帧数(传输比特数)大于一定数量maxNumBits。
for m = 1:length(EbNoVec) % 每个 信噪比跑一遍
snr = snrVec(m);
while errorStats(2) <= maxBitErrors && errorStats(3) <= maxNumBits
dataIn = randi([0,1],frameSize); % Generate binary data
qpskTx = qpskMod(dataIn); % Apply QPSK modulation
% 由于设置了直接能接受二进制数据所以直接用
txSig = ofdmMod(qpskTx); % Apply OFDM modulation
powerDB = 10*log10(var(txSig)); % Calculate Tx signal power
noiseVar = 10.^(0.1*(powerDB-snr)); % Calculate the noise variance
% 根据信噪比推算噪声能量
rxSig = channel(txSig,noiseVar); % Pass the signal through a noisy channel
% 通过一个信道对象
qpskRx = ofdmDemod(rxSig); % Apply OFDM demodulation
dataOut = qpskDemod(qpskRx); % Apply QPSK demodulation
errorStats = errorRate(dataIn,dataOut,0); % Collect error statistics
end
berVec(m,:) = errorStats; % Save BER data
errorStats = errorRate(dataIn,dataOut,1); % Reset the error rate calculator
% 重置错误率
end
用awgn 函数计算 QPSK 的理论误码率(作为参照)
berTheory = berawgn(EbNoVec,'psk',M,'nondiff');
画图
figure
semilogy(EbNoVec,berVec(:,1),'*')
hold on
semilogy(EbNoVec,berTheory)
legend('Simulation','Theory','Location','Best')
xlabel('Eb/No (dB)')
ylabel('Bit Error Rate')
grid on
hold off
案例二:Parallel Computing ToolBox
案例讲述了如何用Parallel Computing ToolBox 并行计算来加速一般的仿真实验。
教程地址
这个案例没啥太大价值,
其实就是 告诉你 matlab 的for 循环非常慢,可以用parfor代替。
简单来说,原来的for循环采用CPU的一个线程(具体是一个核,还是一个线程我也不知道,就不深入讨论了)来跑,现在,可以用parfor将计算任务分配到多个线程中,这些线程并行的计算来缩减仿真时间。
当然这种方式是有一些限制条件的
简单来说就是
- 1、循环次数多一些的
- 2、循环之间没有迭代关系的
- 3、如果循环用到同一个数组、数组最好循环次数一一对应
具体看看官方文档
案例三:Matlab 仿真AWGN信道下的16QAM 的BER曲线
step 1:使用 MATLAB 检查 16-QAM
基本参数
M = 16; % Modulation order (alphabet size or number of points in signal constellation)
k = log2(M); % Number of bits per symbol
n = 30000; % Number of bits to process
sps = 1; % Number of samples per symbol (oversampling factor)
信源
将rng函数设置为其默认状态或任何静态种子值,以便示例产生可重复的结果。然后使用该randi函数生成随机二进制数据。
rng default;
dataIn = randi([0 1],n,1); % Generate vector of binary data
检查生成的前40个二进制数据
stem(dataIn(1:40),'filled');
title('Random Bits');
xlabel('Bit Index');
ylabel('Binary Value');
将二进制数据转化为多进制(16进制)数据 (因为我们用到的matlab调制器 对象/函数 需要输入多进制数据),并画图检查
dataSymbolsIn = bit2int (dataIn, k);
figure; % Create new figure window.
stem(dataSymbolsIn(1:10));
title('Random Symbols');
xlabel('Symbol Index');
ylabel('Integer Value');
利用qammod 函数将16进制数据转换为星座点,映射规则默认为格雷映射。
dataMod = qammod(dataSymbolsIn,M,'bin'); % Natural-encoded binary
dataModG = qammod(dataSymbolsIn,M); % Gray-encoded binary
噪声
awgn函数加噪声,scatterplot函数查看 星座图(加噪声后的散点图)
EbNo = 10;
snr = EbNo+10*log10(k)-10*log10(sps);
receivedSignal = awgn(dataMod,snr,'measured');
receivedSignalG = awgn(dataModG,snr,'measured');
sPlotFig = scatterplot(receivedSignal,1,0,'g.');
hold on
scatterplot(dataMod,1,0,'k*',sPlotFig)
解调
dataSymbolsOut = qamdemod(receivedSignal,M,'bin'); % Natural-encoded binary data symbols
dataSymbolsOutG = qamdemod(receivedSignalG,M); % Gray-coded binary data symbols
变回二进制数据
dataOut = int2bit(dataSymbolsOut,k);
dataOutG = int2bit(dataSymbolsOutG,k);
计算误比特
% 自然映射
[numErrors,ber] = biterr(dataIn,dataOut);
fprintf('\nThe binary coding bit error rate is %5.2e, based on %d errors.\n', ...
ber,numErrors)
% 格雷映射
[numErrorsG,berG] = biterr(dataIn,dataOutG);
fprintf('\nThe Gray coding bit error rate is %5.2e, based on %d errors.\n', ...
berG,numErrorsG)
The binary coding bit error rate is 2.27e-03, based on 68 errors.
The Gray coding bit error rate is 1.63e-03, based on 49 errors.
小技巧,显示 映射关系 的星座图
M = 16; % Modulation order
x = (0:15); % Integer input
symbin = qammod(x,M,'bin'); % 16-QAM output (natural-coded binary)
symgray = qammod(x,M,'gray'); % 16-QAM output (Gray-coded)
scatterplot(symgray,1,0,'b*'); % 先把点 画出来
for k = 1:M % 对16个点
% 格雷映射
% PS:text 函数text(x轴位置、y轴位置,文本,其他参数(颜色))
text(real(symgray(k)) - 0.0,imag(symgray(k)) + 0.3,...
dec2base(x(k),2,4),'Color',[1 1 0]);
text(real(symgray(k)) - 0.5,imag(symgray(k)) + 0.3,...
num2str(x(k)),'Color',[1 1 0]);
% 自然映射
text(real(symbin(k)) - 0.0,imag(symbin(k)) - 0.3,...
dec2base(x(k),2,4),'Color',[1 0 0]);
text(real(symbin(k)) - 0.5,imag(symbin(k)) - 0.3,...
num2str(x(k)),'Color',[1 0 0]);
end
title('16-QAM Symbol Mapping')
axis([-4 4 -4 4])
step2:对 16-QAM 信号使用脉冲整形
上一步以为采样倍率是1 所以一个符号只采样一次 一个符号只用一个数据表示、
为了让仿真会更加贴合实际的效果,还可以加上脉冲成型。
总体来说这个实验就是在 step1 的基础上加上脉冲成型
基本参数
M = 16; % Modulation order
k = log2(M); % Bits per symbol
numBits = k*7.5e4; % Bits to process ;
sps = 4; % Samples per symbol (oversampling factor)
根升余弦滤波器 RRC滤波器
filtlen = 10; % Filter length in symbols 滤波器冲激响应持续时间
rolloff = 0.25; % Filter rolloff factor 滚降因子 记得没错的话(1+α)fs 等于带宽(基带)
rrcFilter = rcosdesign(rolloff,filtlen,sps);
fvtool(rrcFilter,'Analysis','Impulse') % 查看冲击相应
其他操作基本上与step1 基本相同
信源
rng default; % Use default random number generator
dataIn = randi([0 1],numBits,1); % Generate vector of binary data
%dataSymbolsIn = bit2int(dataIn,k);
% 由于 bit2int函数r2021b才有
% 我手写实现了一个
temp=reshape(dataIn,k,numBits/k);
table=(2.^((k:-1:1)-1));
dataSymbolsIn=table*temp;
dataMod = qammod(dataSymbolsIn,M);
上采样 过成型滤波器
(使用该upfirdn函数以sps倍率的过采样倍数对信号进行上采样,并将信号通过 RRC 滤波器。该upfirdn函数在最后用零填充上采样信号以刷新滤波器。) %不是很懂这个什么叫做 用零填
txFiltSignal = upfirdn(dataMod,rrcFilter,sps,1);
%我运行了一下 dataMod size 1×75000, 按理说sps=4 上采样后 应该为 300000
% 通过滤波器(滤波器10 * 4 +1 =41) 如果是卷积操作,应该大小为 300000+41-1=300040
% 但是结果是一个 300037 不知道为啥?
噪声
EbNo = 10;
snr = EbNo + 10*log10(k) - 10*log10(sps);
rxSignal = awgn (txFiltSignal, snr, 'measured' );
接收端
对信号 通过RRC滤波器, 下采样
rxFiltSignal_temp = ...
upfirdn(rxSignal,rrcFilter,1,sps); % Downsample and filter
rxFiltSignal = ...
rxFiltSignal_temp(filtlen + 1:end - filtlen); % Account for delay
第二步的意思是,通过滤波器(卷积操作)都会产生延迟(这种延迟一般以冲激响应最高峰为延迟开始的地方,很显然 延迟是 滤波器 长度的一半filtlen/2),因为 发射、接受 通过RRC滤波器两次 ,所以延迟为filtlen,同理,后面也会因为卷积操作产生多余的部分 所以去掉filtlen长度 唯一不懂得还是upfirdn长度相关的问题
解调解映射
dataSymbolsOut = qamdemod(rxFiltSignal,M);
% dataOut = int2bit(dataSymbolsOut,k);
temp= de2bi(dataSymbolsOut,k,'left-msb');
dataOut=reshape(temp',[],1);
[numErrors,ber] = biterr(dataIn,dataOut);
fprintf('\nFor an EbNo setting of %3.1f dB, the bit error rate is %5.2e, based on %d errors.\n', ...
EbNo,ber,numErrors)
### 查看滤波效果(眼图、星座图)
EbNo = 20;
snr = EbNo + 10*log10(k) - 10*log10(sps);
rxSignal = awgn(txFiltSignal,snr,'measured');
rxFiltSignal_temp = ...
upfirdn(rxSignal,rrcFilter,1,sps); % Downsample and filter
rxFiltSignal = ...
rxFiltSignal(rxFiltSignal_temp + 1:end - filtlen); % Account for delay
为过滤后的无噪声信号的一部分创建眼图,以可视化脉冲整形的效果。传输的信号经过 RRC 滤波,并显示 ISI 为眼图张开变窄。
eyediagram(txFiltSignal(1:2000),sps*2);
eyediagram(rxSignal(1:2000),sps*2);
eyediagram(rxFiltSignal(1:2000),2);
发射端眼图txFiltSignal
![](https://ww2.mathworks.cn/help/examples/comm/win64/PulseShapingA16QAMSignalExample_02.png)
接收端(未经过滤波)眼图rxSignal
![](https://ww2.mathworks.cn/help/examples/comm/win64/PulseShapingA16QAMSignalExample_03.png)
接收端(经过滤波)眼图rxFiltSignal
![](https://ww2.mathworks.cn/help/examples/comm/win64/PulseShapingA16QAMSignalExample_04.png)
接受前后的星座图,不知道接受前后这个星座图的能量是怎么算的? 下面这个为什么乘了一个 sqrt(sps)?
scatplot = scatterplot(sqrt(sps)*...
rxSignal(1:sps*5e3),...
sps,0);
hold on;
scatterplot(rxFiltSignal(1:5e3),1,0,'bx',scatplot);
title('Received Signal, Before and After Filtering');
legend('Before Filtering','After Filtering');
axis([-5 5 -5 5]); % Set axis ranges
hold off;
![](https://ww2.mathworks.cn/help/examples/comm/win64/PulseShapingA16QAMSignalExample_05.png)
## step3 对 16-QAM 信号使用前向纠错
此案例在 step 2 上进一步扩展显示使用前向纠错 (FEC) 编码时的误码率 (BER) 性能改进。
主要是在上一步的基础上加了纠错系统
### 基本参数
M = 16; % Modulation order
k = log2(M); % Bits per symbol
numBits = k*2.5e5; % Total bits to process
sps = 4; % Samples per symbol (oversampling factor)
filtlen = 10; % Filter length in symbols
rolloff = 0.25; % Filter rolloff factor
### 发射端
rng default; % Use default random number generator
dataIn = randi([0 1],numBits,1); % Generate vector of binary data
卷积码编码
采用码率为 2/3的卷积码 , 解码端为 维特比解码、硬判决
关于这一块 卷积码编码函数这一块可以看我的 [一篇CSDN博客](https://blog.csdn.net/differencer/article/details/120352331?spm=1001.2014.3001.5502)写过
% 卷积码编码
constrlen = [5 4]; % Code constraint length 约束长度
genpoly = [23 35 0; 0 5 13] ; % Generator polynomials
tPoly = poly2trellis(constrlen,genpoly);
codeRate = 2/3;
dataEnc = convenc(dataIn,tPoly);
% 进制转换
%dataSymbolsIn = bit2int(dataEnc,k);
temp=reshape(dataEnc,k,numel(dataEnc)/k);
table=(2.^((k:-1:1)-1));
dataSymbolsIn=table*temp;
% 调制
dataMod = qammod(dataSymbolsIn,M);
txSignal = upfirdn(dataMod,rrcFilter,sps,1);
### 信道
这里 噪声EbNo 需要考虑码率的影响
EbNo = 10;
snr = EbNo+10*log10(k*codeRate)-10*log10(sps);
rxSignal = awgn(txSignal,snr,'measured');
### 接收端
% 下采样
rxFiltSignal = ...
upfirdn(rxSignal,rrcFilter,1,sps); % Downsample and filter
rxFiltSignal = ...
rxFiltSignal(filtlen + 1:end - filtlen); % Account for delay
% 解调
dataSymbolsOut = qamdemod(rxFiltSignal,M);
% 进制转换
% dataOut = int2bit(dataSymbolsOut,k);
temp= de2bi(dataSymbolsOut,k,'left-msb');
codedDataOut =reshape(temp',[],1);
%因为是硬判决所以只用输入 比特数据就好,不用LLR数据
traceBack = 16; % Decoding traceback length
% wtf 什么意思????
numCodeWords = ...
floor(length(codedDataOut)*2/3); % Number of complete codewords
dataOut = ...
vitdec(codedDataOut(1:numCodeWords*3/2), ...
tPoly,traceBack,'cont','hard'); % Decode data
由发送和接收 RRC 滤波器引入的延迟已在恢复数据中考虑,但尚未考虑解码器延迟。
**Viterbi 解码器的连续操作模式会产生延迟,其持续时间(以比特为单位)等于回溯长度traceBack,乘以编码器处的输入流的数量。 **
对于本例中使用的 2/3 码率,编码器有两个输入流,因此延迟为 2×traceBack比特。
**结果,因为延迟,解码向量中的前 2xtraceBack位dataOut为 0。**
在计算 BER 时,
丢弃
- dataOut中的前 2×traceBack位
- dataIn的后 2×traceBack位。
decDelay = 2*traceBack; % Decoder delay, in bits 按比特来算的解码器时延
[numErrors,ber] = ...
biterr(dataIn(1:end - decDelay),dataOut(decDelay + 1:end));
fprintf('\nThe bit error rate is %5.2e, based on %d errors.\n', ...
ber,numErrors)
总结
哦吼吼 写(抄了)好多了,这一节就先到这里,后面的再继续写
到这里,已经对一个通信仿真系统基本流程已经了解差不多了
其实里面还有很多不懂得地方,后面搞懂了以后再写写,修改修改(尽量不咕)