协议篇(二)I2C的verilog实现
零.基本协议
数据发送模块
仿真
仿真
接收模块(略),把最后的状态改为接收,三态门在这两天个状态打开并接收数据就可以了
零.基本协议
链接:I2C协议
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。
如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;、
如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下.主机负责产生定时时钟和终止数据传送。这也就是主器件对于从器件的两种操作,即写操作和读操作。
I2C应答信号:
Master每发送完8bit数据后等待Slave的ACK。
即在第9个clock,若Slave发ACK,SDA会被拉低。
若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程,如下所示:
I2C位传输:
数据传输:SCL为高电平时,SDA线保持稳定,那个SDA上是在传输数据bit。
数据改变:SCL为低电平时,SDA线才能改变传输的bit
数据发送模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2019/09/25 10:46:44
// Design Name:
// Module Name: I2C_WR
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
//写 IIC2
module I2C_WR#(
parameter CLK_FREQ = 100_000_000, //100 MHz 时钟周期
parameter I2C_FREQ = 100_000 //10 KHz(< 400KHz) SCL周期
)
(
/*
设备地址,寄存器地址,输出都是8bit,都是先发送最高位,最低位自己改下
*/
input clk, //100M
input rst_n, //模块复位
output SCL, //时钟
inout SDA, //数据线
//控制信号
input [23:0] data_r, //{8'd设备地址,8'd寄存器地址,8'd数据地址}
input tx_start, //数据发送信号
output reg idle, //模块空闲信号
output reg tx_done, //发送完成信号
output reg error = 0, //发送报错
// 仿真使用
output next_state
);
//---------------------复位信号同步化----也可以使用异步复位同步释放-------------------------
reg [4:0] RESETn=5'd31;
always@(posedge clk)//
RESETn <={RESETn[3:0],rst_n};//最终使用的同步信号是RESETn[4]
//------- ----------------上电复位后延迟-----------------
//Delay xxus until i2c slave is steady
reg [16:0] delay_cnt;
localparam DELAY_TOP = CLK_FREQ/10000; //1ms Setting time after software/hardware reset
//localparam DELAY_TOP = 17'hff; //Just for test
always@(posedge clk)
begin
if(!RESETn[4])
delay_cnt <= 0;
else if(delay_cnt < DELAY_TOP)
delay_cnt <= delay_cnt + 1'b1;
else
delay_cnt <= delay_cnt;
end
wire delay_done = (delay_cnt == DELAY_TOP) ? 1'b1 : 1'b0; //1ms 延迟
//------------------接收开始信号,idle控制,tx_done控制---------------------
reg [1:0] tx_start_r = 0;
wire start_sig = ~tx_start_r[1]&tx_start_r[0];
reg i2c_transfer_en;
reg i2c_transfer_en_r = 0;
reg [23:0] data_r0 =0;
reg [23:0] data = 0; // 发送数据缓存
always @ (posedge clk)
begin
tx_start_r <= {tx_start_r[0],tx_start};
data_r0<= data_r;
i2c_transfer_en_r <= i2c_transfer_en;
if(start_sig)
begin
data <= data_r0;
end
end
reg [1:0] i = 0;
reg sig = 0; //发送过程信号
reg [4:0] current_state, next_state; //i2c write and read state
localparam I2C_WR_STOP = 5'd8;
always @ (posedge clk)
if(!RESETn[4])
begin
i<=0;
sig <=0;
idle <= 0;
end
else
begin
case(i)
2'd0:begin
sig <=0;
idle <=0;
tx_done <= 0;
if(delay_done)
begin
i<=2'd1;
end
end
2'd1:begin
sig <=0;
idle <=1;
tx_done <= 0;
if(start_sig)
i<=2'd2;
end
2'd2:begin
sig <=1;
idle <= 0;
if(current_state == I2C_WR_STOP && i2c_transfer_en == 1'b1 )
begin
i<=2'd1;
tx_done <= 1'b1;
end
end
default:begin
i<= 2'd1;
end
endcase
end
//------------------SCL时钟生成和SDA数据变换标志位信号-------------------
reg [15:0] clk_cnt; //时钟计数
reg i2c_ctrl_clk; //i2c control clock, H: valid; L: valid
//reg i2c_transfer_en; //send i2c data before, make sure that sdat is steady when i2c_sclk is High ,时钟的低电平中心
reg i2c_low_en; //时钟的高电平中心
always @ (posedge clk)
begin
if(!RESETn[4])
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_low_en <= 0;
end
else
begin
if(delay_done)
begin
if(clk_cnt < (CLK_FREQ/I2C_FREQ) - 1'b1)
clk_cnt <= clk_cnt + 1'd1;
else
clk_cnt <= 0;
i2c_ctrl_clk <= ((clk_cnt >= (CLK_FREQ/I2C_FREQ)/4 + 1'b1) &&
(clk_cnt < (3*CLK_FREQ/I2C_FREQ)/4 + 1'b1)) ? 1'b1 : 1'b0;
i2c_transfer_en <= (clk_cnt == 16'd0) ? 1'b1 : 1'b0;
i2c_low_en <= (clk_cnt == (2*CLK_FREQ/I2C_FREQ)/4 - 1'b1) ? 1'b1 : 1'b0;
end
else
begin
clk_cnt <= 0;
i2c_ctrl_clk <= 0;
i2c_transfer_en <= 0;
i2c_low_en <= 0;
end
end
end
//I2C Timing state Parameter
localparam I2C_IDLE = 5'd0;
localparam I2C_WR_START = 5'd1;
localparam I2C_WR_IDADDR = 5'd2;
localparam I2C_WR_ACK1 = 5'd3;
localparam I2C_WR_REGADDR = 5'd4;
localparam I2C_WR_ACK2 = 5'd5;
localparam I2C_WR_REGDATA = 5'd6;
localparam I2C_WR_ACK3 = 5'd7;
//localparam I2C_WR_STOP = 5'd8;
//--------------------I2C contral-----------------------
// FSM: always1 时序逻辑
//reg [4:0] current_state, next_state; //i2c write and read state
always@(posedge clk)
begin
if(!RESETn[4])
current_state <= I2C_IDLE;
else if(i2c_transfer_en)
current_state <= next_state;
end
// FSM: always2 状态变换组合逻辑
reg [3:0] i2c_stream_cnt; //i2c data bit stream count
always@(*)
begin
next_state = I2C_IDLE; //state initialization
case(current_state)
I2C_IDLE: //5'd0
begin
if(delay_done) //1ms Setting time after software/hardware reset
begin
if(i2c_transfer_en &&sig)
begin
next_state = I2C_WR_START; //Write Data to I2C
end
else
next_state = next_state;
end
else
next_state = I2C_IDLE; //Wait I2C Bus is steady
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
if(i2c_transfer_en) next_state = I2C_WR_IDADDR;
else next_state = I2C_WR_START;
end
I2C_WR_IDADDR: //5'd2
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK1;
else next_state = I2C_WR_IDADDR;
I2C_WR_ACK1: //5'd3
if(i2c_transfer_en) next_state = I2C_WR_REGADDR;
else next_state = I2C_WR_ACK1;
I2C_WR_REGADDR: //5'd4
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK2;
else next_state = I2C_WR_REGADDR;
I2C_WR_ACK2: //5'd5
if(i2c_transfer_en) next_state = I2C_WR_REGDATA;
else next_state = I2C_WR_ACK2;
I2C_WR_REGDATA: //5'd8
if(i2c_transfer_en == 1'b1 && i2c_stream_cnt == 4'd8)
next_state = I2C_WR_ACK3;
else next_state = I2C_WR_REGDATA;
I2C_WR_ACK3: //5'd9
if(i2c_transfer_en) next_state = I2C_WR_STOP;
else next_state = I2C_WR_ACK3;
I2C_WR_STOP: //5'd10
if(i2c_transfer_en) next_state = I2C_IDLE;
else next_state = I2C_WR_STOP;
default:; //default vaule
endcase
end
//-----------------------------------------
// FSM: always3 时序逻辑与current_state同时输出
reg i2c_sdat_out; //i2c data output
reg [7:0] i2c_wdata; //i2c data prepared to transfer
reg i2c_ack;
always@(posedge clk)
begin
if(!RESETn[4])
begin
i2c_sdat_out <= 1'b1;
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
error <= 0;
end
else if(i2c_transfer_en)
begin
case(next_state)
I2C_IDLE: //5'd0
begin
i2c_sdat_out <= 1'b1; //idle state
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_START: //5'd1
begin
i2c_sdat_out <= 1'b0;
i2c_stream_cnt <= 0;
i2c_wdata <= data[23:16]; //ID_Address
end
I2C_WR_IDADDR: //5'd2
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt]; //最高位先发
end
I2C_WR_ACK1: //5'd3
begin
i2c_stream_cnt <= 0;
i2c_wdata <= data[15:8]; //REG_Address
end
I2C_WR_REGADDR: //5'd4
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt]; //最高位先发
end
I2C_WR_ACK2: //5'd5
begin
i2c_stream_cnt <= 0;
i2c_wdata <= data[7:0]; //REG_Address
end
I2C_WR_REGDATA: //5'd6
begin
i2c_stream_cnt <= i2c_stream_cnt + 1'b1;
i2c_sdat_out <= i2c_wdata[3'd7 - i2c_stream_cnt];
end
I2C_WR_ACK3: //5'd7
i2c_stream_cnt <= 0;
I2C_WR_STOP:begin //5'd8
i2c_sdat_out <= 1'b0;
if(i2c_ack == 1&& i2c_transfer_en_r == 1'b1 ) //应答不成功, 报错
begin
error <= 1'b1;
end
end
default:
begin
i2c_sdat_out <= 1'b1;
i2c_stream_cnt <= 0;
i2c_wdata <= 0;
error <=0;
end
endcase
end
else
begin
i2c_stream_cnt <= i2c_stream_cnt;
i2c_sdat_out <= i2c_sdat_out;
end
end
//------------------------------接收应答信号-----------
//respone from slave for i2c data transfer
reg i2c_ack1, i2c_ack2, i2c_ack3,i2c_ack2a;
//reg i2c_ack;
//reg [7:0] i2c_rdata;
always@(posedge clk)
begin
if(!RESETn[4])
begin
{i2c_ack1, i2c_ack2, i2c_ack3,i2c_ack2a} <= 4'b1111;
i2c_ack <= 1'b1;
end
else if(i2c_low_en)
begin
case(next_state)
I2C_IDLE:
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= 3'b111;
i2c_ack <= 1'b1;
end
//Write I2C: {ID_Address, REG_Address, W_REG_Data}
I2C_WR_ACK1: i2c_ack1 <= SDA;
I2C_WR_ACK2: i2c_ack2 <= SDA;
I2C_WR_ACK3: i2c_ack3 <= SDA;
I2C_WR_STOP: i2c_ack <= (i2c_ack1 | i2c_ack2 | i2c_ack3); //只有三次完成全部成功,才为0;
endcase
end
else
begin
{i2c_ack1, i2c_ack2, i2c_ack3} <= {i2c_ack1, i2c_ack2, i2c_ack3};
i2c_ack <= i2c_ack;
end
end
wire bir_en =(current_state == I2C_WR_ACK1 || current_state == I2C_WR_ACK2 || current_state == I2C_WR_ACK3 ) ? 1'b1 : 1'b0;
//assign tx_done = (current_state == I2C_WR_STOP ) ? 1'b1 : 1'b0;
assign SCL = (current_state >= I2C_WR_IDADDR && current_state <= I2C_WR_ACK3)?i2c_ctrl_clk : 1'b1;
assign SDA = (~bir_en) ? i2c_sdat_out : 1'bz;
endmodule
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
注意:本次设计采用三段式状态机,分别是FSM1(时序),FSM2(current组合),FSM3(next时序),结果就是输出与current状态同步改变没有延时, 功能仿真等价于FSM1(时序),FSM2(current组合),FSM3(current组合),但是之中输出前端为组合逻辑所以会增大关键路径
仿真
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2019/09/25 14:22:14
// Design Name:
// Module Name: test_i2c
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module test_i2c(
);
reg clk;
reg rst_n;
parameter clk_period=10;
parameter clk_half_period=clk_period/2;
initial
begin
clk =0;
rst_n = 0;
#100 rst_n = 1;
end
always
# clk_half_period clk = ~clk;
reg [23:0] data;
reg tx_start;
wire idle;
wire tx_done;
wire [4:0] next_state;
wire SCL;
wire SDA;
wire error;
I2C_WR a1
(
.clk(clk), //100M
.rst_n(rst_n), //模块复位
.SCL(SCL),
.SDA(SDA),
//控制信号
.data_r(data), //{8'd设备地址,8'd寄存器地址,8'd数据地址}
.tx_start(tx_start), //数据发送信号
.idle(idle), //模块空闲信号
.tx_done(tx_done), //发送完成信号
.error(error),
.next_state(next_state)
);
reg [2:0] i = 0;
always @ (posedge clk)
begin
if(~rst_n)
begin
data <=0;
tx_start <=0;
end
else
begin
case(i)
0:begin
if(idle)
begin
tx_start <= 1;
data <= {8'b01100010,8'b11000100,8'b11011100};
i <= 1;
end
end
1:begin
tx_start <= 0;
data <= 0;
end
endcase
end
end
// SLAVE响应
reg ack = 1;
always @ (*)
begin
if(next_state == 5'd3 || next_state == 5'd5 ||next_state == 5'd7)
begin
ack <= 0;
end
else
begin
ack <= 1;
end
end
assign SDA = (next_state == 5'd3 || next_state == 5'd5 ||next_state == 5'd7)?ack:1'bz;
endmodule
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
仿真
接收模块(略),把最后的状态改为接收,三态门在这两天个状态打开并接收数据就可以了
————————————————
版权声明:本文为CSDN博主「季磊」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42219690/article/details/101348910