通信题的纯数字方案中FM调制的FPGA实现显得尤为重要,之前一段时间一直在跟着我查大哥整理收发机的一些原理,然后就是时不时问辉说怎么实现FM调制(初期嘛,一直搞不清楚AM、FM哈哈😏😏),现有终于对收发机的原理以及常见结构有点小悟,下面主要就是对FM调制的实现做一个工程开发记录,具体理论这边就不赘述
生成 BaseBand signal - DDS ~ ROM
这边调用ROM IP核进行基带信号模拟生成,主要是起到一个存储、读取的介质功能,DDS的生成也是有用到ROM,关于ROM可以看看下面这个文章
https://blog.csdn.net/yifantan/article/details/126748312
相对于之前用的直接产生signal.txt
,我觉得会使得生成的基带信号独立于FM调制模块之外,后期做整体发射机时比较容易整合过去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 %ROM设定 width=12; %设置rom的位宽 depth=1024; %设置rom的深度 x=linspace(0,2*pi,depth); %在一个周期内产生depth个采样点 y=sin(x); %生成正弦函数 %y_cos=round(y_cos*(2^(width-1)-1))+2^(width-1)-1; %将数据转化成整数,生成无符号数 y=round(y*(2^(width-1)-1)); %将数据转化成整数,生成有符号数 fid = fopen('D:\ele_match\FM_modulation\cos.coe','wt'); fprintf(fid,'memory_initialization_radix = 10;\nmemory_initialization_vector = '); for i = 1 : depth if mod(i-1,8) == 0 fprintf(fid,'\n'); end fprintf(fid,'%6d,',y(i)); end fclose(fid); %关闭文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Kevin // // Create Date: 2023/03/18 20:23:48 // Design Name: // Module Name: DDS_Mod ////////////////////////////////////////////////////////////////////////////////// module DDS_Mod( input clk, input rst_n, output signed [11:0] sin //调制信号 ); //--------------------------------------------------------// parameter Freq = 32'd214748; //5kHz 注意调整频率时需修改 parameter cnt_width = 8'd32; //--------------------------------------------------------// //--------------------------------------------------------// reg [cnt_width-1:0]cnt_I = 0; wire [9:0] addr_I; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_I <= 0; end else begin cnt_I <= cnt_I + Freq; end end assign addr_I = cnt_I[cnt_width-1:cnt_width-10]; //--------------------------------------------------------// //--------------------调用一个单口ROM核--------------------// blk_mem_gen_0 Sin_inst( .clka (clk), .addra (addr_I), .douta (sin) ); endmodule
如果你的IP核名称有修改,这边调用时记得修改成相应的名称
vivado ROM IP核配置
FM调制 - 细节及开发配置
原理阐述
FM调制模块本质上也是一个DDS,不过区别就在于其输出频率是关于基带信号幅度的函数,为了让其输出频率不断变化。我们可以回到DDS的代码中的Freq
,这个变量就决定了DDS的输出频率,所以实际上我们只需要在载波频率后面加上一个不断变化的值,也就实现了FM调制
目前假设载波频率为5M,频偏(可设置) 为 [-75k 75k]
基带信号大小
对应频偏
对应频偏的频率控制字
0
0
0
2^11(max)
75k
2^32 * 75k / 100M = 3221225
a
b
c
因为这时候假设ADC采进来的位宽是12位,所以基带信号幅度最大为2^11
$$ c=a*3221225/2^{11} $$
总结
通过上面的叙述,实际上FM调制的数字方案 基本原理已经清楚,主要就是根据基带信号的幅度大小来控制相应的频偏,本质上就是输出频率可变的DDS
不过这边计算这加上的频偏控制字c
,就需要用到乘法操作,所以需要进行乘法器IP核的调用;除法操作可以调用除法器IP核来实现,也可以通过移位的方法,考虑到这边是2的整数次幂,因此直接将乘法器的输出结果右移11位
乘法器IP核配置
vivado 代码 FM调制模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Kevin // // Create Date: 2023/03/18 20:26:46 // Design Name: // Module Name: FM_Mod ////////////////////////////////////////////////////////////////////////////////// module FM_Mod( input clk, input rst_n, input [11:0] adc_data, output [11:0] FM_Mod ); parameter Freq_I = 32'd214_748_365; //载波信号的频率5M,时钟100M parameter Freq_Word = 32'd3_221_225; //频偏为75k parameter cnt_width = 8'd32; //-------------计算频偏控制字--------------// wire signed [43:0] mult_data; wire signed [31:0] Freq_Offset; mult_gen_0 MULT_inst( .CLK (clk), .A (adc_data), .B (Freq_Word), .P (mult_data) ); assign Freq_Offset = mult_data[43:12]; //移位 ~ 完成除法操作 //---------------------------------------// reg [cnt_width-1:0]cnt_I; wire [9:0] addr_I; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_I <= 0; end else begin cnt_I <= cnt_I + Freq_I + Freq_Offset;//对比DDS,这边加上了频偏控制字 end end assign addr_I = cnt_I[cnt_width-1:cnt_width-10]; //----------------ROM核-----------------// blk_mem_gen_0 Sin_inst( .clka (clk), .addra (addr_I), .douta (FM_Mod) ); endmodule
顶层文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Kevin ////////////////////////////////////////////////////////////////////////////////// module TOP( input clk, input rst_n, output [11:0] FM_Mod_data ); //----------------ADC-----------------// wire [11:0] adc_data; DDS_Mod DDS_Mod_inst( .clk (clk), .rst_n (rst_n), .sin (adc_data) ); //------------------------------------// //---------------FM调制----------------// FM_Mod FM_Mod_inst( .clk (clk), .rst_n (rst_n), .adc_data (adc_data), .FM_Mod (FM_Mod_data) ); endmodule
TestBench
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 `timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Kevin // // Create Date: 2023/03/18 20:31:06 // Design Name: // Module Name: tb_TOP ////////////////////////////////////////////////////////////////////////////////// module tb_TOP(); reg sclk; reg rst_n; wire [11:0] FM_Mod_data; //---------系统时钟----------// initial sclk = 1; always #5 sclk = !sclk;//100M ~ 1/(2*5*10^-9) //---------复位---------// initial begin rst_n = 0; #100 rst_n = 1; end //-----------------------// TOP TOP_inst( .clk (sclk), .rst_n (rst_n), .FM_Mod_data (FM_Mod_data) ); //-----------------------// endmodule
Simulation
由于我最后FM_Mod模块的输出只有引出FM_Mod
,所以为了更好的看出FM调制效果(成功了没!?)需要自主添加下adc_data
首先点击TOP_inst
,将adc_data
add - waveform
然后你就会发现没有什么软用,所以此时我就开始探索,把所有跟信号显示有关的选项都开了,最后才发现原来只要重跑一遍仿真就行😓😓😓
总结
在后续的项目联调时,可以直接将ADC采集的数据接入到这边的FM_Mod的adc_data
中,同时需要根据相应的位宽等参数,对乘法器IP核以及ROM的参数进行修改,最后的实际效果还是需要将调制信号从DAC输出康康😁
希望后续联调顺利😏