SPI(Serial Peripheral Interface,串行外围设备接口)通讯协议,是 Motorola 公司提出的一种同步串行接口技术,是一种高速、全双工、同步通信总线,在芯片中只占用四根管脚用来控制及数据传输,广泛用于 EEPROM、Flash、ADC(数模转换器)、DSP(数字信号处理器)以及数字信号解码器上,是常用的也是较为重要的通讯协议之一。
SPI 通讯协议的优点是支持全双工通信,通讯方式较为简单,且相对数据传输速率较快;缺点是没有指定的流控制,没有应答机制确认数据是否接收。
SPI 通讯协议包含 1 条时钟信号线、2 条数据总线和 1 条片选信号线, 时钟信号线为SCK,2 条数据总线分别为 MOSI(主输出从输入)、MISO(主输入从输出),片选信号线为CS_N,它们的作用介绍如下:
(1) SCK (Serial Clock):时钟信号线,用于同步通讯数据。由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不同,两个设备之间通讯时,通讯速率受限于低速设备。
(2) MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,数据方向由主机到从机。
(3) MISO (Master Input,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。
(4) CS_N(Chip Select):片选信号线-。当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI总线上,即无论有多少个从设备,都共同使用这 3 条总线;而每个从设备都有独立的这一条 CS_N 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。
SPI 通讯协议的 4 种模式如下。
模式 0:CPOL= 0,CPHA=0。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK时钟的奇数边沿,本模式中,奇数边沿为上升沿;数据更新在 SCK 时钟的偶数边沿,本模式中,偶数边沿为下降沿。
模式 1:CPOL= 0,CPHA=1。空闲状态时 SCK 串行时钟为低电平;数据采样在 SCK时钟的偶数边沿,本模式中,偶数边沿为下降沿;数据更新在 SCK 时钟的奇数边沿,本模式中,偶数边沿为上升沿。
模式 2:CPOL= 1,CPHA=0。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK时钟的奇数边沿,本模式中,奇数边沿为下降沿;数据更新在 SCK 时钟的偶数边沿,本模式中,偶数边沿为上升沿。
模式 3:CPOL= 1,CPHA=1。空闲状态时 SCK 串行时钟为高电平;数据采样在 SCK时钟的偶数边沿,本模式中,偶数边沿为上升沿;数据更新在 SCK 时钟的奇数边沿,本模式中,偶数边沿为下降沿。
//spi: flash全擦除实验
module spi(
sys_clk,
sys_rstn,
key,
miso,
cs_n,
sck,
mosi
);
input sys_clk;
input sys_rstn;
input key;
input miso;
output cs_n;
output sck;
output mosi;
reg cs_n;
reg sck;
reg mosi;
parameter wr_en = 8'h06;//flash 写使能指令
parameter be = 8'hc7;//全擦除指令
reg [1:0] cnt_sck;
reg [4:0] cnt;
reg [2:0] cnt_bit;
reg [2:0] cnt_byte;
reg [1:0] state;
//产生sck,cnt_sck为2'b2时为上升沿
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
cnt_sck <= 2'b0;
end
else if(cnt_sck == 2'd3)begin
cnt_sck <= 2'b0;
end
else if(state == 2'b01&&cnt_byte==3'd1)begin
cnt_sck <= cnt_sck +1'b1;
end
else if(state == 2'b11&&cnt_byte==3'd5)begin
cnt_sck <= cnt_sck +1'b1;
end
else begin
cnt_sck <= 0;
end
end
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
sck <= 1'b0;
end
else if(cnt_sck == 2'b0)begin
sck <= 0;
end
else if(cnt_sck == 2'd2)begin
sck <= 1;
end
else begin
sck <= sck;
end
end
//从高位输出
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
cnt_bit <= 3'b0;
end
else if(cnt_sck == 2'd2)begin
cnt_bit <= cnt_bit + 1'b1;
end
else begin
cnt_bit <= cnt_bit ;
end
end
//产生基础周期32sys_clk
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
cnt <= 5'b0;
end
else if(cnt == 5'd31)begin
cnt <= 5'b0;
end
else begin
cnt <= cnt + 1'b1;
end
end
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
cnt_byte <= 3'b0;
end
else if(cnt_byte==3'd6&&cnt == 5'd31)begin
cnt_byte <= 3'b0;
end
else if(cnt == 5'd31)begin
cnt_byte<= cnt_byte + 1'b1;
end
else begin
cnt_byte <= cnt_byte;
end
end
//状态机
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
state <= 2'b0;
end
else begin
case(state)
2'b00: begin
if(key) begin
state <= 2'b01;
end
else begin
state <= state;
end
end
2'b01: begin
if(cnt_byte==3'd2&&cnt == 5'd31)begin
state <= 2'b10;
end
else begin
state <= state;
end
end
2'b10: begin
if(cnt_byte==3'd3&&cnt == 5'd31)begin
state <= 2'b11;
end
else begin
state <= state;
end
end
2'b11: begin
if(cnt_byte==3'd6&&cnt == 5'd31)begin
state <= 2'b00;
end
else begin
state <= state;
end
end
endcase
end
end
//片选
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
cs_n <= 1'b1;
end
else if(key)begin
cs_n <= 1'b0;
end
else if(state==3'b01&&cnt_byte==3'd2&&cnt == 5'd31)begin
cs_n <= 1'b1;
end
else if(state==3'b10&&cnt_byte==3'd3&&cnt == 5'd31)begin
cs_n<= 1'b0;
end
else if(state==3'b11&&cnt_byte==3'd6&&cnt == 5'd31)begin
cs_n<= 1'b1;
end
else begin
cs_n <= cs_n;
end
end
//mosi
always@(posedge sys_clk)
begin
if(!sys_rstn)begin
mosi <= 3'b0;
end
else if(state==3'b01&&cnt_byte==3'd2)begin
mosi <= 0;
end
else if(state==3'b11&&cnt_byte==3'd6)begin
mosi <= 0;
end
else if(state==3'b01&&cnt_byte==3'd1&&cnt_sck==2'b0)begin
mosi <= wr_en[7-cnt_bit];
end
else if(state==3'b11&&cnt_byte==3'd5&&cnt_sck==2'b0)begin
mosi <= be[7-cnt_bit];
end
else begin
mosi <= mosi;
end
end
endmodule
//test_bench
`timescale 1ns/1ps
module tb_spi();
reg sys_clk;
reg sys_rstn;
reg key;
wire cs_n;
wire sck;
wire mosi;
always #1 sys_clk = ~sys_clk;
initial
begin
sys_clk = 0;
sys_rstn = 0;
key = 0;
#20
sys_rstn = 1;
#20
key = 1;
#2
key = 0;
end
spi inst0(
.sys_clk (sys_clk ),
.sys_rstn (sys_rstn ),
.key (key ),
.cs_n (cs_n ),
.sck (sck ),
.mosi ( mosi )
);
endmodule