用Verilog实现SPI主从协议:FPGA与外部传感器的高速数据交互
SPI是嵌入式系统中应用最广泛的串行同步接口之一,以其全双工、高速率、硬件连线少的优势,在ADC、DAC、MEMS传感器、Flash存储器等场景中占据统治地位。FPGA的硬件并行特性天然适合SPI时序的精确控制——你可以自定义时钟极性/相位、数据位宽和传输速率,不受MCU外设限制。本文给出可直接综合的SPI主模式和从模式Verilog代码,覆盖CPOL=0、CPHA=0的经典模式。
SPI协议回顾
SPI四线制:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、SS(片选,低有效)。全双工传输:每个时钟周期主从各移出一位数据。CPOL=0表示空闲时SCLK为低电平,CPHA=0表示在第一个时钟沿(上升沿)采样数据。
SPI主模式设计
主模式负责产生SCLK和SS,并按位收发数据。核心是一个状态机:空闲→发送/接收→完成。
module spi_master #(parameter DATA_WIDTH = 8, CLK_DIV = 100) (
input clk, rst_n,
input start, // 启动传输
input [DATA_WIDTH-1:0] data_in, // 待发送数据
output reg [DATA_WIDTH-1:0] data_out, // 接收数据
output reg done, // 传输完成标志
output reg sclk, ss, mosi,
input miso
);
localparam IDLE = 0, TRANSFER = 1, FINISH = 2;
reg [1:0] state;
reg [7:0] clk_cnt;
reg [3:0] bit_cnt;
reg [DATA_WIDTH-1:0] shift_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE; sclk <= 0; ss <= 1; mosi <= 0;
done <= 0; clk_cnt <= 0; bit_cnt <= 0;
end else case (state)
IDLE: begin
ss <= 1; done <= 0;
if (start) begin
ss <= 0;
shift_reg <= data_in;
clk_cnt <= 0; bit_cnt <= 0;
state <= TRANSFER;
end
end
TRANSFER: begin
if (clk_cnt == CLK_DIV/2 - 1) begin // 半个周期
sclk <= ~sclk;
if (sclk) begin // 下降沿,输出数据
mosi <= shift_reg[DATA_WIDTH-1];
shift_reg <= {shift_reg[DATA_WIDTH-2:0], 1'b0};
end else begin // 上升沿,采样MISO
shift_reg[0] <= miso;
if (bit_cnt == DATA_WIDTH-1) state <= FINISH;
else bit_cnt <= bit_cnt + 1;
end
clk_cnt <= 0;
end else clk_cnt <= clk_cnt + 1;
end
FINISH: begin
ss <= 1; sclk <= 0;
data_out <= shift_reg;
done <= 1;
state <= IDLE;
end
endcase
end
endmodule
CLK_DIV参数控制SCLK频率:系统时钟÷(2×CLK_DIV)。例如50MHz时钟,CLK_DIV=100时SCLK为250kHz。
SPI从模式设计
从模式被动接收SCLK和SS,在时钟沿同步收发。不需要分频逻辑,但必须确保SCLK不超过FPGA的处理能力。
module spi_slave #(parameter DATA_WIDTH = 8) (
input clk, rst_n,
input sclk, ss, mosi,
output reg miso,
output reg [DATA_WIDTH-1:0] data_out,
output reg data_valid
);
reg [DATA_WIDTH-1:0] shift_reg;
reg [3:0] bit_cnt;
reg sclk_d1, sclk_d2; // 同步与边沿检测
wire pos_edge = sclk_d1 & ~sclk_d2; // 上升沿
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sclk_d1 <= 0; sclk_d2 <= 0;
shift_reg <= 0; bit_cnt <= 0;
data_out <= 0; data_valid <= 0;
miso <= 0;
end else begin
{sclk_d2, sclk_d1} <= {sclk_d1, sclk}; // 两级同步
if (!ss) begin
if (pos_edge) begin
shift_reg <= {shift_reg[DATA_WIDTH-2:0], mosi};
if (bit_cnt == DATA_WIDTH-1) begin
data_out <= {shift_reg[DATA_WIDTH-2:0], mosi};
data_valid <= 1;
bit_cnt <= 0;
end else bit_cnt <= bit_cnt + 1;
end
// 在SCLK下降沿更新MISO(CPHA=0时)
// 此处略,可根据需要添加
end else begin
bit_cnt <= 0;
data_valid <= 0;
end
end
end
endmodule
注意:从模式的SCLK必须用两级触发器同步到FPGA系统时钟域,避免亚稳态。边沿检测在同步后的信号上进行。
仿真与验证
编写testbench,将主从模块相连,验证数据传输的正确性。关键检查点:SS拉低后主模块开始产生SCLK;8个时钟周期后done拉高,data_out等于发送的data_in。
写在最后
SPI主从模块是FPGA接口设计的基本功。掌握这两个模板后,你可以轻松适配各类SPI传感器:调整DATA_WIDTH适配12位ADC,修改CLK_DIV匹配器件最高速率,增加CPOL/CPHA选择逻辑支持不同模式。当你的FPGA需要以10MHz以上的速率与高速ADC交换数据时,自己写的SPI控制器比调用IP核更可控、更节省资源。从仿真通过到板级联调,SPI协议的FPGA实现是通往复杂接口设计的坚实一步。





