SPI
概述
全双工与半双工
全双工是指收发可以在同一时刻进行,而半双工是指在同一时刻只能进行一项操作
SPI
SPI是serial peripheral interface(串行外设接口)的缩写,是一种同步串行通信协议。于1979年,由Motorola公司推出,用于节省自家芯片的PCB空间布局。
特点
主从控制
主机可以输出时钟信号和片选信号来控制从设备,从设备只能从主设备处获取时钟。、
同步传输
主设备根据将要交换的数据来产生相应的时钟脉冲(clock pulse),时钟脉冲组成时钟信号(clock signal),时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制主从设备之间何时交换数据、何时对数据进行采样,来保证数据传输同步进行。
缺点
没有应答机制确认是否接收到数据,所以跟IIC比降低了可靠性
管脚
- MOSI:主设备数据输出,从设备数据输入
- MISO:主设备数据输入,从设备数据输出
- SCLK:主设备产生,发送给从设备
- CS:从设备使能信号,由主设备控制。
传输模式

SPI总线共有四种传输模式,主要是{CPOL,CPHA}的组合。CPOL代表SPI总线空闲时刻的电平,CPHA规定数据在第几个边沿采样,在第几个边沿切换。
比如第一种模式,CPOL=0,CPHA=0,表示空闲时刻为低电平,数据在上升沿采样,数据在时钟下降沿切换。
常用的为模式0和模式3,下面为模式0时序图

