Inter-IntegratedCircuit(集成电路总线)
IIC是一种多向控制总线,由飞利浦半导体公司在八十度年代初设计,主要是用来连接整体电路(ICS)。在IIC中多个芯片可以连接到同一总线结构下,同时每个芯片都可以作为实施数据传输的控制源,这种方式简化了信号传容输总线。
-
IIC的物理层
典型电路如下:
-
IIC协议层
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答。 -
数据帧格式
每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
-
传输信号时序图及时序参数
-
AT24CXX系列的介绍
(1)写操作
AT24CXX系列的EEPROM为了提高写效率,提供了页写功能,内部有个一页大小的写缓冲RAM,地址范围当然就是从00到一页大小,发生写操作时,开始送入的地址对应的页被选中,并将其内容映像到缓冲RAM,数据从低端地址对应的缓冲RAM地址开始修改,超过这个地址范围就回到00,写完后,就会把开始确定的EEPROM页擦除,再把一整页RAM数据写入。所有写数据都发生在开始写地址时确定的页上。
24C01、24C02 这两个型号是 8 个字节一个页,而 24C04、24C08、24C16 是 16 个字节一页。以页容量128为例,一页都是从00开始按128字节分成一个个的页,0页就是0—7F,1页就是80—FF,类推,边界就是128字节的整数倍地址。页RAM的地址范围为7位00—7F,写入时高端地址就是页号。发生写操作,开始送入的地址对应的页被锁存,后续不论写多少,都在这个页中,只是一个页内的地址进行加一,超过就归零开始。从F0开始写32个字节,那么开始送入的地址为F0,就会锁定在1号页(第2个页)上,底端7位页内部地址开始从70H开始写,到达7F时回到00再到10H,也就是写在了F0—FF,80—8F。也就是,从01开始写也只能到7F,再往80写就跑到00上去了,这就是写操作的翻卷,datasheet上都有说明。就是从边界前写两个字节也要分两次写。页是绝对的,按整页大小排列,不是从开始写入的地址开始算。
(2)读操作
读没有页的问题,可以从任意地址开始读取任意大小数据,只是超过整个存储器容量时地址才回卷。但一次性访问的数据长度也不要太大。 -
Verliog代码设计
在正点原子的三段式IIC驱动代码的基础上进行修改,实现对AT24C64的多字节读写操作。这里需要格外注意的有两点:
(1)写操作完成最后一个字节数据后是从机产生应答;读操作完成一个字节数据后是主机产生应答。
(2)分页的存储器要做好存储器管理,尽量将同时读写的数据放在一个页上。
代码如下:`timescale 1ns/1ps module iic_driver #(// slave address(器件地址),放此处方便参数传递 parameter SLAVE_ADDR = 7'b1010000 , parameter CLK_FREQ = 26'd50_000_000, // iic_dri模块的驱动时钟频率(CLK_FREQ) parameter IIC_FREQ = 18'd250_000 // IIC的SCL时钟频率 ) ( input CLK, // iic_dri模块的驱动时钟(CLK_FREQ) input RST_N, // 复位信号 input iic_exec, // IIC触发执行信号 input bit_ctrl, // 字地址位控制(16b/8b) input iic_rh_wl, // IIC读写控制信号(1:read 0:write) input [15:0] iic_addr, // IIC器件内地址 input [ 7:0] iic_data_w, // IIC要写的数据 output reg [ 7:0] iic_data_r, // IIC读出的数据 output wire done, // IIC一次操作完成 output reg SCL, // IIC的SCL时钟信号 inout SDA, // IIC的SDA信号 input [ 7:0] wdata_num, // IIC要写数据的个数 input [ 7:0] rdata_num, // IIC要读数据的个数 output wire wr_data_vaild, // IIC写入数据有效 output wire rd_data_vaild, // IIC读出数据有效 output reg dri_clk // 驱动IIC操作的驱动时钟 ); //localparam define localparam st_idle = 8'b0000_0001; // 空闲状态 1 localparam st_sladdr = 8'b0000_0010; // 发送器件地址(slave address) 2 localparam st_addr16 = 8'b0000_0100; // 发送16位字地址 4 localparam st_addr8 = 8'b0000_1000; // 发送8位字地址 8 localparam st_data_wr = 8'b0001_0000; // 写数据(8 bit) 16 localparam st_addr_rd = 8'b0010_0000; // 发送器件地址读 32 localparam st_data_rd = 8'b0100_0000; // 读数据(8 bit) 64 localparam st_stop = 8'b1000_0000; // 结束IIC操作 128 //reg define reg sda_dir; // I2C数据(SDA)方向控制 reg sda_out; // SDA输出信号 reg st_done; // 状态结束 reg iic_exec_r; // 起始信号寄存器 reg wr_flag; // 写标志 reg [6:0] cnt; // 计数 reg [7:0] cur_state; // 状态机当前状态 reg [7:0] next_state; // 状态机下一状态 reg [15:0] addr_t; // 地址 reg [7:0] data_r; // 读取的数据 reg [9:0] clk_cnt; // 分频时钟计数 reg ack; // SDA应答 reg [7:0] wdata_cnt; // 写数据计数器 reg [7:0] rdata_cnt; // 读数据计数器 reg [1:0] wr_data_vaild_r; // IIC写入数据有效 reg [1:0] rd_data_vaild_r; // IIC写入数据有效 reg iic_done; reg [1:0] iic_done_r; //wire define wire sda_in; // SDA输入信号 wire [7:0] data_wr_t; // IIC需写的数据保存 wire [8:0] clk_divide; // 模块驱动时钟的分频系数 //SDA控制 assign SDA = sda_dir ? (sda_out ? 1'bz :1'b0 ) : 1'bz; assign sda_in = SDA ; // SDA数据输入 assign clk_divide = (CLK_FREQ/IIC_FREQ) >> 3; // 模块驱动时钟的分频系数 //寄存1个时钟周期的起始信号 always @(posedge CLK or negedge RST_N) begin if(!RST_N) iic_exec_r <= 1'b0; else if(iic_exec) iic_exec_r <= 1'b1; else if(iic_done) iic_exec_r <= 1'b0; else iic_exec_r <= iic_exec_r; end //生成IIC的SCL的四倍频率(1M)的驱动时钟用于驱动i2c的操作 always @(posedge CLK or negedge RST_N) begin if(!RST_N) begin dri_clk <= 1'b1; clk_cnt <= 10'd0; end else if(clk_cnt == clk_divide - 1'd1) // F=50M/(25*2)=1M begin clk_cnt <