用Verilog实战SPI协议:从波形分析到FPGA实现的四种模式全解析
SPI协议作为嵌入式系统和FPGA设计中最常用的通信接口之一,其时钟极性和相位配置(CPOL/CPHA)一直是工程师们的痛点。本文将带你从波形本质出发,通过Verilog代码实现一个可配置四种模式的SPI主从机模块,并用Vivado/Modelsim仿真展示不同模式下的波形差异,彻底告别死记硬背。
1. SPI协议核心:时钟极性与相位的本质理解
SPI协议的四种模式本质上只是时钟信号和数据采样时刻的不同组合。我们先抛开抽象定义,直接从物理波形入手:
CPOL(时钟极性):决定了时钟信号在空闲状态时的电平
- CPOL=0:空闲时为低电平
- CPOL=1:空闲时为高电平
CPHA(时钟相位):决定了数据在时钟的哪个边沿被采样
- CPHA=0:在第一个有效时钟边沿采样
- CPHA=1:在第二个有效时钟边沿采样
这四种组合形成了SPI的四种工作模式,具体对应关系如下表:
| 模式 | CPOL | CPHA | 空闲状态 | 采样边沿 | 发送边沿 |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 低电平 | 上升沿 | 下降沿 |
| 1 | 0 | 1 | 低电平 | 下降沿 | 上升沿 |
| 2 | 1 | 0 | 高电平 | 下降沿 | 上升沿 |
| 3 | 1 | 1 | 高电平 | 上升沿 | 下降沿 |
关键提示:实际项目中,主从设备的CPOL/CPHA设置必须完全一致,否则会导致通信失败。务必仔细核对从设备的数据手册。
2. Verilog实现可配置SPI主机模块
下面是一个支持四种模式配置的SPI主机Verilog实现核心代码:
module spi_master #( parameter CPOL = 0, parameter CPHA = 0 )( input clk, input rst_n, input [7:0] tx_data, input tx_valid, output reg tx_ready, output reg sclk, output reg mosi, output reg cs_n ); reg [3:0] state; reg [2:0] bit_cnt; reg [7:0] shift_reg; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin state <= 0; sclk <= CPOL; cs_n <= 1'b1; end else begin case (state) 0: begin // 空闲状态 if (tx_valid) begin cs_n <= 1'b0; shift_reg <= tx_data; bit_cnt <= 0; state <= 1; sclk <= CPOL; end end 1: begin // 准备第一个边沿 sclk <= ~sclk; state <= 2; if (CPHA == 0) mosi <= shift_reg[7]; end 2: begin // 第一个边沿 if (CPHA == 0) shift_reg <= {shift_reg[6:0], 1'b0}; sclk <= ~sclk; state <= 3; if (CPHA == 1) mosi <= shift_reg[7]; end 3: begin // 第二个边沿 if (CPHA == 1) shift_reg <= {shift_reg[6:0], 1'b0}; bit_cnt <= bit_cnt + 1; if (bit_cnt == 7) begin state <= 0; cs_n <= 1'b1; tx_ready <= 1'b1; end else begin state <= 1; end sclk <= ~sclk; end endcase end end endmodule这段代码的关键设计点:
- 使用状态机清晰划分SPI传输的各个阶段
- 根据CPHA参数决定数据发送和移位的时机
- 通过CPOL参数初始化时钟线的空闲状态
- 支持背压机制(tx_ready/tx_valid)实现流控制
3. 四种模式的Modelsim仿真对比
为了直观展示四种模式的区别,我们设计了一个测试平台,发送相同的数据(8'h55)在不同模式下观察波形差异。
3.1 模式0 (CPOL=0, CPHA=0)仿真结果
// 测试用例 initial begin #100; tx_data = 8'h55; tx_valid = 1; #20; tx_valid = 0; #500; $finish; end模式0的波形特征:
- 空闲时SCLK为低电平
- 数据在上升沿被采样
- 主机在下降沿更新数据
3.2 模式1 (CPOL=0, CPHA=1)仿真结果
模式1的波形特征:
- 空闲时SCLK仍为低电平
- 数据在下降沿被采样
- 主机在上升沿更新数据
3.3 模式2 (CPOL=1, CPHA=0)仿真结果
模式2的波形特征:
- 空闲时SCLK为高电平
- 数据在下降沿被采样
- 主机在上升沿更新数据
3.4 模式3 (CPOL=1, CPHA=1)仿真结果
模式3的波形特征:
- 空闲时SCLK为高电平
- 数据在上升沿被采样
- 主机在下降沿更新数据
调试技巧:在Modelsim中可以将四种模式的波形并排对比,使用不同颜色标注采样时刻,这样差异一目了然。
4. SPI从机设计与主从回环测试
完整的SPI系统需要主从配合,下面是一个与主机兼容的从机设计:
module spi_slave #( parameter CPOL = 0, parameter CPHA = 0 )( input sclk, input mosi, input cs_n, output reg [7:0] rx_data, output reg rx_valid ); reg [7:0] shift_reg; reg sclk_prev; always @(negedge cs_n) begin rx_valid <= 1'b0; end always @(posedge sclk or negedge sclk) begin if (cs_n) begin sclk_prev <= CPOL; end else begin if (CPHA == 0 && sclk != sclk_prev && sclk == ~CPOL) begin // 模式0/2的第一个边沿采样 shift_reg <= {shift_reg[6:0], mosi}; end if (CPHA == 1 && sclk != sclk_prev && sclk == CPOL) begin // 模式1/3的第一个边沿采样 shift_reg <= {shift_reg[6:0], mosi}; end sclk_prev <= sclk; end end always @(posedge cs_n) begin if (!rx_valid) begin rx_data <= shift_reg; rx_valid <= 1'b1; end end endmodule主从回环测试的关键点:
- 确保主从CPOL/CPHA参数设置一致
- 添加适当的时序约束
- 在FPGA上使用ILA抓取关键信号验证
// 回环测试顶层模块 module spi_loopback_top( input clk, input rst_n ); wire sclk, mosi, miso, cs_n; wire [7:0] tx_data, rx_data; wire tx_valid, tx_ready, rx_valid; spi_master #(.CPOL(0), .CPHA(0)) master ( .clk(clk), .rst_n(rst_n), .tx_data(tx_data), .tx_valid(tx_valid), .tx_ready(tx_ready), .sclk(sclk), .mosi(mosi), .cs_n(cs_n) ); spi_slave #(.CPOL(0), .CPHA(0)) slave ( .sclk(sclk), .mosi(mosi), .cs_n(cs_n), .rx_data(rx_data), .rx_valid(rx_valid) ); // 测试数据生成 reg [7:0] test_data; always @(posedge clk or negedge rst_n) begin if (!rst_n) begin test_data <= 8'h00; end else if (tx_ready) begin test_data <= test_data + 1; end end assign tx_data = test_data; assign tx_valid = tx_ready; endmodule5. 实际项目中的SPI调试技巧
在真实项目中调试SPI接口时,经常会遇到各种问题。以下是几个实用技巧:
示波器触发设置:
- 使用CS下降沿作为触发条件
- 将时钟和数据信号同步捕获
- 添加解码功能直接显示SPI数据
常见问题排查:
- 数据错位:检查CPHA设置是否正确
- 采样不稳定:检查时序约束是否满足
- 通信完全失败:确认CPOL设置和从设备一致
性能优化:
- 根据从设备特性选择最高支持时钟频率
- 考虑使用DMA减少CPU开销
- 对于长距离传输,添加适当的终端电阻
多从设备系统设计:
- 常规模式:每个从设备独立CS线
- 菊花链模式:共享CS线,数据级联传输
// 多从设备选择示例 module spi_multi_slave( input clk, input [1:0] slave_select, output reg [3:0] cs_n ); always @(*) begin case (slave_select) 2'b00: cs_n = 4'b1110; 2'b01: cs_n = 4'b1101; 2'b10: cs_n = 4'b1011; 2'b11: cs_n = 4'b0111; default: cs_n = 4'b1111; endcase end endmodule掌握SPI协议的核心在于理解时钟与数据的时序关系,通过Verilog实现和仿真可以加深这种理解。实际项目中,建议先通过仿真验证所有四种模式,再上板测试,这样可以大大提高调试效率。