SPI(串行外围设备接口 Serial Peripheral Interface)
文章目录
- SPI(串行外围设备接口 Serial Peripheral Interface)
-
- 一、SPI总线特点
- 二、物理层
-
- 1、一主一从拓扑
- 2. 一主多从拓扑
- 三、协议层
-
- 3.1 时钟极性 CPOL(Clock Polarity)
- 3.2 时钟相位 CPHA(Clock Phase)
- 3.3 完整数据传输流程
- 四、SPI 与 IIC 简单对比(硬件面试高频)
- 五、Verilog 驱动 SPI 主机接口
-
- 5.1 驱动代码
- 5.2 仿真代码及波形
摘要:SPI 是一种高速、同步、串行、全双工的通信总线,采用主从架构,由主机产生 SCLK 时钟并控制片选信号 CS_N 选中从机。本文从总线特点、物理层拓扑(一主一从、一主多从)、协议层(CPOL/CPHA 四种模式)入手,梳理完整数据传输流程,并与 IIC 做对比,最后给出可综合的 Verilog SPI 主机驱动及仿真代码,适合硬件工程师与 FPGA 开发者参考。
一、SPI总线特点
通信属性:同步、串行、全双工
- 同步:由主机输出 SCLK 同步时钟,收发时序完全跟随时钟;
- 串行:数据按 bit 逐位移位传输;
- 全双工:MOSI、MISO两条数据线,同一时刻可以同时发送和接收数据。
二、物理层
SPI 引脚全部为推挽输出,不需要外接上拉电阻。 四根核心信号线:
| SCLK | Serial Clock | 主机输出 | 同步时钟,由主机产生,控制全部通信时序 |
| MOSI | Master Out Slave In | 主机→从机 | 主机输出数据,从机接收数据 |
| MISO | Master In Slave Out | 从机→主机 | 从机输出数据,主机接收数据 |
| CS_N(SS_N) | Chip Select | 主机输出 | 片选信号,低电平有效,选中对应从机设备 |
1、一主一从拓扑
单主机搭配单个从机,主机的 CS_N 直接连接从机片选引脚;通信时 CS_N 拉低选中从机,通信结束拉高释放。
全双工工作:SCLK 驱动下,MOSI 发数据的同时 MISO 接收从机返回数据。
2. 一主多从拓扑
两种常用方案:

SPI 没有总线仲裁,不存在多主机,只能由一台设备作为主机产生 SCLK 时钟。
三、协议层
SPI 通信由两个关键参数决定采样时序:时钟极性 CPOL、时钟相位 CPHA,组合出 4 种 SPI 工作模式,主机和从机必须配置为完全相同模式,否则数据采样错误。
3.1 时钟极性 CPOL(Clock Polarity)
决定总线空闲状态下 SCLK 的电平
- CPOL = 0:空闲时 SCLK 为低电平;
- CPOL = 1:空闲时 SCLK 为高电平。
3.2 时钟相位 CPHA(Clock Phase)
决定在哪一个时钟边沿采样数据。
- CPHA = 0:在 SCLK 第一个跳变沿采样数据;
- CPHA = 1:在 SCLK 第二个跳变沿采样数据。

