1. 简介
SPI总线又称串行外围总线接口:serial peripheral interface。
1.1 总线特点
特点:全双工,串行,同步,高速接口 10Mbps。
- slave设备的CLK由master的SCK管脚提供,slave本身不能产生或控制clock.
- SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步传输协议
- SPI总线协议是一种全双工串行通信协议,数据传输时,高位在前,低位在后
1.2 总线接口:
SCK:串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率。
MOSI: SPI主机向SPI从机发送数据
MISO: SPI主机读取SPI从机的数据
CS/SS:低电平有效,表示从机被选中
1.3 工作模式
2. 传输协议
2.1 时序图
主要是确定数据变换的地方
2.2 结构图
###1.5 时序图:
2.3 状态图:
根据时序图我们也可以把它分为5个状态,IDLE, START, HOLD,GET,DONE.
其中HOLD就是模式0中的偶数状态0,2,4等
GET就是模式0中的奇数状态1,3,5等
2.4 代码实现
module spi_dut #(parameter DATA_NUM = 8
)(
input wire clk,
input wire rst_n,
input wire tx_en,
//input wire start,
input wire rx_en,
output reg [DATA_NUM-1:0] data_out,
output wire done,
output wire spi_mosi,
input wire spi_miso,
output wire ss,
output reg sck
);
parameter IDLE = 5'b00001;
parameter START = 5'b00010;
parameter HOLD = 5'b00100;
parameter GET = 5'b01000;
parameter DONE = 5'b10000;
//parameter DATA_NUM = 8;
parameter CPOL = 0; //=1高电平空闲
parameter CPHA = 0; //=1 第二个边沿进行采样
wire start;
assign start = tx_en | rx_en;
reg [4:0] cur_state, next_state;
reg [DATA_NUM-1 : 0] count;
always @(posedge clk or negedge rst_n) begin
if (~rst_n)
cur_state <= IDLE;
else
cur_state <= next_state;
end
always @(*) begin
case (cur_state)
IDLE : begin
if(start) next_state = START;
else next_state = IDLE;
end
START : begin//
if(CPHA) next_state = GET;
else next_state = HOLD;
end
HOLD : begin//表示数据不变,进行采样
if((CPHA)&&(count == 0))
next_state = DONE;
else if (count < DATA_NUM)
next_state = GET;
else
next_state = next_state;
end
GET : begin //表示数据变化,进行输出
if ((!CPHA)&&(count == 0))
next_state = DONE;
else if (count < DATA_NUM)
next_state = HOLD;
else
next_state = next_state;
end
DONE : begin
next_state = IDLE;
end
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
sck <= CPOL;
else if (cur_state == START)
sck <= (CPHA^CPOL);
else if (cur_state == HOLD || cur_state == GET)
sck = ~sck;
else
sck <= sck;
end
reg [DATA_NUM-1:0] mosi_reg;
wire [DATA_NUM-1 : 0] data_in;
assign data_in = 8'b10110111;
assign spi_mosi = mosi_reg[DATA_NUM-1];
wire [4:0] count_state, shift_state;
assign count_state = CPHA ? GET : HOLD;
assign shift_state = CPHA ? HOLD : GET;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
mosi_reg <= data_in;
else if (cur_state == shift_state)
mosi_reg <= (data_in << (count));
else
mosi_reg <= mosi_reg;
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n)
data_out <= 'd0;
else if (cur_state == shift_state)
data_out[DATA_NUM-1-count] <= spi_miso;
else
data_out <= data_out;
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n)
count <= 'd0;
else if (count == DATA_NUM-1 && cur_state == count_state)
count <= 'd0;
else if (cur_state == count_state)
count <= count + 1'b1;
else
count <= count;
end
reg ss_reg ;
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
ss_reg <= 'b1;
else if (cur_state == START)
ss_reg <= 'b0;
else if (cur_state == DONE)
ss_reg <= 'b1;
else
ss_reg <= ss_reg;
end
assign ss = ss_reg;
assign done = (cur_state == DONE);
endmodule
3. 小结
- 我的代码只是 主机发送的代码,读取从机的代码还没完成
- 代码可以进行数据长度和模式的可配置
- 核心主要是时序图,以及count变量
- CPHA 是用来控制第几个边沿进行采样的,如果为0,那么就是第一个边沿采样,如果是1,那就是第二个变边沿采样;CPOL是用来控制SCK的初始状态的,如果是0,那么初始就是0, 如果是1,那么初始就是高电平。
- get状态和hold状态可以不用管,就当作0 1两个状态,主要是时序图一定要弄清楚,其中关键点是MOSI的数据输出时序
参考链接:
https://www.cnblogs.com/liujinggang/p/9609739.html
https://www.cnblogs.com/simonLiang/p/6091636.html