摘要:
此版本的设计中,笔者将协议里对总线的操作细分为4个,即起始(Start)、写(Write)、读(Read)、停止(Stop),并给对应的操作编码:起始(1000)、写(0100)、读(0010)、停止(0001)。每次读写操作中也包括了一次应答操作。上层模块需要操作总线时,仅需要按照芯片操作步骤给出命令,即可以对任意支持I2C总线的外设进行操作。
此版本中主机不具有应答能力,在对24C02芯片进行操作时,可以进行任意随机读写,但不可进行连续读,这个问题将会在下一版中解决。
设计步骤:
1、双向I/O口的设计
I2C总线的SCL和SDA信号都是双向端口,在操作的时候与一般的单向端口有一定差别,在设计时需要特别注意。
I/O的定义如下:
1 | inout wire scl |
2 | inout wire sda |
当需要进行输出操作时,需将使能信号scl_en/sda_en拉高,并给scl_out/sda_out赋相应的值;输入操作时,拉低scl_en/sda_en,读取scl_in/sda_in信号即可。
01 | reg scl_en, scl_r, scl_out; |
02 | reg sda_en, sda_r, sda_out; |
03 | |
04 | wire scl_in; |
05 | wire sda_in; |
06 | |
07 | assign scl = scl_r; |
08 | assign sda = sda_r; |
09 | assign scl_in = scl; |
10 | assign sda_in = sda; |
11 | |
12 | always@(scl_en or scl_out) begin |
13 | if (scl_en) |
14 | scl_r <= scl_out; |
15 | else |
16 | scl_r <= 1'bZ; |
17 | end |
18 | |
19 | always@(sda_en or sda_out) begin |
20 | if (sda_en) |
21 | sda_r <= sda_out; |
22 | else |
23 | sda_r <= 1'bZ; |
24 | end |
2、时钟的设计
此设计中采用1MHz的输入时钟,进行4分频,总线工作的250KHz的频率下。
在1MHz的输入时钟下,对一个2bit的计数器进行循环计数,产生0、1、2、3四个节拍,在对SCL和SDA进行操作时都基于这4个节拍,如果需要在SCL和SDA间产生相位差,就相当容易。因为在写操作时,需要先送数据,再送时钟,而读操作时,需要写给时钟,再读数据。
1 | reg [1:0] bit = 0; |
2 | always@(posedge sys_clk) bit <= bit + 1’b1; |
3、起始信号(Start, 4’b1000)的产生
在SCL高电平时,SDA上产生一个下降沿,即是起始信号。
01 | always@(posedge sys_clk) begin |
02 | case (start_state) |
03 | 0: |
04 | begin |
05 | if ((cmd == 4'b1000) && (!cmd_busy)) |
06 | begin |
07 | scl_en <= 1; |
08 | scl_out <= 1; |
09 | sda_en <= 1; |
10 | sda_out <= 1; |
11 | start_state <= 1; |
12 | start_busy <= 1; |
13 | end |
14 | else |
15 | start_busy <= 0; |
16 | end |
17 | 1: if (bit == 3) start_state <= 2; |
18 | 2: |
19 | begin |
20 | case (bit) |
21 | 0: sda_out <= 0; |
22 | 1: scl_out <= 0; |
23 | 3: |
24 | begin |
25 | start_state <= 0; |
26 | start_busy <= 0; |
27 | end |
28 | endcase |
29 | end |
30 | endcase |
31 | end |
4、停止信号(Stop, 4’b0001)的产生
在SCL高电平时,SDA上产生一个上升沿,即是停止信号。
01 | always@(posedge sys_clk) begin |
02 | case (stop_state) |
03 | 0: |
04 | begin |
05 | if ((cmd == 4'b0001) && (!cmd_busy)) |
06 | begin |
07 | scl_en <= 1; |
08 | scl_out <= 1; |
09 | sda_en <= 1; |
10 | sda_out <= 0; |
11 | stop_state <= 1; |
12 | stop_busy <= 1; |
13 | end |
14 | else |
15 | stop_busy <= 0; |
16 | end |
17 | 1: if (bit ==3 ) stop_state <= 2; |
18 | 2: |
19 | case (bit) |
20 | 0: sda_out <= 1; |
21 | 3: |
22 | begin |
23 | stop_state <= 0; |
24 | stop_busy <= 0; |
25 | scl_en <= 0; |
26 | scl_out <= 1'bZ; |
27 | sda_en <= 0; |
28 | sda_out <= 1'bZ; |
29 | end |
30 | endcase |
31 | endcase |
32 | end |
5、写操作(Write, 4’b0100)的产生
01 | always@(posedge sys_clk) begin |
02 | case (wr_state) |
03 | 0: |
04 | begin |
05 | if ((cmd == 4'b0100) && (!cmd_busy)) |
06 | begin |
07 | wr_cnt <= 0; |
08 | wr_state <= 1; |
09 | sda_en <= 1; |
10 | sda_out <= dout[7]; |
11 | scl_en <= 1; |
12 | scl_out <= 0; |
13 | wr_busy <= 1; |
14 | end |
15 | else |
16 | wr_busy <= 0; |
17 | end |
18 | 1: if (bit == 3) wr_state <= 2; |
19 | 2: |
20 | begin |
21 | case (bit) |
22 | 0: |
23 | case(wr_cnt) |
24 | 0,1,2,3,4,5,6,7: |
25 | begin |
26 | sda_out <= dout[7-wr_cnt]; |
27 | wr_cnt <= wr_cnt + 1; |
28 | end |
29 | endcase |
30 | 1: scl_out <= ~scl_out; |
31 | 3: |
32 | begin |
33 | scl_out <= ~scl_out; |
34 | if (wr_cnt == 8) |
35 | begin |
36 | wr_cnt <= 0; |
37 | wr_state <= 3; |
38 | sda_en <= 0; |
39 | wr_busy <= 0; |
40 | wr_ack <= 1; |
41 | end |
42 | end |
43 | endcase |
44 | end |
45 | 3: |
46 | if (bit == 3) |
47 | begin |
48 | wr_ack <= 2; |
49 | wr_state <= 0; |
50 | sda_en <= 0; |
51 | sda_out <= 1'bZ; |
52 | end |
53 | endcase |
54 | end |
6、读操作(Read, 4’b0010)的产生
01 | always@(posedge sys_clk) begin |
02 | case (rd_state) |
03 | 0: |
04 | begin |
05 | if ((cmd == 4'b0010) && (!cmd_busy)) |
06 | begin |
07 | rd_cnt <= 0; |
08 | sda_en <= 0; |
09 | rd_state <= 1; |
10 | scl_en <= 1; |
11 | scl_out <= 0; |
12 | rd_busy <= 1; |
13 | end |
14 | else |
15 | rd_busy <= 0; |
16 | end |
17 | 1: if (bit == 3) rd_state <= 2; |
18 | 2: |
19 | begin |
20 | case (bit) |
21 | 0,2: scl_out <= ~scl_out; |
22 | 1: |
23 | begin |
24 | case (rd_cnt) |
25 | 0,1,2,3,4,5,6,7: |
26 | begin |
27 | din[7-rd_cnt] <= sda_in; |
28 | rd_cnt <= rd_cnt + 1; |
29 | end |
30 | endcase |
31 | end |
32 | 3: |
33 | if (rd_cnt == 8) |
34 | begin |
35 | rd_cnt <= 0; |
36 | rd_state <= 3; |
37 | rd_busy <= 0; |
38 | rd_ack <= 1; |
39 | end |
40 | endcase |
41 | end |
42 | 3: |
43 | if (bit == 3) |
44 | begin |
45 | rd_ack <= 0; |
46 | rd_state <= 0; |
47 | end |
48 | endcase |
49 | end |
7、应答信号(Acknowledge)接收
应答信号会在读写进程里自动接收,不需要另外操作。
1 | always@(posedge sys_clk) |
2 | if ((bit == 1)&&(ack_en == 1)) ack <= sda_in; |
8、设计整合
很显然,如果简单地将上面各个操作的代码放在一个Verilog文件里,肯定是综合不过去的,因为每个进程都要对SCL和SDA进行操作。所以笔者又将各操作整合到了一个状态机里,这样就不会出现对SCL和SDA多重操作的情况。
->完整的代码见附件
9、仿真与测试
仿真:起始、写、读、停止信号的产生,都在如下的时序图上。
测试:在Altera的EP2C8Q208C8上测试通过。
10、附件:
I2C总线驱动,V1.0
http://myfpga.googlecode.com/files/i2c_interface_v1.v
对24C02读写的测试程序
http://myfpga.googlecode.com/files/ctrl_24C02.v
用到的分频程序
http://myfpga.googlecode.com/files/clk_div.v
写在最后:
文中及文中提到的代码和设计全为Craftor原创,转载请保留作者信息。
文中代码仅供交流和学习使用,不得用于任何商业用途。
用户124183 2011-11-2 11:58
用户544061 2011-10-22 23:55
用户1374706 2011-9-7 21:59
用户533944 2009-3-14 12:01
用户143781 2008-3-25 21:22