赞
踩
i2c使用scl、sda这两条线进行通信,通信方向是双向的,通信过程有主从之分,是串行、同步的。可以进行多对多的通信,i2c有5种通信速率:标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。
i2c为了进行多对多的通信引入了时钟同步+总线仲裁的机制:
SCL同步是由于总线具有线“与”的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平。当所有的节点都发送高电平时,总线才能表现为高电平。正是由于线“与”逻辑功能的原理,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。
各I2C设备的数据串行线SDA也是线“与”的关系。SDA线的仲裁也是建立在总线具有线“与”逻辑功能的原理上的。节点在发送1位数据后,比较总线上所呈现的数据与自己发送的是否一致。是,继续发送;否则,退出竞争。SDA线的仲裁可以保证I2C总线系统在多个主节点同时企图控制总线时通信正常进行并且数据不丢失。总线系统通过仲裁只允许一个主节点可以继续占据总线
sda可进行传输或者解收数据,因此采用inout端口。主机通过地址找到具体的从机设备并实现数据传输或者接收,任何被寻址的器件都被认为是从机。
在执行数据传输时,器件也可以被看作主器件(主机)或从器件(从机)。主器件是用于启动总线传送数据,并产生时钟的器件,此时任何被寻址的器件均被认为是从器件(一主多从)。在总线上主和从、发和收的关系取决于此时的数据传送方向,而不是恒定的。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件,然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。
硬件上都需要接一个上拉电阻到VCC。各种被控制电路均并联在这条总线上,但就像电话机一样只有拨通各自的号码才能工作,所以每个电路和模块都有唯一的地址,这样,各控制电路虽然挂在同一条总线上,却彼此独立,互不相关.
i2c接口设备有很多,包括温度传感器、模数转换芯片以及eeprom等
sda和scl时序图:
开始和停止条件:
非传输状态下拉高SCL和SDA,SDA处于下降沿SCL为高电平的时候,表示启动信号。SDA处于上升沿SCL为高电平的时候,表示结束信号。注意,在传输时按照数据位从高到低传输,且数据在scl低电平时改变,数据传输时SDA必须在SCL高电平时保持不变。
对一个器件中的存储单元(包括寄存器)进行读写时,首先要指定存储单元的地址即字地址,然后再向该地址写入内容。该地址为一个或两个字节长度,具体长度由器件内部的存储单元的数量决定,当存储单元数量不超过一个字节所能表示的最大数量(2^8=256)时,用一个字节表示,超过一个字节所能表示的最大数量时,就需要用两个字节来表示
下面给出读写的基本流程:
写数据 |
---|
确认器件地址,写入字地址,将sda设置为out,发送写命令等待从机应答。收到应答后开始写入数据,直到写完数据后主机发送停止数据结束发送过程。在写过程中,只有发送ack或者nack是不受主机控制的 |
1、Master在SCL为高电平期间,拉低SDA,发起START。 |
2、Master发送设备地址(7bit)和写操作0(1bit),等待ACK。 |
3、对应的Slave回应ACK。 |
4、Master发送寄存器字地址(8bit),等待ACK。 |
5、对应的Slave回应ACK。 |
6、Master发送数据(8bit),也就是要写入Slave寄存器中的数据,等待ACK。 |
7、对应的Slave回应ACK。 |
8、其中的6,7步可重复执行多次,即按顺序对多个寄存器进行写操作。如果写入的地址是连续的,那么在得到初始的写入地址后继续向下一个地址写入,不必重新传入地址。连续写又被称为页写 |
9、Master发起STOP。 |
读数据: |
---|
将sda设置为in,发送读命令等待主机应答。收到应答后开始读入数据,直到写完数据后主机停止读取数据并发送停止信号 。在读过程中,发送ack或者nack以及接收数据是不受主机控制的 |
1、Master在SCL为高电平期间,拉低SDA,发起START。 |
2、Master发送设备地址(7bit)和写操作0(1bit),等待ACK。 |
3、Slave发送ACK。 |
4、Master发送寄存器字地址(8bit)并释放SDA信号线,slave控制sda低电平并发送ACK。如果是nack则slave控制sda高电平 |
5、Slave发ACK。 |
6、Master发起START。 |
7、Master发送I2C设备地址(7bit)和读操作1(1bit),等待ACK。 |
8、Slave发送ACK。 |
9、Slave发送data(以字节为单位),即对应寄存器中的值。 |
10、Master发送ACK。 |
11、第9步和第10步可重复进行多次,即按顺序读多个寄存器。 |
在通信中还可能需要知道从机内部的寄存器位置(字地址),这样才能把数据成功传输到我们想要的位置,在传输时每一个byte后面附带一个ack或者nack信号
发送模块应该具有以下接口:clk、rtsn、send_en(用来提示开始发送)、device_addr(设备地址,不同设备位宽不同)、word_addr(字节地址,一般是8位)、data_w(发送有效数据,8位)、done(传输完成信号)、scl串行时钟线与sda串行信号线
lk、rtsn、send_en(用来提示开始发送)、device_addr(设备地址,不同设备位宽不同)、word_addr(字节地址,一般是8位)、data_r(接收有效数据,8位)、done(传输完成信号)、scl串行时钟线与sda串行信号线
接收模块的定义类似,
eeprom支持断电后保持数据并可以修改,实验的芯片型号是at24c64,容量为65536bit
器件地址位宽8位, 前4位固定等于1010,5-7位可自行配置所以最大可以支持8个设备,第8位是读写控制位,写为0读为1。sda的控制在fpga和eeprom之间切换
实验任务是先向EEPROM(AT24C64)的存储器地址0至255分别写入数据0~255; 写完之后再读取存储器地址0-255中的数据,若读取的值全部正确则LED灯常亮,否则LED灯闪烁。
状态机设计:
我们把发送接收模块合在一起编写
module i2c_driver( input clk,input rstn, inout sda,output reg scl, input en,input[15:0] device_addr, input wl_rh,input bit_ctrl, output reg ack, input [7:0]data_w, output reg[7:0]data_r, output reg done,output reg dri_clk //driver clock ); parameter clk_freq=50_000_000, i2c_freq= 250_000, bps_cnt=(clk_freq/i2c_freq)>>2'd3,slav_addr=7'b1_010_000; localparam st_idle =8'b0000_0001,st_slv_addr =8'b0000_0010, st_addr16_wr=8'b0000_0100,st_addr8_wr =8'b0000_1000, st_data_wr =8'b0001_0000,st_addr_rd =8'b0010_0000, st_data_rd =8'b0100_0000,st_stop =8'b1000_0000; wire sda_in;reg sda_out;reg sda_dir; reg[7:0]cstate,nstate; reg[7:0]data_rd,data_wr_d0; reg[15:0]addr_t; reg done_flag,wr_flag ; //写标志 reg[6:0]cnt;reg[9:0]clk_cnt; assign sda = sda_dir ? sda_out : 1'bz, sda_in = sda ; always @(posedge dri_clk or negedge rstn) begin if(!rstn) cstate<=st_idle; else cstate<=nstate; end always @(posedge clk or negedge rstn) begin if(!rstn) begin dri_clk<='d0; clk_cnt<='d0; end else if(clk_cnt==bps_cnt-'d1) begin clk_cnt<='d0; dri_clk<=~dri_clk; end else clk_cnt<=clk_cnt+'d1; end always @(*) begin if(!rstn) nstate=st_idle; else begin case(cstate) st_idle: if(en) nstate=st_slv_addr; else ; st_slv_addr: if(done_flag)begin if(bit_ctrl)// bit_ctrl=1,addr width=16 else addr width=8 nstate=st_addr16_wr; else nstate=st_addr8_wr; end else ; st_addr16_wr:begin if(done_flag)nstate=st_addr8_wr; else ; end st_addr8_wr:begin if(done_flag)begin if(wr_flag==1'b0) //读写判断 nstate = st_data_wr; else nstate = st_addr_rd; end else ; end st_data_wr :begin if(done_flag) nstate=st_stop; else ; end st_addr_rd:begin if(done_flag) nstate=st_data_rd; else ; end st_data_rd :begin if(done_flag) nstate=st_stop; else ; end st_stop :begin if(done_flag) nstate=st_idle; else ; end default : nstate=st_idle; endcase end end always @(posedge dri_clk or negedge rstn) begin if(!rstn) scl<='d1; else case(cstate) st_idle: scl<='d1; st_slv_addr :begin case(cnt) 7'd3,7'd7,7'd11,7'd15,7'd19,7'd23, 7'd27,7'd31,7'd35,7'd39:scl<='d0; 7'd5,7'd9,7'd13,7'd17,7'd21, 7'd25,7'd29,7'd33,7'd37:scl<='d1; default :; endcase end st_addr_rd:begin case(cnt) 7'd1,7'd5,7'd9,7'd13,7'd17,7'd21, 7'd25,7'd29,7'd33,7'd37:scl<='d1; 7'd3,7'd7,7'd11,7'd15,7'd19,7'd23, 7'd27,7'd31,7'd35,7'd39:scl<='d0; default :;endcase end st_addr16_wr, st_addr8_wr, st_data_wr,st_data_rd: begin case(cnt) 7'd1,7'd5,7'd9,7'd13,7'd17,7'd21, 7'd25,7'd29,7'd33:scl<='d1; 7'd3,7'd7,7'd11,7'd15,7'd19,7'd23, 7'd27,7'd31,7'd35:scl<='d0; default:; endcase end st_stop:if(cnt==7'd1)scl<='d1; else ; default :; endcase end always @(posedge dri_clk or negedge rstn) begin if(!rstn) begin sda_out<='d1; sda_dir<='d1; ack<='d0; cnt<='d0; done<='d0; done_flag<='d0; data_r<='d0; data_rd<='d0; data_wr_d0<='d0; addr_t<='d0; wr_flag<='d0; end else begin done_flag<='d0; cnt<=cnt+1'b1; case(cstate) st_idle:begin sda_out<='d1; sda_dir<='d1; done<='d0; cnt<='d0; if(en)begin// store data when en addr_t<=device_addr; data_wr_d0<=data_w; wr_flag<=wl_rh; ack<='d0; end else ; end st_slv_addr :begin case(cnt) 7'd1:sda_out<='d0;7'd4 : sda_out <= slav_addr[6]; 7'd8 : sda_out <= slav_addr[5];7'd12:sda_out <= slav_addr[4]; 7'd16 : sda_out <= slav_addr[3];7'd20 : sda_out <= slav_addr[2]; 7'd24 : sda_out <= slav_addr[1];7'd28 : sda_out <= slav_addr[0]; 7'd32:sda_out<='d0; 7'd36: begin sda_dir <= 1'b0;sda_out <= 1'b1; end 7'd38: begin //从机应答 done_flag <= 1'b1; if(sda_in ) //高电平表示nack ack <= 1'b1; //拉高应答标志位 end 7'd39:cnt<='d0; default :; endcase end st_addr16_wr:begin case(cnt) 7'd0:begin sda_dir<='d1; sda_out<=addr_t[15]; end 7'd4:sda_out<=addr_t[14]; 7'd8:sda_out<=addr_t[13]; 7'd12:sda_out<=addr_t[12];7'd16:sda_out<=addr_t[11]; 7'd20:sda_out<=addr_t[10];7'd24:sda_out<=addr_t[9]; 7'd28:sda_out<=addr_t[8]; 7'd32: begin sda_dir <= 1'b0;sda_out <= 1'b1; end 7'd34: begin done_flag<='d1;if(sda_in) ack<='d1;end 7'd35:cnt<='d0; default:; endcase end st_addr8_wr :begin case(cnt) 7'd0:begin sda_dir<='d1; sda_out<=addr_t[7]; end 7'd4:sda_out<=addr_t[6]; 7'd8:sda_out<=addr_t[5]; 7'd12:sda_out<=addr_t[4];7'd16:sda_out<=addr_t[3]; 7'd20:sda_out<=addr_t[2];7'd24:sda_out<=addr_t[1]; 7'd28:sda_out<=addr_t[0]; 7'd32: begin sda_dir <= 1'b0;sda_out <= 1'b1; end 7'd34: begin done_flag<='d1;if(sda_in) ack<='d1;end 7'd35:cnt<='d0; default:; endcase end st_data_wr :begin case(cnt) 7'd0:begin sda_dir<='d1; sda_out<=data_wr_d0[7]; end 7'd4:sda_out<=data_wr_d0[6]; 7'd8:sda_out<=data_wr_d0[5]; 7'd12:sda_out<=data_wr_d0[4];7'd16:sda_out<=data_wr_d0[3]; 7'd20:sda_out<=data_wr_d0[2];7'd24:sda_out<=data_wr_d0[1]; 7'd28:sda_out<=data_wr_d0[0]; 7'd32: begin sda_dir <= 1'b0;sda_out <= 1'b1; end 7'd34: begin done_flag<='d1;if(sda_in) ack<='d1;end 7'd35:cnt<='d0; default:; endcase end st_addr_rd:begin case(cnt) 7'd0:begin sda_dir<='d1;sda_out<='d1;end 7'd2:sda_out<='d0;7'd4:sda_out <= slav_addr[6]; 7'd8:sda_out <= slav_addr[5];7'd12:sda_out <= slav_addr[4]; 7'd16:sda_out <= slav_addr[3];7'd20:sda_out <= slav_addr[2]; 7'd24:sda_out <= slav_addr[1];7'd28:sda_out <= slav_addr[0]; 7'd32:sda_out<='d1; 7'd36: begin sda_dir <= 1'b0;sda_out <= 1'b1; end 7'd38: begin //从机应答 done_flag <= 1'b1; if(sda_in ) //高电平表示未应答 ack <= 1'b1; //拉高应答标志位 end 7'd39:cnt<='d0; default :; endcase end st_data_rd :begin case(cnt) 7'd0:sda_dir<='d0;7'd1:data_rd[7]<=sda_in; 7'd5:data_rd[6]<=sda_in;7'd9:data_rd[5]<=sda_in; 7'd13:data_rd[4]<=sda_in;7'd17:data_rd[3]<=sda_in; 7'd21:data_rd[2]<=sda_in;7'd25:data_rd[1]<=sda_in; 7'd29:data_rd[0]<=sda_in; 7'd32: begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd34:done_flag<='d1; 7'd35: begin data_r<=data_rd; cnt<='d0; end default:; endcase end st_stop :begin case(cnt) 7'd0:begin sda_dir<='d1 ; sda_out<='d0;end 7'd3:sda_out<='d1; 7'd15:done_flag<='d1; 7'd16: begin done<='d1;cnt<='d0; end endcase end default:; endcase end end endmodule
tesdtbench文件:
`timescale 1ns/1ns module i2c_driver_tb(); parameter T=20,wr_cycle = 10_000; reg clk, rstn,en,wl_rh,bit_ctrl; reg[15:0]device_addr; reg[7:0]data_w; reg [3:0] flow_cnt ; reg [13:0] delay_cnt ; wire scl,ack,done, dri_clk,sda; wire [7:0]data_r; always #(T/2)clk=~clk; initial begin clk=0; rstn=0; #(T+1) rstn=1; end always @(posedge dri_clk or negedge rstn) begin if(!rstn) begin en<='d0;bit_ctrl<='d0; wl_rh<='d0;device_addr<='d0; data_w<='d0;delay_cnt<='d0; flow_cnt<='d0; end else begin case(flow_cnt) 'd0:flow_cnt<=flow_cnt+1'b1; 'd1:begin en<='d1; bit_ctrl<='d1; wl_rh<='d0; device_addr<='h0555; data_w<=8'h9c; flow_cnt<=flow_cnt+1'b1; end 'd2:begin en<='d0; flow_cnt<=flow_cnt+'d1; end 'd3:begin if(done)flow_cnt<=flow_cnt+'d1; end 'd4:begin delay_cnt<=delay_cnt+'d1; if(delay_cnt==wr_cycle-'d1)// add delay_cnt until finish write cycle flow_cnt<=flow_cnt+'d1; end 'd5:begin en<='d1; bit_ctrl<='d1; wl_rh<='d1; device_addr<='h0555; data_w<=8'h3a; flow_cnt<=flow_cnt+1'b1; end 'd6:begin en<='d0; flow_cnt<=flow_cnt+1'b1; end 'd7:if(done) flow_cnt<=flow_cnt+1'b1; default :; endcase end end pullup(sda); i2c_driver i2c_driver_u( .clk(clk), .rstn(rstn), .sda(sda), .scl(scl), .en(en), .device_addr(device_addr), .wl_rh(wl_rh), .bit_ctrl(bit_ctrl), .ack(ack), .data_r(data_r), .data_w(data_w), .done(done), .dri_clk(dri_clk) ); EEPROM_AT24C64 eeprom_u( .scl(scl), .sda(sda) ); endmodule
仿真结果:
eeprom模块:
module eeprom_driver( input clk,rstn, output reg wl_rh,en, output reg[15:0]device_addr, output reg[7:0]data_w, input [7:0]data_r, input done, ack, output reg rw_done,output reg suce ); parameter max_byte=256,//max data unm=256 wait_time= 5_000;//delay 5 ms reg [1:0] flow_cnt ; reg [13:0] delay_cnt ; always @(posedge clk or negedge rstn) begin if(!rstn) delay_cnt<='d0; else begin case(flow_cnt) 2'd0:begin if(delay_cnt==wait_time-'d1) delay_cnt<='d0; else delay_cnt<=delay_cnt+'d1; end default:; endcase end end always @(posedge clk or negedge rstn) begin if(!rstn)begin flow_cnt<='d0; delay_cnt<='d0; wl_rh<='d0; en<='d0; device_addr<='d0; data_w<='d0; rw_done<='d0; suce<='d0; end else begin en<='d0; rw_done<='d0; case(flow_cnt) 2'd0:begin if(delay_cnt==wait_time-'d1) begin if(device_addr==max_byte) begin device_addr<='d0; wl_rh<='d1; flow_cnt<=2'd2; end else begin flow_cnt<=flow_cnt+'d1; en<='d1; end//start wr or rd end end 2'd1: begin if(done)begin flow_cnt<=2'd0; device_addr<=device_addr+'d1; data_w<=data_w+'d1; end end 2'd2:begin//when write dfata fulliy, jump to step 2 flow_cnt<=flow_cnt+'d1; en<='d1; end 2'd3:begin//read written data if(done) if(device_addr[7:0]!=data_r||ack)begin//write data failed rw_done<='d1; suce<='d0; end else if(device_addr==max_byte-'d1) begin rw_done<='d1; suce<='d1;//write all data,finish process end else begin flow_cnt<='d2; device_addr<=device_addr+'d1; end end default:; endcase end end endmodule
led模块
module led_light( input clk,rstn, input rw_done,input suce,output reg led ); parameter clk_freq=50_000_000, i2c_freq= 250_000, bps_cnt=(clk_freq/i2c_freq)>>2'd3, slav_addr=7'b1_010_000,bit_ctrl='d1, ltime=18'd225_000; reg rw_done_flag; reg[24:0]led_cnt; always @(posedge clk or negedge rstn) begin if(!rstn) begin rw_done_flag<='d0; end else if(rw_done) begin rw_done_flag<='d1; end else ; end always @(posedge clk or negedge rstn) begin if(!rstn) begin led_cnt<='d0; led<='d0; end else begin if(rw_done_flag) begin if(suce) led<='d1; else begin led_cnt<=led_cnt+25'd1; if(led_cnt==ltime-'d1) begin led_cnt<='d0; led<=~led; end end end else led<='d0; end end endmodule
顶层模块:
module e2prom_top( input clk , //系统时钟 input rstn , //系统复位 //eeprom interface output scl , //eeprom的时钟线scl inout sda , //eeprom的数据线sda //user interface output led //led显示 ); //parameter define parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR) parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b) parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ) parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率 parameter L_TIME = 17'd125_000 ; //led闪烁时间参数 //wire define wire dri_clk ; //I2C操作时钟 wire en ; //I2C触发控制 wire [15:0] device_addr ; //I2C操作地址 wire [ 7:0] data_w; //I2C写入的数据 wire [ 7:0] data_r; //I2C读出的数据 wire done ; //I2C操作结束标志 wire ack ; //I2C应答标志 0:应答 1:未应答 wire wl_rh ; //I2C读写控制 wire rw_done ; //E2PROM读写测试完成 wire suce ; //E2PROM读写测试结果 0:失败 1:成功 //***************************************************** //** main code //***************************************************** //e2prom读写测试模块 eeprom_driver u_e2prom_rw( .clk (dri_clk ), //时钟信号 .rstn (rstn), //复位信号 //i2c interface .en (en ), //I2C触发执行信号 .wl_rh (wl_rh ), //I2C读写控制信号 .device_addr (device_addr ), //I2C器件内地址 .data_w (data_w), //I2C要写的数据 .data_r (data_r), //I2C读出的数据 .done (done ), //I2C一次操作完成 .ack (ack ), //I2C应答标志 //user interface .rw_done (rw_done ), //E2PROM读写测试完成 .suce (suce ) //E2PROM读写测试结果 0:失败 1:成功 ); //i2c驱动模块 i2c_driver #( .slav_addr (SLAVE_ADDR), //EEPROM从机地址 .clk_freq (CLK_FREQ ), //模块输入的时钟频率 .i2c_freq (I2C_FREQ ) //IIC_SCL的时钟频率 ) u_i2c_dri( .clk (clk ), .rstn (rstn ), //i2c interface .en (en ), //I2C触发执行信号 .bit_ctrl (BIT_CTRL ), //器件地址位控制(16b/8b) .wl_rh (wl_rh ), //I2C读写控制信号 .device_addr (device_addr ), //I2C器件内地址 .data_w (data_w), //I2C要写的数据 .data_r (data_r), //I2C读出的数据 .done (done ), //I2C一次操作完成 .ack (ack ), //I2C应答标志 .scl (scl ), //I2C的SCL时钟信号 .sda (sda ), //I2C的SDA信号 .dri_clk (dri_clk ) //I2C操作时钟 ); //led指示模块 led_light u_led_alarm( .clk (dri_clk ), .rstn (rstn), .rw_done (rw_done ), .suce (suce ), .led (led ) ); endmodule
当然,也可以在顶层添加ila查看分析结果,不过个人试过后发现效果不太好,应该是深度不够导致后面的数据还没有出现
协议基本知识:
https://www.cnblogs.com/yjw951012/p/11594694.html
verilog代码实现:
https://www.cnblogs.com/liujinggang/p/9656358.html
eeprom实验:
https://blog.csdn.net/mikusic/article/details/114936770
相关硬件介绍和eeprom实验:
https://www.amobbs.com/thread-5758040-1-5.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。