1. 串口通信基础
串口通信(UART)是一种异步串行通信协议,广泛应用于嵌入式系统和FPGA设计中。它使用简单的两根线(TX和RX)实现全双工通信,是连接上位机和下位机的常用接口。
1.1 串口通信格式
UART通信以帧为单位传输数据,每帧包含以下部分:
- 起始位:1位低电平,标志数据传输开始
- 数据位:5-9位,通常为8位,表示实际传输的数据
- 校验位:可选,用于错误检测(奇校验或偶校验)
- 停止位:1-2位高电平,标志数据传输结束

1.2 波特率与时钟计算
波特率(Baud Rate)表示每秒传输的比特数。常见的波特率有9600、115200等。在FPGA设计中,需要根据系统时钟频率计算每个比特的采样点数:
// 计算每个比特的时钟周期数
parameter CLK_FREQ = 50_000_000; // 系统时钟50MHz
parameter BAUD_RATE = 115200; // 波特率115200
parameter rx_1bit = CLK_FREQ / BAUD_RATE; // 每个比特的时钟周期数
2. 串口接收模块设计(不带校验位)
下面是一个完整的串口接收模块(UART RX)实现,用于接收上位机发送的16进制数据。
module rx(
input wire clk, // 系统时钟输入,50MHz
input wire rst_n, // 异步复位信号,低电平有效
input wire rx, // 串口接收数据线
output wire [7:0] data // 接收到的8位数据输出
);
// 系统参数定义
parameter CLK = 50000000; // 系统时钟频率:50MHz
parameter baudrate = 115200; // 串口波特率:115200 bps
parameter rx_1bit = CLK / baudrate; // 每个比特位所需的时钟周期数
// 同步器:用于消除亚稳态,对异步输入信号rx进行两级同步
reg rx_z1, rx_z2; // 两级同步寄存器
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin // 复位时初始化
rx_z1 <= 1; // 默认高电平(空闲状态)
rx_z2 <= 1;
end
else begin
rx_z1 <= rx; // 第一级同步
rx_z2 <= rx_z1; // 第二级同步
end
end
// 下降沿检测:检测起始位(从高电平跳变到低电平)
wire rx_negedge;
assign rx_negedge = !rx_z1 && rx_z2; // 当前为低电平,上一拍为高电平
// 比特计数器:用于计数每个比特的采样位置
reg [12:0] cnt; // 13位计数器,足够计数10个比特位
reg en; // 计数器使能信号,高电平有效
// 使能控制逻辑:控制接收过程的开始和结束
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
en <= 0; // 复位时禁用计数器
end
else if(rx_negedge && en == 0) // 检测到起始位且当前未在接收
en <= 1; // 开始接收一帧数据
else if(cnt == 10 * rx_1bit - 1) // 10个比特位接收完成(1起始+8数据+1停止)
en <= 0; // 停止接收
end
// 计数器逻辑:在使能期间计数时钟周期
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
cnt <= 0; // 复位时清零
else begin
if(en) begin // 只有在使能期间才计数
if(cnt == 10 * rx_1bit - 1) // 达到一帧的最大计数值
cnt <= 0; // 归零,准备下一帧
else
cnt <= cnt + 1; // 正常计数
end
end
end
// 数据采样寄存器:临时存储接收到的8位数据
reg [7:0] data_out;
// 数据采样逻辑:在每个数据位的中间位置采样
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_out <= 0; // 复位时清零
// 在比特位的中间位置采样,提高抗干扰能力
else if(cnt == rx_1bit + (rx_1bit >> 1)) // 第1个数据位中间(起始位后1.5个周期)
data_out[0] <= rx; // 采样最低位(LSB first)
else if(cnt == rx_1bit * 2 + (rx_1bit >> 1)) // 第2个数据位中间
data_out[1] <= rx;
else if(cnt == rx_1bit * 3 + (rx_1bit >> 1)) // 第3个数据位中间
data_out[2] <= rx;
else if(cnt == rx_1bit * 4 + (rx_1bit >> 1)) // 第4个数据位中间
data_out[3] <= rx;
else if(cnt == rx_1bit * 5 + (rx_1bit >> 1)) // 第5个数据位中间
data_out[4] <= rx;
else if(cnt == rx_1bit * 6 + (rx_1bit >> 1)) // 第6个数据位中间
data_out[5] <= rx;
else if(cnt == rx_1bit * 7 + (rx_1bit >> 1)) // 第7个数据位中间
data_out[6] <= rx;
else if(cnt == rx_1bit * 8 + (rx_1bit >> 1)) // 第8个数据位中间
data_out[7] <= rx; // 采样最高位
end
// 数据输出寄存器:在停止位开始时锁存数据
reg [7:0] data_reg;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
data_reg <= 0; // 复位时清零
else if(cnt == rx_1bit * 9) // 第9个比特位(停止位)开始位置
data_reg <= data_out; // 锁存接收到的完整数据
else
data_reg <= data_reg; // 保持当前值
end
assign data = data_reg; // 输出最终接收到的数据
endmodule

