SPI是串行外设接口(Serial Peripheral Interface)的缩写。它是Motorola公司推出的一种高速、全双工的同步串行通信总线技术。
SPI的通信接口为:SDI或MISO(数据输入),SDO或MOSI(数据输出),SCK(时钟),CS(片选)。
另外,可以通过设置CPOL(时钟极性)和CPHA(时钟相位)来配置主控设备的通信模式。其中时钟极性CPOL用来配置SCLK的空闲态或者有效态电平,时钟相位CPHA 用来配置数据采样是在第几个边沿:
CPOL=0,表示SCLK的空闲态电平为0,有效态电平为1;
CPOL=1,表示SCLK的空闲态电平为1,有效态电平为0;
CPHA=0,表示在时钟的第一个边沿进行数据采集,时钟的第二个边沿进行数据发送;
CPHA=1,表示在时钟的第二个边沿进行数据采集,时钟的第一个边沿进行数据发送。
特别说明:以下所涉及代码参考网络、相关开发板厂家。此处进行了注解。
spi_master.v
module spi_master
(
input sys_clk, //系统时钟
input rst, //复位
output nCS, //SPI 片选
output DCLK, //SPI时钟
output MOSI, //SPI输出
input MISO, //SPI 输入
input CPOL, //时钟极性
input CPHA, //时钟相位
input nCS_ctrl, //片选控制
input[15:0] clk_div, //时钟分频
input wr_req, //读写请求
output wr_ack, //读写应答
input[7:0] data_in, //数据输入
output[7:0] data_out //暑假输出
);
localparam IDLE = 0;
localparam DCLK_EDGE = 1;
localparam DCLK_IDLE = 2;
localparam ACK = 3;
localparam LAST_HALF_CYCLE = 4;
localparam ACK_WAIT = 5;
reg DCLK_reg;
reg[7:0] MOSI_shift;
reg[7:0] MISO_shift;
reg[2:0] state;
reg[2:0] next_state;
reg[15:0] clk_cnt;
reg[4:0] clk_edge_cnt;
assign MOSI = MOSI_shift[7];
assign DCLK = DCLK_reg;
assign data_out = MISO_shift;
assign wr_ack = (state == ACK);
assign nCS = nCS_ctrl;
//说明:以下代码实现状态机状态更新
always@(posedge sys_clk or posedge rst)
begin
if(rst)
state <= IDLE;
else
state <= next_state;
end
//状态机
always@(*)
begin
case(state)
IDLE:
if(wr_req == 1'b1) //如果有读写请求,进入时钟空闲态
next_state <= DCLK_IDLE;
else
next_state <= IDLE;
DCLK_IDLE:
//half a SPI clockcycle produces a clock edge
if(clk_cnt == clk_div)//在时钟空闲态中,如果分频时钟满,则进入时钟采集态
next_state <= DCLK_EDGE;
else
next_state <= DCLK_IDLE;
DCLK_EDGE:
//a SPI byte with a total of 16 clock edges
if(clk_edge_cnt ==5'd15) //在时钟采集态,如果时钟沿达到15个,则进入最后半个时钟沿(因为8位数据,需要8个脉冲即16个时钟沿)
next_state <= LAST_HALF_CYCLE;
else
next_state <= DCLK_IDLE;
//this is the last data edge
LAST_HALF_CYCLE:
if(clk_cnt == clk_div) //如果在最后半个时钟沿,预分频时间到后进入应答态
next_state <= ACK;
else
next_state <= LAST_HALF_CYCLE;
//send one byte complete
ACK:
next_state <=ACK_WAIT; //进入应答等待态
//wait for one clock cycle, to ensure that the cancel request signal
ACK_WAIT:
next_state <= IDLE;
default:
next_state <= IDLE;
endcase
end
//说明:产生数据时钟信号
always@(posedge sys_clk or posedge rst)
begin
if(rst)
DCLK_reg <= 1'b0;
else if(state == IDLE)
DCLK_reg <= CPOL;
else if(state == DCLK_EDGE)
DCLK_reg <= ~DCLK_reg;//SPI clock edge
end
//SPI 时钟等待计数
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_cnt <= 16'd0;
else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
clk_cnt <= clk_cnt + 16'd1;
else
clk_cnt <= 16'd0;
end
//SPI 时钟边沿计数
always@(posedge sys_clk or posedge rst)
begin
if(rst)
clk_edge_cnt <= 5'd0;
else if(state == DCLK_EDGE)
clk_edge_cnt <= clk_edge_cnt + 5'd1;
else if(state == IDLE)
clk_edge_cnt <= 5'd0;
end
//SPI数据输出
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MOSI_shift <= 8'd0;
else if(state == IDLE && wr_req)
MOSI_shift <= data_in;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b1) //数据输出在第二个边沿进行
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
else
if(CPHA == 1'b1 && (clk_edge_cnt != 5'd0 && clk_edge_cnt[0] ==1'b0)) //数据输出在第一个边沿进行
MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
end
//SPI 数据输入
always@(posedge sys_clk or posedge rst)
begin
if(rst)
MISO_shift <= 8'd0;
else if(state == IDLE && wr_req)
MISO_shift <= 8'h00;
else if(state == DCLK_EDGE)
if(CPHA == 1'b0 &&
clk_edge_cnt[0] == 1'b0) //输入数据采集在第一个边沿进行
MISO_shift <= {MISO_shift[6:0],MISO};
else if(CPHA == 1'b1 &&(clk_edge_cnt[0] == 1'b1)) //输入数据采集在第二个边沿进行
MISO_shift <={MISO_shift[6:0],MISO};
end
endmodule