恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

FPGA实战:SPI通信协议详解与Verilog控制器实现

  • 首页
  • 资讯中心
  • /
  • FPGA实战:SPI通信协议详解与Verilog控制器实现

相关资讯

大模型训练全流程与数据存储实践:从数据准备到本地微调部署 2026/9/5 3:29:28
开源桌宠项目:从零构建个性化桌面互动应用 2026/9/5 3:29:28
方程式赛车算法核心:油门解析、扭矩控制与状态估计实战 2026/9/5 3:29:28

最新资讯

空心杯舵机的选型、测试与调试:OSR6猎鹰实战指南
从单层氧化石墨烯到家用陶瓷水壶:远红外加热与小分子团水的物理基础(白露饮水场景应用)
从交通灯到LCVCO:射频振荡器设计与相位噪声优化
校园气象站厂家怎么选才不踩坑?从传感器到云平台的全流程避坑指南
STM32开发三大坑:调试连接、工程模板与黑盒思维
游戏开发中的三角剖分算法:从耳切法原理到C++工程实践

今日推荐

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流
幂等性设计:在 Agent 自动重试与工具执行中的防重复扣费实战
向量检索与标量过滤混合查询:PostgreSQL pgvector 与 Milvus 的过滤下推实操

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

FPGA实战:SPI通信协议详解与Verilog控制器实现

