FM调制解调---FPGA

本文介绍了一种基于AXI协议的DMA读模块设计,用于高效读取DDR内存中的FM调制数据,并详细阐述了FM调制及解调模块的具体实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        实验通过编写一个DMA读模块获取FM调制的数据源,DMA模块的实现是基于AXI协议。因为在数据的传输中,Xilinx提供的官方DMA IP核在传输完一次突发数据后需要在PS 端重新启动一次都或者写操作,如此的话,在进行大量数据的传输工作时,尤其是对DDR 不同地址区域同时进行读写操作时,IP 核不能有效工作。所以通过创建一个模块用于读取DDR,无需PS端参与即可完成读DDR操作。

 AXI_DMA_RD模块突发读时序:

        该模块读取的内容是先由PS端提前写入DDR某一地址区间的音频数据,按照AXI突发读时序进行数据读取,同时需要添加FIFO IP核来完成数据位宽转换和跨时钟域处理。为了防止数据丢失,当判断FIFO写入数据计数器为0,才启动一次突发读DDR。

 FIFO 读时序:

         FM调制模块:

        依据调频信号的数学表达式:

        

        实现图中公式高亮部分,需要用到两个ROM IP用于存放正余弦波形,正余弦波形可由matlab获得,以及一个乘法器IP完成调制中的乘法操作。

c = 0:1/1024:1023/1024; %ROM 中有 1024 点
c_rom = fix(1024*sin(2*pi*c));
%c_rom = fix(1024*cos(2*pi*c));

         重要参数:调频灵敏度Kf 和 最大频偏频率控制字 W

         

         FM解调模块:

        首先用两个寄存器将接收到的 I Q 两路数据进行缓存,再用两个寄存器进行延时一拍,就可以得到 i(i)i(i-1)q(i)q(i-1),再调用乘法器完成乘法运算,再将乘法器结果进行加减运算,再进行除法,即可得到解调结果。

        

module fm_demod(
		input	wire 			sclk,
		input	wire 			rst_n,
		input	wire 			adc_valid,
		input	wire 	[11:0]	adc_data_i,
		input	wire 	[11:0]	adc_data_q,
		input	wire 	[31:0]	end_count,

		output	reg 			dmsout,
		output	reg		[15:0]	dmout

	);

reg 	[11:0]	i_fm=0,q_fm=0,i_fm_r=0,q_fm_r=0;
reg 	isin,isin_r;
wire  [23:0] cr1,cr2,cj1,cj2;
reg 	[23:0] cr,cj;
reg 			flag1=0;
reg 	[23:0]	cjabs=0;
reg 	[23:0]	cj_r,cr_r;
reg 			flag2;
reg 	[23:0]	dividend=0;
reg 	[23:0]	divisor =1;
reg 			flag3;
wire 			div_valid;
wire 	[39:0]  div_dout;
reg 	[23:0]	cr_r3,cj_r3;
wire 	[23:0]	cr_fifo_data,cj_fifo_data;
reg 	[39:0]	angle=0;	
reg 			div_valid_r;	
wire 	[15:0]	pi4;
wire 	[55:0]	angle_r;
reg 	[23:0]	cj_r4;
reg 	[23:0]	cj_r5;
reg 	[55:0]	angle_dout;		
reg 			div_valid_rr;
reg 			angel_dsout;
reg 	[11:0]		cnt_mean;
reg 	[49:0]	dm_sum;

reg		[11:0]	i_data,q_data;
wire	[11:0]	i_data_16,q_data_16;
reg				adc_valid_r;


