如何建设网站兴田德润可信赖企业网站 建设 流程
2026/5/21 19:36:11 网站建设 项目流程
如何建设网站兴田德润可信赖,企业网站 建设 流程,服务器上构建企业网站,图片转换成网址链接用FPGA搭一座通信桥#xff1a;手把手教你用Vivado实现SPI转UART桥接器你有没有遇到过这种情况——手头的主控只有SPI接口#xff0c;但要连的模块偏偏只支持UART#xff1f;比如想把传感器数据通过蓝牙#xff08;UART#xff09;发出去#xff0c;结果MCU腾不出串口手把手教你用Vivado实现SPI转UART桥接器你有没有遇到过这种情况——手头的主控只有SPI接口但要连的模块偏偏只支持UART比如想把传感器数据通过蓝牙UART发出去结果MCU腾不出串口只能走SPI。这时候软件模拟太慢、外接协议转换芯片又占PCB空间……怎么办答案是用FPGA自己做一个硬件级协议转换桥。今天我们就来实战一次使用Xilinx Vivado工具链在一块常见的Zynq或Artix-7开发板上从零搭建一个SPI转UART桥接器。整个过程不调用复杂IP核全程基于Verilog行为描述带你真正理解底层逻辑是如何打通两种“语言不通”的通信协议的。为什么选FPGA做协议转换先说清楚一个问题这种桥接功能难道不能用单片机写个转发程序搞定吗可以但有代价延迟高软件需要中断响应、协议解析、再封装发送吞吐受限主频不够时容易丢包资源占用本就不宽裕的MCU还要分心处理转发任务。而FPGA不同。它用纯硬件并行逻辑完成协议解析与重构数据来了直接搬几乎无延迟还能同时处理多个通道。更重要的是你可以完全自定义帧格式、波特率、SPI模式灵活性远超成品转换芯片。我们这次的目标很明确让外部SPI主机比如STM32向FPGA发送任意字节FPGA自动将其以115200bps的UART格式广播出去PC端串口助手能实时看到数据。听起来简单别急中间藏着不少坑。接下来咱们一步步拆解。协议层解剖SPI和UART到底差在哪SPI —— 同步高速靠时钟说话SPI是典型的同步串行总线四根线打天下SCLK、MOSI、MISO、CS。它的特点是有时钟线SCLK收发双方步调一致数据按位传输通常8位为一帧主设备掌控全局从设备被动响应常见工作模式由CPOL空闲电平和CPHA采样边沿决定最常用的是模式0CPOL0, CPHA0时钟空闲低电平上升沿采样。在我们的设计中FPGA作为SPI从机等待主机拉低CS后在每个SCLK上升沿读取MOSI上的数据位移入寄存器直到凑成一个字节。关键点来了SCLK是外来时钟这意味着我们必须把它同步到FPGA内部系统时钟域否则会出亚稳态轻则误码重则死机。UART —— 异步通信全靠约定UART完全不同它是异步串行协议没有共享时钟。发送方和接收方只靠事先约好的波特率来对齐每一位的时间长度。标准8N1帧结构如下[起始位(0)] [D0][D1]...[D7] [停止位(1)] 共10位发送时先拉低1 bit时间作为起始信号然后低位先行逐位输出最后以高电平停止位收尾。接收端检测到下降沿后启动定时器在每位中间点采样多次取多数提高抗干扰能力。常见波特率如9600、115200等。我们选用115200bps对应每位持续约8.68μs。若系统时钟为50MHz周期20ns则每bit需计数约434次。所以我们需要一个波特率发生器本质就是一个分频计数器。模块设计四个核心部件拼出完整桥梁整个系统架构非常清晰[SPI Master] │ SCLK, MOSI, CS ▼ ┌──────────────┐ │ SPI从机接收模块 │←─ 外部时钟域 └──────────────┘ │ 字节数据 valid ▼ ┌──────────────┐ │ FIFO缓冲区 │←─ 跨时钟域同步 └──────────────┘ │ 数据 请求 ▼ ┌──────────────┐ │ UART发送模块 │←─ 内部时钟域 (50MHz) └──────────────┘ │ TXD ▼ [PC / UART设备]下面我们逐个击破这四个模块。核心模块1SPI从机接收器FPGA作为SPI Slave面临的最大挑战是——SCLK不是我自己的时钟解决办法只有一个所有输入信号必须先同步到本地时钟域。我们假设系统主时钟为clk_50m来自板载晶振。而SCLK可能高达10MHz甚至更高但我们并不关心它的频率只要能在每个有效边沿准确捕获MOSI即可。module spi_slave_rx ( input clk_50m, // 系统时钟 input rst_n, input sclk_in, // 外部SCLK input cs_n_in, // 片选低有效 input mosi_in, // 主机发送数据 output reg data_valid, // 数据就绪标志 output reg [7:0] rx_data // 接收到的字节 ); // 双级同步器防止亚稳态 reg sclk_sync1, sclk_sync2; reg cs_n_sync1, cs_n_sync2; reg mosi_sync; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin sclk_sync1 1b1; sclk_sync2 1b1; cs_n_sync1 1b1; cs_n_sync2 1b1; end else begin sclk_sync1 sclk_in; sclk_sync2 sclk_sync1; cs_n_sync1 cs_n_in; cs_n_sync2 cs_n_sync1; end end assign mosi_sync sclk_sync2 ? mosi_in : mosi_sync; // 在下降沿锁住值 // 上升沿检测 wire sclk_rising (sclk_sync2 1b0) (sclk_sync1 1b1); // 移位寄存器 计数器 reg [7:0] shift_reg; reg [2:0] bit_count; reg rx_busy; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin shift_reg 8h00; bit_count 3d0; rx_busy 1b0; rx_data 8h00; data_valid 1b0; end else begin data_valid 1b0; // 默认无效 if (cs_n_sync2 1b0 sclk_rising) begin // 正在传输中移位计数 shift_reg {shift_reg[6:0], mosi_sync}; bit_count bit_count 1d1; if (bit_count 3d7) begin // 最后一位已接收 rx_data {shift_reg[6:0], mosi_sync}; data_valid 1b1; bit_count 3d0; rx_busy 1b0; end else begin rx_busy 1b1; end end // 新传输开始CS刚拉低 if (cs_n_sync2 1b0 cs_n_sync1 1b1) begin bit_count 3d0; rx_busy 1b1; end end end endmodule✅重点说明- 所有外部输入都经过两级触发器同步-mosi_sync在SCLK下降沿锁定确保在上升沿采样时稳定- 使用bit_count记录当前位数第8位到来时打包输出并置data_valid1-data_valid脉冲宽度为一个时钟周期可用于触发后续操作。核心模块2跨时钟域FIFO缓冲SPI和UART很可能运行在不同时钟下。更麻烦的是SPI的SCLK是外部给的节奏不定而UART发送依赖内部稳定时钟。如果不加缓冲一旦UART正在发前一字节新来的SPI数据就会被覆盖丢失。解决方案加一级异步FIFO。当然你可以手动写双口RAM格雷码指针但Vivado提供了傻瓜式生成方式IP Catalog → Search “FIFO Generator”设置- Interface Type:Common Clock Block RAM- Width: 8- Depth: 16够用了- Almost Full/Empty Threshold: 可选Generate生成后的模块名叫类似fifo_8x16_async接口如下fifo_8x16_async u_fifo ( .rst(rst_n), .wr_clk(clk_50m), .rd_clk(clk_50m), // 如果写读同频可共用 .din(rx_data), .wr_en(data_valid), .dout(tx_data), .rd_en(tx_take), .full(), .empty(fifo_empty), .valid(fifo_valid) );虽然叫“异步”但因为我们把SCLK同步到了clk_50m所以这里实际上可以用同步FIFO简化设计。不过留着也无妨将来扩展成真正异步场景也能兼容。核心模块3UART发送器带状态机这个模块负责把字节变成UART波形。核心是一个四状态机IDLE空闲TX1START发起始位0DATA_X依次发出D0~D7STOP发停止位1同时配合一个波特率计数器控制每个状态持续约1位时间。module uart_tx ( input clk_50m, input rst_n, input tx_start, // 启动发送 input [7:0] tx_data, // 待发数据 output reg uart_txd, output tx_done // 发送完成 ); localparam CLK_FREQ 50_000_000; localparam BAUD 115200; localparam BIT_CYCLE CLK_FREQ / BAUD; // ~434 reg [15:0] counter; reg [3:0] state; reg [7:0] shift_reg; wire timeout (counter BIT_CYCLE - 1); parameter IDLE 4d0; parameter START 4d1; parameter D0 4d2; parameter D1 4d3; parameter D2 4d4; parameter D3 4d5; parameter D4 4d6; parameter D5 4d7; parameter D6 4d8; parameter D7 4d9; parameter STOP 4d10; always (posedge clk_50m or negedge rst_n) begin if (!rst_n) begin state IDLE; counter 16d0; shift_reg 8h00; uart_txd 1b1; end else begin case (state) IDLE: begin uart_txd 1b1; if (tx_start) begin shift_reg tx_data; state START; counter 16d0; end end START: begin uart_txd 1b0; if (timeout) begin state D0; counter 16d0; end else counter counter 1; end D0: begin uart_txd shift_reg[0]; if (timeout) begin state D1; counter 0; end else counter counter 1; end D1: begin uart_txd shift_reg[1]; if(timeout) begin stateD2;counter0;end else countercounter1; end D2: begin uart_txd shift_reg[2]; if(timeout) begin stateD3;counter0;end else countercounter1; end D3: begin uart_txd shift_reg[3]; if(timeout) begin stateD4;counter0;end else countercounter1; end D4: begin uart_txd shift_reg[4]; if(timeout) begin stateD5;counter0;end else countercounter1; end D5: begin uart_txd shift_reg[5]; if(timeout) begin stateD6;counter0;end else countercounter1; end D6: begin uart_txd shift_reg[6]; if(timeout) begin stateD7;counter0;end else countercounter1; end D7: begin uart_txd shift_reg[7]; if(timeout) begin stateSTOP;counter0;end else countercounter1; end STOP: begin uart_txd 1b1; if (timeout) begin state IDLE; end else counter counter 1; end default: state IDLE; endcase end end assign tx_done (state IDLE) !(tx_start state IDLE); // 避免误触发 endmodule⚠️ 注意事项- 波特率精度很重要50MHz/115200 ≈ 434.027实际取整会有±0.06%误差在容限范围内- 若换其他时钟或波特率请重新计算分频值-tx_done用于通知FIFO可以弹出下一字节。顶层整合把积木搭起来现在三个核心模块都有了最后一块拼图是控制逻辑当SPI收到数据 → 写FIFOUART空闲且FIFO非空 → 读FIFO并启动发送。module spi_uart_bridge_top ( input clk_50m, input rst_n, input spi_sclk, input spi_cs_n, input spi_mosi, output uart_txd ); wire data_valid; wire [7:0] rx_data; wire fifo_valid; wire [7:0] tx_data; wire tx_done; wire fifo_empty; wire tx_start; // 模块实例化 spi_slave_rx u_spi_rx ( .clk_50m(clk_50m), .rst_n(rst_n), .sclk_in(spi_sclk), .cs_n_in(spi_cs_n), .mosi_in(spi_mosi), .data_valid(data_valid), .rx_data(rx_data) ); fifo_8x16_async u_fifo ( .rst(!rst_n), .wr_clk(clk_50m), .rd_clk(clk_50m), .din(rx_data), .wr_en(data_valid), .dout(tx_data), .rd_en(tx_done), // 发完自动读下一个 .full(), .empty(fifo_empty), .valid(fifo_valid) ); uart_tx u_uart_tx ( .clk_50m(clk_50m), .rst_n(rst_n), .tx_start(fifo_valid !fifo_empty), // 有数据且FIFO非空才启动 .tx_data(tx_data), .uart_txd(uart_txd), .tx_done(tx_done) ); endmodule你看整个逻辑就像流水线SPI进来 → 缓冲排队 → UART依次发出简洁高效全部硬件并行运行CPU零参与。Vivado实战工程怎么建别以为写完代码就完了下面才是真正考验——进Vivado跑通全流程。第一步创建工程打开Vivado 2023.1版本影响不大Create Project工程名填spi_uart_bridge选择 RTL Project → 不立即添加源文件添加刚才写的.v文件器件选型根据你的开发板选例如 Digilent Zybo Z7 选xc7z020clg400-1第二步管脚约束XDC文件这是最容易翻车的地方引脚一定得对应原理图。示例适用于某些Arty A7板# 输入时钟 set_property PACKAGE_PIN W5 [get_ports clk_50m] set_property IOSTANDARD LVCMOS33 [get_ports clk_50m] create_clock -period 20.000 -name clk_50m [get_ports clk_50m] # SPI信号 set_property PACKAGE_PIN U18 [get_ports spi_mosi] set_property PACKAGE_PIN V19 [get_ports spi_sclk] set_property PACKAGE_PIN T18 [get_ports spi_cs_n] set_property IOSTANDARD LVCMOS33 [get_ports {spi_*}] # UART输出 set_property PACKAGE_PIN W19 [get_ports uart_txd] set_property IOSTANDARD LVCMOS33 [get_ports uart_txd]务必查清你开发板的实际引脚编号第三步仿真验证不可少别急着烧板子先仿真看看波形对不对。写个简单的Testbench模拟SPI主机连续发两个字节0x55 和 0xAA。你会发现- 每次CS拉低后MOSI按位送出- FPGA在第八个SCLK后产生data_valid- UART随后依次发出两帧8N1数据间隔合理。用Vivado自带的Waveform Viewer查看确认无毛刺、无竞争。第四步综合 → 实现 → 生成比特流点击三步走1. Run Synthesis2. Run Implementation3. Generate Bitstream重点关注- 是否有时序违例Timing Violation- 资源占用多少LUT100FF200就算很轻量最后打开Hardware Manager通过JTAG下载.bit文件。实测效果连上串口助手看结果用USB转TTL模块CH340/CP2102将FPGA的uart_txd接到电脑。打开串口助手如XCOM、PuTTY设置波特率1152008N1。然后让SPI主机发几个字节试试—— 成功看到55 AA出现在窗口里了吗恭喜桥通了还能怎么升级这个基础版已经够用了但如果你想玩得更深可以考虑这些方向双向桥接加上UART接收 SPI发送实现全双工互转动态波特率检测通过首帧自动识别波特率集成ILA抓波形在线调试神器加几行属性就行结合MicroBlaze软核跑轻量FreeRTOS做智能路由上LVDS或RS485电平适应工业现场长距离传输。写在最后掌握的不只是工具而是思维方式很多人学Vivado只是学会了点按钮。但真正有价值的是什么是你开始思考如何把一个抽象需求拆解成可落地的硬件模块SPI转UART看似是个小项目但它涵盖了- 协议分析- 时序建模- 跨时钟域处理- 状态机设计- FIFO应用- 引脚约束与时序收敛这些能力才是你在FPGA世界立足的根本。下次当你面对I2C转CAN、SDIO转Ethernet的需求时就不会再慌了——因为你知道一切协议终将归于比特流。如果你动手实现了这个项目欢迎留言交流踩过的坑。也别忘了点赞分享让更多工程师少走弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询