FPGA通信——实现串口通信(Uart)

FPGA通信——实现串口通信(Uart)

一、串口通信介绍

1.1、核心概念

并行通信 (Parallel):像高速公路,8车道同时跑8辆车。速度快,但占用引脚多,且在长距离传输时容易出现“时钟偏差(Skew)”导致数据错位。

串行通信 (Serial):像单行道,车必须一辆接一辆地排队走。引脚少,成本低,且现代高速串行技术(如PCIE, SATA)通过差分信号解决了速度问题。

我们常说的“串口”通常特指 UART (Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)

1.2、逻辑层面

UART 是一种异步通信协议。

  • 异步 (Asynchronous):发送方和接收方之间没有公共的时钟线(不像 SPI 或 I2C 有 CLK 线)。
  • 约定:双方必须提前约定好相同的波特率 (Baud Rate),否则就像两个人语速不同,无法交流。

数据帧格式 (Frame Format)

FPGA 的状态机就是根据这个时序图来写的:

  1. 空闲位 (Idle):默认高电平(1)。
  2. 起始位 (Start Bit):拉低 1 个周期。告诉接收方:“注意,我要开始说话了!”(这是我们在 Verilog 里检测下降沿的原因)。
  3. 数据位 (Data Bits):通常是 8 位(1 Byte),LSB (低位) 先发
  4. 校验位 (Parity):可选(奇/偶/无)。用于简单的检错,但在工业应用中常被 CRC 取代。
  5. 停止位 (Stop Bit):拉高 1 或 2 个周期。表示一帧结束,并为空闲状态做准备。

1.3、物理层

        FPGA 产生的信号只是逻辑上的 0 和 1(TTL电平),要传输出去,需要穿上不同的“外衣”(电气标准),也就是PCB版上的芯片。

标准传输方式电平特征典型距离拓扑典型应用
TTL单端0~5V<30cm点对点芯片间通信
RS232单端±5~±12V<15m点对点老式PC,工控机
RS485差分A-B电压差<1200m多点/总线PLC、电表
RS422差分4线全双工<1200m点对多较少见,类485

注意:

  • RS-232 是负逻辑(-12V是逻辑1),但 MAX3232 芯片会自动帮你翻转,你在 FPGA 里只需按正逻辑写。
  • RS-485 是抗干扰之王,因为它看的是两根线的压差,外界的共模噪声会被抵消。

1.4、FPGA 实现

在设计代码是应特别关注以下影响设计稳定性的细节:

A. 采样策略 (Sampling)

  • 做法:在波特率计数的中间点读取一次 rx_pin
  • 进阶做法 (过采样):为提高抗噪能力,例如使用 16 倍波特率的时钟去采样,在中间连续采 3 次,取众数(多数表决)。

B. 跨时钟域 (CDC)

  • 外部进来的 rx 信号是异步的,与 FPGA 的 sys_clk 无关。
  • 必须使用“打两拍”处理,否则状态机会因为亚稳态而跑飞。

C. 波特率误差

  • 公式:DIV_CNT = CLK_FREQ / BAUD_RATE
  • 如果系统时钟是 50MHz,波特率 115200:
    • 50,000,000 / 115200 ≈ 434.02
    • 取整 434,误差非常小,可以忽略。
  • 但如果时钟频率很奇葩,导致除不尽,累积误差可能会导致这一帧的最后一位采样偏移出界。一般来说,累积误差控制在 5% 以内都能正常通信。

二、Verilog代码

为了保证代码的鲁棒性,我们将设计分为三个部分:

  • UART_RX (接收模块):关键在于跨时钟域处理(打两拍)和中心对齐采样或者过采样,防止亚稳态和噪声干扰。
  • UART_TX (发送模块):相对简单,主要是状态机控制时序。
  • Top_Loopback (顶层回环):用于板级验证,接收什么就发送什么。

2.1、接收模块(uart_rx)

核心设计:加入了输入两级寄存器同步(消除亚稳态)和比特中心采样逻辑。