3.数码管显示模块(在我之前的博客当中也有提到,这里就不再详细介绍)使用数码管设计秒表:Verilog实现与原理详解-CSDN博客
1.数码管显示模块
module seven_seg (
input wire clk,
input wire rst_n,
input wire [11:0] data,
output wire [5:0] sel,
output wire [7:0] seg
);
parameter cnt_qiehuan_max=5000;
reg [12:0]cnt_qiehuan;
reg flag;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
cnt_qiehuan<=0;
flag<=0;
end
else if(cnt_qiehuan==cnt_qiehuan_max-1)begin
cnt_qiehuan<=0;
flag<=1;
end
else
begin
cnt_qiehuan<=cnt_qiehuan+1;
flag<=0;
end
end
reg [2:0]state;
parameter [2:0] STATE0=0,
STATE1=1,
STATE2=2,
STATE3=3,
STATE4=4,
STATE5=5;
reg [5:0] sel_out;
reg [3:0]data_out;
//位选
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
state=STATE0;
sel_out=6'b111111;
data_out=4'b0000;
end
else if(flag)begin
case(state)
STATE0:begin
sel_out=6'b111110;
state=STATE1;
data_out=data[3:0];
end
STATE1:begin
sel_out=6'b111101;
state=STATE2;
data_out=data[7:4];
end
STATE2:begin
sel_out=6'b111011;
state=STATE0;
data_out=data[11:8];
end
// STATE3:begin
// sel_out=6'b110111;
// state=STATE4;
// data_out=data[15:12];
// end
// STATE4:begin
// sel_out=6'b101111;
// state=STATE5;
// data_out=data[19:16];
// end
// STATE5:begin
// sel_out=6'b011111;
// state=STATE0;
// data_out=data[23:20];
// end
default:;
endcase
end
end
//段选
reg [7:0] seg_out;
always@(*)begin
case(data_out)
0:seg_out=8'b11000000;
1:seg_out=8'b11111001;
2:seg_out=8'b10100100;
3:seg_out=8'b10110000;
4:seg_out=8'b10011001;
5:seg_out=8'b10010010;
6:seg_out=8'b10000010;
7:seg_out=8'b11111000;
8:seg_out=8'b10000000;
9:seg_out=8'b10010000;
default: seg_out = 8'b11111111;
endcase
end
assign sel=sel_out;
assign seg=seg_out;
endmodule
2.BCD模块:
module BCD (
input [7:0] data,
output[11:0] data_out
);
reg [19:0] temp;
integer i;
always @(*) begin
temp={8'b0,data};
for(i=0;i<8;i=i+1)begin
if(temp[11:8]>=5)begin
temp[11:8]=temp[11:8]+3;
end
if(temp[15:12]>=5)begin
temp[15:12]=temp[15:12]+3;
end
if(temp[19:16]>=5)begin
temp[19:16]=temp[19:16]+3;
end
temp=temp<<1;
end
end
assign data_out=temp[19:8];
endmodule
4. 顶层模块与系统集成
4.1 顶层模块设计(只需要将我的子模块例化一遍即可)
module top #(
parameter CLK=50000000,
parameter baudrate=115200,
parameter rx_1bit=CLK/baudrate
) (
input wire clk,
input wire rst_n,
input wire rx,
output wire [5:0]sel,
output wire [7:0]seg
);
wire [7:0] data;
wire [11:0] data_out;
BCD bcd_tb(
.data(data),
.data_out(data_out)
);
seven_seg seven_seg_tb(
.clk(clk),
.rst_n(rst_n),
.sel(sel),
.seg(seg),
.data(data_out)
);
rx rx_test(
.clk(clk),
.rst_n(rst_n),
.rx(rx),
.data(data)
) ;
endmodule
5. 总结与注意事项
5.1 关键设计要点
- 波特率匹配:确保FPGA内部时钟分频与上位机波特率一致(115200)
- 采样点选择:在比特中间采样可以提高抗干扰能力
- 同步处理:使用两级寄存器同步异步信号,避免亚稳态
- 帧完整性:完整接收10个比特(1起始+8数据+1停止)
5.2 常见问题与调试
- 数据接收错误:检查波特率计算是否正确
- 数码管不显示:验证段码表和位选信号
- 时序问题:使用仿真工具验证时序逻辑
- 硬件连接:确认串口线连接正确(TX接RX,RX接TX)
5.3 扩展功能建议
- 添加奇偶校验功能
- 实现多字节数据接收
- 添加接收缓冲区
- 支持多种波特率可配置
- 添加错误检测和重传机制
通过本文介绍的串口接收模块和数码管显示模块,可以实现上位机发送16进制数据并在FPGA开发板的数码管上显示的功能。读者可以根据实际需求修改波特率、数据位数等参数,并添加更多功能模块。

245

被折叠的 条评论
为什么被折叠?



