实验 15:UART 串口接收
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/10 (Arora V 系列)
一、实验目的
- 理解 UART 接收的核心挑战——异步时钟域同步,掌握多级同步器消除亚稳态的技术。
- 掌握 16 倍过采样技术:在每位中间对信号进行多次采样,用 3 取 2 表决 消除噪声干扰。
- 学会编写带验证的接收状态机:从起始位下降沿检测 → 中心点验证 → 数据位采样 → 停止位 → 回到空闲。
- 实现串口指令控制 LED——通过发送特定字节(0x00~0xFF)控制 8 路蓝色 LED 的亮灭。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | Bank | IO 标准 | 说明 |
|---|---|---|---|---|---|
| CLK | 输入 | T9 | Bank 4 | LVCMOS33 | 50MHz 有源晶振 |
| UART_RXD | 输入 | U13 | Bank 2 | LVCMOS33 | CH340 USB 转串口,来自 PC |
| LED[7:0] | 输出 | G9/E7/E6/A5/A4/B4/A3/C5 | Bank 0/1 | LVCMOS33 | 蓝色 LED,高电平点亮 |
2.2 设计思路
核心问题:接收端的时钟(50MHz)与发送端的波特率时钟(115200Hz)存在频率误差和相位偏移。直接用 50MHz 时钟采样异步 UART 信号会产生亚稳态。
解决方案——16 倍过采样 + 3 取 2 表决:
发送端波特率: 115200 bps
过采样率: 115200 × 16 = 1,843,200 Hz
采样周期: 50,000,000 / 1,843,200 ≈ 27 个时钟
每比特取 16 个采样点:
┌─────────────────────────────────┐
│ 0 1 2 3 4 5 6 7 8 9 10...15 │ ← 采样点编号
│ ↑取样区↑ │
│ [7][8][9] = 3个连续采样 │
│ → 3取2表决 → 确定该位值 │
└─────────────────────────────────┘
接收状态机:
IDLE ──fall──→ START ──确认──→ DATA(8位) ──→ STOP ──→ IDLE
↑ │ 假起始位 │rx_done=1
└───────────────┘ (噪声) │
LED更新 ←────────────────────────────┘
2.3 模块设计
模块名:uart_rx
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
uart_rxd | input | 1 | UART 接收引脚 |
led | output | [7:0] | 8 路 LED |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
rxd_sync | [2:0] | 3 级同步器,消除亚稳态 |
rxd_fall | 1 | 下降沿检测 (1→0) |
sample_cnt | [5:0] | 采样分频计数器 (0~26) |
oversample_cnt | [3:0] | 过采样计数器 (0~15) |
rx_state | [1:0] | 接收状态:IDLE/START/DATA/STOP |
rx_shift | [7:0] | 接收移位寄存器(LSB first) |
vote_buf | [1:0] | 连续采样缓冲(用于表决) |
voted_bit | 1 | 3 取 2 表决结果 |
三、代码逐段讲解
3.1 三级同步器
always @(posedge clk) begin
rxd_sync <= {rxd_sync[1:0], uart_rxd};
end
wire rxd_fall = (rxd_sync[2] == 1'b1) && (rxd_sync[1] == 1'b0);
- 异步输入
uart_rxd经 3 级触发器同步到 50MHz 时钟域。 - 下降沿检测使用同步后的
rxd_sync[2]和rxd_sync[1],安全可靠。 - 三级同步使亚稳态窗口缩小到几乎为零(MTBF > 数百年)。
3.2 采样分频与过采样
localparam SAMPLE_DIV = 27 - 1;
always @(posedge clk) begin
if (rx_state == ST_IDLE)
sample_cnt <= 0;
else if (sample_cnt == SAMPLE_DIV)
sample_cnt <= 0;
else
sample_cnt <= sample_cnt + 1'b1;
end
wire sample_tick = (sample_cnt == SAMPLE_DIV);
sample_tick每 27 个系统时钟(≈540ns)产生一个脉冲。- 每位有 16 个
sample_tick,对应 16 倍过采样。 - 采样率 = 50M / 27 ≈ 1.85MHz,与理论值 1.8432MHz 误差 0.47%,可接受。
3.3 3 取 2 表决
always @(posedge clk) begin
if (sample_tick) begin
vote_buf <= {vote_buf[0], rxd_sync[2]};
end
end
always @(posedge clk) begin
if (sample_tick && oversample_cnt == 4'd9) begin
case ({vote_buf, rxd_sync[2]})
3'b000, 3'b001, 3'b010, 3'b100: voted_bit <= 1'b0;
3'b011, 3'b101, 3'b110, 3'b111: voted_bit <= 1'b1;
endcase
end
end
- 连续 3 个采样值中,如果 ≥2 个为 1 则判为 1,否则判为 0。
- 有效过滤单个采样毛刺,提高抗干扰能力。
3.4 接收状态机
case (rx_state)
ST_IDLE: begin
if (rxd_fall) begin
rx_state <= ST_START; // 检测到下降沿,进入起始位
end
end
ST_START: begin
if (sample_tick && start_center) begin
if (rxd_sync[2] == 1'b1)
rx_state <= ST_IDLE; // 假起始位(噪声),回到空闲
// 否则确认起始位有效
end
if (sample_tick && oversample_cnt == SAMPLE_NUM)
rx_state <= ST_DATA; // 起始位结束,进入数据位
end
ST_DATA: begin
// ... 在 oversample_cnt==9 时锁存 voted_bit
if (bit_idx == 7) rx_state <= ST_STOP;
end
ST_STOP: begin
if (sample_tick && oversample_cnt == SAMPLE_NUM) begin
rx_state <= ST_IDLE;
rx_done <= 1'b1; // 帧接收完成
end
end
endcase
起始位验证:在起始位中心点(oversample_cnt==8,即第 9 个采样点)检查信号是否仍为低,如已回到高则为噪声干扰,丢弃。
3.5 LED 指令控制
always @(posedge clk) begin
if (rx_done) begin
case (rx_shift)
8'h00: led <= 8'b00000000; // 全灭
8'h01: led <= 8'b00000001; // LED1 亮
8'h02: led <= 8'b00000010; // LED2 亮
// ...
8'h08: led <= 8'b10000000; // LED8 亮
8'hFF: led <= 8'b11111111; // 全亮
default: led <= led; // 其他值保持
endcase
end
end
四、实验现象
| 操作 | 预期结果 |
|---|---|
| 上电 | LED 保持默认状态 |
| 串口发送 0x01 | LED1 点亮 |
| 串口发送 0x02 | LED2 点亮 |
| 串口发送 0x08 | LED8 点亮 |
| 串口发送 0xFF | 8 个 LED 全亮 |
| 串口发送 0x00 | 8 个 LED 全灭 |
| 串口发送 0x55 | 无任何变化(保持) |
| 快速连续发送 | 每次响应准确,无漏帧 |
验证要点:
- ✅ 115200,8,N,1 配置下发送数据,LED 精确响应。
- ✅ 发送非指令字节(如 0x55),LED 状态保持不变。
- ✅ 在波特率误差允许范围(±2%)内正常工作。
五、掌握知识点
| 知识点 | 说明 | 在本实验中的体现 |
|---|---|---|
| 亚稳态 | 异步信号采样导致的不稳定状态 | 3 级同步器 rxd_sync[2:0] |
| 16 倍过采样 | 每位取 16 个采样点提高精度 | oversample_cnt 0~15 |
| 3 取 2 表决 | 多数判决滤除噪声 | vote_buf + rxd_sync[2] 统计 |
| 起始位验证 | 中心点确认避免假起始位 | ST_START 状态中的 start_center |
| 状态机 | 4 状态 FSM 控制接收流程 | IDLE→START→DATA→STOP |
| 下降沿检测 | prev && !current | rxd_fall = rxd_sync[2] & !rxd_sync[1] |
附录:源码
top.v
//////////////////////////////////////////////////////////////////////////////////
// Company: 武汉芯路恒科技有限公司
// Engineer: 小梅哥团队
// Web: www.corecourse.cn
//
// Create Date: 2020/07/20 00:00:00
// Design Name: uart_rx
// Module Name: uart_rx
// Project Name: EK301 实验13
// Description: UART串口接收顶层 — 根据接收字节控制LED
// 115200bps 8N1, 16倍过采样
// 指令: 0x00→全灭, 0x01→LED1亮, ..., 0x07→LED8亮, 0xFF→全亮, 其他→保持
//
// Dependencies: uart_byte_rx.v
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 芯片: GW5A-LV25UG324C1/I0, 主时钟50MHz
// 引脚: clk→T9, uart_rxd→U13, reset_n→A11
//////////////////////////////////////////////////////////////////////////////////
module uart_rx (
input wire clk, // 50MHz 系统时钟
input wire reset_n, // 复位信号,低有效
input wire uart_rxd, // UART接收引脚
output wire [7:0] led // 8路LED (1=亮)
);
wire reset = ~reset_n;
// ---- 内部信号 ----
wire [7:0] data_byte; // 接收数据
wire rx_done; // 接收完成脉冲
// ---- UART接收模块例化 ----
// baud_set=4 → 115200bps
uart_byte_rx u_uart_rx (
.clk (clk),
.reset_n (reset_n),
.baud_set (3'd4),
.uart_rx (uart_rxd),
.data_byte (data_byte),
.rx_done (rx_done)
);
// ---- LED控制: 根据接收字节点亮对应LED ----
// 0x00→全灭, 0x01→LED1, 0x02→LED2, ..., 0x07→LED8, 0xFF→全亮, 其他→保持
reg [7:0] led_r;
assign led = led_r;
always@(posedge clk or posedge reset)
if(reset)
led_r <= 8'b00000000;
else if(rx_done) begin
case(data_byte)
8'h00: led_r <= 8'b00000000; // 全灭
8'h01: led_r <= 8'b00000001; // LED1亮
8'h02: led_r <= 8'b00000010; // LED2亮
8'h03: led_r <= 8'b00000100; // LED3亮
8'h04: led_r <= 8'b00001000; // LED4亮
8'h05: led_r <= 8'b00010000; // LED5亮
8'h06: led_r <= 8'b00100000; // LED6亮
8'h07: led_r <= 8'b01000000; // LED7亮
8'h08: led_r <= 8'b10000000; // LED8亮
8'hFF: led_r <= 8'b11111111; // 全亮
default: led_r <= led_r; // 其他值: 保持
endcase
end
endmodule