module uart_rx #( parameter CLK_FREQ = 50_000_000, // 系统时钟频率 parameter BAUD_RATE = 115200 // 目标波特率 )( input wire clk, input wire rst_n, input wire rx_pin, // 异步串口输入 output reg [7:0] rx_data, // 接收到的数据 output reg rx_done // 接收完成脉冲 ); // 计算分频计数最大值 localparam CNT_MAX = CLK_FREQ / BAUD_RATE; localparam CNT_MID = CNT_MAX / 2; // 采样点(波特率中心) // 状态定义 localparam IDLE = 0; localparam START = 1; localparam DATA = 2; localparam STOP = 3; reg [1:0] state; reg [31:0] clk_cnt; reg [2:0] bit_cnt; // 消除亚稳态:对异步信号 rx_pin 打两拍 reg rx_d1, rx_d2; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin rx_d1 <= 1'b1; rx_d2 <= 1'b1; end else begin rx_d1 <= rx_pin; rx_d2 <= rx_d1; end end // 下降沿检测(用于检测起始位) wire rx_negedge = rx_d2 & (~rx_d1); always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; clk_cnt <= 0; bit_cnt <= 0; rx_data <= 0; rx_done <= 0; end else begin rx_done <= 0; // 默认拉低 case (state) IDLE: begin if (rx_negedge) begin // 检测到起始位下降沿 state <= START; clk_cnt <= 0; end end START: begin if (clk_cnt == CNT_MID) begin // 在起始位中间再次确认电平 if (rx_d2 == 1'b0) begin clk_cnt <= 0; state <= DATA; end else begin state <= IDLE; // 误触发,或者是毛刺 end end else begin clk_cnt <= clk_cnt + 1; end end DATA: begin if (clk_cnt == CNT_MAX - 1) begin clk_cnt <= 0; rx_data[bit_cnt] <= rx_d2; // 移位接收 if (bit_cnt == 7) begin bit_cnt <= 0; state <= STOP; end else begin bit_cnt <= bit_cnt + 1; end end else begin clk_cnt <= clk_cnt + 1; end end STOP: begin if (clk_cnt == CNT_MAX - 1) begin state <= IDLE; clk_cnt <= 0; rx_done <= 1'b1; // 接收完成,产生一个脉冲 end else begin clk_cnt <= clk_cnt + 1; end end default: state <= IDLE; endcase end end endmodule

2.2、发送模块(uart_tx)

核心设计:加入了输入两级寄存器同步(消除亚稳态)和比特中心采样逻辑。

module uart_tx #( parameter CLK_FREQ = 50_000_000, parameter BAUD_RATE = 115200 )( input wire clk, input wire rst_n, input wire tx_start, // 发送使能信号 input wire [7:0] tx_data, // 待发送数据 output reg tx_pin, // 串口发送引脚 output reg tx_busy // 忙信号 ); localparam CNT_MAX = CLK_FREQ / BAUD_RATE; localparam IDLE = 0; localparam START = 1; localparam DATA = 2; localparam STOP = 3; reg [1:0] state; reg [31:0] clk_cnt; reg [2:0] bit_cnt; reg [7:0] data_reg; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= IDLE; tx_pin <= 1'b1; // 空闲时为高 tx_busy <= 1'b0; clk_cnt <= 0; bit_cnt <= 0; data_reg <= 0; end else begin case (state) IDLE: begin tx_pin <= 1'b1; if (tx_start) begin state <= START; data_reg <= tx_data; tx_busy <= 1'b1; clk_cnt <= 0; end else begin tx_busy <= 1'b0; end end START: begin // 发送起始位 0 tx_pin <= 1'b0; if (clk_cnt == CNT_MAX - 1) begin clk_cnt <= 0; state <= DATA; end else begin clk_cnt <= clk_cnt + 1; end end DATA: begin // 发送8位数据(LSB first) tx_pin <= data_reg[bit_cnt]; if (clk_cnt == CNT_MAX - 1) begin clk_cnt <= 0; if (bit_cnt == 7) begin bit_cnt <= 0; state <= STOP; end else begin bit_cnt <= bit_cnt + 1; end end else begin clk_cnt <= clk_cnt + 1; end end STOP: begin // 发送停止位 1 tx_pin <= 1'b1; if (clk_cnt == CNT_MAX - 1) begin clk_cnt <= 0; state <= IDLE; tx_busy <= 1'b0; // 释放忙信号 end else begin clk_cnt <= clk_cnt + 1; end end default: state <= IDLE; endcase end end endmodule

2.3、顶层模块(uart_top)

核心设计:将RX的 rx_done 直接作为 TX的 tx_start,实现“收到什么发回什么”的回环测试功能,这是验证串口最快的方法。

module uart_loopback_top( input wire clk, // 连接到 PL 时钟 ( 50MHz) input wire rst_n, // 复位信号 input wire uart_rx, output wire uart_tx ); // 参数定义 parameter CLK_FREQ = 50_000_000; parameter BAUD_RATE = 115200; wire [7:0] rx_data; wire rx_done; wire tx_busy; // 实例化 RX uart_rx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) u_rx ( .clk(clk), .rst_n(rst_n), .rx_pin(uart_rx), .rx_data(rx_data), .rx_done(rx_done) ); // 实例化 TX // 当接收完成(rx_done)且发送不忙时,启动发送 uart_tx #( .CLK_FREQ(CLK_FREQ), .BAUD_RATE(BAUD_RATE) ) u_tx ( .clk(clk), .rst_n(rst_n), .tx_start(rx_done), .tx_data(rx_data), .tx_pin(uart_tx), .tx_busy(tx_busy) ); endmodule

2.4、引脚约束(XDC)

create_clock -period 20.000 -name sys_clk [get_ports clk] set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports clk] set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports rst_n] set_property -dict {PACKAGE_PIN T19 IOSTANDARD LVCMOS33} [get_ports uart_rx] set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports uart_tx] set_property SLEW SLOW [get_ports uart_tx]

