news 2026/9/24 21:19:09

基于FPGA的AM信号调制度测量系统设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于FPGA的AM信号调制度测量系统设计与实现

这篇稿子拖了挺久。前阵子做了一个基于FPGA的调制度测量系统,从方案设计到仿真、上板调试,前后折腾了一个多月,中间踩了不少坑。趁着记忆还热乎,把整个过程整理成手记,工程代码也做了详细注释,希望能给做信号检测、通信测试类开发的朋友一些参考。

做这个项目的起因不算复杂。当时需要测量一台射频信号源输出的AM信号实际调制度是否准确,传统做法是用示波器看包络,再用公式手算,效率低不说,精度也很难保证。手头正好有一块搭载ADC和FPGA的开发板,干脆就用FPGA搭一个实时调制度测量系统,把中频信号采进来,在数字域完成解调、包络提取和调制度计算,最后通过串口把结果显示到上位机。实际做下来,这套方案的实时性、精度和可扩展性都比预想中好,整个设计思路和关键代码今天完整写出来。

1. 项目背景与整体设计思路

1.1 调制度测量到底是什么

调制度是衡量幅度调制信号调制深度的核心参数,直接反映调制信号对载波幅度的控制程度。对于标准的AM信号,波形表达式可以写成:

s(t) = A0 [1 + m·cos(Ωt)] · cos(ωt)

其中A0是未调制时的载波幅度,m就是调制度,取值范围一般在0到1之间。在时域波形上,调制度决定了包络的起伏程度。包络最大值Amax = A0(1+m),包络最小值Amin = A0(1-m),所以:

m = (Amax - Amin) / (Amax + Amin)

举个例子,m = 0.3时,包络最大值和最小值之比为(1+0.3)/(1-0.3) ≈ 1.857。把这两个极值测出来,调制度就能算出来。这是时域包络法的思路,也是这个项目采用的核心方法。

测量场景很明确:产线测试射频模块的输出信号质量,或者计量校准信号源的调制度精度。这类需求要求仪器响应快、读数稳定、误差可控。传统方案用模拟包络检波器加ADC采样,电路复杂,温漂明显;用示波器手动测量又太依赖人工操作。FPGA方案可以做到全数字处理链路,从采样到输出结果只经过几百纳秒的流水线延迟,而且逻辑可以随时调整算法,适应不同频段和带宽需求。

1.2 FPGA方案的优势与选型考量

设计之前我对比过几类实现载体:MCU、DSP、FPGA。MCU软件处理方便,但ADC采样率稍高就应付不过来;DSP做浮点运算能力强,但外设和并行处理能力弱一些;FPGA的优势在于数据流的流水线处理,ADC送进来的数据可以按时钟节拍逐级往下传,混频、滤波、幅度提取全部硬件化并行执行,实时性是最强的。

对于调制度测量系统,输入信号频率往往在几十MHz量级,后续调制信号带宽可能从几百Hz到几十kHz。这个频率范围对MCU来说已经很吃力,但对FPGA来说非常轻松。而且FPGA内部可以同时处理I/Q两路数据,天然适合做正交解调。综合考虑后我选择了Xilinx Artix-7系列,资源不算多但完全够用,DSP Slice数量足够支撑乘法器,逻辑单元跑CORDIC迭代算法也绰绰有余。

系统指标的设定也要提前想清楚。这次设计的目标是:

  • 输入中频:10.7MHz,幅度范围0.1V到1V
  • ADC采样率:50MSPS,16bit分辨率
  • 调制度测量范围:0到100%
  • 测量分辨率:0.1%
  • 测量误差:±1%以内
  • 调制频率范围:300Hz到10kHz

这些指标决定了后续模块的参数选择。定指标时宁可留一点余量,后面调试会省很多事。

1.3 系统整体架构

整个系统由模拟前端、ADC、FPGA、串口四大部分组成。模拟前端负责把中频信号放大或衰减到ADC输入范围,ADC以50MSPS采样率对信号数字化,FPGA完成正交下变频、抽取滤波、幅度提取、峰值检测和调制度计算,最后通过UART把结果发送到PC端串口助手显示。

FPGA内部逻辑按数据流向分成了几个模块:

  • NCO本振生成模块:产生正交的sin/cos本振信号
  • 混频器模块:将ADC采样数据与NCO信号相乘,把信号从10.7MHz中频搬到基带
  • CIC抽取滤波器:大幅降低数据率,同时滤除高频分量
  • FIR补偿滤波器:补偿CIC的通带衰减,整形频谱
  • CORDIC幅度计算模块:从I/Q两路信号中提取包络幅度
  • 包络平滑与峰值检测模块:对包络进行平滑,提取峰谷值
  • 调制度计算模块:根据峰谷值计算调制度并编码输出
  • UART收发模块:接收上位机参数配置指令,上报测量结果

这个架构是经典的数字中频接收机结构,结构清晰、模块间耦合低,每个模块都可以单独仿真验证。实际开发时我就是按这个顺序一个个模块写下来,写完一个仿一个,最后再做顶层联调,效率比整体写完再调试高得多。

2. 核心算法原理与关键参数设计

2.1 数字下变频:为什么必须用I/Q两路

直接对10.7MHz中频信号做包络检测在硬件上可行,但在数字域这样处理会有很多麻烦。最直观的方法是把ADC数据取绝对值再低通滤波,但这样做的问题是:如果采样率不是载波频率的整数倍,波形采样点会不均匀地分布在相位上,取绝对值后会有强烈的纹波,低通滤波器很难滤干净。

更稳健的做法是正交下变频。将输入信号分别与cos(ωt)和sin(ωt)相乘,得到I路和Q路信号。对于AM信号:

I = A0[1 + m·cos(Ωt)] · cos(ωt) · cos(ωt) = A0[1 + m·cos(Ωt)] · (1 + cos(2ωt))/2 Q = A0[1 + m·cos(Ωt)] · cos(ωt) · sin(ωt) = A0[1 + m·cos(Ωt)] · sin(2ωt)/2

低通滤波后,高频分量2ω被滤除,剩下:

I_lpf = A0[1 + m·cos(Ωt)] / 2 Q_lpf = 0

实际上由于NCO频率和输入信号频率存在偏差,且相位未知,Q路不会是零,但I/Q两路的平方和就代表信号幅度:

A(t) = sqrt(I² + Q²) = A0[1 + m·cos(Ωt)] / 2

也就是说,无论本振和输入信号之间存在多大的频偏和相差,只要频率差落在低通滤波器带宽内,I/Q正交解调都能把包络幅度完整提取出来。这就是I/Q解调最大的优势:对载波相位不敏感,只解调幅度信息,天然适合AM信号的包络恢复。

2.2 NCO本振的参数计算

NCO(数控振荡器)输出频率由相位累加器和查找表实现。核心公式是:

f_out = f_clk × phase_inc / 2^N

其中N是相位累加器位宽。我的系统里f_clk = 50MHz,需要输出的本振频率是10.7MHz,取N = 32位:

phase_inc = f_out × 2^N / f_clk = 10.7e6 × 4294967296 / 50e6 ≈ 919,276,000

这个数换算成十六进制就是0x36C9E600。实际使用中我把它做成参数,方便日后调整频率。查找表我存了1024点的正弦波,相位累加器的高10位作为查表地址。Q路的余弦信号不用单独存一张表,只需要查表地址偏移256个点(1024的1/4)即可,这样可以节省一半的存储资源。

2.3 CIC抽取滤波器的设计重点

下变频后的信号数据率还是50MSPS,但调制信号带宽只有10kHz左右,数据率远超需求。直接在这个速率上跑后续的FIR滤波器和CORDIC计算,资源浪费太大。所以必须做抽取,把数据率降下来。

抽取滤波器我选择了CIC(级联积分梳状)滤波器。CIC为什么好?因为它不需要乘法器,只做加法和延迟,在FPGA里实现成本极低,特别适合第一级大比率抽取。它的传递函数是:

H(z) = (1 - z^(-M))^N / (1 - z^(-1))^N

其中M是微分延迟,N是级数。我的参数选择是:抽取倍数R = 100,级数N = 3,微分延迟M = 1。这样50MSPS的数据率经过CIC后变成500kHz,已经很接近最终需要的速率。

必须注意的是CIC滤波器有增益,积分器内部位宽必须足够大,否则会溢出导致数据损坏。位宽扩展量的经验公式是:

G = N × log2(R × M) = 3 × log2(100) ≈ 3 × 6.64 ≈ 20bit

ADC输出是16bit,那么CIC内部累加器位宽至少是16 + 20 = 36bit。我实际取了40bit,留了裕量,输出端再截取到24bit,后续处理动态范围足够了。

2.4 FIR补偿滤波器设计

CIC的频率响应有明显的通带下垂,事实上它的幅频特性近似为sinc函数,在通带边缘会有明显衰减。调制信号分量如果落在通带边缘,幅度会被压低,算出来的调制度会偏小。补偿办法是级联一个FIR滤波器,频率响应设计成CIC的反函数,把通带拉平。

FIR滤波器的系数我用MATLAB的fdatool生成的,采样率设为500kHz,通带截止频率10kHz,通带纹波0.1dB,阻带衰减60dB。最终生成了31阶、32个系数的FIR滤波器。系数做了16bit量化后导出到FPGA的coe文件里。这个滤波器在FPGA里用传统的乘累加结构实现,消耗约32个DSP Slice,Artix-7用起来毫无压力。

2.5 CORDIC算法提取包络幅度的原理

包络幅度A = sqrt(I² + Q²),最直接的实现是用乘法器算平方和再开方。FPGA里的除法器和开方器都消耗大量资源,延迟也不可控。我改用CORDIC算法,把它配置成向量模式,通过一系列固定的旋转迭代,把向量旋转到x轴上,旋转后的x坐标就是向量的模长。

CORDIC的迭代公式是:

x(i+1) = x(i) - d(i)·y(i)·2^(-i) y(i+1) = y(i) + d(i)·x(i)·2^(-i) z(i+1) = z(i) - d(i)·arctan(2^(-i))

其中d(i)根据y(i)的符号决定,迭代N次后向量被旋转到x轴方向,x(N)就是幅度值。这个算法的好处是只需要加法和移位运算,硬件实现极其干净。我设置了16次迭代,输入16bit定点数,每次迭代的2^(-i)项通过右移实现。要注意CORDIC迭代会引入约1.6468倍的增益,最终结果需要乘以0.6073校正因子,这个系数我用移位加乘法近似实现。

3. 工程代码实现与关键模块解析

3.1 工程整体结构

工程在Vivado 2019.2下开发,顶层模块名为modulation_meter_top。整个工程代码量在2000行左右,主要模块划分如下:

  • modulation_meter_top.v(顶层例化所有子模块)
  • nco_sin_cos.v(NCO本振生成32位相位累加器+1024点查找表)
  • ddc_mixer.v(I/Q混频器)
  • cic_decimator.v(CIC抽取滤波器,参数化设计)
  • fir_compensator.v(FIR补偿滤波器,系数从coe文件加载)
  • cordic_magnitude.v(CORDIC幅度计算模块)
  • envelope_process.v(包络平滑+峰值检测状态机)
  • modulation_index_calc.v(调制度计算与结果编码)
  • uart_rx.v(UART接收模块,用于接收上位机配置)
  • uart_tx.v(UART发送模块,用于上报测量结果)

我把参数都定义成parameter,方便调整。比如CIC的抽取倍数、FIR的抽头数、CORDIC迭代次数等,改参数就能适配不同场景,不需要改逻辑本身。

3.2 NCO模块代码解析

NCO模块的相位累加器是核心,代码如下:

module nco_sin_cos #( parameter PHASE_WIDTH = 32, parameter LUT_ADDR_WIDTH = 10, parameter LUT_DATA_WIDTH = 16 )( input wire clk, input wire rst_n, input wire [PHASE_WIDTH-1:0] phase_inc, output reg signed [LUT_DATA_WIDTH-1:0] sin_out, output reg signed [LUT_DATA_WIDTH-1:0] cos_out ); reg [PHASE_WIDTH-1:0] phase_acc; always @(posedge clk or negedge rst_n) begin if (!rst_n) phase_acc <= 32'd0; else phase_acc <= phase_acc + phase_inc; end wire [LUT_ADDR_WIDTH-1:0] sin_addr = phase_acc[PHASE_WIDTH-1:PHASE_WIDTH-LUT_ADDR_WIDTH]; wire [LUT_ADDR_WIDTH-1:0] cos_addr = sin_addr + 10'd256; // 例化正弦ROM查找表 // 实际工程中用Block Memory Generator IP,深度1024,位宽16bit // cos地址通过加1024/4获得90度相位偏移 endmodule

相位累加器每个时钟累加一次phase_inc,累加器的溢出频率就是NCO的输出频率。高10位用来查表,查找表中存放的是16bit的有符号正弦值,范围是-32768到32767。余弦通过地址偏移1024/4=256个点得到,这样做的好处是只需一块ROM,省了一半存储资源。

3.3 混频模块代码解析

混频模块做的事很简单:两路乘法。但要注意数据位宽匹配。ADC数据是16bit有符号数,NCO输出也是16bit有符号数,两者相乘得到32bit结果。这个结果直接送CIC要扩展位宽,为了节省资源我先截取高24位作为CIC的输入,因为CIC内部本身有40bit的动态范围,输入端的精度损失不会影响最终测量精度。

module ddc_mixer #( parameter DATA_WIDTH = 16, parameter MIX_OUT_WIDTH = 24 )( input wire clk, input wire signed [DATA_WIDTH-1:0] adc_data, input wire signed [DATA_WIDTH-1:0] nco_sin, input wire signed [DATA_WIDTH-1:0] nco_cos, output reg signed [MIX_OUT_WIDTH-1:0] i_out, output reg signed [MIX_OUT_WIDTH-1:0] q_out ); always @(posedge clk) begin i_out <= $signed(adc_data) * $signed(nco_cos) >>> (2*DATA_WIDTH - MIX_OUT_WIDTH); q_out <= $signed(adc_data) * $signed(nco_sin) >>> (2*DATA_WIDTH - MIX_OUT_WIDTH); end endmodule

这里用了算术右移操作符>>>,确保负数截取时保持符号位正确。这是初学者最容易犯的错:直接右移会丢符号位,导致负值变成错误的正值,整个链路就全乱了。

3.4 CIC抽取滤波器实现

CIC滤波器的Verilog实现采用积分器-抽取-梳状器的经典结构。三段积分器在抽取前,三段梳状器在抽取后。积分器的反馈系数是1,本质就是累加器,需要防止溢出;梳状器做差分运算,输出转回补码。

module cic_decimator #( parameter INPUT_WIDTH = 24, parameter ACC_WIDTH = 40, parameter OUTPUT_WIDTH = 24, parameter N = 3, parameter M = 1, parameter R = 100 )( input wire clk, input wire rst_n, input wire signed [INPUT_WIDTH-1:0] din, input wire din_valid, output reg signed [OUTPUT_WIDTH-1:0] dout, output reg dout_valid ); // 积分器级联 reg signed [ACC_WIDTH-1:0] integ_reg [0:N-1]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin for (int k = 0; k < N; k++) integ_reg[k] <= 'd0; end else if (din_valid) begin integ_reg[0] <= integ_reg[0] + din; for (int k = 1; k < N; k++) integ_reg[k] <= integ_reg[k] + integ_reg[k-1]; end end // 抽取计数器 reg [$clog2(R)-1:0] dec_cnt; wire dec_pulse = (dec_cnt == R-1); always @(posedge clk or negedge rst_n) begin if (!rst_n) dec_cnt <= 'd0; else if (din_valid) dec_cnt <= dec_cnt + 1'b1; end // 梳状器级联 reg signed [ACC_WIDTH-1:0] comb_reg [0:N-1][0:M]; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin // 初始化清零 end else if (dec_pulse) begin comb_reg[0][0] <= integ_reg[N-1]; comb_reg[0][1] <= comb_reg[0][0]; // 依此类推完成差分和级联 end end // 输出截取 // 按gain normalization公式截取ACC_WIDTH到OUTPUT_WIDTH endmodule

CIC的输出需要做归一化截取。由于CIC的直流增益是(R×M)^N,输出数据大概率已经超过24bit范围,我的做法是找到合适的移位量,对40bit累加结果右移后截取。具体移位量由仿真确定:输入满量程正弦波,观察CIC输出最大值,再调整移位量让输出不超过24bit的90%。这个方法虽然土但很实用,比纯理论计算来得直观。

3.5 峰值检测状态机与调制度计算

峰值检测是整个设计中比较微妙的部分。CORDIC输出的包络信号是带纹波的,不能直接用来找最大值最小值,否则会把每个载波周期的纹波峰值都当作包络峰值。处理方法是先对包络做一次IIR低通平滑:

y[n] = alpha × y[n-1] + (1-alpha) × x[n]

alpha取0.95到0.98之间,相当于将包络的高频纹波滤掉,只保留调制信号的起伏。

平滑后的包络进入峰谷检测状态机。状态机有三个状态:探测上升、跟踪峰值、跟踪谷值。

module envelope_process #( parameter DATA_WIDTH = 24, parameter ALPHA_Q = 14 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] env_in, input wire env_valid, output reg signed [DATA_WIDTH-1:0] peak_max, output reg signed [DATA_WIDTH-1:0] peak_min, output reg measure_valid ); // 包络平滑IIR reg signed [DATA_WIDTH-1:0] env_smooth; always @(posedge clk or negedge rst_n) begin if (!rst_n) env_smooth <= 'd0; else env_smooth <= env_smooth - (env_smooth >>> ALPHA_Q) + env_in; end // 峰值/谷值跟踪 reg [1:0] state; reg signed [DATA_WIDTH-1:0] current_peak, current_valley; localparam IDLE = 2'd0; localparam TRACK_MAX = 2'd1; localparam TRACK_MIN = 2'd2; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; current_peak <= 'd0; current_valley <= 'd0; end else if (env_valid) begin case (state) IDLE: begin current_peak <= env_smooth; current_valley <= env_smooth; state <= TRACK_MAX; end TRACK_MAX: begin if (env_smooth > current_peak) begin current_peak <= env_smooth; end else if (env_smooth < current_peak - PEAK_HYSTERESIS) begin // 包络从峰值开始回落,切换跟踪谷值 peak_max <= current_peak; state <= TRACK_MIN; current_valley <= env_smooth; end end TRACK_MIN: begin if (env_smooth < current_valley) begin current_valley <= env_smooth; end else if (env_smooth > current_valley + PEAK_HYSTERESIS) begin peak_min <= current_valley; measure_valid <= 1'b1; state <= TRACK_MAX; current_peak <= env_smooth; end end endcase end end endmodule

滞回比较器PEAK_HYSTERESIS是关键。没有滞回时,包络在峰值附近微小波动会反复触发状态切换,出现抖动。加上滞回就稳多了。这个滞回量要根据实际噪声水平调整,我最后取的是包络满量程的2%左右。

调制度计算模块做的事很简单:等measure_valid信号到来后,用峰谷值计算调制度:

module modulation_index_calc #( parameter DATA_WIDTH = 24, parameter OUTPUT_WIDTH = 16 )( input wire clk, input wire rst_n, input wire signed [DATA_WIDTH-1:0] peak_max, input wire signed [DATA_WIDTH-1:0] peak_min, output reg signed [OUTPUT_WIDTH-1:0] mod_index_q15 ); reg signed [DATA_WIDTH*2-1:0] sum_peak; reg signed [DATA_WIDTH*2-1:0] diff_peak; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sum_peak <= 'd0; diff_peak <= 'd0; mod_index_q15 <= 'd0; end else begin sum_peak <= peak_max + peak_min; diff_peak <= peak_max - peak_min; // 调制度 = 差值/和值, 乘以32768映射到Q15 // 除法用移位减法恢复算法实现 if (sum_peak != 0) mod_index_q15 <= divide_q15(diff_peak, sum_peak); end end endmodule

除法器我用的是移位减法恢复算法,每次迭代输出一位商,16次迭代出结果。用异步fifo把除法流水线串起来就行,在500kHz数据率下,这个延迟完全不影响实时性。调制度结果用Q15格式编码,传到串口后上位机除以32768再乘以100,就得到百分比数值。例如mod_index_q15 = 9830时,调制度 = 9830 / 32768 × 100% ≈ 30%。

4. 仿真验证流程与关键测试用例

4.1 搭建AM信号Testbench

仿真验证是整个项目里花时间最多也最值得的部分。Testbench里我写了一个AM信号源,用Verilog生成10.7MHz载波被1kHz正弦调制的波形。生成公式:

am_signal = A0 × (1 + m × sin(2π × 1kHz × t)) × sin(2π × 10.7MHz × t)

载波幅度A0取0.5,调制度m通过task参数传入,这样可以在一次仿真里连续测试多个调制度场景。

task generate_am_signal; input real modulation_index; integer i; begin // 预生成1024点AM波形 for (i = 0; i < 1024; i = i + 1) begin am_lut[i] = 0.5 * (1.0 + modulation_index * $sin(2*3.14159*1000*i/50000000)) * $sin(2*3.14159*10700000*i/50000000); end end endtask

