[1]、V3学院——尤老师(微信号:15921999232) (此博客有不少借鉴尤老师的内容,通过此界面或联系我购买尤老师的FPGA就业课或软件无线电课程会有优惠)
[2]、网易云课堂
好久没写博客了,因为这段时间太忙,熟悉博客的同学都应该知道,我的博客每一篇都干货慢慢,从来不水博客。因为自己就是信号处理专业的学生所以对信号与通信的知识比较了解。最近学习了尤老师的FM调制,感觉对调制的原理有了更进一步的理解,这里想把自己的一些理解放出来做个记录以及载波恢复的扩展,想系统学习软件无线电的同学可以联系尤老师。 这个系列我将分为两个章节来完成,一个是载波已知的情况下的相干解调,另一篇博客关于如何从调制之后的信号恢复出载波信号。这篇博客我们讲解基于MATLAB的FM调制解调,这篇文章我们主要分为FM的调制与载波已知情况下的解调。
先验知识:
1、通信原理——忘记的同学可以翻看一下通信原理的课本,这篇博客中的代码将于书本上面的模型一摸一样。
本次实验所用到的软件环境如下:
1、MATLAB 2015b
FM 调频是使载波的频率随调制信号的大小变化而变化, 而振幅保持不变的调制方式。 因为已调信号反映出载波矢量角度上的变化,所以又被称为调角(这里要重点区分一下FM与PM之间的联系与区别)。
上面最重要的知识是基带信号与瞬时频率偏移成线性对应关系 。通过这个知识瞬时频率偏移等于基带信号的线性放大,通过这句话可以直接确定瞬时频率偏移与基带信号的关系,进而确定最终调制之后信号的表达式。
那么首先以余弦信号的调制解调过程来讲解并且扩展到一般信号中去:
这里对于绝大多数同学不理解的一个地方是,连续域的积分变成了离散域的累加操作,这是符合离散数学原理的,如下:
这里的 k f k_f kf被称为调频灵敏度,是FM中最常出现的一个信号,相信绝大多数的同学不明白调频灵敏度的含义。调频灵敏度说白了就是一个收缩因子,这个因子主要作用是线性控制调制信号所引起的瞬时频率偏移。
将上式转换为IQ调制,如下图:
在MATLAB与FPGA中 s i n ( w t ) 、 c o s ( w t ) sin(wt)、cos(wt) sin(wt)、cos(wt)主要式使用DDS产生的信号,那么这里就需要知道如何利用DDS产生如下信号:
1 、 s i n ( w c t ) 1、sin(w_ct) 1、sin(wct) 2 、 c o s ( w c t ) 2、cos(w_ct) 2、cos(wct) 3、 c o s ( k f ∗ ∑ m ( τ ) ) cos(k_f*\sum m(\tau )) cos(kf∗∑m(τ)) 4、 c o s ( k f ∗ ∑ m ( τ ) ) cos(k_f*\sum m(\tau )) cos(kf∗∑m(τ))。
这里需要注意,上述信号的产生使用MATLAB非常简单,但是我们之后需要进一步硬件实现该算法,那么进行MATLAB算法验证的失手应该尽可能逼近硬件实现。从博客之前的文章不难了解,FPGA实验正余弦波有两种方式,一种是使用DDS的方式控制频率与相位控制字产生,另一种是使用CORDIC算法。这里使用的MATLAB代码主要尽可能接近DDS的产生方式,如果同学们对DDS的原理不了解可以查看博主之前的文章,这里不再赘述。
经过上面关于FM调制原理的介绍,相信同学们对FM调制的数学模型有了相应的认识。接下来将直接给出接近FPGA实现的MATLAB代码,与上述数学模型相互验证学习。
clc;
clear all;
close all;
%% ========================================================================================\
%%******************************* System Parameter **********************************
%%========================================================================================/
fs = 16e6; %采样率 载波的采样率
fc = 1e6; %载波中心频率
df = 75e3; %最大频偏
fm = 16e3; %音频的采样率
kf = (df*2^32/fs)/32767;
AC= 1024;%幅度
%% ========================================================================================\
%%******************************* Loaded modulated signal **********************************
%%========================================================================================/
load voice.mat
m_len=length(m)*fs/fm;%按照载波采样率的长度计算
%% ========================================================================================\
%%******************************* carrier generator **********************************
%%========================================================================================/
w=fc*2^32/fs; %1Mhz 载波的频率控制字
% 载波的 ROM
n=0:1/1024:1023/1024;
s_rom=sin(2*pi*n);
c_rom=cos(2*pi*n);
w_r=0;%相位累加器
cw_sin=zeros(1,m_len);
cw_cos=zeros(1,m_len);
for i=1:m_len
w_r = w_r + w;
if(w_r > 2^32) % 做 32 位累加器的溢出判断
w_r= w_r - 2^32;
end
rrom_addr=round(w_r/2^22);%读查找表的地址
if rrom_addr == 0
rrom_addr =1;
end
cw_sin(i)=s_rom(rrom_addr);
cw_cos(i)=c_rom(rrom_addr);%载波
end
%% ========================================================================================\
%%******************************* Modulation **********************************
%%========================================================================================/
m_t=zeros(1,m_len);
for i=1:length(m)
for j=1:fix(fs/fm)
m_t((i-1)*fix(fs/fm)+j)=m(i);%同一个 16k 的采样点复制 1000 次这样就是 16M 采样点
end
end
w_r=0;%相位累加器
rrom_addr=0;%
dac_i=zeros(1,m_len);
dac_q=zeros(1,m_len);
for i=1:m_len
w_r = w_r + kf*m_t(i);
if(w_r > 2^32) % 做 32 位累加器的溢出判断
w_r= w_r - 2^32;
elseif(w_r <0)
w_r = w_r + 2^32; % 负的溢出时
end
rrom_addr=round(w_r/2^22);%读查找表的地址
if rrom_addr == 0
rrom_addr =1;
end
dac_q(i)=AC*s_rom(rrom_addr);
dac_i(i)=AC*c_rom(rrom_addr);%载波
end
s_t=zeros(1,m_len);
for i=1:m_len
s_t(i) = dac_i(i)*cw_cos(i) + dac_q(i)*cw_sin(i)*(-1);
end
%% ========================================================================================\
%%******************************* Plot Modulation **********************************
%%========================================================================================/
figure(1)
subplot(2,1,1);
plot(m_t);
title('调制信号');
subplot(2,1,2);
plot(s_t);
title('调制之后的信号');
这里简要介绍一下上述代码。上述代码是尽可能按照FPGA的语言来写的,每块的描述如下:
该块代码用来描述FM的参数配置;
上述代码加载FM调制的语音信号与根据载波的采样率计算采样之后的语音信号需要计算的点数。
根据DDS的原因产生FM调制所需要的载波
该部分的代码是根据DDS的原理产生
最后该部分的代码是根据FM调制公式进行调制
上述运行结果虽然无法从直观上判断FM调制的正确性,但是接下来给出的解调代码可以证明调制解调两方面的正确性。
FM解调分为相干解调与非相干解调,具体的区别可以查阅通信原理这本书。本博客将主要介绍非相干解调,这也是最常用的解调方式。语音信号经过FM调制之后变为:
非相干解调的方式是将接收到的信号分别乘以与载波相同中心频率的正弦与余弦,得到I 路与 Q 路数据, 经过低通滤波处理后得到的数据表达式如下。如下框图
经过正交与低通滤波处理之后的信号为:
传统的 FM 解调方案, 是将正交分量 Q(t)与同相 I(t)比值进行反正切后差分运算,就能恢复出原始的基带信号
此时 m(n)序列就是最终解调结果, 而求 m(n)的过程被称为鉴频。此方法计算量大, 且计算反正切占用资源多,运算时间长。
为了减少计算量,需要对上述计算继续改进,改进方法将差分与反正切运算结合,过程如下:
这样计算中就没有了 arctan 运算, 对 m(t)离散化处理后可得:
根据上述方程,我们便可以编写相应的Matlab代码完成相应FM的解调逻辑。
根据上述正交解调过程,可以给出如下FM解调的Matlab代码:
%% ========================================================================================\
%%******************************* Demodulation **********************************
%%========================================================================================/
i_data=zeros(1,m_len);
q_data=zeros(1,m_len);
for i=1:m_len
i_data(i)=s_t(i)*cw_cos(i);
q_data(i)=(-1)*s_t(i)*cw_sin(i);
end
%I Q数据进行低通滤波把高频分量滤除
%taps filter
NUM = [-7.894e-05, -2.483e-04, -4.516e-04, -7.089e-04, -1.035e-03, -1.435e-03, -1.903e-03, -2.417e-03, -2.938e-03, -3.415e-03, -3.778e-03, -3.948e-03, -3.837e-03, -3.357e-03, -2.42e-03, -9.524e-04, 1.106e-03, 3.791e-03, 7.113e-03, 1.105e-02, 1.554e-02, 2.051e-02, 2.583e-02, 3.135e-02, 3.691e-02, 4.233e-02, 4.742e-02, 5.199e-02, 5.588e-02, 5.894e-02, 6.105e-02, 6.213e-02, 6.213e-02, 6.105e-02, 5.894e-02, 5.588e-02, 5.199e-02, 4.742e-02, 4.233e-02, 3.691e-02, 3.135e-02, 2.583e-02, 2.051e-02, 1.554e-02, 1.105e-02, 7.113e-03, 3.791e-03, 1.106e-03, -9.524e-04, -2.42e-03, -3.357e-03, -3.837e-03, -3.948e-03, -3.778e-03, -3.415e-03, -2.938e-03, -2.417e-03, -1.903e-03, -1.435e-03, -1.035e-03, -7.089e-04, -4.516e-04, -2.483e-04, -7.894e-05];
adc_i=conv(i_data,NUM);
adc_q=conv(q_data,NUM);
%FM 解调(1)
c_len=length(adc_i);
cr=zeros(1,c_len);
cj=zeros(1,c_len);
for i=2:c_len
cr(i) = adc_i(i)*adc_i(i) + adc_q(i)*adc_q(i);%I(n)^2+Q(n)^2
cj(i) = adc_i(i-1)*adc_q(i) - adc_i(i)*adc_q(i-1); %I(n-1)*Q(n) -I(n)*Q(n-1)
end
angle = zeros(1,c_len);
for i=1:c_len
if cr(i) == 0
angle(i) =0;
else
angle(i) =(cj(i)/cr(i))*2^16;
end
end
%以取均值的方式来完成降采样16M降低到16K
d_len=length(angle);
sum=0;
cnt =0;
demout=zeros(1,fix(d_len*(fm/fs)));
dcnt=1;
for i=1:d_len
sum=sum+angle(i);
if cnt == fs/fm %完成了1000次的累加进行平均并且存储到demout
demout(dcnt)=sum/cnt;
dcnt=dcnt+1;
cnt=0;
sum=0;
end
cnt=cnt+1;
end
figure(2);
plot(demout);
sound(demout)
正交解调,调制信号乘以载波:
相应的公式为:
低通滤波器的抽头系数与低通滤波器滤波处理:
求导后的解调代码:
与上述代码相对应的解调公式:
将解调出来的16MHz的频率降采样成16KHz的频率:
观察FM的调制与解调结果可以很容易发现,语音信号成功解调,证明上述FM调制解调Matlab代码的准确性。
接下来会定时的更新一些新的文章,一方面是为了总结知识,另一方面如果能对后来者一定的启发也是非常有意义的。
创作不易,认为文章有帮助的同学们可以关注、点赞、转发支持。(txt文件、图片文件在群中)对文章有什么看法或者需要更近一步交流的同学,可以加入下面的群: