在I2C发送对设备的地址和读操作后,就要向设备发送读数据操作,这时,控制器就需要连续发送SCL信号给设备,并且在SCL的上升沿读取设备返回的数据,在连续接收到8个数据后,控制器发送ack/nack信号给设备。这个部分就是控制器对设备的读操作。在I2C PHY层 Verilog程序结构化设计 文章中的op_cmd[1:0] = 2’b10 来实现。如图1:

图1
1.I2C读数据部分控制器程序设计
-
接口要求:
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
- input rst,
- input sys_clk,
- input clk_en, clk_en_half,
- 从设备地址、启动信号、读/写标志(即该段程序之后的I2C 总线行为
- output [7:0] data_out, // device send data to controller
- input [1:0] op_cmd, // 2’b10 means controller receive data from device
- input op_cmd_en, // op_cmd enable , sys_clk single pulse
- I2C 总线的内部信号:由于inout信号一般只在端口使用,因此在FPGA的内部逻辑(内部模块)将会把inout(双向口)变换成input, output类型进行传递
- input sda_i,
- output sda_o,
- output sda_t,
- input scl_i,
- output scl_o,
- output scl_t,
- 握手信号:op_cmd_done 操作完成、从设备应答。当op_cmd_done产生,从设备应答后(dev_ack)同时送给上层模块。op_cmd_done 保持一个sys_clk 时钟宽度。
- output op_cmd_done, // operation finished
- input master_ack //controller send ACK/NACK to device
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
-
主体程序设计
该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b01),控制器发送数据进行串行化,等待从设备应答。串行化期间对SCL,SDA的要求是,在SCL为高电平期间设备接收控制器发送的数据,在SCL低电平后,等待半个时钟周期再变化数据,这样可以保证在pcb layout,或者FPGA 布局布线不会影响到 SCL 低电平内发送数据。当主控制器接收到8个bit数据后,控制器向设备发送ACK/NACK 。
程序的流程图如下:

图2
-
程序代码如下:
|
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 |
`timescale 1ns / 1ps module i2c_read( input sys_clk, input [1:0] op_cmd, input op_cmd_en, output reg op_cmd_done, input master_ack, output reg [7:0] data_out, input clk_en, input clk_en_half, output reg sda_t, output sda_o, input sda_i, output reg scl_t, output scl_o, input scl_i, input rst ); /* op_cmd[1:0] : 00 send start + address + wr/rd (data_in) 01 send data 10 receive data (data_out) 11 send stop flag */ assign scl_o = 1'b0; assign sda_o = 1'b0; reg [7:0] i2c_st; reg [2:0] sft_count; reg [7:0] i2c_wr_data; //将需要写的数据存入本地寄存器中 always@(posedge sys_clk or posedge rst) if(rst) begin op_cmd_done <= 0; data_out <= 0; scl_t <= 1'b0; sda_t <= 1'b1; sft_count <= 7; i2c_wr_data <= 0; i2c_st <= 0; end else case(i2c_st) 0: begin sft_count <= 7; op_cmd_done <= 1'b0; scl_t <= scl_i; sda_t <= sda_i; if({op_cmd_en,op_cmd[1:0]} == 3'b110) i2c_st <= 1; // read data end 1: //read data begin sda_t <= 1'b1; if(clk_en) begin scl_t <= 1'b0; i2c_st <= 2; end end 2: begin if(clk_en) begin scl_t <= 1'b1; data_out[sft_count] <= sda_i; sft_count <= sft_count - 1; i2c_st <= 1; if(sft_count == 0) i2c_st <= 3; end end 3: begin if( clk_en ) begin scl_t <= 1'b0; i2c_st <= 4; end end 4: //send ack/nack to device begin if( clk_en_half ) sda_t <= master_ack; if( clk_en ) begin scl_t <= 1'b1; i2c_st <= 5; end end 5: begin if( clk_en ) begin scl_t <= 1'b0; i2c_st <= 6; end end 6: begin if(clk_en_half) begin scl_t <= 1'b0; sda_t <= 1'b1; //兼容性设计, 防止pcb layout 时, sda 延迟,造成事实上的start 信号 op_cmd_done <= 1'b1; i2c_st <=0; end end default:i2c_st <= 0; endcase endmodule |
- iic_phy 模块代码
|
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 |
`timescale 1ns / 1ps module iic_phy( input sys_clk, input [1:0] op_cmd, input op_cmd_en, output reg op_cmd_done, output reg dev_ack, input master_ack, input [7:0] data_in, output [7:0] data_out, input clk_en, input clk_en_half, output reg sda_t, output sda_o, input sda_i, output reg scl_t, output scl_o, input scl_i, input rst ); /* op_cmd[1:0] : 00 send start + address + wr/rd (data_in) 01 send data 10 receive data (data_out) 11 send stop flag */ wire dev_ack_addr; wire dev_ack_wr; always @(*) begin case(op_cmd) 2'b00: dev_ack = dev_ack_addr; 2'b01: dev_ack = dev_ack_wr; default: ; endcase end wire op_cmd_done_addr; wire op_cmd_done_wr; wire op_cmd_done_read; wire op_cmd_done_stop; always @(*) begin case(op_cmd) 2'b00: op_cmd_done = op_cmd_done_addr; 2'b01: op_cmd_done = op_cmd_done_wr; 2'b10: op_cmd_done = op_cmd_done_read; 2'b11: op_cmd_done = op_cmd_done_stop; endcase end wire sda_t_addr; wire sda_t_wr; wire sda_t_read; wire sda_t_stop; wire scl_t_addr; wire scl_t_wr; wire scl_t_read; wire scl_t_stop; always @(*) begin case(op_cmd) 2'b00: begin sda_t = sda_t_addr; scl_t = scl_t_addr; end 2'b01: begin sda_t = sda_t_wr; scl_t = scl_t_wr; end 2'b10: begin sda_t = sda_t_read; scl_t = scl_t_read; end 2'b11: begin sda_t = sda_t_stop; scl_t = scl_t_stop; end endcase end i2c_addr i2c_addr_inst ( .sys_clk (sys_clk), .op_cmd (op_cmd), .op_cmd_en (op_cmd_en), .op_cmd_done (op_cmd_done_addr), .dev_ack (dev_ack_addr), .data_in (data_in), .clk_en (clk_en), .clk_en_half (clk_en_half), .sda_t (sda_t_addr), .sda_i (sda_i), .scl_t (scl_t_addr), .scl_i (scl_i), .rst (rst) ); i2c_write i2c_write_inst ( .sys_clk (sys_clk), .op_cmd (op_cmd), .op_cmd_en (op_cmd_en), .op_cmd_done (op_cmd_done_wr), .dev_ack (dev_ack_wr), .data_in (data_in), .clk_en (clk_en), .clk_en_half (clk_en_half), .sda_t (sda_t_wr), .sda_i (sda_i), .scl_t (scl_t_wr), .scl_i (scl_i), .rst (rst) ); i2c_read i2c_read_inst ( .sys_clk (sys_clk), .op_cmd (op_cmd), .op_cmd_en (op_cmd_en), .op_cmd_done (op_cmd_done_read), .master_ack (master_ack), .data_out (data_out), .clk_en (clk_en), .clk_en_half (clk_en_half), .sda_t (sda_t_read), .sda_i (sda_i), .scl_t (scl_t_read), .scl_i (scl_i), .rst (rst) ); assign sda_o = 1'b0; assign scl_o = 1'b0; endmodule |
- 将设计的模块例化,并组装到 iic_engine的模块中,这样就可以使用SCl_en模块,充分利用以设计的成果,逐步组装成程序代码及接口如下:
|
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 |
`timescale 1ns / 1ps module iic_engine # ( parameter DIV_ADDR = 32'hFFFF_0001, parameter DEV_ADDR = 7'b101_0000, parameter CLK_F = 100_000_000, parameter SPEED = 100_000 ) ( input sys_clk, inout SDA, inout SCL, input [31:0] div_addr, input [31:0] div_in, input div_en, input div_rd, output [31:0] div_dout, input [1:0] op_cmd, input op_cmd_en, output op_cmd_done, output dev_ack, input master_ack, input [7:0] data_in, output [7:0] data_out, output i2c_error, input rst ); wire sda_i; wire sda_o; wire sda_t; wire scl_o; wire scl_t; wire scl_i; assign SDA = sda_t ? 1'bz: sda_o; assign sda_i = SDA; assign SCL = scl_t ? 1'bz: scl_o; assign scl_i = SCL; wire clk_en; wire clk_en_half; //=========================== i2c clock baudtick scl_en # ( .DIV_ADDR ( DIV_ADDR ), .CLK_F ( CLK_F ), .SPEED ( SPEED ) ) scl_en_inst ( .sys_clk ( sys_clk ), .div_addr ( div_addr ), .div_in ( div_in ), .div_en ( div_en ), .clk_en ( clk_en ), .div_rd ( div_rd ), .div_dout ( div_dout ), .clk_en_half ( clk_en_half ), .rst ( rst ) ); //===============================iic phy iic_phy iic_phy_inst( .sys_clk (sys_clk), .op_cmd ( op_cmd ), .op_cmd_en ( op_cmd_en ), .op_cmd_done ( op_cmd_done ), .dev_ack ( dev_ack ), .master_ack ( master_ack ), .data_in ( data_in ), .data_out ( data_out ), .clk_en ( clk_en ), .clk_en_half ( clk_en_half ), .sda_t ( sda_t ), .sda_o ( sda_o ), .sda_i ( sda_i ), .scl_t ( scl_t ), .scl_o ( scl_o ), .scl_i ( scl_i ), .rst ( rst ) ); 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 |
`timescale 1ns / 1ps module tb_dev_read(); parameter PERIOD = 10 ; parameter FREQUANCY = 100000000; //100M parameter SPEED = 100000; // 100K parameter SPEED1 = 400000; //400K parameter DEV_ADDR = 7'b101_0001; reg inclk; always #( PERIOD / 2 ) inclk = ~inclk; //133M wire sys_clk = inclk; reg rst; // reg [ 31: 0 ] div_in; //for baudrate register wire [ 31: 0 ] div_addr = 32'hFFFF_0001; reg div_en = 0; reg [1:0] op_cmd = 0; reg op_cmd_en = 0; wire op_cmd_done; wire dev_ack; reg master_ack = 0; reg [7:0] data_in = 0; wire [7:0] data_out; reg i2c_error = 0; initial begin rst = 1'b1; inclk = 0; #50 rst = 1'b0; end reg [7:0] dev_data = 0; reg dev_data_valid = 0; reg [3:0] st = 0; reg [31:0] cnt = 0; always@( posedge sys_clk or posedge rst ) if ( rst ) begin master_ack <= 0; i2c_error <= 0; cnt <= 0; st <= 0; op_cmd_en <= 1'b0; op_cmd <= 0; end else begin case ( st ) 0: begin master_ack <= 0; dev_data <= 0; dev_data_valid <= 0; i2c_error <= 0; op_cmd <= 2'b00; op_cmd_en <= 0; if(cnt == 5000) begin cnt <= 0; st <= 1; end else cnt <= cnt + 1; end 1: begin data_in <= {DEV_ADDR, 1'b0}; op_cmd <= 2'b00; op_cmd_en <= 1; st <= 2; end 2: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) i2c_error <= 1; data_in <= 8'h00; op_cmd <= 2'b01; op_cmd_en <= 1; cnt <= 0; st <= 3; end end 3: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) i2c_error <= 1; data_in <= data_in + 1; op_cmd <= 2'b01; op_cmd_en <= 1; cnt <= cnt + 1; if(cnt == 7) st <= 4; end end 4: begin cnt <= 0; op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) i2c_error <= 1; st <= 5; end end 5: begin if(cnt == 5_000_00) begin cnt <= 0; st <= 6; end else cnt <= cnt + 1; end 6: begin cnt <= 0; op_cmd_en <= 0; data_in <= {DEV_ADDR, 1'b1}; op_cmd <= 2'b00; op_cmd_en <= 1; st <= 7; end 7: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) i2c_error <= 1; master_ack <= 0; op_cmd <= 2'b10; op_cmd_en <= 1; cnt <= cnt + 1; st <= 8; end end 8: begin op_cmd_en <= 0; dev_data_valid <= 0; if(op_cmd_done) begin dev_data <= data_out; dev_data_valid <= 1; op_cmd <= 2'b10; op_cmd_en <= 1; cnt <= cnt + 1; if(cnt == 7) begin master_ack <= 1; st <= 9; end end end 9: begin dev_data_valid <= 0; cnt <= 0; op_cmd_en <= 0; if(op_cmd_done) begin dev_data <= data_out; dev_data_valid <= 1; st <= 10; end end 10: begin dev_data_valid <= 0; if(cnt == 5000) st <= 15; else cnt <= cnt + 1; end 15: begin st <= 15; $stop; end default: st <= 0; endcase end wire SDA; wire SCL; iic_engine #( .CLK_F ( FREQUANCY ) , //default with 100Mhz .SPEED ( SPEED ) //default speed 100k ) iic_engine_inst ( .sys_clk ( sys_clk ), .div_addr ( div_addr ), .div_in ( div_in ), .div_en ( div_en ), .op_cmd ( op_cmd ), .op_cmd_en ( op_cmd_en ), .op_cmd_done( op_cmd_done ), .dev_ack ( dev_ack ), .master_ack ( master_ack ), .data_in ( data_in ), .data_out ( data_out ), .SDA ( SDA ), .SCL ( SCL ), .rst ( rst ) ); PULLUP SDA_inst ( .O(SDA) ); PULLUP SCL_inst ( .O(SCL) ); AT24C02D # (.SLAVE_ADDRESS(DEV_ADDR)) AT24C02D_inst ( .SDA (SDA), .SCL (SCL), .WP (0) ); endmodule |
-
仿真结果
仿真波形如图3

图3
从图3可以看出,写入读命令 7’b1010_001 + read。 等待设备会ACK, 然后控制器读取设备数据,控制器回ACK 给设备。 当接收完最后一个数据后,控制器会NACK,通知设备,之后不再读取数据了。 当前的仿真程序中没有使用STOP 命令,在下面的课程中对STOP 命令再做介绍。