考虑到仿真速度,Testbench里把采样率保持50MSPS不变,但调制频率可以适当提高,比如用5kHz调制信号,这样仿真时间内能看到更多调制周期,统计结果更可靠。

4.2 仿真结果与误差分析

我做了m = 0.1、0.3、0.5、0.8、0.9五组仿真,每组跑完2万个点。以m = 0.3为例,仿真结果显示peak_max和peak_min的比值约为1.86,计算出的调制度为30.2%左右,与理论值30%相差0.2个百分点,在±1%指标范围内。

误差来源主要有两个:一是CIC和FIR滤波器的幅频响应在调制频率处不是理想平坦,会引入细微的幅度误差;二是包络平滑IIR滤波器的alpha值会带来一定的包络跟踪滞后,导致峰值检测略微偏低。这两个误差方向相反,实际叠加后反而相互抵消了一部分。仿真多组后发现,调制频率越低,误差越小,这是因为IIR平滑器的滞后在低频时影响更小。

4.3 边界情况测试

除了标准测试,我还专门跑了几个边界场景。

第一个是调制度为0的情况,也就是纯载波输入。此时包络是一条直线,峰值和谷值相等,除法器的分母为0。我在RTL里加了保护逻辑,当sum_peak小于某个阈值时直接输出0,避免除零错误。

第二个是m接近1的情况,比如m = 0.98。此时包络谷值接近0,信噪比下降,峰值检测容易出现误判。实测在m = 0.98时计算结果是96.5%,虽然偏差略大但还在可接受范围内。

第三个是频率变换场景,用10.7MHz ± 200Hz的输入信号验证NCO频率偏差的影响。当输入频率偏移200Hz时,I/Q解调仍能正确提取包络,调制度计算结果几乎不变。这说明I/Q正交解调对载波频率偏差不敏感,设计目的达到了。

5. 上板实测与问题排查纪实

5.1 实测数据与误差分析

代码通过仿真后烧写到开发板上,连接信号源输出10.7MHz中频AM信号。实测结果用串口助手显示,每隔500ms上报一次调制度。信号源设置为30%调制度、1kHz调制频率时,FPGA上报值为29.6%到30.3%之间跳动,平均值约29.9%,精度满足设计指标。

我又测试了不同载波幅度的场景。ADC输入从0.1Vpp增大到0.8Vpp,调制度读数稳定在29.8%到30.1%之间。这说明FPGA内部的全数字处理对模拟增益不敏感,AGC不是必须项,只要信号不进入ADC的饱和或噪声区,测量结果基本一致。

5.2 踩过的三个坑

第一个坑是DC偏置问题。仿真时一切正常,上板后却发现调制度计算结果忽大忽小。排查了半天发现是混频器输出的直流偏置在作怪。ADC前端的运放电路存在约5mV的直流偏置,混频后直流分量被叠加在基带信号上,导致包络的峰谷检测出现系统性偏移。解决方法是加入DC阻塞滤波器,我在混频器和CIC之间加了一个一阶高通滤波器,截止频率约100Hz,把直流分量滤掉后读数立刻恢复了稳定。

第二个坑是CIC输出数据的符号扩展问题。CIC内部用无符号累加实现积分器时,需要把输入从有符号转成无符号偏置形式,否则负数输入时累加结果会出错。我第一次写CIC时忽略了这一步,仿真结果显示输出波形明显异常。后来在输入端加上2^(ACC_WIDTH-1)的偏置,输出端再减回来做符号恢复,问题才解决。这个坑在教科书上经常被一笔带过,实际写代码时很容易踩到。

第三个坑是UART发送的数据格式问题。FPGA计算出的调制度是Q15格式,直接按十六进制发到上位机,显示出来是一串看不懂的数字。后来我在FPGA内部把调制度转换成ASCII码字符串,比如"29.9%",再通过UART逐字符发送,上位机串口助手直接就能显示。这个转换逻辑代码量不大,但非常影响使用体验。如果你的项目也需要把测量结果发给上位机,建议在FPGA端就完成格式化,不要依赖上位机做二次处理。

5.3 工程代码使用说明

工程代码已经整理好并加了详细注释,顶层模块的例化关系在代码里用注释块标得很清楚。使用时有几个注意点想强调一下:

  • 开发环境为Vivado 2019.2,NCO和FIR滤波器用到了Block Memory Generator和Multiplier IP核,低版本Vivado可能打不开生成好的IP,建议用相同版本重新生成。
  • NCO的phase_inc参数需要根据你的实际输入载波频率重新计算,计算公式上面已经写明,不要偷懒直接沿用我的参数。
  • CIC的输出位宽截取需要根据你的输入幅度重新标定。最稳妥的方法是先用满量程正弦波仿真,找到输出最大值,再反推合适的移位量。
  • UART波特率我默认配置为115200,8N1格式。如果你需要其他波特率,修改uart_tx.v和uart_rx.v中的时钟分频参数即可。

这个项目做完后我又在它基础上扩展了载波频率测量和调制频率测量功能,原理也简单,载波频率用NCO的频偏估算,调制频率用包络过零检测。如果你也有类似需求,可以在现有架构上继续叠加模块,FPGA的资源余量完全够用。

最后再分享一个经验:这种信号测量类项目,一定要把仿真阶段的Testbench当成一等公民来写,多花点时间把测试信号的数学模型搞清楚、把边界条件覆盖到位,上板调试的时间能减少一半。我自己就是仿真阶段省了事,结果上板后花了三倍时间在示波器和逻辑分析仪上反复折腾。测试用例多写几行,后面少做几天亏心工。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 21:18:52

文献综述高效写作指南:9款工具实测与全流程实操

1. 文献综述为什么难写&#xff1a;9款工具到底在帮你解决什么问题毕业论文里最磨人的一关&#xff0c;十个人有九个会说是文献综述。我当年写硕士论文的时候&#xff0c;光是文献综述就拖了一个半月。不是不想写&#xff0c;是真的无从下手&#xff1a;文献库检索出来几百篇论…

作者头像 李华
网站建设 2026/9/24 21:18:45

YOLOv8捕鱼识别实战:1813张数据集从标注检查到模型训练全流程

简介&#xff1a;这是一套面向YOLO系列目标检测任务的捕鱼识别数据集&#xff0c;适合需要训练、验证或测试渔业检测模型的算法工程师与研究者&#xff0c;可直接用于智慧渔场、捕捞监控等场景。压缩包内标注文件共2000个&#xff0c;含1584个XML与416个TXT&#xff0c;分别对应…

作者头像 李华
网站建设 2026/9/24 21:18:36

我靠这套脚本搞定自媒体批量改写,绕开了90%的审核坑

上周运营组扔过来270篇历史旧稿&#xff0c;要求3天内改完过平台原创校验&#xff0c;我头直接大了一圈。之前手动改个十几篇我还能摸鱼做完&#xff0c;这次量翻了十几倍&#xff0c;硬扛肯定不现实。前后花了一天半搭完这套自动化的自媒体批量改写流水线&#xff0c;踩了一堆…

作者头像 李华
网站建设 2026/9/24 21:17:58

YOLO钢筋检测实战:从数据集标注到模型部署全流程

简介&#xff1a;这份资源是面向建筑行业智能化检测与计算机视觉学习者的YOLO钢筋检测数据集&#xff0c;适合从事目标检测训练、施工安全评估或相关课程实践的中高级开发者使用。压缩包共751个文件&#xff0c;包含250张jpg图像、250个xml标注和251个txt标签&#xff0c;整体约…

作者头像 李华
网站建设 2026/9/24 21:17:33

低压注塑天线防护方案:防水防震、材料选型与量产实践

低压注塑在行业里其实不算新工艺&#xff0c;但最近两年被频繁问到&#xff0c;大多是同一类诉求&#xff1a;天线产品要过防水测试、要扛振动冲击、又要控制成本。以前大家习惯用密封圈加胶水、超声波焊接或者整体灌封&#xff0c;但遇到结构复杂、数量又大的天线模组&#xf…

作者头像 李华
网站建设 2026/9/24 21:16:18

ECharts关系图graph实战:从数据设计到性能优化

前两天有个做数据可视化大屏的朋友问我&#xff0c;想把公司几十个业务系统之间的依赖关系画出来&#xff0c;问我用什么方案快。我脱口而出&#xff1a;ECharts的graph——关系图。他愣了一下&#xff0c;说平时柱状图、饼图、折线图用得挺多&#xff0c;关系图还真没碰过。这…

作者头像 李华