SPI协议

一.SPI协议简介

  • SPI(Serial Peripheral interface)定义的一种串行外围设备接口,是一种全双工、同步的通信总线,只需四根信号线即可,节约引脚,同时有利于PCB的布局。正是出于这种简单易用的特性,现在越来越多的芯片集成了SPI通信协议,如FLASH、AD转换器等。

  • SPI通信协议的优点是支持全双工通信,通讯方式比较简单,且相对数据传输速率较快(相比UART、I2C),灵活的数据传输方式,不限于8位,可以是任意大小的字;缺点是没有应答机制,在数据可靠性上有一定缺陷

二.SPI协议通信原理

1.SPI协议物理层

  • SPI通信设备的通信模式是主从通信模式,通信双方有主从之分,根据从机设备的数量,SPI通信设备之间的连接方式可分为一主(FPGA)一从和一主多从(eg:flash芯片M25P16)。
    SPI一主多从

  • SPI总线传输需要4根线就能完成,这四根线的作用分别如下:

    • SCK(Serial Clock)时钟信号线:用于同步通信数据。由通信主机产生,决定了通信的速率,不同的设备支持的最高时钟频率不同。
    • CS(Chip Select)片选信号线:当有多个SPI从设备与SPI主机相连时,设备的其他信号线SCK,MOSI,MISO同时并联到相同的SPI总线上,即无论有多少个从设备,都共用这3条总线;而每个从设备都有独立的片选信号线,即有多少个从设备,就有多少条片选信号线。相当于由SPI构成的通信系统中,通过CS片选信号来决定通信的从机设备是哪一台。通信器件低电平有效,表示对应从机被选中。
    • MOSI(Master Output,Slave Input)主设备输出/从设备输入引脚:主机的数据从这条信号线读入数据,从机由这条信号线读入主机发送的数据,数据方向由主机到从机
    • MISO(Master Input,Slave Output)主设备输入/从设备输出引脚:主机从这条信号读入数据,从机的数据由这条信号线输出到主机,数据方向由从机到主机。

2.SPI协议协议层

  • SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据实在SCK时钟的上升沿被采样还是下降沿被采样。
    • SPI总线的极性-时钟极性

      时钟极性决定SPI总线空闲时的时钟信号高电平还是低电平。

      CPOL=1:表示空闲时是高电平;

      CPOL=0: 表示空闲时是低电平。
    • SPI总线的相位-时钟相位

      时钟相位决定SPI总线从哪个跳变沿开始采样数据。

      CPHA=0:在时钟信号SCK的第一个跳变沿采样,第二个跳变沿输出;

      CPHA=1:在时钟信号SCK的第一个跳变沿输出,第二个跳变沿采样。
      SPI通信模式
  • 模式0:CPOL= 0,CPHA=0。SCK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换
  • 模式1:CPOL= 0,CPHA=1。SCK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
  • 模式2:CPOL= 1,CPHA=0。SCK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在SCK时钟的上升沿切换
  • 模式3:CPOL= 1,CPHA=1。SCK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在SCK时钟的下降沿切换

3.SPI协议通信过程

SCK,CS_N,MOSI信号为主机控制,且MOSI和MISO信号在CS_N信号为低时才有效。当CS_N信号拉低时,表示某一从机被主机选中,且可以开始通信。MOSI和MISO在每个SCK的时钟周期传输一位数据(一般为MSB,高位先行),且数据的输入输出是同时进行的。数据采集和数据变化是按上述SPI通信模式进行。

SPI每次传输数据可以8位或16位为单位,每次传输的单位数不受限制。

4.SPI协议端口

SPI_Drive

SPI_Drive端口程序图

4.SPI协议程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
module spi_interface (
input sys_clk ,
input rst_n ,

//flash_controler接口
input req ,
input [7:0]din ,
output[7:0]dout ,
output done ,

//flash接口
input miso ,
output mosi ,
output sclk ,
output cs_n
);



assign cs_n = !req; //把flash_controler模块传输过来的req取反信号转化为片选信号

//////////////////////////////////////////////////////////////////////////////////////////////
//SPI内部参数定义


reg [2:0] state_c;
reg [2:0] state_n;

localparam SCLK_PERIOD = 16,
SCLK_FALL = 4 ,
SCLK_RISE = 12;


//////////////////////////////////////////////////////////////////////////////////////////////
//状态机状态参数


localparam IDLE = 3'b001,
READY= 3'b010,
TRANS= 3'b100;


//////////////////////////////////////////////////////////////////////////////////////////////
//sys_clk转sclk计数内部参数


reg [4:0]cnt;
wire add_cnt;
wire end_cnt;


reg [3:0]cnt_bit;
wire add_cnt_bit;
wire end_cnt_bit;




//////////////////////////////////////////////////////////////////////////////////////////////
//状态机跳转条件


wire idle2ready ;
wire ready2trans;
wire trans2idle ;


assign idle2ready = (state_c == IDLE ) && (req) ;
assign ready2trans= (state_c == READY) && (1'b1);
assign trans2idle = (state_c == TRANS) && (end_cnt_bit);


//////////////////////////////////////////////////////////////////////////////////////////////
//时序描述状态转移


always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE;
end
else state_c <= state_n;
end


//////////////////////////////////////////////////////////////////////////////////////////////
//组合逻辑描述状态跳转
always @(*) begin
case (state_c)


IDLE :
if (idle2ready) begin
state_n <= READY;
end
else begin
state_n <= state_c;
end


READY:
if (ready2trans) begin
state_n <= TRANS;
end
else begin
state_n <= state_c;
end


TRANS:
if (trans2idle) begin
state_n <= IDLE;
end
else begin
state_n <= state_c;
end



default: state_n <= IDLE;
endcase

end




//////////////////////////////////////////////////////////////////////////////////////////////
//sys_clk转sclk计数标志信号


reg add_cnt_flag;

always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
add_cnt_flag <= 1'd0;
end
else if (ready2trans) begin
add_cnt_flag <= 1'd1;
end
else if (trans2idle) begin
add_cnt_flag <= 1'd0; //相当于state == TRANS
end
end


//////////////////////////////////////////////////////////////////////////////////////////////
//sys_clk转sclk计数


assign add_cnt = add_cnt_flag;
assign end_cnt = add_cnt && (cnt == SCLK_PERIOD - 1'd1);


always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 5'd0;
end
else if (add_cnt) begin
if (end_cnt) begin
cnt <= 5'd0;
end
else cnt <= cnt +5'd1;
end
end


//////////////////////////////////////////////////////////////////////////////////////////////
//sclk时钟翻转


reg sclk_r ;
assign sclk = sclk_r;

always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
sclk_r <= 1'd1;
end
else if (add_cnt && (cnt == SCLK_FALL-1'd1)) begin
sclk_r <= 1'd0;
end
else if (add_cnt && (cnt == SCLK_RISE-1'd1)) begin
sclk_r <= 1'd1;
end
end


//////////////////////////////////////////////////////////////////////////////////////////////
//bit计数


localparam CNT_BIT_MAX = 8;
assign add_cnt_bit = end_cnt;
assign end_cnt_bit = add_cnt_bit && (cnt_bit == CNT_BIT_MAX - 3'd1);


always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
cnt_bit <= 3'd0;
end
else if (add_cnt_bit) begin
if (end_cnt_bit) begin
cnt_bit <= 3'd0;
end
else cnt_bit <= cnt_bit + 3'd1;
end
end


//////////////////////////////////////////////////////////////////////////////////////////////
//flash_controler发送的并行数据传入SPI,进行串并转换,在时钟为低电平时切换数据,转化为串行数据,输入到flash

reg [7:0] tx_data;//发送数据寄存器
always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
tx_data <= 0;
end
else if (state_c == READY) begin
tx_data <= din;
end
end

assign mosi = tx_data[7-cnt_bit];


//////////////////////////////////////////////////////////////////////////////////////////////
//flash ID串行数据传入SPI,串行数据转并行数据

reg [7:0]rx_data;//采样数据寄存器
assign dout = rx_data ;

always @(posedge sys_clk or negedge rst_n) begin
if (!rst_n) begin
rx_data <= 0;
end
else if (add_cnt && (cnt == SCLK_RISE - 1)) begin
rx_data <= {rx_data[6:0],miso}; //左移 //需要考虑IF内部判断条件不一定是一个时钟周期
//rx_data[7-cnt_bit] <= miso;
end
end

//////////////////////////////////////////////////////////////////////////////////////////////
//done
assign done = trans2idle;








endmodule