// LPF
assign i_data_16 = {i_data[11],i_data[11],i_data[11],i_data[11],i_data[11:4]};
assign q_data_16 = {q_data[11],q_data[11],q_data[11],q_data[11],q_data[11:4]};
always @(posedge sclk) begin
	if (rst_n == 1'b0) begin		// reset
		i_data	<=	0;
		q_data	<=	0;
	 end
	else if (adc_valid == 1'b1) begin
		i_data	<=	{adc_data_i[11],adc_data_i[11],adc_data_i[11],adc_data_i[11],adc_data_i[11:4]} + i_data - i_data_16;
		q_data	<=	{adc_data_q[11],adc_data_q[11],adc_data_q[11],adc_data_q[11],adc_data_q[11:4]} + q_data - q_data_16;
	 end
end

always @(posedge sclk) begin
	adc_valid_r <= adc_valid;
end


always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		{i_fm_r,i_fm} <= 'd0;
		{q_fm_r,q_fm} <= 'd0;
	end
	else if(adc_valid_r == 1'b1) begin
		{i_fm_r,i_fm} <= {i_fm,i_data};
		{q_fm_r,q_fm} <= {q_fm,q_data};
	end
end

always @(posedge sclk) begin
	{isin_r,isin} <= {isin,adc_valid_r};
end

mult_crcj mult_cr1 (
  .CLK(sclk),  // input wire CLK
  .A(i_fm),      // input wire [11 : 0] A
  .B(i_fm_r),      // input wire [11 : 0] B
  .P(cr1)      // output wire [23 : 0] P
);

mult_crcj mult_cr2 (
  .CLK(sclk),  // input wire CLK
  .A(q_fm),      // input wire [11 : 0] A
  .B(q_fm_r),      // input wire [11 : 0] B
  .P(cr2)      // output wire [23 : 0] P
);


mult_crcj mult_cj1 (
  .CLK(sclk),  // input wire CLK
  .A(q_fm),      // input wire [11 : 0] A
  .B(i_fm_r),      // input wire [11 : 0] B
  .P(cj1)      // output wire [23 : 0] P
);

mult_crcj mult_cj2 (
  .CLK(sclk),  // input wire CLK
  .A(i_fm),      // input wire [11 : 0] A
  .B(q_fm_r),      // input wire [11 : 0] B
  .P(cj2)      // output wire [23 : 0] P
);

always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		cr <= 'd0;
		cj <= 'd0;
	end
	else begin
		cr <= cr1+ cr2;
		cj <= cj1-cj2;
	end
end

always @(posedge sclk) begin
	flag1 <= isin_r;
end

always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		cjabs <= 'd0;
	end
	else if (flag1 == 1'b1 && cj[23] == 1'b1 ) begin
		cjabs <= (~cj) +1'b1;
	end
	else if (flag1 == 1'b1 && cj[23] == 1'b0 ) begin
		cjabs <= cj;
	end
end

always @(posedge sclk) begin
	{cj_r,cr_r} <= {cj,cr};
end

always @(posedge sclk) begin
	flag2 <= flag1;
end


always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		dividend <='d0;
		divisor <= 'd1;
	end
	else if (flag2 == 1'b1 && cr_r[23] == 1'b0) begin
		dividend <= cr_r - cjabs;
		divisor <= cr_r + cjabs;
	end
	else if (flag2 == 1'b1 && cr_r[23] == 1'b1) begin
		dividend <= cr_r + cjabs;
		divisor <= cjabs - cr_r;
	end
end

always @(posedge sclk) begin
	flag3 <= flag2;
end

div_gen_0 div_gen_0_inst (
  .aclk(sclk),                                      // input wire aclk
  .s_axis_divisor_tvalid(flag3),    // input wire s_axis_divisor_tvalid
  .s_axis_divisor_tdata(divisor),      // input wire [23 : 0] s_axis_divisor_tdata
  .s_axis_dividend_tvalid(flag3),  // input wire s_axis_dividend_tvalid
  .s_axis_dividend_tdata(dividend),    // input wire [23 : 0] s_axis_dividend_tdata
  .m_axis_dout_tvalid(div_valid),          // output wire m_axis_dout_tvalid
  .m_axis_dout_tdata(div_dout)            // output wire [39 : 0] m_axis_dout_tdata
);

always @(posedge sclk) begin
	cr_r3 <= cr_r;
	cj_r3 <= cj_r;
end


fifo_w24x64_r24x64 cr_buffer (
  .clk(sclk),      // input wire clk
  .din(cr_r3),      // input wire [23 : 0] din
  .wr_en(flag3),  // input wire wr_en
  .rd_en(div_valid),  // input wire rd_en
  .dout(cr_fifo_data),    // output wire [23 : 0] dout
  .full(),    // output wire full
  .empty()  // output wire empty
);

fifo_w24x64_r24x64 cj_buffer (
  .clk(sclk),      // input wire clk
  .din(cj_r3),      // input wire [23 : 0] din
  .wr_en(flag3),  // input wire wr_en
  .rd_en(div_valid),  // input wire rd_en
  .dout(cj_fifo_data),    // output wire [23 : 0] dout
  .full(),    // output wire full
  .empty()  // output wire empty
);

always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		angle <='d0;
	end
	else if(div_valid == 1'b1 && cr_fifo_data == 'd0 && cj_fifo_data == 'd0) begin
		angle <='d0;
	end
	else if (div_valid == 1'b1 && cr_fifo_data[23] == 1'b0) begin
		angle <= 40'd65536 - div_dout;
	end
	else if(div_valid == 1'b1 && cr_fifo_data[23] == 1'b1) begin
		angle <= 40'd196608 - div_dout;
	end
end


always @(posedge sclk) begin
	div_valid_r <= div_valid;
end

assign pi4 =16'd51472;//PI/4  (3.1415926/4)*65536
mult_a40xb16 mult_a40xb16_inst (
  .CLK(sclk),  // input wire CLK
  .A(angle),      // input wire [39 : 0] A
  .B(pi4),      // input wire [15 : 0] B
  .P(angle_r)      // output wire [55 : 0] P
);

always @(posedge sclk) begin
	cj_r4 <= cj_fifo_data;
end

always @(posedge sclk) begin
	cj_r5 <= cj_r4;
end

always @(posedge sclk) begin
	div_valid_rr <= div_valid_r;
end

always @(posedge sclk) begin
	if(rst_n==  1'b0) begin
		angle_dout <='d0;
	end
	else if (div_valid_rr == 1'b1 &&  cj_r5[23] == 1'b0) begin
		angle_dout <= angle_r;
	end
	else if (div_valid_rr == 1'b1 &&  cj_r5[23] == 1'b1) begin
		angle_dout <= (~angle_r) + 1'b1;
	end
end

always @(posedge sclk) begin
	angel_dsout <= div_valid_rr;
end

always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		cnt_mean <='d0;
	end
	else if (angel_dsout == 1'b1 && cnt_mean == end_count[12:1]-1) begin
		cnt_mean <= 'd0;
	end
	else if (angel_dsout == 1'b1) begin
		cnt_mean <= cnt_mean + 1'b1;
	end
end

always @(posedge sclk)begin
	if(rst_n == 1'b0) begin
		dm_sum <= 'd0;
	end
	else if (angel_dsout == 1'b1 && cnt_mean == end_count[12:1]-1) begin
		dm_sum <='d0;
	end
	else if (angel_dsout == 1'b1 ) begin
		dm_sum <= dm_sum + {{10{angle_dout[55]}},angle_dout[55:16]};
	end
end

always @(posedge sclk) begin
	if(rst_n == 1'b0) begin
		dmout <='d0;
		dmsout <= 'd0;
	end
	else if (angel_dsout == 1'b1 && cnt_mean == end_count[12:1]-1) begin
		dmout <= {dm_sum[49],dm_sum[24:10]};
		dmsout <= 1'b1;
	end
	else begin
		dmsout <= 1'b0;
		dmout <= dmout; 
	end
end

endmodule 

<think>好的,用户想了解如何用FPGA实现FM调制解调的方法或教程。首先,我需要回顾一下FM的基本原理,然后结合FPGA的实现步骤。根据提供的引用内容,用户提到了使用DDS(直接数字频率合成)和NCO(数控振荡器)来生成调制信号,这可能涉及到频率控制字的调整。 接下来,我需要整理调制解调的步骤。调制部分,根据引用[2]和[3],FM调制是通过将调制信号的幅度作为频率控制字输入到DDS中,从而改变载波频率。这里需要注意调制系数的计算,确保最大频偏符合要求。解调部分,引用[1]和[4]提到使用FPGA进行解调,可能涉及数字鉴频器或相位差检测,比如使用两个ROM存储正余弦波形,再通过乘法器进行解调运算。 然后,需要考虑具体的实现细节,比如NCO核的配置、调制系数的确定、MATLAB验证的重要性。用户提供的引用中提到了一些工具,如Vivado和MATLAB,需要强调这些工具在开发和验证中的作用。 此外,用户可能对如何优化调制波形、解调过程中的FFT分析以及性能问题感兴趣。需要确保回答覆盖这些方面,并引用相关的资料。同时,生成的问题部分要围绕实现中的关键点,比如参数计算、解调方法选择、优化策略和验证方法。 最后,检查是否符合系统级指令:使用正确的中文,LaTeX格式的数学表达式,引用标识的正确添加,以及相关问题的生成。确保回答结构清晰,步骤分明,引用来源准确。</think>### FPGA实现FM调制解调方法详解 #### **1. FM调制实现原理** FM调制的核心是通过调制信号控制载波频率的变化。在FPGA中,通常采用**DDS(直接数字频率合成)**结合**NCO(数控振荡器)**实现: - **数学表达式**:设载波频率为$f_c$,调制信号为$m(t)$,则调频信号为: $$s(t) = A \cos\left(2\pi f_c t + 2\pi k_f \int_0^t m(\tau)d\tau\right)$$ 其中$k_f$为调制灵敏度,决定频偏大小[^2]。 - **FPGA实现步骤**: 1. 使用NCO生成基带调制信号和载波信号。 2. 将调制信号的幅度转换为**频率控制字**,输入载波NCO的频率控制端口。 3. 通过调整调制信号的幅度与调制系数,控制输出信号的**最大频偏**(如±75kHz)[^3]。 #### **2. FM解调实现方法** 解调目标是还原原始调制信号,常用**数字鉴频器法**: - **数学原理**: 1. 对接收信号$s(t)$进行正交分解,得到同相分量$I(t)$和正交分量$Q(t)$。 2. 计算相位差: $$\Delta \phi = \arctan\left(\frac{Q(t)}{I(t)}\right)$$ 3. 通过微分提取频率变化: $$m(t) \propto \frac{d}{dt}\Delta \phi$$[^4] - **FPGA实现步骤**: 1. 使用两个ROM分别存储正弦和余弦波形(通过MATLAB生成)。 2. 通过乘法器IP实现正交混频。 3. 采用CORDIC算法计算相位差并微分。 #### **3. 关键实现细节** - **调制系数计算**:根据目标频偏$\Delta f_{max}$和DDS相位累加器位宽$N$,调制系数为: $$k = \frac{\Delta f_{max} \cdot 2^N}{f_s}$$ 其中$f_s$为系统时钟频率[^2]。 - **NCO配置**:需调整输出数据位宽以避免量化噪声,例如引用[3]中提到增加第一个NCO的输出位数以改善调频波形。 #### **4. 开发验证流程** 1. **MATLAB仿真**:预先验证调制/解调算法,生成ROM波形数据。 2. **Vivado工程搭建**: - 调用NCO IP核生成载波与调制信号 - 使用乘法器IP实现混频 - 配置CORDIC核进行相位解调 3. **联合调试**:通过ILA抓取信号,与MATLAB仿真结果对比[^1]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值