一、概述
SPI(串行外设接口)是一种同步、全双工、高速的通信总线,以主从方式工作,通常有一主一(多)从。
SPI接口有4根信号线:
SCLK:串行时钟信号,由主机输出,从机在时钟边沿采样。
CS:片选信号,由主机输出,一般为低电平有效,用于选择要操作的从设备。
MOSI:主出从入,主机向从机发送数据。
MISO:从出主入,主机接收从机发送的数据。
二、工作模式
SPI通信一般有4种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)决定,在出厂时已配置好,用户不可修改。
CPOL决定了SCLK的时钟极性:
CPOL=1:SLCK在空闲时为高电平,发起通信的第一个时钟沿为下降沿。
CPOL=0:SLCK在空闲时为低电平,发起通信的第一个时钟沿为上升沿。
CPHA决定了采样是在奇数边沿还是偶数边沿:
CPHA=1:在时钟的第2(偶)个边沿采样数据(此时数据要稳定),在时钟的第1(奇)个边沿改变数据。
CPHA=0:在时钟的第1(奇)个边沿采样数据(此时数据要稳定),在时钟的第2(偶)个边沿改变数据。
三、数据传输时序
SPI协议规定通信过程中设备不能仅仅发或收,在每个时钟周期内SPI主从设备都会交换1bit数据。
所以SPI没有读和写的说法,发送1个数据必然会收到1个数据,而想要接收1个数据必然要先发送1个数据,主从设备是一个链路环回,通过双向移位寄存器实现。
- 主机拉低片选信号线CS,表示与该设备通信;
- 主机发送时钟信号SCLK;
- 主机将数据采样写到发送缓冲区(memory),缓冲区的数据经过移位寄存器;
- 主机通过移位寄存器将数据顺次输出到MOSI,SIMO收到的数据通过移位寄存器接收到缓冲区;
- 同样地,从机通过移位寄存器将数据输出到SOMI发送给主机,MISO通过移位寄存器接收发送的数据到缓冲区;
数据各位传输顺序无明确规定,一般先发高位、一次通信过程传输1字节
四、特点
优点:
- 有较高的数据传输速率,可达Mbps,适合高速传输的应用;
- 主机可以单独控制每个从机的数据传输;
- 全双工通信,可同时进行数据的发送和接收;
缺点:
- 每个从机都需要独立的CS,从机数量增加时,硬件设计成本高;
- 没有仲裁机制,不能解决多主机的冲突问题;
- 只支持单个主机;
- 没有应答等错误检查机制;
五、程序设计
//copy
//CPOL=0,CPHA=0
module spi_module
(
input I_clk , // 全局时钟50MHz
input I_rst_n , // 复位信号,低电平有效
input I_rx_en , // 读使能信号
input I_tx_en , // 发送使能信号
input [7:0] I_data_in , // 要发送的数据
output reg [7:0] O_data_out , // 接收到的数据
output reg O_tx_done , // 发送一个字节完毕标志位
output reg O_rx_done , // 接收一个字节完毕标志位
// 四线标准SPI信号定义
input I_spi_miso , // SPI串行输入,用来接收从机的数据
output reg O_spi_sck , // SPI时钟
output reg O_spi_cs , // SPI片选信号
output reg O_spi_mosi // SPI输出,用来给从机发送数据
);
reg [3:0] R_tx_state ;
reg [3:0] R_rx_state ;
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
begin
R_tx_state <= 4'd0 ;
R_rx_state <= 4'd0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b0 ;
O_tx_done <= 1'b0 ;
O_rx_done <= 1'b0 ;
O_data_out <= 8'd0 ;
end
else if(I_tx_en) // 发送使能信号打开的情况下
begin
O_spi_cs <= 1'b0 ; // 把片选CS拉低
case(R_tx_state)
4'd1, 4'd3 , 4'd5 , 4'd7 ,
4'd9, 4'd11, 4'd13, 4'd15 : //整合奇数状态
begin
O_spi_sck <= 1'b1 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd0: // 发送第7位
begin
O_spi_mosi <= I_data_in[7] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd2: // 发送第6位
begin
O_spi_mosi <= I_data_in[6] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd4: // 发送第5位
begin
O_spi_mosi <= I_data_in[5] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd6: // 发送第4位
begin
O_spi_mosi <= I_data_in[4] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd8: // 发送第3位
begin
O_spi_mosi <= I_data_in[3] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd10: // 发送第2位
begin
O_spi_mosi <= I_data_in[2] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd12: // 发送第1位
begin
O_spi_mosi <= I_data_in[1] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b0 ;
end
4'd14: // 发送第0位
begin
O_spi_mosi <= I_data_in[0] ;
O_spi_sck <= 1'b0 ;
R_tx_state <= R_tx_state + 1'b1 ;
O_tx_done <= 1'b1 ;
end
default:R_tx_state <= 4'd0 ;
endcase
end
else if(I_rx_en) // 接收使能信号打开的情况下
begin
O_spi_cs <= 1'b0 ; // 拉低片选信号CS
case(R_rx_state)
4'd0, 4'd2 , 4'd4 , 4'd6 ,
4'd8, 4'd10, 4'd12, 4'd14 : //整合偶数状态
begin
O_spi_sck <= 1'b0 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
end
4'd1: // 接收第7位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[7] <= I_spi_miso ;
end
4'd3: // 接收第6位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[6] <= I_spi_miso ;
end
4'd5: // 接收第5位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[5] <= I_spi_miso ;
end
4'd7: // 接收第4位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[4] <= I_spi_miso ;
end
4'd9: // 接收第3位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[3] <= I_spi_miso ;
end
4'd11: // 接收第2位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[2] <= I_spi_miso ;
end
4'd13: // 接收第1位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b0 ;
O_data_out[1] <= I_spi_miso ;
end
4'd15: // 接收第0位
begin
O_spi_sck <= 1'b1 ;
R_rx_state <= R_rx_state + 1'b1 ;
O_rx_done <= 1'b1 ;
O_data_out[0] <= I_spi_miso ;
end
default:R_rx_state <= 4'd0 ;
endcase
end
else
begin
R_tx_state <= 4'd0 ;
R_rx_state <= 4'd0 ;
O_tx_done <= 1'b0 ;
O_rx_done <= 1'b0 ;
O_spi_cs <= 1'b1 ;
O_spi_sck <= 1'b0 ;
O_spi_mosi <= 1'b0 ;
O_data_out <= 8'd0 ;
end
end
endmodule
//testbench
`timescale 1ns / 1ps
module tb_spi_module;
// Inputs
reg I_clk;
reg I_rst_n;
reg I_rx_en;
reg I_tx_en;
reg [7:0] I_data_in;
reg I_spi_miso;
// Outputs
wire [7:0] O_data_out;
wire O_tx_done;
wire O_rx_done;
wire O_spi_sck;
wire O_spi_cs;
wire O_spi_mosi;
// Instantiate the Unit Under Test (UUT)
spi_module uut (
.I_clk (I_clk ),
.I_rst_n (I_rst_n ),
.I_rx_en (I_rx_en ),
.I_tx_en (I_tx_en ),
.I_data_in (I_data_in ),
.O_data_out (O_data_out ),
.O_tx_done (O_tx_done ),
.O_rx_done (O_rx_done ),
.I_spi_miso (I_spi_miso ),
.O_spi_sck (O_spi_sck ),
.O_spi_cs (O_spi_cs ),
.O_spi_mosi (O_spi_mosi )
);
initial begin
// Initialize Inputs
I_clk = 0;
I_rst_n = 0;
I_rx_en = 0;
I_tx_en = 1;
I_data_in = 8'h00;
I_spi_miso = 0;
// Wait 100 ns for global reset to finish
#100;
I_rst_n = 1;
end
always #10 I_clk = ~I_clk ;
always @(posedge I_clk or negedge I_rst_n)
begin
if(!I_rst_n)
I_data_in <= 8'h00;
else if(I_data_in == 8'hff)
begin
I_data_in <= 8'hff;
I_tx_en <= 0;
end
else if(O_tx_done)
I_data_in <= I_data_in + 1'b1 ;
end
endmodule