项目之前研究了I2C通信协议的实现,完成FPGA对视频解码芯片SAA7111A的初始化配置,设计实现了I2C主机对从机(SAA7111A)32个寄存器的写操作,因此只简单实现了I2C的写时序。
这次重新梳理学习了I2C协议,借助黑金开发板设计I2C主机控制器完成对EEPROM(24LC02)的读写操作,设计单字节的写时序和随机读时序。通过按键将数据先入EEPROM,再通过按键选择将数据显示在数码管上进行验证。
1. 时序介绍
主要的时序如下所示:
数据线SDA在空闲状态时为高电平,在SCL高电平时拉低SDA表示开始,在SCL低电平时拉高SDA表示结束。数据在SCL低电平时变化,8位数据,高位在前,低位在后。一个数据字节后,接收器需要产生一个低电平,即拉低SDA,表示接收正确。
写时序:
读时序:
其中,应答位一般由接收器产生,在读时序时主机接收数据一般不产生应答位(NO ACK),除了在连续读模式下,一个数据读完需要拉低SDA产生应答位。
2. 串行时钟线(SCL)
首先要确定SCL时钟,根据系统时钟利用计数器完成SCL的100KHz的设置,这里SCL作为输出信号,因此为输出单向口。

1 //分频部分 2 reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间 3 reg[8:0] cnt_delay; //500循环计数,产生iic所需要的时钟 4 reg scl_r; //时钟脉冲寄存器 5 6 always @ (posedge clk or negedge rst_n) 7 if(!rst_n) cnt_delay <= 9'd0; 8 else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz 9 else cnt_delay <= cnt_delay+1'b1; //时钟计数 10 11 always @ (posedge clk or negedge rst_n) begin 12 if(!rst_n) cnt <= 3'd5; 13 else begin 14 case (cnt_delay) 15 9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样 16 9'd249: cnt <= 3'd2; //cnt=2:scl下降沿 17 9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化 18 9'd499: cnt <= 3'd0; //cnt=0:scl上升沿 19 default: cnt <= 3'd5; 20 endcase 21 end 22 end 23 24 25 `define SCL_POS (cnt==3'd0) //cnt=0:scl上升沿 26 `define SCL_HIG (cnt==3'd1) //cnt=1:scl高电平中间,用于数据采样 27 `define SCL_NEG (cnt==3'd2) //cnt=2:scl下降沿 28 `define SCL_LOW (cnt==3'd3) //cnt=3:scl低电平中间,用于数据变化 29 30 31 always @ (posedge clk or negedge rst_n) 32 if(!rst_n) scl_r <= 1'b0; 33 else if(cnt==3'd0) scl_r <= 1'b1; //scl信号上升沿 34 else if(cnt==3'd2) scl_r <= 1'b0; //scl信号下降沿 35 36 assign scl = scl_r; //产生iic所需要的时钟
根据计数器的计数结果获得SCL的上升沿、高电平中间时刻、下降沿和低电平中间时刻。四个信号作为系统时钟的使能信号,保持信号的同步,完成发送和接收。
3. 串行数据线(SDA)
串行数据线是双向口,作为输出口时,完成开始信号、结束信号、从机地址、字节地址和写数据的输出;作为输入口时,完成从机应答位和读数据的输入。因此需要实现一个三态口控制:
assign sda = sda_link ? sda_r:1'bz;
本实验设计了一段式的状态机控制串行数据口的输入和输出,涉及单字节写时序和随机读时序。
由时序可知,前两次数据字节操作一样,可共享代码。在第3个数据字节处理时,写时序进行之前同样的操作即可,最后产生停止位;读时序时先发送从机地址读操作命令字节(最后一位为1),然后SDA口设置为输入口读取数据,最后FPGA无需产生应答位而产生停止位即可。返回IDLE状态前,产生清零标志以清零上次按键结果。
写一个字节:

