总线通信协议-SPI

1. 简介

SPI总线又称串行外围总线接口:serial peripheral interface。

1.1 总线特点

特点:全双工,串行,同步,高速接口 10Mbps。

  1. slave设备的CLK由master的SCK管脚提供,slave本身不能产生或控制clock.
  2. SPI总线在传输数据的同时也传输了时钟信号,所以SPI协议是一种同步传输协议
  3. SPI总线协议是一种全双工串行通信协议,数据传输时,高位在前,低位在后

1.2 总线接口:

SCK:串行时钟线,作用是Master向Slave传输时钟信号,控制数据交换的时机和速率。
MOSI: SPI主机向SPI从机发送数据
MISO: SPI主机读取SPI从机的数据
CS/SS:低电平有效,表示从机被选中

1.3 工作模式

image.png

2. 传输协议

2.1 时序图

主要是确定数据变换的地方
image.png

2.2 结构图

image.png

image.png

###1.5 时序图:
image.png

2.3 状态图:

image.png
根据时序图我们也可以把它分为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

image.png
image.png
image.png

3. 小结

  1. 我的代码只是 主机发送的代码,读取从机的代码还没完成
  2. 代码可以进行数据长度和模式的可配置
  3. 核心主要是时序图,以及count变量
  4. CPHA 是用来控制第几个边沿进行采样的,如果为0,那么就是第一个边沿采样,如果是1,那就是第二个变边沿采样;CPOL是用来控制SCK的初始状态的,如果是0,那么初始就是0, 如果是1,那么初始就是高电平。
  5. get状态和hold状态可以不用管,就当作0 1两个状态,主要是时序图一定要弄清楚,其中关键点是MOSI的数据输出时序

参考链接:
https://www.cnblogs.com/liujinggang/p/9609739.html
https://www.cnblogs.com/simonLiang/p/6091636.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值