一、SPI接口的简单自我介绍

SPI就像是一个热闹的派对,主机(Master)负责组织大家交流,从机(Slave)则安静等待被点名。这个派对有三条基本规则:

  1. 主机通过SCLK时钟线打拍子控制节奏
  2. MOSI线是主机说话的通道
  3. MISO线是从机回应的通道

实际项目中,我们经常会遇到需要同时管理多个从机的情况。比如智能家居中,主控芯片要同时与温湿度传感器、LED控制器、电机驱动器等多个设备对话。这时候就需要在Verilog中设计可靠的SPI主从接口。

二、Verilog实现SPI主设备

让我们先看看如何用Verilog搭建SPI主机。关键是要处理好时钟分频和设备选择信号。

// 技术栈:Verilog-2001
module spi_master(
    input clk,         // 系统时钟
    input rst,         // 复位信号
    input [7:0] tx_data, // 要发送的数据
    input start,       // 开始传输信号
    output reg busy,   // 忙状态指示
    output reg [7:0] rx_data, // 接收到的数据
    output reg sclk,    // SPI时钟
    output reg mosi,    // 主机输出从机输入
    input miso,         // 主机输入从机输出
    output reg cs       // 片选信号(低有效)
);

reg [2:0] bit_counter;  // 位计数器
reg [7:0] tx_reg;       // 发送移位寄存器
reg [7:0] rx_reg;       // 接收移位寄存器

