协议篇(二)I2Cverilog实现

协议篇(二)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

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
I2C是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。它是一种同步、半双工的通信总线,最初用于音频和视频设备,现在主要用于服务器管理中。I2C总线可以实现多个master和多个slave的通信,需要实现信号的“线与”逻辑,以及时钟同步和总线仲裁的硬件基础。在I2C总线上,数据传输速率有标准模式、快速模式和高速模式等不同的速率。在Verilog实现I2C时,需要注意SDA是双向口,可以使用inout声明,使用三态门控制双向口。根据I2C协议的规定,SDA数据线只能在SCL的低电平时发生数据变化。可以使用时钟的上升沿来触发SDA的变化,使SDA与SCL相差半个SCL周期,满足I2C总线上的传输协议。此外,Verilog中可以使用task语句块来封装一段程序,任务可以被调用执行,并可以有输入和输出。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [VerilogI2C实现](https://blog.csdn.net/u010945683/article/details/40904631)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Verilog | I2C详解与Verilog实现](https://blog.csdn.net/qq_45776815/article/details/128078491)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值