一、系统概述
基于FPGA(Field-Programmable Gate Array) 实现数字频率计与UART串口通信功能,核心通过测频法测量外部输入信号的频率,并将结果以ASCII字符串形式通过串口发送至PC端(如串口助手)。系统采用模块化设计,包含时钟分频、信号整形、计数控制、UART发送四大核心模块,支持1Hz~10MHz频率测量(可扩展),精度达±1Hz(1秒闸门时间)。
二、硬件设计
2.1 核心组件
| 模块 | 型号/参数 | 功能说明 |
|---|---|---|
| FPGA | Altera Cyclone IV EP4CE6F17C8 | 主控制器,实现频率测量与串口通信逻辑 |
| 时钟源 | 50MHz有源晶振(FPGA外部输入) | 提供系统基准时钟(用于分频、计数、UART) |
| 信号输入 | SMA接口(外接信号发生器/待测信号) | 输入待测频率信号(TTL电平,0~3.3V) |
| 串口输出 | USB-TTL模块(CH340芯片) | FPGA的UART_TX引脚→PC的USB接口(虚拟COM口) |
| 电源 | 5V/2A DC电源 | 为FPGA开发板、信号源、USB-TTL供电 |
2.2 硬件连接
| 模块 | FPGA引脚(EP4CE6F17C8) | 说明 |
|---|---|---|
| 50MHz时钟 | PIN_23(CLK_50M) | 系统主时钟输入 |
| 待测信号 | PIN_45(SIG_IN) | 外部信号输入(TTL电平) |
| UART_TX | PIN_67(UART_TXD) | 串口发送引脚(接CH340 RX) |
| 复位信号 | PIN_12(RST_N) | 低电平复位(接按键) |
三、软件设计(Verilog HDL)
3.1 系统架构
50MHz主时钟
时钟分频模块
1Hz门控信号
UART波特率时钟
待测信号SIG_IN
信号整形模块
计数模块
控制逻辑
数据转换模块
UART发送模块
UART_TXD输出至PC
3.2 核心模块代码实现
3.2.1 时钟分频模块(生成1Hz门控与UART波特率时钟)
module clk_divider(
input clk_50m, // 50MHz主时钟
input rst_n, // 复位(低有效)
output reg gate_1hz, // 1Hz门控信号(高电平1秒)
output reg uart_clk // UART波特率时钟(9600bps,5200分频)
);
// 1Hz门控信号(50MHz→1Hz,分频系数=50,000,000-1=49,999,999)
reg [25:0] cnt_1hz; // 26位计数器(2^26=67,108,864 > 50M)
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
cnt_1hz <= 26'd0;
gate_1hz <= 1'b0;
end else begin
if (cnt_1hz == 26'd49_999_999) begin // 1秒计数
cnt_1hz <= 26'd0;
gate_1hz <= ~gate_1hz; // 翻转生成1Hz方波(高电平1秒,低电平1秒)
end else begin
cnt_1hz <= cnt_1hz + 1'b1;
end
end
end
// UART波特率时钟(50MHz→9600bps,分频系数=50,000,000/9600≈5208.33→取5200)
reg [12:0] cnt_uart; // 13位计数器(2^13=8192 > 5200)
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
cnt_uart <= 13'd0;
uart_clk <= 1'b0;
end else begin
if (cnt_uart == 13'd5199) begin // 5200分频(0~5199共5200个周期)
cnt_uart <= 13'd0;
uart_clk <= ~uart_clk;
end else begin
cnt_uart <= cnt_uart + 1'b1;
end
end
end
endmodule
3.2.2 信号整形与计数模块(测频法核心)
module freq_counter(
input clk_50m, // 50MHz时钟
input rst_n, // 复位
input gate_1hz, // 1Hz门控信号(高电平计数)
input sig_in, // 待测信号(TTL电平)
output reg [23:0] cnt_out // 24位计数值(最大16,777,216,支持10MHz@1秒)
);
// 信号边沿检测(生成单周期脉冲)
reg sig_d1, sig_d2;
wire sig_pos = sig_d1 & ~sig_d2; // 上升沿检测
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
sig_d1 <= 1'b0;
sig_d2 <= 1'b0;
end else begin
sig_d1 <= sig_in;
sig_d2 <= sig_d1;
end
end
// 计数逻辑(门控高电平时计数)
always @(posedge clk_50m or negedge rst_n) begin
if (!rst_n) begin
cnt_out <= 24'd0;
end else if (gate_1hz) begin // 门控高电平期间计数
if (sig_pos) begin // 检测到上升沿计数
cnt_out <= cnt_out + 1'b1;
end
end else begin // 门控低电平期间清零
cnt_out <= 24'd0;
end
end
endmodule
3.2.3 UART发送模块(按协议发送频率数据)
module uart_tx(
input clk_50m, // 50MHz时钟
input rst_n, // 复位
input uart_clk, // 9600bps波特率时钟
input [23:0] data_in, // 24位计数值(频率=计数值/1秒)
output reg txd // UART发送引脚
);
// 数据转换:计数值→ASCII字符串(格式:"FREQ:XXXXXX Hz\r\n")
reg [7:0] tx_data[0:15]; // 发送缓冲区(16字节)
reg [3:0] tx_cnt; // 发送字节计数(0~15)
reg [3:0] bit_cnt; // 位计数(0~9:起始位+8数据位+停止位)
reg [7:0] current_byte; // 当前发送字节
// 计数值转ASCII(示例:计数值1234→"1234")
task convert_data;
input [23:0] data;
integer i;
begin
tx_data[0] = "F"; tx_data[1] = "R"; tx_data[2] = "E"; tx_data[3] = "Q"; // "FREQ:"
tx_data[4] = ":"; tx_data[5] = " ";
// 千位(假设最大6位,补空格)
tx_data[6] = (data / 100000) % 10 + "0"; // 十万位
tx_data[7] = (data / 10000) % 10 + "0"; // 万位
tx_data[8] = (data / 1000) % 10 + "0"; // 千位
tx_data[9] = (data / 100) % 10 + "0"; // 百位
tx_data[10] = (data / 10) % 10 + "0"; // 十位
tx_data[11] = data % 10 + "0"; // 个位
tx_data[12] = " "; tx_data[13] = "H"; tx_data[14] = "z"; tx_data[15] = "\r\n"; // " Hz\r\n"
end
endtask
// UART发送状态机(空闲→发送起始位→发送8数据位→发送停止位)
always @(posedge uart_clk or negedge rst_n) begin
if (!rst_n) begin
txd <= 1'b1; // 空闲状态高电平
tx_cnt <= 4'd0;
bit_cnt <= 4'd0;
current_byte <= 8'd0;
end else begin
if (tx_cnt < 4'd16) begin // 16字节待发送
if (bit_cnt == 4'd0) begin // 起始位(低电平)
txd <= 1'b0;
current_byte <= tx_data[tx_cnt]; // 加载当前字节
bit_cnt <= bit_cnt + 1'b1;
end else if (bit_cnt >= 4'd1 && bit_cnt <= 4'd8) begin // 数据位(LSB先发)
txd <= current_byte[bit_cnt-1];
bit_cnt <= bit_cnt + 1'b1;
end else if (bit_cnt == 4'd9) begin // 停止位(高电平)
txd <= 1'b1;
bit_cnt <= 4'd0;
tx_cnt <= tx_cnt + 1'b1; // 下一个字节
end
end else begin // 发送完成,空闲
txd <= 1'b1;
tx_cnt <= 4'd0;
end
end
end
// 顶层调用数据转换(每次计数完成后触发)
always @(posedge clk_50m) begin
if (gate_1hz == 1'b0 && cnt_out != 24'd0) begin // 门控结束且有计数
convert_data(cnt_out); // 转换计数值为ASCII
tx_cnt <= 4'd0; // 重置发送计数
end
end
endmodule
3.2.4 顶层模块(模块互联)
module top_freq_meter(
input clk_50m, // 50MHz时钟
input rst_n, // 复位(低有效)
input sig_in, // 待测信号输入
output txd // UART发送引脚
);
// 内部信号
wire gate_1hz;
wire uart_clk;
wire [23:0] cnt_out;
// 实例化时钟分频模块
clk_divider u_clk_divider(
.clk_50m(clk_50m),
.rst_n(rst_n),
.gate_1hz(gate_1hz),
.uart_clk(uart_clk)
);
// 实例化频率计数模块
freq_counter u_freq_counter(
.clk_50m(clk_50m),
.rst_n(rst_n),
.gate_1hz(gate_1hz),
.sig_in(sig_in),
.cnt_out(cnt_out)
);
// 实例化UART发送模块
uart_tx u_uart_tx(
.clk_50m(clk_50m),
.rst_n(rst_n),
.uart_clk(uart_clk),
.data_in(cnt_out),
.txd(txd)
);
endmodule
参考代码 基于FPGA的频率计与串口通信 www.youwenfan.com/contentcss/161291.html
四、关键问题与解决方案
4.1 频率测量精度
-
问题:门控信号非精确1秒导致计数误差。
-
解决:用50MHz高精度时钟分频生成1Hz门控(误差<1μs),并通过FPGA内部PLL校准时钟。
4.2 信号抖动干扰
-
问题:待测信号含毛刺导致误计数。
-
解决 :在信号输入端添加施密特触发器 (如74HC14),或在FPGA中用两级寄存器同步+边沿检测滤除抖动。
4.3 UART通信丢包
-
问题:波特率时钟分频误差导致数据错位。
-
解决:精确计算分频系数(50MHz/9600=5208.33,取5200或5208,实测调整后取5200误差最小)。
五、测试与验证
5.1 硬件测试
-
信号输入:用信号发生器输出1Hz、100Hz、1kHz、1MHz方波,接入FPGA的SIG_IN引脚;
-
串口连接:FPGA的UART_TX通过USB-TTL模块连接PC,打开串口助手(波特率9600,8N1);
-
预期结果:串口助手接收字符串如"FREQ: 01000000 Hz"(对应1MHz信号,计数值10,000,000)。
5.2 精度验证
- 输入10MHz信号,计数值应为10,000,000±1(误差<0.0001%),符合设计要求。
六、总结
基于FPGA实现了频率计与串口通信系统,核心是测频法计数与UART协议发送。通过模块化Verilog代码,完成了从信号输入、整形计数到数据转换与串口输出的全流程。系统扩展性强,可通过增加测周法模块(低频信号)、LCD显示模块或以太网传输进一步提升功能。