SPI是一种高速,全双工,同步的通信总线,在芯片上主要占用四根线(CS、MOSI、MISO、SCK),极大的节约了芯片的引脚。
CS:片选信号,空闲状态为高电平
MOSI(master output slave input):主机输出数据,从机输入数据
MISO(master input slave output):主机输入数据,从机输出数据
SCK:用于输出时钟(在从模式下,SPI相对于该时钟传输数据)或接收时钟。
基本结构如下图:
SPI总线主要有四种工作状态,SPI0,SPI1,SPI2,SPI3,其中常用的为了SPI0和SPI3。SPI的工作状态主要由CPOL(时钟极性)和CPHA(时钟相位)决定。
CPOL=0时,SCK空闲时为低电平,CPOL=1时,SCK空闲时为高电平;
CPHA=0时,SCK的第一个跳变沿为采样数据即读取发送的数据,第二个跳变沿为输出数据,CPHA=1时,SCK的第一个跳变沿为输出数据,第二个跳变沿为采样数据。
当CPOL=1,CPHA=1时,即SCK空闲为高电平,第二个跳变沿为采集数据;
当主机需要发送的spi_data=8’b10101010时,在第一个跳变沿会发送mosi=1,从最高位开始输出,第二个跳变沿会读取0,第三个跳变沿输出mosi=0,以此类推;
在这个通信过程中需要三个组成部分:
计数器:计数16次,每次代表一个跳变沿;
移位器:将spi_data不断向左移,输出最高位到mosi;
分频器:生成sck波形,当空闲时拉高,启动时拉低,然后有规律的波动;
在top顶层文件中需要对状态切换进行定义,在三个子模块中,对于计数器,移位器和分频器进行编写;
SPI的输入主要有clk,rst,spi_data,spi_start;
SPI的输出主要有sck,spi_done,mosi,cs;
module top(
input clk,
input rst,
input [7:0] spi_data,
input spi_start,
output sck,
output mosi,
output reg cs,
output reg spi_done
);
reg a,en_a;//计数器清零和启动信号
reg b,en_b;//分频器,拉高和使能信号
reg c,en_c;//移位器,读取spi_data和移位标志信号
wire [4:0] i;//跳变沿计数;
reg [5:0] state='b0;//当前状态
reg [5:0] next_state='b0;//下一个状态
parameter s0='b000001;
parameter s1='b000010;
parameter s2='b000100;
parameter s3='b001000;
parameter s4='b010000;
parameter s5='b100000;
always@(posedge clk or negedge rst)
begin
if(!rst)
state<=s0;
else
state<=next_state;
end
always@(*)
begin
case(state)
s0: begin
if(spi_start)
next_state<=s1;
else
next_state<=s0;
end
s1:begin
if(i=='b1)//开始计数,第一个跳变沿发送数据
next_state<=s2;
else
next_state<=s1;
end
s2:begin
if(i[0]=='b0)//偶数跳变沿为采样数据
next_state<=s3;
else
next_state<=s2;
end
s3:begin
if(i==5'd15)
next_state<=s4;
else begin
if(i[0]=='b1)//奇数跳变沿为输出数据
next_state<=s2;
else
next_state<=s3;
end
end
s4:begin
if(i=='d16)
next_state<=s5;
else
next_state<=s4;
end
s5:begin
if(i=='b0)
next_state<=s0;
else
next_state<=s5;
end
default:next_state<=s0;
endcase
end
always@(*)begin
case(state)
s0:begin
a<='b1;//计数器清零
en_a<='b0;
b<='b1;//CPOL=1,空闲拉高平
en_b<='b0;
c<='b0;//未载入输入数据
en_c<='b0;
spi_done<='b0;
cs<='b1;//空闲 片选拉高
end
s1:begin
a<='b0;
en_a<='b1;//开始计数
b<='b0;
en_b<='b1;//产生SCK
c<='b1;//载入数据
en_c<='b0;
spi_done<='b0;
cs<='b0;
end
s2:begin
a<='b0;
en_a<='b1;
b<='b0;
en_b<='b1;
c<='b0;
en_c<='b1;//开始移位 输出mosi
spi_done<='b0;
cs<='b0;
end
s3:begin
a<='b0;
en_a<='b1;
b<='b0;
en_b<='b1;
c<='b0;
en_c<='b0;//停止移位 保持数据不变 没有采样阶段
spi_done<='b0;
cs<='b0;
end
s4:begin
a<='b0;
en_a<='b1;
b<='b0;
en_b<='b0;
c<='b0;
en_c<='b0;
spi_done<='b0;
cs<='b0;
end
s5:begin
a<='b0;
en_a<='b0;
b<='b0;
en_b<='b0;
c<='b0;
en_c<='b0;
spi_done<='b1;
cs<='b1;
end
default:begin
a<='b1;
en_a<='b0;
b<='b1;
en_b<='b0;
c<='b0;
en_c<='b0;
spi_done<='b0;
cs<='b1;
end
endcase
end
count u1(
.clk(clk),
.i(i),
.a(a),
.en_a(en_a)
);
sck u2(
.clk(clk),
.b(b),
.en_b(en_b),
.sck(sck)
);
shifter u3(
.clk(clk),
.c(c),
.en_c(en_c),
.spi_data(spi_data),
.mosi(mosi)
);
endmodule
module count(
input clk,
input a,
input en_a,
output reg [4:0] i
);
always@(posedge clk)begin
if(a)
i<='b0;
else
if(en_a) begin
if(i=='d16)
i<='d0;
else
i<=i+'d1;
end
else
i<=i;
end
endmodule
module sck(
input clk,
input b,
input en_b,
output reg sck
);
always@(posedge clk)begin
if(b)
sck<='b1;
else begin
if(en_b)
sck<=~sck;
else
sck<='b1;
end
end
endmodule
module shifter(
input clk,
input [7:0] spi_data,
input c,
input en_c,
output mosi
);
reg [7:0] data;
always@(posedge clk) begin
if(c)
data<=spi_data;
else begin
if(en_c)
data<= {data[6:0],1'b0};
else
data<=data;
end
end
assign mosi=data[7];
endmodule
module test( );
reg clk;
reg rst;
reg spi_start;
reg [7:0] spi_data;
wire spi_done;
wire sck;
wire mosi;
wire cs;
initial begin
rst<=1'b0;
clk<=1'b0;
spi_start<=1'b0;
spi_data<=8'b0;
#100
rst<=1'b1;
#100
spi_start<=1'b1;
#100
spi_data<=8'b10101010;
end
always #5 clk=~clk;
top uu(
.clk(clk),
.rst(rst),
.spi_data(spi_data),
.spi_start(spi_start),
.sck(sck),
.cs(cs),
.spi_done(spi_done),
.mosi(mosi)
);
endmodule
可以看到在SCK从高跳变到低时,即第一个跳变沿时mosi由高到低输出spi_data;
CSDN-基于FPGA的SPI协议实现
spi协议(英文版)