1. FSK调制解调系统基础入门
FSK(频移键控)是数字通信中最基础的调制方式之一,它的核心思想是通过改变载波频率来表示不同的数字信号。比如用高频代表1,低频代表0。这种调制方式在中低速通信场景中特别受欢迎,因为它实现简单,抗干扰能力也不错。
我第一次接触FSK是在做一个无线遥控小车项目时。当时需要把控制信号从遥控器传到小车上,试了几种方案后发现FSK既容易实现又稳定可靠。在FPGA上实现FSK最大的优势就是可以充分发挥硬件并行处理的特性,让调制解调过程更加高效。
FPGA实现FSK通常需要这几个关键模块:数字频率合成器(产生不同频率的载波)、调制器(根据输入数据选择频率)、解调器(检测接收信号的频率)以及一些辅助的滤波和同步电路。Verilog作为硬件描述语言,可以很自然地描述这些数字电路的行为。
2. Verilog实现FSK调制器
2.1 数字频率合成器设计
数字频率合成器(DDS)是FSK调制的核心。我常用的实现方式是查表法,通过一个相位累加器和正弦波查找表来生成不同频率的信号。这种方法资源占用少,频率切换也快。
module dds ( input clk, input rst, input [15:0] freq_ctrl, // 频率控制字 output reg [11:0] sin_out // 正弦波输出 ); reg [31:0] phase_acc; always @(posedge clk or posedge rst) begin if (rst) phase_acc <= 0; else phase_acc <= phase_acc + freq_ctrl; end // 使用Block RAM存储正弦波表 reg [11:0] sin_table [0:4095]; initial $readmemh("sin_table.hex", sin_table); always @(posedge clk) begin sin_out <= sin_table[phase_acc[31:20]]; // 取高12位作为查表地址 end endmodule这个DDS模块可以通过改变freq_ctrl的值来调整输出频率。比如设置freq_ctrl为1000时输出1MHz信号,设置为2000时就输出2MHz信号。实际项目中我会根据系统时钟频率和需要的载波频率来计算合适的控制字。
2.2 FSK调制器实现
有了DDS模块后,FSK调制器就很简单了。只需要根据输入数据位选择不同的频率控制字即可:
module fsk_modulator ( input clk, input data_in, // 输入数据位 output [11:0] modulated // 调制输出 ); // 定义两个频率的控制字 parameter FREQ_0 = 16'd1000; // 0对应的频率 parameter FREQ_1 = 16'd2000; // 1对应的频率 wire [15:0] current_freq = data_in ? FREQ_1 : FREQ_0; dds dds_inst ( .clk(clk), .rst(1'b0), .freq_ctrl(current_freq), .sin_out(modulated) ); endmodule这里有个实用技巧:为了减少相位跳变带来的频谱扩散,我会在频率切换时保持相位连续。可以在DDS模块中添加相位补偿逻辑,确保切换频率时相位不会突变。
3. FSK解调器设计与实现
3.1 非相干解调方案
解调比调制要复杂一些。我最常用的是非相干解调,因为它实现简单且不需要恢复载波。其中,过零检测法是个不错的选择:
module zero_cross_detector ( input clk, input [11:0] signal_in, output reg zero_cross ); reg [11:0] prev_sample; always @(posedge clk) begin prev_sample <= signal_in; // 检测过零点:前一个样值为正,当前为负 zero_cross <= (prev_sample[11] == 0 && signal_in[11] == 1); end endmodule module fsk_demodulator ( input clk, input [11:0] rx_signal, output reg data_out ); wire zero_cross; zero_cross_detector zcd ( .clk(clk), .signal_in(rx_signal), .zero_cross(zero_cross) ); reg [15:0] period_counter; always @(posedge clk) begin if (zero_cross) begin // 根据过零间隔判断频率 data_out <= (period_counter > 16'd500); period_counter <= 0; end else begin period_counter <= period_counter + 1; end end endmodule这个解调器通过测量信号过零点的间隔来判断频率。间隔短说明频率高(代表1),间隔长说明频率低(代表0)。实际应用中需要根据具体频率设置合适的阈值。
3.2 改进型解调方案
基础解调器在噪声环境下性能会下降。我后来改进了一个带滤波的版本,加入了数字带通滤波器和自动阈值调整:
module improved_fsk_demod ( input clk, input [11:0] rx_signal, output reg data_out ); // 带通滤波器实现(简化版) reg [23:0] filter_reg; always @(posedge clk) begin filter_reg <= {filter_reg[11:0], rx_signal}; end wire [11:0] filtered = (filter_reg[23:12] + filter_reg[11:0]) >> 1; // 自适应阈值 reg [11:0] peak_high, peak_low; always @(posedge clk) begin if (filtered > peak_high) peak_high <= filtered; if (filtered < peak_low) peak_low <= filtered; end wire [11:0] threshold = (peak_high + peak_low) >> 1; // 数据判决 always @(posedge clk) begin data_out <= (filtered > threshold); end endmodule这个版本在实测中误码率能降低一个数量级,特别适合无线传输场景。当然,资源消耗也会大一些,需要根据FPGA资源情况做权衡。
4. 系统级优化与调试技巧
4.1 时序优化策略
在把各个模块集成到完整系统时,时序问题经常让人头疼。我有几个实用经验:
- 对高频路径进行流水线化。比如在DDS的输出端加一级寄存器:
always @(posedge clk) begin dds_out <= sin_table[phase_acc[31:20]]; modulated_out <= dds_out; // 增加一级流水线 end- 对跨时钟域的信号采用双寄存器同步:
reg [1:0] sync_reg; always @(posedge rx_clk) begin sync_reg <= {sync_reg[0], tx_data}; end- 使用FPGA内置的DSP块实现乘法运算,可以显著提高性能。
4.2 资源优化技巧
FPGA资源有限,特别是在低端器件上。我常用的优化方法包括:
共享DDS资源。可以让调制器和解调器共用一个DDS核,通过时分复用的方式交替使用。
使用对称性压缩查找表。正弦波具有对称性,可以只存储1/4周期的波形数据,然后通过地址变换还原完整波形。
适当降低数据位宽。比如经过测试发现12位的DDS输出已经足够,就没必要用16位。
4.3 调试与测试方法
调试数字通信系统时,我有几个常用方法:
使用ChipScope/SignalTap抓取内部信号波形,这是最直接的调试手段。
建立测试平台,用Verilog的$display语句输出关键变量值:
always @(posedge clk) begin if (zero_cross) $display("Period: %d, Data: %b", period_counter, data_out); end- 做蒙特卡洛仿真,模拟不同信噪比下的系统性能:
initial begin // 添加高斯白噪声 for (int i=0; i<10000; i++) begin rx_signal = ideal_signal + $random % 100; #10; end end- 实际测试时,先用信号发生器产生标准FSK信号测试解调器,再用调制器输出测试接收设备,最后做端到端测试。
5. 实际应用案例
去年我做了一个基于FSK的水表抄表系统,这里分享一些实战经验。系统要求传输距离100米以上,功耗要低,因为水表是电池供电的。
我选用了以下参数:
- 载波频率:433MHz ISM频段
- 频偏:±25kHz
- 数据速率:1kbps
- 调制方式:2FSK
在FPGA中实现的几个关键点:
- 低功耗设计:
// 只在有数据发送时启动调制器 always @(posedge clk) begin if (tx_enable) begin modulator_on <= 1'b1; idle_counter <= 0; end else if (idle_counter > 24'hffffff) begin modulator_on <= 1'b0; end else begin idle_counter <= idle_counter + 1; end end- 前导码设计:在数据前加16位的"1010..."前导码,帮助接收端同步:
reg [3:0] preamble_cnt; always @(posedge clk) begin if (tx_start) preamble_cnt <= 0; else if (preamble_cnt < 15) preamble_cnt <= preamble_cnt + 1; tx_bit <= (preamble_cnt < 16) ? preamble_cnt[0] : tx_data; end- 抗干扰处理:在解调端加入简单的纠错机制,连续收到3个相同的bit才确认:
reg [1:0] bit_history; always @(posedge clk) begin bit_history <= {bit_history[0], demod_bit}; if (bit_history[0] == bit_history[1]) begin stable_bit <= bit_history[0]; bit_counter <= 0; end else if (bit_counter > 3) begin stable_bit <= demod_bit; bit_counter <= 0; end else begin bit_counter <= bit_counter + 1; end end这个系统最终实测传输距离达到120米,误码率低于1e-5,平均功耗只有50uA,完全满足项目需求。