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

Stable-Diffusion-v1-5-archive效果可解释性:注意力热力图可视化与Prompt关键token分析

Stable-Diffusion-v1-5-archive效果可解释性:注意力热力图可视化与Prompt关键token分析 你有没有遇到过这样的情况:精心构思了一段提示词,满怀期待地点击生成,结果出来的图片却和你想的完全不一样?或者,你只是微调了几个词,生成的图片却天差地别。 这背后,是Stable Diffusion这个“黑盒”在作祟。我们输入文字,它输出图片,但中间发生了什么,我们一无所知。今天,我们就来给这个“黑盒”开一扇窗,通过注意力热力图可视化和Prompt关键token分析,看看SD1.5模型到底是如何“理解”你的提示词,并一步步“画”出图片的。 理解这个过程,不仅能让你从“玄学调参”走向“科学创作”,更能让你精准控制画面,让AI真正成为你手中得心应手的画笔。 1. 为什么需要可解释性?告别“抽卡”式生成 在使用Stable Diffusion v1.5 Archive这类文生图模型时,很多用户的感觉像是在“抽卡”——输入提示词,

机器人具身智能概念

机器人具身智能概念 用"核心定义→指标表现→标准体系"的三段式结构。核心定义部分强调"身体"与"智能"融合的本质,指标部分结合EIBench和GM-100两个评测体系的具体指标,标准部分引用工信部标委会的工作方向。这样既有理论高度,又有具体的量化方法和官方标准依据。 具身智能(Embodied AI) 是人工智能领域一种更为高级的范式。它不仅仅是给机器人装上一个"大脑",而是强调智能必须通过物理身体与环境的实时互动才能产生和进化。简单来说,具身智能 = 机器人的"身体" + 人工智能的"大脑" + 与真实世界互动的能力。 要判断一个机器人是否属于具身智能,不能只看它是否能动,而是要系统性地考察它的"大脑"是否聪明、“身体"

FPGA 和 IC,哪个前景更好?怎么选?

FPGA 和 IC,哪个前景更好?怎么选?

这几年,经常有人来问我: “老师,我是做 FPGA 的,要不要转 IC?” “FPGA 是不是天花板低?” “IC 听起来更高端,是不是更有前景?” 这个问题,本质不是技术问题,而是路径问题。 今天我们把这两个方向掰开讲清楚。 —— 01 先讲定位 如果把整个芯片产业链拆开来看,大致是: 架构 → RTL → 前端验证 → 后端实现 → 流片 → 封测 → 量产 IC 属于“芯片最终形态”,FPGA 属于“可重构硬件平台”。 IC 的目标,是做出一颗定制化、极致性能、极致功耗、极致成本的芯片。 FPGA 的目标,是用可编程逻辑,在无需流片的前提下,实现接近硬件级别的性能。 两者不是上下级关系,而是不同阶段、不同诉求下的解决方案。 很多真正量产前的芯片项目,都会先在

当AI绘画遇上风格翻译:SDXL Prompt Styler如何破解创作失控难题

当AI绘画遇上风格翻译:SDXL Prompt Styler如何破解创作失控难题 【免费下载链接】sdxl_prompt_styler 项目地址: https://gitcode.com/gh_mirrors/sd/sdxl_prompt_styler 为什么同样的"未来主义建筑"提示词,有人能生成杂志级渲染图,而你得到的却是模糊不清的拼凑图像?在AI绘画的世界里,业余爱好者与专业创作者之间隔着一道看不见的鸿沟——这道鸿沟并非来自创意差异,而是源于对提示词工程的掌握程度。SDXL Prompt Styler的出现,正在将这道鸿沟转化为通途,让每个创作者都能获得专业级的风格控制能力。 创作困境:当灵感遭遇技术瓶颈 你是否经历过这样的创作挫折:脑海中清晰的画面,却无法通过文字准确传达给AI?输入"梦幻森林",得到的可能是色彩俗艳的儿童插画;尝试"赛博朋克城市",结果却是元素堆砌的混乱场景。这种&