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

图1
1.I2C写数据部分控制器程序设计
-
接口要求:
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
- input rst,
- input sys_clk,
- input clk_en, clk_en_half,
- 从设备地址、启动信号、读/写标志(即该段程序之后的I2C 总线行为
- input [7:0] data_in, // controller send data to device
- input [1:0] op_cmd, // 2’b01 means controller send data to device command
- 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
- output reg dev_ack//从设备是否有应答
- 复位信号,系统时钟,系统时钟分频(由于I2C不同速度匹配)
-
主体程序设计
该部分程序实现的主体内容如下:初始化,在IDLE状态下检测启动请求(op_cmd = 2’b01),控制器发送数据进行串行化,等待从设备应答。串行化期间对SCL,SDA的要求是,在SCL为高电平期间设备接收控制器发送的数据,在SCL低电平后,等待半个时钟周期再变化数据,这样可以保证在pcb layout,或者FPGA 布局布线不会影响到 SCL 低电平内发送数据。
程序的流程图如下:

图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 |
`timescale 1ns / 1ps module i2c_write( input sys_clk, input [1:0] op_cmd, input op_cmd_en, output reg op_cmd_done, output reg dev_ack, input [7:0] data_in, 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 [2: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; dev_ack <= 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; dev_ack <= 0; scl_t <= scl_i; sda_t <= sda_i; i2c_wr_data <= data_in; if({op_cmd_en,op_cmd[1:0]} == 3'b101) i2c_st <= 1; // write data end 1: //write begin if(clk_en) begin scl_t <= 1'b0; i2c_st <= 2; end end 2: begin if(clk_en_half) sda_t <= i2c_wr_data[sft_count]; if(clk_en) begin scl_t <= 1'b1; 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: //read ack/nack from device begin if( clk_en_half ) sda_t <= 1'b1; if(clk_en) begin scl_t <= 1'b1; dev_ack <= sda_i; i2c_st <= 5; end end 5: begin if(clk_en) begin scl_t <= 1'b0; 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 |
`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) ); 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 |
`timescale 1ns / 1ps module tb_dev_write(); 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 [3:0] st = 0; reg [31:0] cnt = 0; always@( posedge sys_clk or posedge rst ) if ( rst ) begin i2c_error <= 0; cnt <= 0; st <= 0; op_cmd_en <= 1'b0; op_cmd <= 0; end else begin case ( st ) 0: begin 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 <= 15; end end 15: begin st <= 15; #200; $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可以看出在写阶段串行数据。24c02 iic设备应答。在I2C PHY层 Verilog程序结构化设计中介绍过op_cmd 定义:op_cmd[1:0] = 2’b01;代表 write + dev_ack。 op_cmd[1:0] 和op_cmd_en 同时由仿真程序发出,当iic_phy完成相关操作后,返回op_cmd_done 信号, 代表当前操作命令(op_cmd)完成。