台南市网站建设_网站建设公司_色彩搭配_seo优化
2025/12/23 2:08:47 网站建设 项目流程

从零构建SPI主控制器:Vivado实战全记录

你有没有遇到过这样的场景?手头有个传感器,文档写得清清楚楚“支持SPI接口”,可你的FPGA板子上偏偏没有现成的IP核可用。这时候,是去翻Xilinx库找现成模块,还是自己动手写一个?

我选后者。

今天就带你用Vivado + Verilog,从无到有实现一个SPI主控制器——不靠IP、不调例程,一行行代码敲出来,一步步验证调通。这不仅是一次接口设计实践,更是对FPGA开发全流程的深度锤炼。


SPI不是“串口”,它是高速通信的底层利器

先别急着打开Vivado,咱们先把协议吃透。很多人把SPI当成和UART一样的“串口”,其实大错特错。

SPI(Serial Peripheral Interface)是由Motorola提出的一种同步、全双工、主从式串行总线,典型四线结构:

  • SCLK:时钟线,由主设备驱动;
  • MOSI:主发从收;
  • MISO:主收从发;
  • CS/SS:片选信号,低电平有效。

它没有地址帧、不需要应答机制,通信完全由主设备掌控。正因为这种“霸道总裁”式的控制方式,SPI才能做到几十MHz的传输速率——远超I2C,也比UART灵活得多。

但自由意味着责任。SPI最大的坑在哪?四个字:模式匹配

四种模式怎么选?看懂CPOL和CPHA就够了

SPI允许配置两种关键参数:

  • CPOL(Clock Polarity):空闲时钟电平
  • CPHA(Clock Phase):数据采样边沿

组合起来就是四种工作模式:

CPOLCPHA模式说明
00空闲低,上升沿采样
01空闲低,下降沿采样
10空闲高,上升沿采样
11空闲高,下降沿采样

比如ADXL345加速度计常用Mode 3(CPOL=1, CPHA=1),而W25Q64 Flash芯片多用Mode 0(CPOL=0, CPHA=0)。两边配错了,哪怕波形再漂亮,数据也是乱的。

所以第一原则:查手册!查手册!查手册!

我们这次的目标是从Mode 0开始,搞定最常见的配置。


动手写Verilog:状态机才是灵魂

打开Vivado,新建RTL工程,目标器件选Zybo Z7上的XC7Z020。然后新建spi_master.v文件,准备开干。

核心思路很清晰:用状态机控制流程,分频器生成SCLK,移位寄存器处理数据

系统时钟100MHz,目标SCLK为10MHz,那就要每5个周期翻转一次(半周期计数)。数据8位,高位先行,CPHA=0 → 在SCLK第一个边沿采样。

module spi_master ( input clk, input rst_n, input start, input [7:0] data_in, output reg sclk, output mosi, input miso, output reg cs_n, output reg done, output reg [7:0] data_out );

端口定义简单直接。重点来了——状态机怎么划分?

四个状态走天下:IDLE → START → TRANSFER → STOP

localparam STATE_IDLE = 2'd0; localparam STATE_START = 2'd1; localparam STATE_TRANSFER = 2'd2; localparam STATE_STOP = 2'd3; reg [1:0] state;
  • IDLE:等start信号;
  • START:拉低CS,准备发数据;
  • TRANSFER:逐位移出MOSI,同时从MISO接收;
  • STOP:释放CS,置位done

中间最关键的TRANSFER阶段,我们要在每个SCLK周期完成三件事:
1. 把当前最高位送到MOSI;
2. 读取MISO并左移进接收寄存器;
3. 发送寄存器左移一位,补0;

因为是Mode 0,SCLK空闲为低,上升沿驱动数据,下降沿采样。所以我们让SCLK在计数器归零时翻转,并在此刻更新MOSI。

always @(posedge clk or negedge rst_n) begin if (!rst_n) begin sclk <= 1'b0; end else if (sclk_en && sclk_cnt == 0) sclk <= ~sclk; end

注意这里sclk_en只在TRANSFER状态下使能,避免干扰其他阶段。

至于数据传输部分:

recv_reg <= {recv_reg[6:0], miso}; shift_reg <= {shift_reg[6:0], 1'b0};

典型的移位操作。发送寄存器不断左移,低位补0;接收寄存器则把新来的bit挤进去。

最后当bit_cnt == 7时,表示第8位已经发出,下一个SCLK后就可以进入STOP状态了。

整个逻辑干净利落,资源占用极小——一个小型状态机+几个寄存器,就能撑起一套完整通信协议。


Vivado实战:仿真、约束、烧录一步不能少

代码写完只是开始,真正考验在工具链。

先仿真:别急着上板,波形对了再说

创建测试平台tb_spi_master.v,给个100MHz时钟,复位后拉高start信号。

关键点在于模拟从机行为。我们可以假设从机回传固定值0xA5:

wire [7:0] slave_out = 8'hA5; assign miso = (!cs_n && sclk) ? slave_out[7-bit_cnt] : 1'bz;

运行Behavioral Simulation,观察波形:

  • cs_n是否在start后及时拉低?
  • SCLK是否稳定输出10MHz方波?
  • MOSI是否依次送出0x5A的每一位(即0101_1010)?
  • MISO回传的数据能否被正确采集成0xA5?

重点关注建立时间与保持时间。如果发现数据还没稳定就被采样,就得调整时序逻辑。

小技巧:在Waveform窗口右键选择“Radix → Binary”,方便查看每一位变化。

再约束:引脚分配决定生死

仿真通过后,必须添加.xdc文件绑定物理引脚。这个步骤极其重要——哪怕逻辑完美,接错一个PIN就全盘皆输。

根据Zybo Z7原理图,分配如下:

set_property PACKAGE_PIN J15 [get_ports clk] ; # 100MHz clock set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN G18 [get_ports rst_n] ; # Reset button set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property PACKAGE_PIN E18 [get_ports mosi] set_property IOSTANDARD LVCMOS33 [get_ports mosi] set_property PACKAGE_PIN D18 [get_ports miso] set_property IOSTANDARD LVCMOS33 [get_ports miso] set_property PACKAGE_PIN F18 [get_ports sclk] set_property IOSTANDARD LVCMOS33 [get_ports sclk] set_property PACKAGE_PIN G19 [get_ports cs_n] set_property IOSTANDARD LVCMOS33 [get_ports cs_n]

建议使用Vivado的“I/O Planning”视图进行图形化布局,直观又不易出错。

最后综合与实现

点击“Run Synthesis” → “Run Implementation” → “Generate Bitstream”。

如果出现警告“Some pins have no driver”,检查是否遗漏了输出连接;
如果有时序违例(Timing Violation),可能需要优化分频逻辑或插入流水级。

一切顺利的话,你会得到一个.bit文件。


上板调试:ILA让你看清每一拍

下载前,强烈建议插入ILA核抓波形。毕竟实际硬件环境复杂,线长、噪声、电源波动都可能导致仿真没出现的问题。

如何添加ILA?

  1. 在Sources面板中右键 → Add Source → Add IP;
  2. 搜索ila,添加ila_0
  3. 配置输入探针数量(至少5个:sclk, mosi, miso, cs_n, done);
  4. 将待观测信号连接过去:
ila_0 u_ila ( .clk(clk), .probe0(sclk), .probe1(mosi), .probe2(miso), .probe3(cs_n), .probe4(done) );

重新综合并生成比特流。

烧录后打开Hardware Manager,连接设备,启动ILA捕获。设置触发条件为start == 1,你就能看到真实运行中的SPI时序!

常见问题排查:

  • CS没拉低?→ 检查状态跳转逻辑;
  • SCLK频率不对?→ 核对分频系数;
  • 接收数据错位?→ 查看采样边沿是否与外设要求一致;
  • done信号未置起?→ 检查bit_cnt计数边界。

有了ILA,调试效率提升十倍不止。


能不能更进一步?这些扩展值得尝试

基础版跑通之后,不妨思考几个进阶方向:

多从机管理怎么做?

可以通过解码逻辑扩展多个CS信号:

output reg [3:0] cs_array_n; // 控制4个从设备

CPU先发命令指定目标设备编号,再启动传输。

支持不同SPI模式?

可以把CPOL/CPHA作为输入参数,动态配置SCLK生成逻辑和采样时机。不过要注意,某些模式下需要延迟半个周期再开始采样。

和MicroBlaze联动?

完全可以把这个模块封装成AXI-Lite外设,挂在总线上。CPU通过写寄存器来发送数据,读寄存器获取结果。这才是真正的“软硬协同”。

甚至可以结合AXI DMA,实现大批量数据自动搬运,适用于图像传感器或高速ADC采集场景。


写在最后:为什么我们要亲手造轮子?

你可能会问:Xilinx不是有AXI Quad SPI IP吗?干嘛还要自己写?

答案很简单:理解本质,掌控细节

IP黑盒固然方便,但它屏蔽了太多底层信息。当你面对一个通信失败的现场,只知道“IP没输出”是没有意义的。而如果你亲手写过SPI控制器,你会本能地想到:

  • 是不是CPOL配反了?
  • 分频系数算错了?
  • CS释放太早?
  • 时钟域没同步?

这些问题的答案,藏在每一行代码里,也藏在每一次ILA抓波中。

掌握Vivado全流程,不只是会点按钮生成比特流。而是要能从协议理解、代码实现、仿真验证、物理约束到硬件调试,形成闭环能力。

下次当你需要对接一个新的SPI传感器,或者要在资源紧张的低端FPGA上省下几百LUT,你会发现:原来我自己就是一个IP generator

如果你正在学习FPGA开发,不妨就从这个SPI控制器开始练起。评论区留下你的实现心得,我们一起讨论优化方案。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询