SPI&FPGA

现今,在低端数字通信应用领域,我们随处可见IIC (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips(for IIC)和Motorola(for SPI) 出于不同背景和市场需求制定了这两种标准通信协议。

SPI是种四根信号线协议,并且支持一主机对多从机的通信模式(如图):

image-20230419181214696

其中:

SCLK: Serial Clock (output from master);
主机输出时钟SCK

MOSI; SIMO: Master Output, Slave Input(output from master);
主机输出,从机输入SDI

MISO; SOMI: Master Input, Slave Output(output from slave);
主机输入,从机输出SDO

CSB: Slave Select (active low, outputfrom master).

主机片选CS,低电平有效,平时拉高

代码实现(代码通用,本实例控制从机芯片为ADMV8818-EP):

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
`timescale 1ns / 1ps
module SPI(
//
input clk ,//10Mhz
input miso ,
input[23:0] data_wr ,
input start ,
//
output mosi ,
output[7:0] data_rd ,
output sck_out ,
output cs ,
//output sfl

);
reg[3:0] clk_cnt ;
reg[4:0] cycle_cnt ;
reg[4:0] rd_cnt ;
reg[7:0] data_8 ;
reg mosi_reg ;
reg cs_reg ;
reg sck_reg ; //1Mhz
//reg sfl_reg ;
reg start_reg_1 ;
reg start_reg_2 ;
wire start_posedge ;


always @ (posedge clk) begin //clk_cnt from 0 to 9
if(clk_cnt < 9)
begin
clk_cnt <= clk_cnt + 1 ;
end
else
begin
clk_cnt <= 0 ;
end
end

always @ (posedge clk) begin //sck update
if((clk_cnt < 4)||(clk_cnt == 9))
begin
sck_reg <= 0;
end
else
if(clk_cnt >= 4)
begin
sck_reg <= 1;
end
end

always @ (posedge clk) begin //when start_posedge come,cs & SFL stay low,recover high when cycle_cnt is 24
if(start_posedge)
begin
if(clk_cnt == 0)
begin
cs_reg <= 0;
//sfl_reg<= 0;
end
end
else
if(cs_reg == 0)
begin
if((cycle_cnt == 24) && (clk_cnt == 3))
begin
cs_reg <= 1;
end
end
else
begin
cs_reg <= 1;
end
end

always @ (posedge clk) begin //cycle_cnt from 0 to 24,stay 0 when cs is high,update when sck_posedge
if(cs_reg == 0)
begin
if(clk_cnt == 4)
begin
if(cycle_cnt < 24)
begin
cycle_cnt <= cycle_cnt + 1;
end
else
begin
cycle_cnt <= 0;
end
end
else
begin
cycle_cnt <= cycle_cnt;
end
end
else
begin
cycle_cnt <= 0;
end
end

always @ (posedge clk) begin //write data[23:8] then write data[7:0] or read data_8[7:0]
if(cs_reg == 0)
begin
if(cycle_cnt < 16)
begin
if(clk_cnt == 2)
begin
mosi_reg <= data_wr[23-cycle_cnt];
end
end
else
if(cycle_cnt >= 16)
begin
if(data_wr[23] == 0) //write
begin
if(clk_cnt == 2)
begin
mosi_reg <= data_wr[23-cycle_cnt];
end
end
else
if(data_wr[23] == 1) //read
begin
if(clk_cnt == 4)
begin
data_8[7-rd_cnt] <= miso;
end
end
end
end
else
begin
mosi_reg <= 0;
end
end

always @ (posedge clk) begin //rd_cnt from 0 to 7,stay 0 when cs is high or cycle_cnt is <= 15,update when sck_posedge
if(cs_reg == 0)
begin
if(clk_cnt == 4)
begin
if((rd_cnt < 7) && (cycle_cnt > 15))
begin
rd_cnt <= rd_cnt + 1;
end
else
begin
rd_cnt <= 0;
end
end
else
begin
rd_cnt <= rd_cnt;
end
end
else
begin
rd_cnt <= 0;
end
end

always @ (posedge clk) begin //activate start_posedge,update when sck_posedge
if(clk_cnt == 4)
begin
start_reg_1 <= start;
start_reg_2 <= start_reg_1;
end
else
begin
start_reg_1 <= start_reg_1;
start_reg_2 <= start_reg_2;
end
end

assign mosi = mosi_reg ;
assign data_rd = data_8 ;
assign cs = cs_reg ;
assign sck_out = (sck_reg &&(~cs_reg)) ;
//assign sfl = 1'b0 ;
assign start_posedge = start_reg_1 & (~start_reg_2) ;

endmodule

仿真TB代码实现:

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
`timescale 1ns/1ps

module testbench();

///////////
//参数定义

`define CLK_PERIORD 100 //时钟周期 100ns

///////////
//接口声明

//输入
reg clk ;
reg miso ;
reg[23:0] data_wr ;
reg[7:0] data_miso ;
reg[4:0] cnt ;
reg start ;
//输出
wire mosi ;
wire[7:0] data_rd ;
wire sck ;
wire cs ;
wire sfl ;


///////////
//例化
spi uut_spi(
.clk (clk ) ,
.miso (miso ) ,
.data_wr(data_wr) ,
.start (start ) ,
//
.mosi (mosi ) ,
.data_rd(data_rd) ,
.sck_out(sck ) ,
.cs (cs )
//.sfl (sfl )
);

///////////
//复位和时钟产生

initial begin
clk <= 0;
miso <= 0;
data_wr <= 24'h0;
start <= 0;
end

always #(`CLK_PERIORD/2) clk <= ~clk;

///////////
//测试激励产生

initial begin
#(`CLK_PERIORD*20);
#(`CLK_PERIORD*20);
//测试读功能 24'b 1011 1010 0111 1101 0101 0111 接收8'b 1111 0111
start <= 1;
data_wr <= 24'b101110100111110101010111;
data_miso <= 8'b11110111;
cnt <= 5'd0;
repeat(31) begin
@(negedge sck)begin
if(cnt > 14)
miso <= data_miso[22 - cnt];
end
cnt <= cnt + 1;
end
start <= 0;
cnt <= 5'd0;
//读功能测试完毕
#(`CLK_PERIORD*20);
#(`CLK_PERIORD*20);
//测试读功能2 24'b 1011 1010 0111 1101 1001 1010 接收8'b 1001 1010
start <= 1;
data_wr <= 24'b101110100111110110011010;
data_miso <= 8'b10011010;
//cnt <= 5'd0;
repeat(31) begin
@(negedge sck)begin
if(cnt > 14)
miso <= data_miso[22 - cnt];
end
cnt <= cnt + 1;
end
//读功能测试完毕
start <= 0;
#(`CLK_PERIORD*20);
#(`CLK_PERIORD*20);

$stop;

end

endmodule

仿真结果如下图:

image-20230419182815669

实际波形如图:

image-20230419182836551

image-20230419182852658

注意事项:

实际通信时波形并不一定稳定,可适当分频降低整体信号(SCK\SDI\SDO)时钟频率;

SPI属于短距离芯片通信,对信号线有一定要求,四条控制线不要拉太长,否则线内反射会导致波形不稳导致控制失败,最好使用专门PCB板而不是外拉杜邦线进行通信;

参考文档:

ADI_SPI