在空闲时刻,片选信号为高电平,时钟信号为低电平。在需要传送数据时,片选信号拉低,MOSI于MISO在时钟上升沿进行采样,在时钟下降沿数据切换。可以看到时钟上升沿正对数据中间。
SPI设备可以理解为一个大的移位寄存器,它在通信过程中不能单纯充当发送或接受,而应该是在每个CLK周期内进行1bit数据交换。
代码实现
spi-master
module spi_master
#(
parameter CLK_FRE = 50 , // system clk frequence
parameter SPI_FRE = 5 , // SCLK frequence
parameter DATA_WIDTH = 8 , // serial bit number
parameter CPOL = 0 , // clock polarity
parameter CPHA = 0 // clock phase
)(
input clk ,
input rst_n ,
input [DATA_WIDTH-1:0] data_tx , //the serial data from master to slave
input start ,
input miso ,
output o_sclk ,
output reg mosi ,
output reg cs_n ,
output reg [DATA_WIDTH-1:0] data_rx , //the serial data from slave to master
output reg finish
);
localparam CYCLE = CLK_FRE / SPI_FRE - 1 ;
localparam SHIFT_CNT = clogb2(DATA_WIDTH) ;
localparam SCLK_CNT = clogb2(CYCLE) ;
function integer clogb2 ( input integer bit_depth) ;
begin
for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
bit_depth = bit_depth >> 1;
end
endfunction
localparam IDLE = 3'b000 ;
localparam LOAD_DATA = 3'b001 ;
localparam SEND_DATA = 3'b010 ;
localparam DONE = 3'b100 ;
reg [2:0] state,next_state ;
reg sclk ;
reg sclk_valid ;
reg [SCLK_CNT-1:0] sclk_cnt ;
reg [SHIFT_CNT-1:0]shift_cnt ;
reg [DATA_WIDTH-1:0] data_tx_temp;
reg sclk_delay ;
wire sclk_posedge ;
wire sclk_negedge ;
reg data_sample ;
reg data_switch ;
// generate sclk
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_cnt <= 'd0;
else if(sclk_valid)
begin
if(sclk_cnt == CYCLE)
sclk_cnt <= 'd0 ;
else
sclk_cnt <= sclk_cnt + 1 ;
end
else
sclk_cnt <= 'd0;
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk <= CPOL ;
else if(sclk_valid)
begin
if(sclk_cnt == CYCLE)
sclk <= ~sclk ;
else
sclk <= sclk ;
end
else
sclk <= CPOL ;
end
// detect the sclk edge
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_delay <= CPOL ;
else if (sclk_valid)
sclk_delay <= sclk ;
else
sclk_delay <= CPOL ;
end
assign sclk_posedge = sclk & (!sclk_delay) ;
assign sclk_negedge = (!sclk) & sclk_delay ;
always @(*)
begin
case ({CPOL,CPHA})
2'b00 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
2'b01 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b10 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b11 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
default : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
endcase
end
//FSM
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
state <= IDLE ;
else
state <= next_state ;
end
always @(*)
begin
case(state)
IDLE : next_state = start ? LOAD_DATA : IDLE ;
LOAD_DATA : next_state = SEND_DATA ;
SEND_DATA : next_state = (shift_cnt == DATA_WIDTH) ? DONE : SEND_DATA;
DONE : next_state = IDLE ;
endcase
end
//the control signal
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
sclk_valid <= 1'b0;
data_tx_temp<= 'd0;
cs_n <= 1'b1;
shift_cnt <= 'd0 ;
finish <= 1'b0;
end
else
begin
case(state)
IDLE : begin
sclk_valid <= 1'b0 ;
data_tx_temp <= 'd0 ;
cs_n <= 1'b1 ;
shift_cnt <= 'd0 ;
finish <= 1'b0 ;
end
LOAD_DATA : begin
sclk_valid <= 1'b0 ;
data_tx_temp <= data_tx ;
cs_n <= 1'b0 ;
shift_cnt <= 'd0 ;
finish <= 1'b0 ;
end
SEND_DATA : begin
sclk_valid <= 1'b1 ;
if(data_switch)
begin
shift_cnt <= shift_cnt + 1;
data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
end
else
begin
shift_cnt <= shift_cnt ;
data_tx_temp <= data_tx_temp ;
end
end
DONE : begin
sclk_valid <= 1'b0;
data_tx_temp <= 'd0;
cs_n <= 1'b1 ;
finish <= 1'b1;
shift_cnt <= 'd0 ;
end
endcase
end
end
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_rx <= 'd0;
else if(data_sample)
data_rx <= {data_rx[DATA_WIDTH-2:0],miso} ;
else
data_rx <= data_rx ;
end
assign mosi = data_tx_temp[DATA_WIDTH-1];
assign o_sclk = sclk_delay;
endmodule
仿真结果
思路
作为spi master端,可以先产生sclk,通过检测sclk的上升沿和下降沿来进行数据采样和数据切换进行数据传输。
在实际应用中spi最为接口信号,可以约定指令格式,分为指令位、读写位、数据位,通过spi master端向slave端发送指令。
spi-slave
module spi_slave
#(
parameter CLK_FRE = 50 ,
parameter SCLK_FRE = 5 ,
parameter DATA_WIDTH = 8 ,
parameter CPOL = 0 ,
parameter CPHA = 0
)(
input clk ,
input rst_n ,
input [DATA_WIDTH-1:0] data_tx,
input data_tx_valid ,
input sclk ,
input cs_n ,
input mosi ,
output miso ,
output finish ,
output reg [DATA_WIDTH-1:0] data_rx
);
localparam SAMPLE_CNT = clogb2(DATA_WIDTH);
reg [DATA_WIDTH-1:0] data_tx_temp ;
reg sclk_delay ;
reg cs_n_delay ;
wire sclk_posedge ;
wire sclk_negedge ;
wire cs_negedge ;
reg data_sample ;
reg data_switch ;
reg [SAMPLE_CNT-1:0] sample_cnt ;
//capture the edge of sclk
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sclk_delay <= CPOL ;
else if(!cs_n)
sclk_delay <= sclk ;
else
sclk_delay <= CPOL ;
end
assign sclk_posedge = sclk & (!sclk_delay) ;
assign sclk_negedge = (!sclk) & sclk_delay ;
//capture the edge of cs_n
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
cs_n_delay <= 1'b1 ;
else
cs_n_delay <= cs_n ;
end
assign cs_negedge = (!cs_n) & cs_n_delay ;
always @(*)
begin
case ({CPOL,CPHA})
2'b00 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
2'b01 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b10 : begin data_sample = sclk_negedge ; data_switch = sclk_posedge ; end
2'b11 : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
default : begin data_sample = sclk_posedge ; data_switch = sclk_negedge ; end
endcase
end
//trans data from slave to master
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_tx_temp <= 'd0;
else if(data_tx_valid)
data_tx_temp <= data_tx ;
else if(!cs_n && data_switch)
data_tx_temp <= {data_tx_temp[DATA_WIDTH-2:0],1'b0};
else
data_tx_temp <= data_tx_temp ;
end
assign miso = !cs_n ? data_tx_temp[DATA_WIDTH-1] : 1'b0 ;
//sample data from master to slave
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
data_rx <= 'd0;
else if(!cs_n && data_sample)
data_rx <= {data_rx[DATA_WIDTH-2:0],mosi} ;
else
data_rx <= data_rx ;
end
//finish signal
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
sample_cnt <= 'd0;
else if(!cs_n && data_sample)
sample_cnt = sample_cnt + 1 ;
else if(cs_n)
sample_cnt <= 'd0;
else
sample_cnt <= sample_cnt ;
end
assign finish = (sample_cnt == DATA_WIDTH) ;
function integer clogb2 ( input integer bit_depth);
begin
for(clogb2 = 0 ; bit_depth > 0 ; clogb2 = clogb2 + 1)
bit_depth = bit_depth >> 1;
end
endfunction
endmodule
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】