发布时间:2026/9/5 3:29:28
FPGA实战:SPI通信协议详解与Verilog控制器实现 1. 项目定位与整体方案设计FPGA开发里SPI通信算是最常碰到的接口之一。搞过几年FPGA的人基本都有这种体会刚入门时用SPI驱动ADC采集数据后来用SPI配置射频芯片的寄存器再后来发现SPI Flash、SD卡、传感器模块全都在用它。这玩意儿简单归简单但真要在一个FPGA工程里把它做稳、做快、做好用还是有不少门道值得掰扯掰扯。先说清楚这个项目到底在干嘛。所谓SPI通信的FPGA实现就是不用芯片厂商封装好的SPI外设IP核而是用Verilog或者VHDL语言在FPGA内部把SPI主从机的时序逻辑自己搭出来。你可能要问Xilinx、Intel这些FPGA厂商的IP核库里明明就有现成的SPI IP直接拖出来用不就行了这个问题我后面会专门聊这里先记住一个结论自己写SPI逻辑不是吃饱了撑的而是为了搞懂时序、方便定制、方便移植、方便集成到自己的状态机框架里。这个项目的目标很明确在FPGA上实现一个可配置的SPI主机控制器支持四种工作模式能够通过简单的寄存器接口控制读写操作同时预留扩展能力方便后续挂不同的从设备。说人话就是写一个通用的SPI master模块拿它去和板子上的各种SPI从设备打交道。比如我现在常用的搭配是STM32H743做主控FPGA做协处理器两者之间通过FMC接口通信。FPGA这边负责采集前端的高速ADC数据做预处理然后通过FMC总线把处理结果交给ARM同时ARM也能通过FMC配置FPGA内部寄存器控制采样率、滤波参数这些东西。而SPI呢则是FPGA去配置ADC芯片的寄存器、读取传感器数据、操作Flash存储这些粗活累活。所以这个项目适合谁来参考如果你是FPGA初学者想找个合适的练手项目SPI控制器是个非常好的切入点逻辑量不大但麻雀虽小五脏俱全能学到状态机的设计、跨时钟域处理、时序约束这些基本功。如果你已经在做FPGA开发但手里的SPI模块总是有各种小毛病比如频率上不去、时序不干净、偶尔丢数据那你也能从这里找到一些排查思路。2. SPI通信的核心原理与四种工作模式2.1 为什么说SPI是“四条线讲清楚的事”SPI的全称是Serial Peripheral Interface串行外设接口。它本质上是一个主从架构的同步串行通信协议。同步这两个字很关键意思是通信双方共用同一个时钟信号时钟沿一到双方就知道该采样或者该移出数据了不像UART那样需要约定波特率再去对齐每一位。一个标准的SPI主机往外拉出来至少四根线。SCLK串行时钟由主机产生频率决定了通信速度。MOSIMaster Output Slave Input主机输出从机输入数据从主机流向从机的这条线上走。MISOMaster Input Slave Output从机输出主机输入数据从从机流回主机的通道。再就是CS片选信号低电平有效主机选中哪个从设备就把对应的CS拉低。片选在整个通信期间要保持有效通信结束再拉高。这里有个容易忽略的细节SPI是全双工的。什么意思就是在同一个时钟周期里主机通过MOSI发出去一位同时通过MISO收回来一位。发送和接收是同时进行的。这个特性在实际写代码的时候特别重要因为这意味着你每一次移位操作既是在发送新数据也是在接收旧数据。很多初学者写SPI master的时候只想着怎么把数据发出去忘了同时把收到的数据存下来结果读操作就废了。关于从设备的CS处理还有个小坑可以提前说。有些传感器模块支持多个从设备挂在同一条SPI总线上CS用来区分目标。但有些器件对CS的电平时序有特殊要求比如要求CS拉低后等待一小段时间才能开始给时钟通信结束后CS必须拉高保持一段时间才能再次拉低。这些细节规格书里都会写做驱动的时候要逐条对别偷懒。2.2 CPOL和CPHA到底在描述什么SPI通信最容易让人懵的就是四种模式也就是CPOL和CPHA的四种组合。搜SPI通信原理的时候肯定会看到这两个词但很多教程只是给个表格列一下四种模式对应的极性相位然后让你去查从设备的数据手册选对模式。但你要是不理解这两个参数到底在描述什么靠查手册选模式很容易翻车。CPOL叫时钟极性它决定的是空闲状态下SCLK的电平。CPOL为0空闲时SCLK是低电平有效状态是高电平。CPOL为1空闲时SCLK是高电平有效状态是低电平。就这么简单极性只管空闲状态长什么样不管别的。CPHA叫时钟相位它决定的是数据在时钟的哪个沿被采样。CPHA为0数据在第一个边沿被采样也就是第一个有效的时钟沿。CPHA为1数据在第二个边沿被采样。这里说的第一个和第二个边沿是从CS拉低之后开始算的。把这两个参数组合起来就有了四种模式。模式0CPOL0CPHA0空闲低电平第一个边沿也就是上升沿采样数据。模式1CPOL0CPHA1空闲低电平第二个边沿也就是下降沿采样。模式2CPOL1CPHA0空闲高电平第一个边沿也就是下降沿采样。模式3CPOL1CPHA1空闲高电平第二个边沿也就是上升沿采样。表格可以整理成这样的形式方便对照模式CPOLCPHA空闲SCLK电平数据采样边沿模式000低电平上升沿第一个边沿模式101低电平下降沿第二个边沿模式210高电平下降沿第一个边沿模式311高电平上升沿第二个边沿实际上绝大多数从设备默认支持模式0和模式3很多器件手册会明确标注支持SPI Mode 0/1/2/3你照着配就行。但这里有一个特别容易踩的坑有的FPGA工程里时钟极性配反了从设备那边也能偶尔通信成功因为数据的建立时间和保持时间还有余量不是每次都会出错。这种间歇性故障最恶心正常状态下可能跑几个小时都不出错温度一变或者线一长就开始随机丢数据。排查了半天最后发现是CPOL配反了真想抽自己一巴掌。所以写代码的时候最好把模式做成参数从设备一换顶层改个参数就行不需要动逻辑。这个习惯能帮你省下大量的调试时间。2.3 位宽、字节序和传输速率怎么定除了四种模式还有三个参数需要确定数据位宽、字节序和SCLK频率。数据位宽最常见的自然是8位因为大多数SPI从设备寄存器都是8位或者16位访问的。也有一些器件是12位、24位甚至32位的比如一些高精度ADC的转换结果就是24位。设计SPI master的时候位宽最好也是参数化的至少做到8位、16位、32位可配这样通用性才有保障。字节序的问题容易被忽略。同一个16位寄存器有的器件要求先发高字节再发低字节也就是大端模式有的器件反过来先发低字节再发高字节。尤其是一些混合信号芯片规格书里会画一个寄存器map显示MSB还是LSB先行。如果你写的SPI master固定从最高位开始发遇到那些要求LSB先行的器件就得在外层逻辑里做位序转换很麻烦。好的做法是把字节序也做成参数默认MSB先行需要的时候能切LSB先行。SCLK频率决定了SPI速度。这个速度能跑多快不完全由FPGA决定主要看从设备能承受多快的时钟。一般从设备的数据手册里会写最大SCLK频率比如10MHz、20MHz、50MHz不等。FPGA这边SPI时钟是从系统时钟分频得到的分频系数决定了实际频率。这里要说一个重要概念SPI时钟频率不一定要取整也不一定是系统时钟的整数分之一只要不超过从设备的上限就行。不过为了时序干净还是建议用偶数分频让高低电平时间尽量对称。3. SPI master控制器的Verilog实现3.1 模块划分和接口定义写FPGA代码的时候我习惯先把模块的接口定义清楚再往里填逻辑。SPI master这个模块从功能上可以分成三个层次。最外层是和上位机交互的寄存器接口可以是简单的并行读写接口也可以挂到AXI总线上。中间层是SPI协议引擎负责产生SCLK、控制CS、移位收发数据。最底层就是FPGA的引脚连接到外面的从设备。一个可复用的SPI master模块我一般会定义这些接口。系统信号是时钟和复位这个不用多说。寄存器接口部分有片选信号、读写地址、写数据、读数据、写使能、读使能这些。为了降低复杂度我通常把寄存器接口设计成类似SRAM的接口复用性好不管后面挂的是FMC总线还是AXI总线中间加一个转换桥就行。SPI物理接口部分有SCLK输出、MOSI输出、MISO输入、CS输出。这里要特别强调一下MISO的处理。MISO是从设备的数据输出线当CS拉高、从设备未被选中的时候MISO应该处于高阻态多个从设备的MISO线才能共连在一起。FPGA这边如果直接用内部逻辑去读一个来自引脚的三态信号需要做特殊处理。更稳妥的做法是做一个引脚三态控制逻辑把MISO先从引脚缓冲进来再经过三态门处理。在Xilinx的FPGA上这通常用IBUF和OBUFT原语组合实现。有的工程师图省事直接把MISO接内部逻辑也不做三态控制在只有一个从设备的场景下侥幸能用一旦挂多个从设备就开始打架了。寄存器接口这块我再多说一点。既然是要给ARM或者其他主控用的接口尽量简单直接。写操作主控把数据和地址放好拉一个时钟周期的写使能模块内部就把数据锁存到对应的控制寄存器。读操作主控给地址拉读使能模块把数据放到读数据总线上。这个接口风格和STM32的FSMC总线对RAM的访问时序非常接近后面接FMC的时候只需要做简单的时序对齐就行不用搞复杂的协议解析。3.2 状态机设计思路SPI master的核心是一个状态机。我见过有人用计数器硬撸出来的SPI时序也能跑但可读性和扩展性都比较差一旦要支持不同位宽或者插入延时代码改起来想死的心都有。用状态机来做逻辑更清晰也更容易加功能。我常用的状态划分是这样的。IDLE空闲状态CS保持高电平SCLK空闲等待触发信号。START准备状态CS拉低让从设备感知到片选有效如果有需要可以在这里插入延时。TRANSFER传输状态按照配置的模式产生SCLK每个时钟周期移出一位同时采样一位持续配置的位宽次数。STOP结束状态CS拉高如果有需要可以在这里插入延时然后回到IDLE。这里面设计的关键点有两个。第一个是SCLK的产生方式。如果直接用系统时钟去二分频产生SCLK在TRANSFER状态里SCLK每翻转一次就需要两个系统时钟周期。为了干净地产生符合CPOL和CPHA要求的时序我习惯把SCLK的边沿和系统时钟的边沿对齐用计数器来精确控制SCLK的翻转时刻。同时数据移出和采样的时刻也要严格对应到SCLK的边沿上去。第二个关键点是采样时刻的控制。以模式0为例数据在上升沿被采样。那么主机应该在SCLK上升沿到来之前就把数据放到MOSI线上也就是数据在上升沿之前就已经稳定了这样才能满足建立时间要求。实际上SPI通信中数据的移出通常发生在时钟的边沿而采样发生在另一个边沿。设计的时候要让数据移出的时刻比采样时刻提前半个时钟周期才能确保采样稳定。很多新手的SPI时序问题都是数据移出和采样的时刻没对齐导致的。我自己的实现方法是用一个内部计数器bit_cnt来记录已经传输的位数在每个SCLK有效沿判断是否移出和采样。移出数据的时刻我放在SCLK的下降沿处理采样数据的时刻放在SCLK的上升沿处理。这样对于模式0数据在下降沿切换到下一位在上升沿被采样每个bit有半个SCLK周期的建立时间满足大多数从设备的时序要求。对于其他模式只要调整移出和采样的边沿对应关系就能适配。3.3 关键代码片段解析以模式0为例我贴一段核心代码片段来说明状态机和移位的处理方式。这段代码的风格是标准的一种写法可读性优先没做什么激进的优化方便移植和修改。// SPI Master 核心状态机模式0CPOL0, CPHA0 localparam IDLE 2d0; localparam START 2d1; localparam TRANSFER 2d2; localparam STOP 2d3; reg [1:0] state, next_state; reg [7:0] clk_div_cnt; // SCLK分频计数器 reg sclk_reg; // SCLK寄存器输出 reg cs_reg; // CS寄存器输出 reg [4:0] bit_cnt; // 传输位计数器 reg [31:0] shift_reg; // 移位寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; sclk_reg 1b0; cs_reg 1b1; bit_cnt 0; shift_reg 0; end else begin state next_state; case (state) IDLE: begin cs_reg 1b1; // 空闲时CS拉高 sclk_reg 1b0; // CPOL0空闲低电平 end START: begin cs_reg 1b0; // 片选拉低通信开始 end TRANSFER: begin // SCLK翻转逻辑用clk_div_cnt控制二分频或N分频 if (clk_div_cnt DIV_PARAM-1) begin sclk_reg ~sclk_reg; clk_div_cnt 0; end else begin clk_div_cnt clk_div_cnt 1b1; end // 在SCLK的上升沿采样MISO下降沿移出MOSI // 模式0数据在上升沿采样 if (sclk_reg 1b0 sclk_next 1b1) begin mosi_reg shift_reg[BIT_WIDTH-1]; shift_reg {shift_reg[BIT_WIDTH-2:0], 1b0}; end else if (sclk_reg 1b1 sclk_next 1b0) begin shift_reg[0] miso_in; end // 传输完成判断 if (bit_cnt BIT_WIDTH-1) begin bit_cnt 0; next_state STOP; end else begin bit_cnt bit_cnt 1b1; end end STOP: begin cs_reg 1b1; // CS拉高 end endcase end end这里要说明几个容易搞错的细节。第一sclk_reg不是直接接在系统时钟上的它经过了分频和控制逻辑生成所以用sclk_reg的边沿作为事件来触发采样和移位实际上是在系统时钟域里做事不要用sclk_reg去做always块的时钟。这是新手最容易犯的错误——想用生成的SCLK作为时钟沿去驱动数据逻辑。这样做的危害在于SCLK在FPGA内部不是时钟网络而是普通信号用它做时钟会产生时序收敛问题而且布线延迟大容易出毛刺。正确做法是把SCLK作为普通信号对待在系统时钟域里检测它的边沿。第二数据移出和采样的边沿要严格按照模式要求设置。我上面代码里写的模式0逻辑实际是上升沿采样、下降沿移出这样在上升沿到来时MOSI已经稳定能满足从设备的数据建立时间要求。不要想着让数据在SCLK的同一个边沿移出又采样物理上做不到时序上也根本站不住脚。第三DIV_PARAM这个分频参数决定了SCLK的频率。如果你希望SCLK是系统时钟的N分频那么DIV_PARAM应该是N的整数倍的一半因为SCLK翻转一次需要N/2个系统周期。如果N选小了比如N2那么SCLK的最高频率就是系统时钟的一半。实际设计中我一般会限制SCLK频率保证系统时钟至少是SCLK的四倍以上否则边沿检测和数据处理的时间余量很难保证。3.4 怎么把模式切换和字节序做成参数化刚才代码里只写了模式0的情况实际项目里怎么可能只用一种模式。换一个从设备可能就需要切到模式3或者模式1。好的模块设计一定要把模式做成参数。这个参数化过程并不复杂无非是控制空闲时SCLK的电平以及决定采样和移出边沿的组合。我习惯把CPOL和CPHA分别做参数然后根据这两个参数生成相应的控制逻辑。CPOL参数容易空闲电平直接由它决定注意IDLE状态里赋初值就可以了。CPHA参数麻烦一点它决定的是采样发生在第一个边沿还是第二个边沿。对于不同CPHA的取值采样沿和移出沿的组合是CPHA采样沿移出沿0第一个边沿第二个边沿1第二个边沿第一个边沿把这个对应关系转换成具体的SCLK边沿再配合CPOL确定哪个边沿是上升沿、哪个边沿是下降沿就能用两个参数推导出一组完整的控制逻辑。写代码的时候用generate或者if-else把映射关系展开就行。对于字节序我一般是在退出TRANSFER状态之后把移位寄存器里的数据做一次位反转处理。如果LSB先行使能就把结果数组倒过来再写到读数据寄存器。这样做的好处是SPI物理层逻辑不用改只在数据收发的边界做转换逻辑上比较干净。4. 时序约束与仿真验证4.1 为什么要给SPI信号加约束很多入门FPGA的人会有个疑问SPI接口速度又不高系统时钟几十兆上百兆都跑得好好的SCLK才几兆还需要做时序约束吗这个问题我被问过不少次答案是需要而且必须加。不加约束的后果是怎样的在Xilinx Vivado或者Intel Quartus里如果不对SPI引脚加约束工具默认会按照系统时钟域的约束去分析生成时钟和被驱动逻辑之间没有明确的关系工具只会对系统时钟内部的路径做时序分析。SPI接口因为跨时钟域时序分析报告里可能不会报出问题但实际板子上就是偶尔出错这是最难受的。正确做法是给SPI接口加上输出约束设置output delay让工具知道SCLK到板子上的负载之间存在多大的延迟预算。对于SCLK引脚自身应该声明为生成时钟然后通过set_output_delay告诉工具数据相对于时钟的延迟范围。我一般使用虚拟时钟的方式把外部从设备的时钟特性描述清楚然后约束FPGA输出到引脚的路径。举一个实际的例子。如果SPI从设备要求SCLK上升沿前5ns数据就绪那么在Vivado里可以这样约束# 创建虚拟时钟模拟外部SCLK特性 create_clock -name virtual_sclk -period 50 [get_ports {sclk_out}] # 约束MOSI相对于SCLK的输出延迟 set_output_delay -clock virtual_sclk -min -1.0 [get_ports {mosi_out}] set_output_delay -clock virtual_sclk -max 5.0 [get_ports {mosi_out}]这里的数值要根据具体器件的建立时间要求来定。我上面写的只是一个示意真实项目里要从数据手册里查到建立时间要求再去算出具体的output delay值。这个约束的意义在于让FPGA工具在布局布线时就知道你的SCLK和MOSI之间要有足够的时序余量避免把MOSI的数据路径布得太长导致建立时间不够。4.2 仿真的套路与技巧写完SPI master第一步不是上板测试而是做行为仿真。这是FPGA开发的基本素养。写一个testbench例化SPI master在仿真环境里模拟一个SPI从设备跑几组不同模式、不同位宽的数据看波形对不对。仿真从设备怎么建最简单的做法就是写一个寄存器级的behavioral model用同样的SPI协议去响应主机的读写。这个模型里面可以用一个always块监测SCLK和CS在采样沿把MOSI线上的数据收进来在移出沿把数据放上MISO线。这样仿真的结果才能真实反映实际通信。仿真里面有个细节需要特别注意MISO在仿真中也可能需要三态控制。如果从设备模型在CS无效时强行驱动MISO为固定电平那么多个从设备在仿真中就冲突了。好的习惯是仿真从设备的MISO也用三态门描述CS有效时才输出数据其他时候输出高阻。这样既逼真又能顺便验证FPGA侧MISO三态逻辑的正确性。仿真过程里我习惯关注几个检查点。CS的建立时间和保持时间从设备规格书里要求的CS相对SCLK的时序。SCLK频率是否和设计一致用波形上的光标量一下周期。MOSI数据的移出位置是否正确从起始位开始一位一位对照。MISO数据的采样是否正确和在testbench里写入从设备寄存器的值做比对。这些检查点全部通过才能把代码烧到板子上。4.3 上板实测的逻辑分析仪联调上板调试SPI调试手段主要靠逻辑分析仪或者FPGA内部自带的集成逻辑分析仪ILA。有的情况也可以用示波器看波形。我自己的习惯是先在Vivado里用ILA抓内部的sclk_reg、mosi_reg、miso采样寄存器、bit_cnt这些信号把内部工作状态看透再用逻辑分析仪或者示波器抓FPGA引脚上的真实物理信号确认引脚级的时序和电气特性。用ILA调试有一个技巧采样时钟一定要选系统时钟不要选SCLK。因为ILA的采样机制需要稳定的时钟网络SCLK是普通信号拿它做采样时钟容易采到毛刺而且无法触发边沿。把ILA的触发条件设为检测START状态或者CS下降沿然后在波形里看SCLK的翻转和MOSI数据的变化基本能定位大多数问题。实际联调阶段我最常用的是用逻辑分析仪的SPI协议解码功能。市面上常见的逻辑分析仪比如几十块钱那种USB逻辑分析仪配合Sigrok或者厂商的软件基本都支持SPI解码。接好线之后设置好模式和采样率逻辑分析仪会自动把SCLK、MOSI、MISO、CS的波形解成十六进制数据和预期值一比对立刻就能定位是时序问题还是数据内容错误。这个小工具做SPI调试效率比纯肉眼抠波形高太多了。有一类问题仿真里完全看不出来只有上板才能暴露。比如板上的走线过长导致SCLK和MOSI之间的skew变大数据建立时间不够了。又比如多个SPI从设备的MISO共线有一个设备的MISO三态控制没做好导致总线冲突MISO上出现沿毛刺。这类问题靠仿真解决不了只能靠实测和逻辑分析仪去抓。5. 常见问题排查与实战经验总结5.1 一张速查表搞定大部分SPI问题SPI通信出问题排查思路其实都是有规律可循的。我把自己这几年在FPGA上调SPI踩过的坑、别人踩过的坑整理成了一张速查表。碰到问题先对照这张表大部分情况能找到方向。现象可能原因排查方法通信完全失败数据全是0xFF模式配置错误SCLK极性反了用逻辑分析仪看SCLK空闲电平和规格书对照偶尔通信错误重启后可能掉线CPOL/CPHA配置不稳定时序刚好踩在临界点用示波器测SCLK和MOSI的建立时间算余量读回来的数据全是0x00MISO三态控制失败或者从设备被片选选中异常检查CS时序用逻辑分析仪看MISO总线状态第一个字节正常后续字节乱掉状态机在START状态没有做延时从设备需要准备时间查规格书的CS有效到第一个SCLK的时间要求SCLK频率和配置的不一致分频参数计算错误用逻辑分析仪测SCLK实际频率数据顺序颠倒字节序配置和从设备要求不一致和寄存器map对照调整MSB/LSB先行高速通信偶尔出错低频正常信号完整性或skew问题降低SCLK频率检查布线加输出延迟约束上面这张表我实际项目里用了很久基本每次调SPI都能按图索骥。特别是第三条MISO三态控制失败导致读回来全零的问题我自己碰上过不止一次。那种感觉就是你明明看着逻辑没问题仿真也过了为什么板上读出来全是零后来一看从设备的MISO输出使能引脚没拉对CS也没彻底拉低器件压根没进入激活状态。用逻辑分析仪怼上去一抓MISO线上完全没有数据活动问题立刻暴露。5.2 最容易忽略的跨时钟域隐患SPI master逻辑工作在系统时钟域但外部信号SCLK、MISO这些本质上都是来自不同时钟域的信号或者说是同源但非同步的信号。特别是MISO从设备的输出变化是相对SCLK边沿的而你的系统时钟和SCLK只是分频关系它们的边沿有时间偏差。如果你直接用系统时钟去采样MISO就有可能出现亚稳态。解决亚稳态的标准做法是打拍也就是用两级寄存器同步。MISO进来之后先打一拍再用打拍后的信号去参与逻辑判断。因为SPI速率不高两级寄存器的延迟完全不影响数据正确性。我看到很多初学者的代码直接用MISO原信号参与移位在仿真里没问题但上板后偶尔会有某个bit不对原因就在这里。另外还有一个隐患是SCLK边沿检测。如果你用sclk_reg 1b0 sclk_next 1b1这样的组合逻辑去检测上升沿容易产生毛刺因为组合逻辑中间状态可能会闪一下。稳妥做法是先把SCLK打一拍得到sclk_delay再用sclk_delay 0 sclk_reg 1来检测上升沿。这样检测到的边沿是稳定的不会产生虚假沿。5.3 怎么让SPI模块更通用、更可复用写这个SPI master项目的时候我心里一直有一个标准换一块FPGA板子、换一个ARM主控、换一种从设备这个模块能不能只改参数和顶层映射就能直接跑起来还是说需要大改内部逻辑。如果答案是后者说明设计还不够通用。为了达到这个目标我做了几件事。第一把协议层和物理层分离。协议层负责状态机、移位、模式切换物理层负责引脚映射、三态控制、引脚约束。换板子的时候只改物理层协议层一根线不用动。第二把寄存器接口抽象成通用的SRAM-like接口。不管后面接的是FMC、AXI-Lite还是简单的GPIO模拟时序都只用做一个小转换桥不用动SPI核心。第三把模式、位宽、分频系数、字节序全部参数化。顶层的参数列表一改整个模块的行为就跟着变不用翻内部代码。这样做有一个直接的好处项目积累性变强了。第一个项目写好SPI master之后后面做ADC采集、驱动Flash、读传感器全都直接复用同一个模块只是参数不同。新项目刚开始SPI这部分的代码量几乎可以忽略不计省下来的时间可以专心做业务逻辑。5.4 从SPI出发可以扩展的方向SPI通信这个项目本身不算大但它是FPGA开发中一个非常典型的接口实现范例。做完这个项目你会对状态机设计、跨时钟域处理、时序约束、仿真验证、上板调试这一整套流程有直观的认知。这些能力是通用的不管后面做PCIe、DDR、以太网还是MIPI底层的方法论都是一样的。我自己做完SPI之后后面的路线是往高速接口方向走。先是做了SPI Flash的远程升级功能涉及把SPI master扩展成支持四线QSPI的控制器这个对数据吞吐量有更高要求时序上更需要精雕细琢。然后做了通过SPI接口配置ADC芯片同时把ADC采集的数据通过FMC总线送给STM32H743。再后来开始接触PCIe听起来跨度很大但实际上也只是在接口控制和时序收敛这些基本功上面叠加新的协议而已。对于阅读这篇文章的读者如果你刚开始学FPGA我的建议是把SPI这个项目完整吃透不要停留在能跑就行的层面。把状态机画清楚把时序图画明白把约束看懂把每一个边沿变化都想通。这个小项目能让你建立起来的所有好习惯会跟着你走进后面每一个更复杂的项目里。根据我个人的经验FPGA学习里最值得投资的事情就是亲手写一个你自己能完全掌控的SPI控制器。可能有的人觉得它太简单不值得花时间但实际上能把简单的东西做对、做稳、做通用恰恰是FPGA工程师从初级走向中高级的分水岭。希望这篇东西能帮你少走点弯路早日把SPI这个模块从“能用”打磨到“好用”。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号