1 IDLE: begin
2 sda_link <= 1'b1; //数据线sda为output
3 sda_r <= 1'b1;
4 if(!sw1_r || !sw2_r) begin //SW1,SW2键有一个被按下
5 db_r <= `DEVICE_WRITE; //送器件地址(写操作) //写读控制字节
6 cstate <= START1;
7 end
8 else cstate <= IDLE; //没有任何键被按下
9 end
10 START1:if(`SCL_HIG) begin //scl为高电平期间
11 sda_link <= 1'b1; //数据线sda为output
12 sda_r <= 1'b0; //拉低数据线sda,产生起始位信号
13 cstate <= ADD1;
14 num <= 4'd0; //num计数清零
15 end
16 else cstate <= START1; //等待scl高电平中间位置到来
17
18 ADD1: if(`SCL_LOW) begin
19 if(num == 4'd8) begin
20 num <= 4'd0; //num计数清零
21 // sda_r <= 1'b1;
22 sda_link <= 1'b0; //sda置为高阻态(input)
23 cstate <= ACK1;
24 end
25 else begin
26 cstate <= ADD1;
27 num <= num+1'b1;
28 case (num)
29 4'd0: sda_r <= db_r[7];
30 4'd1: sda_r <= db_r[6];
31 4'd2: sda_r <= db_r[5];
32 4'd3: sda_r <= db_r[4];
33 4'd4: sda_r <= db_r[3];
34 4'd5: sda_r <= db_r[2];
35 4'd6: sda_r <= db_r[1];
36 4'd7: sda_r <= db_r[0];
37 default: ;
38 endcase
39 // sda_r <= db_r[4'd7-num]; //送器件地址,从高位开始
40 end
41 end
42 // else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
43 else cstate <= ADD1;
44
45 ACK1:
46 // if(/*!sda*/`SCL_NEG) begin //注:24C01/02/04/08/16器件可以不考虑应答位
47 if(`SCL_HIG && !sda) begin // SCL_HIG高电平时sda稳定,可以考虑`SCL_HIG && !sda和!sda效果一样
48 cstate <= ADD2; //从机响应信号
49 db_r <= `BYTE_ADDR; // 存储器读写地址
50 end
51 else cstate <= ACK1; //等待从机响应
读一个字节:

1 //*********读操作起始位,先拉高SDA,再拉低SDA ******************************//
2 START2:if(`SCL_LOW) begin //等待应答位高电平过去,检测一下个SCL的低电平!!!!
3 sda_link <= 1'b1; //sda作为output
4 sda_r <= 1'b1; //拉高数据线sda
5 cstate <= READ;
6 end
7 else cstate <= START2;
8
9 READ: if(`SCL_HIG) begin //scl为高电平中间
10 sda_r <= 1'b0; //拉低数据线sda,产生起始位信号
11 cstate <= ADD3;
12 end
13
14 ADD3: //送读控制字节
15 if(`SCL_LOW) begin
16 if(num==4'd8) begin
17 num <= 4'd0; //num计数清零
18 // sda_r <= 1'b1;
19 sda_link <= 1'b0; //sda置为高阻态(input)
20 cstate <= ACK3;
21 end
22 else begin
23 num <= num+1'b1;
24 case (num)
25 4'd0: sda_r <= db_r[7];
26 4'd1: sda_r <= db_r[6];
27 4'd2: sda_r <= db_r[5];
28 4'd3: sda_r <= db_r[4];
29 4'd4: sda_r <= db_r[3];
30 4'd5: sda_r <= db_r[2];
31 4'd6: sda_r <= db_r[1];
32 4'd7: sda_r <= db_r[0];
33 default: ;
34 endcase
35 // sda_r <= db_r[4'd7-num]; //送EEPROM地址(高bit开始)
36 cstate <= ADD3;
37 end
38 end
39 // else if(`SCL_POS) db_r <= {db_r[6:0],1'b0}; //器件地址左移1bit
40 else cstate <= ADD3;
41
42 ACK3: begin
43 // if(/*!sda*/`SCL_NEG) begin
44 if(`SCL_HIG && !sda) begin
45 cstate <= wait_L; //从机响应信号
46 // sda_link <= 1'b0;
47 end
48 else cstate <= ACK3; //等待从机响应
49 end
50
51 wait_L: if(/*`SCL_NEG*/`SCL_LOW) cstate <= DATA; //等待应答位高电平过去,检测一下个SCL的低电平!!!!
52
53 DATA: if(!sw2_r) begin //读操作
54 if((`SCL_LOW) && (num==4'd8)) begin
55 num <= 4'd0; //num计数清零
56 cstate <= NO_ACK;
57 end
58 else if(`SCL_HIG && (num<=4'd7) ) begin
59 num <= num+1'b1;
60 case (num)
61 4'd0: read_data[7] <= sda;
62 4'd1: read_data[6] <= sda;
63 4'd2: read_data[5] <= sda;
64 4'd3: read_data[4] <= sda;
65 4'd4: read_data[3] <= sda;
66 4'd5: read_data[2] <= sda;
67 4'd6: read_data[1] <= sda;
68 4'd7: read_data[0] <= sda;
69 default: ;
70 endcase
71 // read_data[4'd7-num] <= sda; //读数据(高bit开始)
72 cstate <= DATA;
73 // else if(`SCL_NEG) read_data <= {read_data[6:0],read_data[7]}; //数据循环右移
74 end
75 else cstate <= DATA;
76 end
来源:https://www.cnblogs.com/aikimi7/p/3905252.html






