FPGA工业常用接口:FPGA 的 SPI 总线多从机通信设计与时序优化

https://pan.baidu.com/s/1rDsLAXGj8WbX82teSkhuIw?pwd=1234

这份FPGA 系统学习详细资料包是个人花大量时间精心整理的,超多干货全覆盖,从基础到实战一站式搞定,不用再到处薅资料!网盘链接随时可能失效,提取码 1234,先保存再学习,别等失效拍大腿!🔗链接:https://pan.baidu.com/s/1rDsLAXGj8WbX82teSkhuIw?pwd=1234


工业常用接口:FPGA的SPI总线多从机通信设计与时序优化

SPI(Serial Peripheral Interface)是工业应用中最常见的同步串行接口之一。相比UART,它能提供更高的传输速率和全双工通信能力,非常适合FPGA与ADC、DAC、传感器、存储器等设备的数据交换。当系统中存在多个从机时,设计挑战会显著增加。下面我将从实战角度,详细讲解如何在FPGA上实现高效可靠的多从机SPI通信。


1. SPI多从机通信的两种主流架构

在开始设计之前,我们需要先选择合适的多从机连接方式。根据工业应用场景,主要有两种架构:

架构类型 连接方式 优点 缺点 适用场景
独立片选模式 每个从机有独立的CS线,共享SCLK、MOSI、MISO 通信独立,速率高,调试简单 占用GPIO多,从机数量受引脚限制 少量高速设备(如SPI Flash+显示屏)
菊花链模式 所有从机共享CS和SCLK,数据依次传递 节省GPIO,布线简单 通信效率低,需从机支持链式功能 LED驱动、多通道ADC/DAC级联

独立片选模式是工业现场最常用的方案。主设备通过拉低对应从机的CS线来选中目标,未选中的从机必须将MISO引脚置为高阻态,避免总线冲突。

菊花链模式则适用于需要控制大量同类型设备的场景,比如多个LED驱动芯片或移位寄存器级联。数据从第一个从机的MOSI进入,通过内部移位传递到下一个,最后一个从机的MISO返回给主机。


2. FPGA端SPI主控设计(独立片选模式)

作为FPGA工程师,我们通常需要自己实现SPI主控逻辑。下面给出一个可配置的多从机SPI主控模块设计。

2.1 模块接口定义
verilog 复制代码
module spi_master #(
    parameter CLK_FREQ = 50_000_000,      // 系统时钟频率 50MHz
    parameter SPI_FREQ = 5_000_000,       // SPI时钟频率 5MHz
    parameter DATA_WIDTH = 8,              // 数据宽度 8位
    parameter NUM_SLAVES = 4                // 从机数量
)(
    input clk, reset_n,
    
    // 用户接口
    input [NUM_SLAVES-1:0] cs_sel,         // 要选中的从机(独热码)
    input [DATA_WIDTH-1:0] tx_data,        // 发送数据
    output reg [DATA_WIDTH-1:0] rx_data,   // 接收数据
    input start,                            // 启动传输
    output reg busy,                         // 忙标志
    
    // SPI物理接口
    output reg sclk,
    output reg mosi,
    input miso,
    output reg [NUM_SLAVES-1:0] cs_n        // 片选,低有效
);
2.2 核心参数计算

SPI时钟由系统时钟分频产生:

verilog 复制代码
localparam CLK_DIV = CLK_FREQ / SPI_FREQ / 2;  // 半周期计数
// 例如 50MHz/5MHz/2 = 5,即每5个系统时钟翻转一次SCLK
2.3 状态机设计

SPI主控的核心是一个有限状态机,控制片选、时钟和数据传输:

verilog 复制代码
typedef enum logic [2:0] {IDLE, CS_SETUP, TRANSFER, CS_HOLD} state_t;
state_t state;