// 时钟分频计数器(假设系统时钟是SPI时钟的4倍)
reg [1:0] clk_divider;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        sclk <= 1'b0;
        clk_divider <= 2'b00;
        busy <= 1'b0;
        cs <= 1'b1;     // 默认不选中任何从机
    end else begin
        if (start && !busy) begin
            busy <= 1'b1;
            cs <= 1'b0;  // 选中从机
            tx_reg <= tx_data;
            bit_counter <= 3'b0;
            clk_divider <= 2'b00;
        end
        
        if (busy) begin
            clk_divider <= clk_divider + 1;
            
            // 每个SPI时钟周期分为4个阶段
            case (clk_divider)
                2'b00: sclk <= 1'b0;       // 时钟低电平
                2'b01: begin               // 上升沿前准备数据
                    mosi <= tx_reg[7];     // 发送最高位
                end
                2'b10: begin               // 上升沿
                    sclk <= 1'b1;
                    rx_reg[7:1] <= rx_reg[6:0]; // 移位接收寄存器
                    rx_reg[0] <= miso;     // 采样输入
                end
                2'b11: begin               // 下降沿处理
                    tx_reg <= {tx_reg[6:0], 1'b0}; // 移位发送寄存器
                    bit_counter <= bit_counter + 1;
                    
                    if (bit_counter == 3'b111) begin
                        busy <= 1'b0;
                        cs <= 1'b1;        // 取消选中
                        rx_data <= rx_reg; // 保存接收数据
                    end
                end
            endcase
        end
    end
end

endmodule

这个主机模块有几个关键点:

  1. 使用时钟分频产生SPI时钟
  2. 在时钟上升沿采样输入数据
  3. 在时钟下降沿更新输出数据
  4. 通过8位计数器控制传输长度

三、Verilog实现SPI从设备

从机设备需要时刻监听主机召唤,这里给出一个典型的从机实现:

// 技术栈:Verilog-2001
module spi_slave(
    input sclk,        // SPI时钟(来自主机)
    input mosi,        // 主机输出从机输入
    output reg miso,   // 主机输入从机输出
    input cs,          // 片选信号(低有效)
    input [7:0] tx_data, // 要发送的数据
    output reg [7:0] rx_data, // 接收到的数据
    output reg ready    // 数据接收完成标志
);

reg [7:0] tx_reg;      // 发送移位寄存器
reg [7:0] rx_reg;      // 接收移位寄存器
reg [2:0] bit_counter; // 位计数器
reg sclk_prev;         // 用于边沿检测

always @(posedge sclk or posedge cs) begin
    if (cs) begin
        // 未被选中时复位状态
        bit_counter <= 3'b0;
        ready <= 1'b0;
        miso <= 1'b0;
    end else begin
        // 在上升沿采样输入
        rx_reg <= {rx_reg[6:0], mosi};
        bit_counter <= bit_counter + 1;
        
        if (bit_counter == 3'b111) begin
            rx_data <= {rx_reg[6:0], mosi}; // 保存完整字节
            ready <= 1'b1;
        end
    end
end

always @(negedge sclk or posedge cs) begin
    if (cs) begin
        miso <= 1'b0;
    end else begin
        // 在下降沿更新输出
        miso <= tx_reg[7];
        tx_reg <= {tx_reg[6:0], 1'b0};
        
        if (bit_counter == 3'b0) begin
            // 新传输开始时加载发送数据
            tx_reg <= tx_data;
        end
    end
end

endmodule

从机设计要点:

  1. 只在被选中时响应(cs为低)
  2. 上升沿采样主机数据
  3. 下降沿更新输出数据
  4. 自动处理8位数据帧

四、多设备通信的时序协调

当系统中有多个从机时,最大的挑战是如何避免通信冲突。这里给出一个支持4个从机的解决方案:

// 技术栈:Verilog-2001
module spi_controller(
    input clk,
    input rst,
    input [7:0] tx_data [0:3], // 给4个从机的数据
    output reg [7:0] rx_data [0:3], // 来自4个从机的数据
    output reg [3:0] cs,       // 4个片选信号
    output reg sclk,
    output reg mosi,
    input [3:0] miso           // 来自4个从机的数据线
);

reg [1:0] current_slave;       // 当前选择的从机编号
reg [2:0] state;               // 状态机状态
reg [7:0] master_tx_reg;       // 主机发送寄存器
reg [7:0] master_rx_reg [0:3]; // 主机接收寄存器(4个)
reg [2:0] bit_counter;         // 位计数器

// 状态定义
localparam IDLE = 3'd0;
localparam SELECT = 3'd1;
localparam TRANSFER = 3'd2;
localparam DESELECT = 3'd3;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state <= IDLE;
        cs <= 4'b1111;         // 所有从机取消选中
        sclk <= 1'b0;
        current_slave <= 2'b0;
    end else begin
        case (state)
            IDLE: begin
                // 轮询选择下一个从机
                current_slave <= current_slave + 1;
                state <= SELECT;
            end
            
            SELECT: begin
                // 激活当前从机
                cs <= ~(1 << current_slave);
                master_tx_reg <= tx_data[current_slave];
                bit_counter <= 3'b0;
                state <= TRANSFER;
            end
            
            TRANSFER: begin
                // 生成SPI时钟
                sclk <= ~sclk;
                
                if (sclk) begin
                    // 上升沿:采样输入
                    master_rx_reg[current_slave][7:1] <= master_rx_reg[current_slave][6:0];
                    master_rx_reg[current_slave][0] <= miso[current_slave];
                end else begin
                    // 下降沿:更新输出和计数器
                    master_tx_reg <= {master_tx_reg[6:0], 1'b0};
                    mosi <= master_tx_reg[7];
                    bit_counter <= bit_counter + 1;
                    
                    if (bit_counter == 3'b111) begin
                        state <= DESELECT;
                    end
                end
            end
            
            DESELECT: begin
                cs <= 4'b1111;  // 取消选中当前从机
                rx_data[current_slave] <= master_rx_reg[current_slave];
                state <= IDLE;
            end
        endcase
    end
end

endmodule

这个多从机控制器特点:

  1. 采用轮询方式依次访问每个从机
  2. 为每个从机维护独立的接收缓冲区
  3. 使用状态机清晰管理通信流程
  4. 自动处理从机选择和数据传输

五、实际应用中的注意事项

在真实项目中部署SPI接口时,有几个常见陷阱需要注意:

  1. 时钟极性和相位:SPI有4种工作模式(CPOL/CPHA),主从设备必须配置一致。我们的示例使用的是模式0(CPOL=0, CPHA=0)。

  2. 信号完整性:长距离传输时需要考虑:

    • 添加适当的终端电阻
    • 使用差分信号(如RS485)增强抗干扰能力
    • 在PCB布局时保持信号线等长
  3. 多从机冲突:当多个从机共享MISO线时:

    • 确保任何时候只有一个从机驱动MISO线
    • 可以添加三态缓冲器控制MISO输出
  4. 速度匹配:主机应该:

    • 初始使用较低时钟频率
    • 通过协商逐步提高速度
    • 监测误码率调整速度

六、技术优缺点分析

优点

  1. 全双工通信,效率高于I2C
  2. 协议简单,硬件实现成本低
  3. 支持高速传输(可达几十MHz)
  4. 灵活的传输长度(不限于8位倍数)

缺点

  1. 需要比I2C更多的引脚(每个从机单独CS线)
  2. 没有内置的冲突检测机制
  3. 标准SPI没有确认机制
  4. 不同厂商实现可能有细微差异

七、典型应用场景

  1. 传感器网络:如环境监测系统中连接多个温湿度、气压传感器
  2. 显示系统:驱动多个LED屏或OLED显示模块
  3. 存储系统:连接多个SPI Flash或SD卡
  4. 工业控制:PLC与多个IO模块通信
  5. 音频设备:数字音频接口(如I2S基于SPI变种)

八、总结

通过Verilog实现SPI主从接口,我们能够创建灵活可靠的多设备通信系统。关键点在于:

  • 精确控制时钟和数据的时序关系
  • 合理处理多从机的选择与隔离
  • 根据实际需求调整传输参数
  • 注意信号完整性和抗干扰设计

随着FPGA在嵌入式系统中的广泛应用,掌握SPI的硬件实现技能将大大提升我们设计复杂系统的能力。希望本文的示例和分析能为你的项目开发提供实用参考。