SPI协议
SPI是一种同步串行通信协议,有一个主设备和一个或者多个从设备组成,主设备启动与从设备的同步通信,同步是通过同一个时钟完成的。
有4个引脚:
1、SCK
2、SSEL:片选信号
3、MOSI:主机发送从机接收
4、MISO:从机发送主机接收
CPOL,SPI总线的极性,决定SPI总线空闲时的时钟信号是高电平还是低电平。
CPOL=1表示空闲时高电平;反之。
CPHA,SPI总线的相位,决定SPI总线在哪个跳变沿采样数据。
CPHA=0表示从第一个跳变沿开始采样;反之从第二个跳变沿开始采样
注意:这里不是通过固定的上升沿和下降沿采样
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时。
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时。
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿。
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿。
由于CPOL和CPHA的不同组合,形成了SPI总线的4种不同模式
如图为模式0的波形图:
verilog
SPI mater文件:
`timescale 1ns / 1ps
/*
SPI的主机模块,该模块工作在SPI的模式0
在这里,用100MHZ的时钟分频产生了一个1MHZ的SCK时钟,将它作为程序的主时钟
*/
module spi_master(
input clk,
output reg sck, //1MHZ
input rst,
input spi_send,
input [7:0] spi_data_out,
output reg spi_send_done,
// input busy,
input miso,
output reg mosi,
output reg cs
);
reg [3:0]count; //数据计数 0~7
reg [7:0] delay_count; //时钟分频计数 0~49
//状态机分为4个状态:等待、拉低CS、发送数据、结束数据
localparam IDLE = 0,
CS_L = 1,
DATA = 2,
FINISH = 3;
reg [4:0] cur_st, nxt_st;
reg [7:0] reg_data;
reg sck_reg;
//时钟分频
always @(posedge clk) begin
if(~rst)
delay_count <= 0;
else if(delay_count == 49)
delay_count <= 0;
else
delay_count <= delay_count+1;
end
always @(posedge clk) begin
if(~rst)
sck_reg <= 0;
else if(delay_count == 49)
sck_reg <= ~sck_reg;
end
//模式0,sck只有在cs拉低时才变化,其他时刻都为低电平
always @(*) begin
if(cs)
sck <= 0;
// else if(cur_st == FINISH)
// sck <= 0;
else if(~cs)
sck <= sck_reg;
else
sck <= 0;
end
always @(posedge sck_reg) begin
if(~rst)
cur_st <= IDLE;
else
cur_st <= nxt_st;
end
always @(*) begin
case(cur_st)
IDLE: begin
if(spi_send)
nxt_st = CS_L;
else
nxt_st = IDLE;
end
CS_L:
nxt_st = DATA;
DATA: begin
if(count == 4'd7)
nxt_st = FINISH;
else
nxt_st = DATA;
end
FINISH:
nxt_st = IDLE;
default: nxt_st = IDLE;
endcase
end
//产生发送结束标志
always @(*) begin
if(~rst)
spi_send_done = 0;
else if(cur_st == FINISH)
spi_send_done = 1;
else
spi_send_done = 0;
end
//产生CS
always @(posedge sck_reg) begin
if(~rst)
cs <= 1;
else if(cur_st == CS_L)
cs <= 0;
else if(cur_st == DATA)
cs <= 0;
else cs <= 1;
end
//发送数据计数
always @(posedge sck_reg) begin
if(~rst)
count <= 'b0;
else if(cur_st == DATA)
count <= count+1'b1;
else if(cur_st==IDLE || cur_st==FINISH)
count <= 'b0;
end
//MOSI数据,上升沿采集数据,下降沿切换数据
always @(negedge sck_reg) begin
if(~rst)
mosi <= 0;
else if(cur_st == DATA) begin
reg_data[7:1] <= reg_data[6:0];
mosi <= reg_data[7];
end
else if(spi_send) //当状态机从IDLE 变为CS_L时,立马更新数据
reg_data <= spi_data_out;
end
endmodule
tb文件:
`timescale 1ns / 1ps
module tb();
reg clk;
wire sck;
reg rst;
reg spi_send;
reg [7:0] spi_data_out;
wire spi_send_done;
reg miso;
wire mosi;
wire cs;
always #5 clk=~clk;
integer i;
initial begin
clk = 0;
rst = 0;
#10;
rst = 1;
spi_send = 1;
miso = 0;
for(i=0;i<10;i=i+1) begin
@(negedge clk) begin
spi_data_out = i+1;
#10000;
end
end
spi_send = 0;
end
spi_master u1(
.clk(clk),
.sck(sck),
.rst(rst),
.spi_send(spi_send),
.spi_data_out(spi_data_out),
.spi_send_done(spi_send_done),
.miso(miso),
.mosi(mosi),
.cs(cs)
);
endmodule