always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        state <= IDLE;
        busy <= 1'b0;
        cs_n <= {NUM_SLAVES{1'b1}};  // 所有片选无效
        sclk <= 1'b0;                  // 默认模式0:空闲低电平
        bit_cnt <= 0;
    end else begin
        case (state)
            IDLE: begin
                busy <= 1'b0;
                if (start) begin
                    busy <= 1'b1;
                    cs_n <= ~cs_sel;          // 拉低选中的从机片选
                    state <= CS_SETUP;
                    clk_cnt <= 0;
                    // 锁存发送数据
                    shift_reg <= tx_data;
                end
            end
            
            CS_SETUP: begin
                // tCSC:片选建立到第一个时钟边沿的延时
                if (clk_cnt < CS_SETUP_CYCLES) begin
                    clk_cnt <= clk_cnt + 1;
                end else begin
                    clk_cnt <= 0;
                    state <= TRANSFER;
                end
            end
            
            TRANSFER: begin
                // SPI时钟产生逻辑
                if (clk_cnt == CLK_DIV-1) begin
                    clk_cnt <= 0;
                    sclk <= ~sclk;  // 翻转SCLK
                    
                    // 在SCLK边沿进行数据操作
                    if (sclk) begin  // 下降沿(取决于模式)
                        // 发送下一位(MSB first)
                        mosi <= shift_reg[7];
                        shift_reg <= {shift_reg[6:0], 1'b0};
                    end else begin   // 上升沿
                        // 采样MISO
                        rx_temp <= {rx_temp[6:0], miso};
                        bit_cnt <= bit_cnt + 1;
                        
                        // 8位传输完成
                        if (bit_cnt == DATA_WIDTH-1) begin
                            state <= CS_HOLD;
                            clk_cnt <= 0;
                        end
                    end
                end else begin
                    clk_cnt <= clk_cnt + 1;
                end
            end
            
            CS_HOLD: begin
                // tASC:最后时钟边沿到片选释放的延时
                if (clk_cnt < CS_HOLD_CYCLES) begin
                    clk_cnt <= clk_cnt + 1;
                end else begin
                    cs_n <= {NUM_SLAVES{1'b1}};  // 释放片选
                    rx_data <= rx_temp;            // 锁存接收数据
                    state <= IDLE;
                end
            end
        endcase
    end
end
2.4 支持四种SPI模式

SPI有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)决定:

模式 CPOL CPHA 空闲时钟 数据采样边沿 数据变化边沿
模式0 0 0 低电平 上升沿 下降沿
模式1 0 1 低电平 下降沿 上升沿
模式2 1 0 高电平 下降沿 上升沿
模式3 1 1 高电平 上升沿 下降沿

可以通过参数化配置来支持不同从机的需求:

verilog 复制代码
// 在模块参数中添加
parameter CPOL = 0, CPHA = 0;

// 在状态机中根据CPOL设置空闲电平
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        sclk <= CPOL;  // 空闲电平由CPOL决定
    end
end

// 在TRANSFER状态中,根据CPHA决定采样边沿
always @(posedge clk) begin
    if (state == TRANSFER) begin
        if (clk_cnt == CLK_DIV-1) begin
            clk_cnt <= 0;
            sclk <= ~sclk;
            
            // 根据CPHA决定何时采样和发送
            if (CPHA == 0) begin
                // CPHA=0:在第一个时钟边沿采样(CS有效后立即准备数据)
                if (sclk == CPOL) begin  // 第一个边沿
                    // 采样MISO
                end else begin
                    // 发送MOSI
                end
            end else begin
                // CPHA=1:在第二个时钟边沿采样
                if (sclk == CPOL) begin
                    // 发送MOSI
                end else begin
                    // 采样MISO
                end
            end
        end
    end
end

3. 多从机通信的关键设计要点

3.1 片选信号的互斥控制

在独立片选模式下,绝对不允许同时选中多个从机,否则MISO线上的数据会发生冲突。建议在用户接口层做保护:

verilog 复制代码
// 检查cs_sel是否为独热码(只有一个bit为1)
always @(posedge clk) begin
    if (start && ($countones(cs_sel) != 1)) begin
        // 错误:同时选中了多个从机
        error_flag <= 1'b1;
        // 不启动传输
    end
end

如果FPGA引脚不够,可以使用3-8译码器(如74HC238)扩展片选信号,只需3个GPIO就能控制8个从机。

3.2 MISO的三态控制

未被选中的从机必须将MISO引脚置为高阻态,否则多个从机同时驱动MISO会导致总线冲突。在FPGA作为从机时,需要实现三态输出:

verilog 复制代码
// FPGA作为SPI从机时的MISO三态控制
assign miso = (cs_selected && tx_enable) ? tx_data_bit : 1'bz;
3.3 跨从机的模式兼容性

不同从机可能要求不同的SPI模式(CPOL/CPHA组合)。如果系统中有多个模式不兼容的从机,可以在主控中为每个从机独立配置模式:

verilog 复制代码
// 为每个从机存储配置参数
reg [1:0] spi_mode [0:NUM_SLAVES-1];

// 传输开始前,根据cs_sel加载对应从机的模式
always @(posedge clk) begin
    if (start) begin
        case (cs_sel)
            4'b0001: {cpol, cpha} <= spi_mode[0];
            4'b0010: {cpol, cpha} <= spi_mode[1];
            4'b0100: {cpol, cpha} <= spi_mode[2];
            4'b1000: {cpol, cpha} <= spi_mode[3];
        endcase
    end
end

4. 时序优化实战技巧

4.1 信号完整性优化

问题:高速SPI通信时,长走线会导致信号反射和过冲。

解决方案

  • 在SCLK、MOSI线上串联22-33Ω的电阻,靠近FPGA引脚放置,匹配阻抗。
  • CS线上拉10kΩ电阻到VCC,防止浮空误触发。
  • 时钟频率超过10MHz时,建议使用差分SPI或降低速率。
4.2 片选与时钟的时序关系

很多SPI从机对片选建立时间(tCSC)和保持时间(tASC)有要求。默认0延时可能导致数据错乱:

verilog 复制代码
// 根据从机数据手册配置延时
localparam CS_SETUP_CYCLES = CLK_FREQ / 1_000_000 * 10;  // 10us建立时间
localparam CS_HOLD_CYCLES = CLK_FREQ / 1_000_000 * 5;    // 5us保持时间

Linux内核的SPI驱动中有一个典型案例:当CS保持有效进行连续传输时,如果没有足够延时,SCK会产生毛刺,导致数据丢失。建议tCSC和tASC至少为半个SCK周期

4.3 连续传输模式(Burst Mode)

对于需要传输大量数据的场景(如读取ADC采样流),每次传输都重新拉低CS会产生额外开销。可以设计连续模式,让CS保持有效,连续传输多个字节:

verilog 复制代码
// 在模块中添加cont信号
input cont;  // 1:连续传输,保持CS有效

// 在TRANSFER完成后判断
if (bit_cnt == DATA_WIDTH-1) begin
    if (cont) begin
        // 连续模式:不清除CS,直接开始下一字节
        bit_cnt <= 0;
        shift_reg <= next_tx_data;  // 加载新数据
        // 保持state为TRANSFER
    end else begin
        state <= CS_HOLD;
    end
end
4.4 使用FIFO解耦

当SPI速率较高或数据量较大时,可以在主控前级加入FIFO,实现数据缓冲:

verilog 复制代码
// 例化FIFO用于发送缓冲
fifo #(.WIDTH(8), .DEPTH(16)) tx_fifo (
    .clk(clk),
    .wr_en(user_tx_valid),
    .wr_data(user_tx_data),
    .rd_en(spi_tx_ready),
    .rd_data(tx_data_to_spi),
    .full(tx_fifo_full),
    .empty(tx_fifo_empty)
);

这样用户逻辑可以突发写入,SPI主控按自身节奏发送,避免数据断流。


5. 常见问题排查指南

现象 可能原因 排查方法
数据全为0xFF或0x00 MISO未正确连接,或从机未选中 用示波器测CS是否拉低,测MISO波形
数据错位(如0x55变成0xAA) 模式不匹配(CPOL/CPHA错误) 检查从机手册,调整模式配置
偶发性数据错误 时序裕量不足 降低SPI时钟,增加CS建立/保持时间
多个从机同时响应 CS控制错误,或从机未释放MISO 检查CS互斥逻辑,确认从机MISO三态能力
高速下通信失败 信号完整性差 串联匹配电阻,缩短走线,降低速率

6. 总结

FPGA实现SPI多从机通信的核心要点:

  1. 架构选择:根据从机数量和性能需求,选择独立片选或菊花链模式。
  2. 参数可配置:将时钟分频、模式选择、数据宽度等参数化,适应不同从机。
  3. 时序严谨:严格遵循从机手册的建立/保持时间,必要时增加延时。
  4. 总线保护:确保MISO三态控制,避免总线冲突。
  5. 调试先行:用仿真验证波形,再用示波器实测确认。

掌握了这些技巧,你就能在工业现场从容应对各种SPI多从机通信需求,构建稳定可靠的FPGA控制系统。

相关推荐
艾莉丝努力练剑2 小时前
【MYSQL】MYSQL学习的一大重点:MYSQL库的操作
android·linux·运维·数据库·人工智能·学习·mysql
shanght12 小时前
尝试用rules--code.md
学习
放下华子我只抽RuiKe52 小时前
机器学习全景指南-总结与展望——构建你的机器学习工具箱
人工智能·深度学习·opencv·学习·目标检测·机器学习·自然语言处理
tritone2 小时前
标题:用阿贝云免费云服务器配置SSL/TLS,学习证书部署的实用经历
服务器·学习·ssl
PNP Robotics2 小时前
连接AI产业·链动全球|PNP机器人亮相2026杭州全球人工智能大会
人工智能·python·学习·开源
朗迹 - 张伟2 小时前
UE5粒子特效Niagara学习笔记
笔记·学习·ue5
Cat_Rocky2 小时前
shell脚本初学习
学习
留白_2 小时前
MySQL学习(4)——多表操作
学习
im_AMBER3 小时前
编辑器项目开发复盘:主题切换
前端·学习·前端框架·编辑器·html5