对时钟信号、复位信号、uart输入输出信号进行引脚及电平绑定,同时对时钟信号进行时序约束。Slew Rate是压摆率控制输出引脚电平变化的“陡峭程度”,通常默认就是SLOW,可用可不用。

三、上板验证

板卡:正点原子启明星ZYNQ7020
软件:Vivado2019.2
串口助手:XCOM

步骤与结果验证:

  1. 参数配置:启动串口调试终端,设置波特率为 115200,8 位数据位,1 位停止位,无校验模式,并打开串口。
  2. 数据收发:在发送缓冲区输入指令“ABCD”等回环测试数据并发送。
  3. 结果判定:接收缓冲区显示的数据与发送数据一致(参见上图)。此现象表明串口数据环回功能运行正常,链路通信可靠。

Read more

【Part 3 Unity VR眼镜端播放器开发与优化】第四节|高分辨率VR全景视频播放性能优化

【Part 3 Unity VR眼镜端播放器开发与优化】第四节|高分辨率VR全景视频播放性能优化

文章目录 * 《VR 360°全景视频开发》专栏 * Part 3|Unity VR眼镜端播放器开发与优化 * 第一节|基于Unity的360°全景视频播放实现方案 * 第二节|VR眼镜端的开发适配与交互设计 * 第三节|Unity VR手势交互开发与深度优化 * 第四节|高分辨率VR全景视频播放性能优化 * 一、挑战分析与目标设定 * 1.1 主要瓶颈 * 1.2 目标设定 * 二、硬解与软解方案选型 * 2.1 平台解码能力检测 * 2.2 推荐策略 * 三、视野裁剪与分块播放 * 3.1 原理说明 * 3.2 实现流程图 * 3.3 伪代码 * 四、动态降级与多码率自适应 * 4.1

吃透 AM32 无人机电调:从源码架构到工作原理的全方位解析(附实践指南)(上)

开篇:为什么要深度剖析 AM32 电调? 作为多旋翼无人机的 “动力心脏”,电调(电子调速器)的性能直接决定了无人机的飞行稳定性、响应速度和续航能力。而 AM32 系列电调凭借开源性、高性价比、适配性强三大优势,成为了开源无人机社区的热门选择 —— 从入门级的 2204 电机到专业级的 2306 电机,从 3S 锂电池到 6S 高压电池,AM32 都能稳定驱动。 但很多开发者和爱好者在接触 AM32 源码时,常会陷入 “看得懂代码,看不懂逻辑” 的困境:为什么 FOC 算法要做坐标变换?DShot 协议的脉冲怎么解析?保护机制是如何实时触发的? 这篇博客将从硬件基础→源码架构→模块解析→工作原理→实践操作五个维度,逐行拆解 AM32 电调固件源码,帮你彻底搞懂

【选型】地瓜机器人RDK系列选型指南:X3 vs X5 vs S100 vs S100P(含资源对比图)

【选型】地瓜机器人RDK系列选型指南:X3 vs X5 vs S100 vs S100P(含资源对比图)

在机器人开发领域,地瓜机器人(D-Robotics)凭借其“RDK(Robot Developer Kit)”系列开发套件,已成为众多开发者和创业团队的首选平台。从轻量级边缘计算到高性能具身智能,地瓜机器人已构建了覆盖多场景的完整产品线,致力于为开发者提供高性价比、高集成度、高扩展性的解决方案。其核心芯片“旭日®”系列持续迭代,推动AI与机器人深度融合,助力实现从感知到控制的全链路自主化。 本文将深入对比当前主流的四款RDK开发套件:RDK X3、RDK X5、RDK S100、RDK S100P,并提供详细的资源对比图与应用场景分析,帮助你快速完成技术选型,降低开发门槛,提升项目落地效率。 一、产品定位概览 在深入参数前,先明确每款产品的核心定位,以便根据项目阶段、预算和性能需求做出合理选择。 ● RDK X3:轻量级边缘AI计算模组,适合入门级机器人、智能摄像头、无人机等低功耗、小体积场景。是初学者和教育项目的理想起点,具备基础AI推理能力,可快速搭建视觉识别系统。 ● RDK

面向无人机和智能手机的YOLOv8模型改进实战—棉叶病虫害多尺度目标检测

面向无人机和智能手机的YOLOv8模型改进实战—棉叶病虫害多尺度目标检测

一、引言        YOLO(You Only Look Once)作为一种非常流行的目标检测算法,以其高效和准确性而闻名。YOLOv8作为这一系列算法较新颖的版本,相较于其前身在准确性、速度和模型大小方面都有所改进。博主将分享1篇发表在《Industrial Crops and Products》(中科院1区TOP)的“CPD-YOLO: A cross-platform detection method for cotton pests and diseases using UAV and smartphone imaging”,说明YOLOv8模型在跨平台(无人机+手机)场景下的棉花病虫害多尺度目标检测中的应用。主要改进点如下: (1)特征融合改善:构建Bi-FPN +RepTransformer网络,实现多尺度特征的高效融合与推理加速。 (2)检测头增加:设计4头动态检测网络,赋予模型多维动态感知能力。 (3)损失函数优化: