SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200。
SCK(Serial Clock):SCK是串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率;
MOSI(Master Out Slave in):在SPI Master上也被称为Tx-channel,作用是SPI主机给SPI从机发送数据;
CS/SS(Chip Select/Slave Select):作用是SPI Master选择与哪一个SPI Slave通信,低电平表示从机被选中(低电平有效);
MISO(Master In Slave Out):在SPI Master上也被称为Rx-channel,作用是SPI主机接收SPI从机传输过来的数据;
SPI协议的主要特点:
1.采用主从模式(Master-Slave)的控制方式,支持单Master多Slave。SPI规定了两个SPI设备之间通信必须由主设备Master来控制从设备Slave。
2.SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步(Synchronous)传输协议。
3. SPI总线协议是一种全双工的串行通信协议,数据传输时高位在前,低位在后。
在SPI中,主机可以选择时钟极性和时钟相位。在空闲状态期间,CPOL位设置时钟信号的极性。空闲状态是指传输开始时CS为高电平且在向低电平转变的期间,以及传输结束时CS为低电平且在向高电平转变的期间。CPHA位选择时钟相位。根据CPHA位的状态,使用时钟上升沿或下降沿来采样和/或移位数据。主机必须根据从机的要求选择时钟极性和时钟相位。根据CPOL和CPHA位的选择,有四种SPI模式可用。
//极性特点:时钟空闲时为高电平
//在上升沿采样数据,获取MOSI的输入
//在下降沿发送数据,将数据发送到MISO
//sck的时钟要大大小于clk的时钟,至多为clk/8
module spi_slaver(
clk,
rst,
cs,
sck,
MOSI,
MISO,
rxd_out,
txd_data,
rxd_flag //接受数据脉冲信号 接受到一个数据会产生一个正脉冲信号
);
input clk,cs,rst,sck,MOSI;
output rxd_flag;
output reg MISO;
output reg[7:0] rxd_out;
reg[7:0] rxd_data;
input [7:0] txd_data;
reg sck_r0,sck_r1;
wire sck_n,sck_p;
always @(posedge clk or negedge rst)
begin
if(!rst)
begin
sck_r0<=1'b1;
sck_r1<=1'b1;
end
else
begin
sck_r0 <= sck;
sck_r1 <= sck_r0;
end
end
assign sck_n = (~sck_r0 & sck_r1)? 1'b1:1'b0;
assign sck_p = (~sck_r1 & sck_r0)? 1'b1:1'b0;
//-----------------------spi_slaver read data-------------------------------
reg rxd_flag_r;
reg [2:0] rxd_state;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
rxd_data <= 1'b0;
rxd_flag_r <= 1'b0;
rxd_state <= 1'b0;
end
else if(sck_p && !cs)
begin
case(rxd_state)
3'd0:begin
rxd_data[7] <= MOSI;
rxd_flag_r <= 1'b0; //reset rxd_flag
rxd_state <= 3'd1;
end
3'd1:begin
rxd_data[6] <= MOSI;
rxd_state <= 3'd2;
end
3'd2:begin
rxd_data[5] <= MOSI;
rxd_state <= 3'd3;
end
3'd3:begin
rxd_data[4] <= MOSI;
rxd_state <= 3'd4;
end
3'd4:begin
rxd_data[3] <= MOSI;
rxd_state <= 3'd5;
end
3'd5:begin
rxd_data[2] <= MOSI;
rxd_state <= 3'd6;
end
3'd6:begin
rxd_data[1] <= MOSI;
rxd_state <= 3'd7;
end
3'd7:begin
rxd_out<={rxd_data[7:1],MOSI};
rxd_data[0] <= MOSI;
rxd_flag_r <= 1'b1; //set rxd_flag
rxd_state <= 3'd0;
end
default: ;
endcase
end
end
//--------------------capture spi_flag posedge--------------------------------
reg rxd_flag_r0,rxd_flag_r1;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
rxd_flag_r0 <= 1'b0;
rxd_flag_r1 <= 1'b0;
end
else
begin
rxd_flag_r0 <= rxd_flag_r;
rxd_flag_r1 <= rxd_flag_r0;
end
end
assign rxd_flag = (~rxd_flag_r1 & rxd_flag_r0)? 1'b1:1'b0;
//---------------------spi_slaver send data---------------------------
reg [2:0] txd_state;
always@(posedge clk or negedge rst)
begin
if(!rst)
begin
txd_state <= 3'd0;
MISO<=1'bz;
end
else if(sck_n && !cs)
begin
case(txd_state)
3'd0:begin
MISO <= txd_data[7];
txd_state <= 3'd1;
end
3'd1:begin
MISO <= txd_data[6];
txd_state <= 3'd2;
end
3'd2:begin
MISO <= txd_data[5];
txd_state <= 3'd3;
end
3'd3:begin
MISO <= txd_data[4];
txd_state <= 3'd4;
end
3'd4:begin
MISO <= txd_data[3];
txd_state <= 3'd5;
end
3'd5:begin
MISO <= txd_data[2];
txd_state <= 3'd6;
end
3'd6:begin
MISO <= txd_data[1];
txd_state <= 3'd7;
end
3'd7:begin
MISO <= txd_data[0];
txd_state <= 3'd0;
end
default: ;
endcase
end
end
endmodule
/*测试spi从机用 每个接受一个8位的数据
然后把接收到的数据加一在发送回去
*/
module spi_slaver_ctrl(
clk,
rst,
cs,
sck,
MOSI,
MISO,
rxd_out,
rxd_flag
);
input clk,rst,cs,sck,MOSI;
output MISO,rxd_flag;
output [7:0] rxd_out;
reg [7:0] txd_dat;
wire rxd_flag;
reg [2:0] cnt;
always@(posedge rxd_flag or negedge rst)
begin
if(!rst)
//cnt <= 3'd0;
txd_dat <= 8'b11000011;
else
begin
txd_dat <= rxd_out + 1'b1; //把数据+1送到发送端
end
end
spi_slaver spi_slaver1(
.clk(clk),
.rst(rst),
.cs(cs),
.sck(sck),
.MOSI(MOSI),
.MISO(MISO),
.rxd_out(rxd_out),
.txd_data(txd_dat),
.rxd_flag(rxd_flag)
);
endmodule
控制模块中,当接收到第一个数据时,把txd_dat的初始数据发回SPI主机,在接下来的通信中,没接收到一个数据,在下一次通信时把上一次接收到的数据+1,发回主机。