在I2C在完成相关的读写操作后,就要向设备发送STOP操作,STOP作为一个独立模块,是因为在I2c总线发生错误时,也需要发送STOP,不仅仅是正常的I2c读写结束时。STOP 信号是在SCL信号为高电平期间,SDA信号由低变高。这个部分就是控制器对设备的STOP操作。在I2C PHY层 Verilog程序结构化设计 文章中的op_cmd[1:0] = 2’b11 来实现。如图1:
图1
1.I2C读数据部分控制器程序设计
-
接口要求:
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
- input rst,
- input sys_clk,
- input clk_en, clk_en_half,
- 命令信号
- input [1:0] op_cmd, // 2’b11 means controller send STOP
- 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
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
-
主体程序设计
该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b11),在SCL为高电平期间等待半个时钟周期再将SDA由低变高。在STOP之后,总线处于idle状态,即控制器和设备都不去控制SDA,SCL, SDA,SCL 都是处于高阻状态。
程序的流程图如下:
图2
-
程序代码如下:
`timescale 1ns / 1ps module i2c_stop( input sys_clk, input [1:0] op_cmd, input op_cmd_en, output reg op_cmd_done, 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; 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'b111) i2c_st <= 1; // stop flag end 1: //stop flag begin sda_t <= 1'b0; if(clk_en) begin scl_t <= 1'b1; i2c_st <= 2; end end 2: begin if(clk_en) begin sda_t <= 1'b1; i2c_st <= 3; end end 3: begin if(clk_en) begin sda_t <= 1'b1; scl_t <= 1'b1; op_cmd_done <= 1'b1; i2c_st <= 0; end end default:i2c_st <= 0; endcase endmodule
- iic_phy 模块代码
`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) ); i2c_stop i2c_stop_inst ( .sys_clk (sys_clk), .op_cmd (op_cmd), .op_cmd_en (op_cmd_en), .op_cmd_done (op_cmd_done_stop), .clk_en (clk_en), .clk_en_half (clk_en_half), .sda_t (sda_t_stop), .sda_i (sda_i), .scl_t (scl_t_stop), .scl_i (scl_i), .rst (rst) ); assign sda_o = 1'b0; assign scl_o = 1'b0; endmodule
- 将设计的模块例化,并组装到 iic_engine的模块中,这样就可以使用SCl_en模块,充分利用以设计的成果,逐步组装成程序代码及接口如下:
`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
-
仿真程序设计:
`timescale 1ns / 1ps module tb_dev_stop(); 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; // send device_addr + write op_cmd_en <= 1; st <= 2; end 2: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin data_in <= 8'h00; // write memory address 8'h00 op_cmd <= 2'b01; op_cmd_en <= 1; cnt <= 0; st <= 3; end end end 3: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin data_in <= data_in + 1; // wirte data to device op_cmd <= 2'b01; op_cmd_en <= 1; cnt <= cnt + 1; if(cnt == 7) st <= 4; end end end 4: begin cnt <= 0; op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin op_cmd <= 2'b11; //send STOP command op_cmd_en <= 1; st <= 5; end end end 5: begin cnt <= 0; op_cmd_en <= 0; if(op_cmd_done) st <= 6; end 6: // delay 5ms according to EEPROM spec begin if(cnt == 5_000_00) begin cnt <= 0; st <= 7; end else cnt <= cnt + 1; end 7: begin cnt <= 0; op_cmd_en <= 0; data_in <= {DEV_ADDR, 1'b0}; op_cmd <= 2'b00; // send START + dev_addr + write op_cmd_en <= 1; st <= 8; end 8: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin data_in <= 8'h00; op_cmd <= 2'b01; // send memory address 8'h00 op_cmd_en <= 1; st <= 9; end end end 9: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin data_in <= {DEV_ADDR, 1'b1}; op_cmd <= 2'b00; // send RESTART + dev_addr + read op_cmd_en <= 1; st <= 10; end end end 10: begin op_cmd_en <= 0; if(op_cmd_done) begin if(dev_ack == 1) begin i2c_error <= 1; st <= 13; end else begin op_cmd <= 2'b10; // send read op_cmd_en <= 1; master_ack <= 0; cnt <= cnt + 1; st <= 11; end end end 11: 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; //send read op_cmd_en <= 1; cnt <= cnt + 1; if(cnt == 7) begin master_ack <= 1; // send NACK for the last data st <= 12; end end end 12: 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 <= 13; end end 13: // STOP command begin dev_data_valid <= 0; op_cmd <= 2'b11; // send STOP command op_cmd_en <= 1; st <= 14; end 14: begin cnt <= 0; op_cmd_en <= 0; if(op_cmd_done) begin st <= 15; end end 15: begin if(cnt == 5000) begin st <= 15; $stop; end else cnt <= cnt + 1; 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可以看出:
1)start + dev_addr +wr。设备回ack
2)send memory addr, 设备会ack
3)restart + dev_addr +rd, 设备回ack
4)receive data, 控制器回ack
5) 。。。。
6)receive the last data ,控制器回nack
7 )send STOP command
在等待设备回ack期间,如果设备回nack, I2c总线发生错误(设备无应答)。这时,控制器需要停止总线(发送STOP),将SDA,SCL 线变为高阻。之后其他的I2c 命令才能正常执行。