四种工作模式组合:
- Mode 0:CPOL=0,CPHA=0(最常用模式)
- Mode1:CPOL=0,CPHA=1
- Mode2:CPOL=1,CPHA=0
- Mode3:CPOL=1,CPHA=1
3.3 完整数据传输流程
- 主机先把待发送 bit 放置到 MOSI 数据线,保证数据电平稳定;
- 在约定的时钟边沿,从机对 MOSI 进行采样,获取主机发来的数据;
- 时钟下一个边沿到来前,主机更新 MOSI 上的下一位数据。
- 和MOSI同步并行工作;从机将输出bit放到MISO线上;
- 在约定时钟边沿,主机采样MISO获取从机返回的数据;
SPI本质是移位寄存器交互:每一个SCLK时钟,同时完成一位发送+一位接收。
重要特性:SPI是移位交换机制,只要输出一个时钟脉冲,就会同时完成1bit发送 + 1bit接收;哪怕你只想写数据,MISO线上依然会收到从机输出的无效bit;只想读数据,MOSI也需要输出填充bit(一般输出0)。
四、SPI 与 IIC 简单对比(硬件面试高频)
| 信号线 | 4 线(3 线) | SDA、SCL 两线 |
| 速率 | 几十 MHz,速度快 | 标准 100k,快速 400k |
| 应答 | 无 ACK 应答 | 有 ACK/NACK 应答 |
| 拓扑 | 一主多从,不支持多主机 | 支持多主机+总线仲裁 |
| 输出类型 | 推挽输出,无需上拉 | 开漏输出,必须外接上拉电阻 |
| 全双工 | 支持全双工 | 半双工 |
五、Verilog 驱动 SPI 主机接口
5.1 驱动代码
//写spi的主机
//1.接口分析:时钟、复位、上层给到spi接口的开始信号、数据、定义的总传输长度、要写(发送给从机的长度)、接口之间的四线结构
//输出的数据、以及读写、一次通信完成的握手信号
//2.四个时序图转换为代码
module top(
input wire clk ,
input wire rst_n ,
input wire start_spi ,//上层给到spi接口的开始信号
input wire [7:0] data_i_spi ,//上层给到spi接口的数据
input wire [7:0] data_len ,//上层给到spi接口的总传输长度
input wire [7:0] wr_lenth ,//上层给到spi接口的要写(发送给从机的长度)
output wire [7:0] data_o_spi ,//输出的数据
output wire done_w ,//握手信号
output wire done_r ,//握手信号
output wire done_spi ,//握手信号
output wire SCK ,//四线
output wire CS_N ,//四线
output wire MOSI ,//四线
input wire MISO //四线
);
//参数
parameter CPOL = 1 ,//时钟极性
CPHA = 0 ,//时钟相位
delay = 6 ;//分频系数
//状态定义
localparam IDLE = 4'b0001,
START = 4'b0010,
DATA = 4'b0100,
STOP = 4'b1000;
reg [3:0] state_c,state_n;
//状态转移
wire IDLE_START,
START_DATA,
DATA_STOP ,
STOP_IDLE ;
//内部信号
reg [2:0] cnt_clk;//分频时钟计数
reg [7:0] cnt_bit;//比特计数
reg [1:0] start_reg ;//输入寄存信号
reg [7:0] data_reg ;//输入寄存信号
reg [7:0] data_len_r ;//输入寄存信号
reg [7:0] wr_lenth_r ;//输入寄存信号
reg SCK_R ;//时钟寄存
reg [7:0] data_temp ;//输出数据的暂存信号
reg MOSI_R ;//主机输出寄存
reg CS_N_R ;//片选信号寄存
//开始信号的寄存
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
start_reg <= 2'b00;
end
else begin
start_reg <= {start_reg[0],start_spi};
end
end
//状态机第一段
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机第二段
always @(*) begin
if (rst_n == 0) begin
state_n = IDLE;
end
else begin
case (state_c)
IDLE :state_n = (IDLE_START) ? START : state_c ;
START :state_n = (START_DATA) ? DATA : state_c ;
DATA :state_n = (DATA_STOP ) ? STOP : state_c ;
STOP :state_n = (STOP_IDLE ) ? IDLE : state_c ;
default:state_n = IDLE;
endcase
end
end
//状态转移条件
assign IDLE_START = (state_c == IDLE ) && start_reg == 2'b01;
assign START_DATA = (state_c == START) ;
assign DATA_STOP = (state_c == DATA ) && cnt_bit == data_len_r*8 – 1 && cnt_clk == delay – 1;
assign STOP_IDLE = (state_c == STOP ) && cnt_clk == delay – 1;
//状态机第三段
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
cnt_clk <= 0 ;//分频时钟计数
cnt_bit <= 0 ;//比特计数
CS_N_R <= 1 ;//片选信号
SCK_R <= CPOL;//分频时钟信号
data_reg <= 0 ;//输入寄存信号
data_len_r <= 0 ;//输入寄存信号
wr_lenth_r <= 0 ;//输入寄存信号
data_temp <= 0 ;//输出数据的暂存信号
end
else begin
case (state_c)
IDLE :begin//寄存、等待开始信号
cnt_clk <= 0;//分频时钟计数
cnt_bit <= 0;//比特计数
CS_N_R <= 1;//片选信号
SCK_R <= CPOL;
data_reg <= data_i_spi;//输入寄存信号
data_len_r <= data_len;//输入寄存信号
wr_lenth_r <= wr_lenth;//输入寄存信号
data_temp <= 0;//输出数据的暂存信号
end
START :begin//拉低片选信号
CS_N_R <= 0;//片选信号拉低
end
DATA :begin//数据的更新,接收,发送
if (cnt_clk == delay – 1) begin//分频时钟计数,也是一个bit计数器
cnt_clk <= 0;
end
else begin
cnt_clk <= cnt_clk + 1;
end
if (cnt_clk == delay – 1) begin//比特计数
if (cnt_bit == data_len_r*8 – 1) begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
//时钟
if (cnt_clk == delay/2 || (cnt_clk == 0 && cnt_bit > 0 )) begin
SCK_R <= ~SCK_R;
end
//数据更新
if ( cnt_clk == 0) begin
data_reg <= data_i_spi;
end
//根据极性来进行采样和寄存
case (CPHA)
0:begin//第一个沿进行采集
//数据发送
if (cnt_clk == 0) begin//数据已经在上一个字节末尾进行更新
MOSI_R <= data_reg[7 – cnt_bit%8];//高位优先
end
//数据接收/采集
if (cnt_clk == delay/2 – 2) begin
data_temp <= {data_temp[6:0],MISO};
end
end
default:begin//第二个沿进行采集
//数据发送
if (cnt_clk == delay/2 ) begin
MOSI_R <= data_reg[7 – cnt_bit%8];//高位优先
end
//数据接收/采集
if (cnt_clk == delay – 2) begin
data_temp <= {data_temp[6:0],MISO};
end
end
endcase
end
STOP :begin//持续一个周期后拉高片选产生done_spi结束本次通信
if (cnt_clk == delay – 1) begin
cnt_clk <= 0;
CS_N_R <= 1;
end
else begin
cnt_clk <= cnt_clk + 1;
end
SCK_R <= CPOL;
end
default:begin
cnt_clk <= 0 ;//分频时钟计数
cnt_bit <= 0 ;//比特计数
CS_N_R <= 1 ;//片选信号
SCK_R <= CPOL;//分频时钟信号
data_reg <= 0 ;//输入寄存信号
data_len_r <= 0 ;//输入寄存信号
wr_lenth_r <= 0 ;//输入寄存信号
data_temp <= 0 ;//输出数据的暂存信号
end
endcase
end
end
//输出信号
// assign data_o_spi = ((CPHA == 0 && cnt_clk == delay/2 && cnt_bit%8 == 7) || (CPHA == 1 && cnt_clk == delay – 1 && cnt_bit%8 == 7)) ? data_temp : data_o_spi ;
assign data_o_spi = data_temp ;
assign done_r = (CPHA == 0 && cnt_clk == delay/2 + 1 && cnt_bit%8 == 7 && cnt_bit > wr_lenth*8) || (CPHA == 1 && cnt_clk == 1 && cnt_bit%8 == 0 && ((cnt_bit > wr_lenth*8) || state_c == STOP)) ;
assign done_w = (CPHA == 0 && cnt_clk == 1 && cnt_bit%8 == 7 && cnt_bit <= wr_lenth*8) || (CPHA == 1 && cnt_clk == delay/2 + 1 && cnt_bit%8 == 7 && cnt_bit <= wr_lenth*8) ;
assign done_spi = STOP_IDLE ;
assign SCK = SCK_R ;
assign CS_N = CS_N_R ;
assign MOSI = MOSI_R ;
endmodule
5.2 仿真代码及波形
`timescale 1ns/1ps
module tb ();
reg clk ;
reg rst_n ;
reg start_spi ;
reg [7:0] data_i_spi;
wire [7:0] data_o_spi;
wire done_w ;
wire done_r ;
wire done_spi ;
wire SCK ;
wire CS_N ;
wire MOSI ;
initial begin
clk <= 0;
rst_n <= 0;
start_spi <= 0;
#100
rst_n <= 1;
#100
start_spi <= 1;
#100
start_spi <= 0;
end
always #10 clk = ~clk;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 0) begin
data_i_spi <= 8'haa;
end
else if (done_r || done_w) begin
data_i_spi <= data_i_spi + 8'h11;
end
end
top top_inst(
.clk (clk ),
.rst_n (rst_n ),
.start_spi (start_spi ),//上层给到spi接口的开始信号
.data_i_spi (data_i_spi),//上层给到spi接口的数据
.data_len (5 ),//上层给到spi接口的总传输长度
.wr_lenth (3 ),//上层给到spi接口的要写(发送给从机的长度)
.data_o_spi (data_o_spi),//输出的数据
.done_w (done_w ),//握手信号
.done_r (done_r ),//握手信号
.done_spi (done_spi ),//握手信号
.SCK (SCK ),//四线
.CS_N (CS_N ),//四线
.MOSI (MOSI ),//四线
.MISO (MOSI ) //四线
);
endmodule







