IIC是一种多向控制总线,由飞利浦半导体公司在八十度年代初设计,主要是用来连接整体电路(ICS)。在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 <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge RST_N) begin
if(!RST_N)
cur_state <= st_idle;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件
always @( * ) begin
case(cur_state)
st_idle: begin // 空闲状态
if(iic_exec_r)
begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done)
begin
if(bit_ctrl) // 判断是16位还是8位字地址
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin // 写16位字地址
if(st_done)
begin
next_state = st_addr8;
end
else
begin
next_state = st_addr16;
end
end
st_addr8: begin // 8位字地址
if(st_done)
begin
if(wr_flag==1'b0) // 读写判断
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else
begin
next_state = st_addr8;
end
end
st_data_wr: begin // 写数据(8 bit)
if(st_done)
begin
next_state = st_stop;
end
else
next_state = st_data_wr;
end
st_addr_rd: begin // 写地址以进行读数据
if(st_done)
begin
next_state = st_data_rd;
end
else
next_state = st_addr_rd;
end
st_data_rd: begin // 读取数据(8 bit)
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin // 结束IIC操作
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default:
next_state = st_idle;
endcase
end
//时序电路描述状态输出
always @(posedge dri_clk or negedge RST_N) begin
//复位初始化
if(!RST_N) begin
SCL <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
iic_done <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
iic_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
ack <= 1'b1;
wdata_cnt <= 1'b0;
rdata_cnt <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin // 空闲状态
SCL <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
iic_done<= 1'b0;
cnt <= 7'b0;
if(iic_exec_r) begin
wr_flag <= iic_rh_wl ;
addr_t <= iic_addr ;
end
end
st_sladdr: begin // 写地址(器件地址和字地址)
case(cnt)
7'd1 : sda_out <= 1'b0; // 开始IIC 起始位
7'd3 : SCL <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 传送器件地址 SCL低电平中间改变数据
7'd5 : SCL <= 1'b1;
7'd7 : SCL <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : SCL <= 1'b1;
7'd11: SCL <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: SCL <= 1'b1;
7'd15: SCL <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: SCL <= 1'b1;
7'd19: SCL <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: SCL <= 1'b1;
7'd23: SCL <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: SCL <= 1'b1;
7'd27: SCL <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: SCL <= 1'b1;
7'd31: SCL <= 1'b0;
7'd32: sda_out <= 1'b0; // 0:写
7'd33: SCL <= 1'b1;
7'd35: SCL <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; ack <= sda_in; // 从机应答
end
7'd37: SCL <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
SCL <= 1'b0; cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1; sda_out <= addr_t[15]; // 传送字地址
end
7'd1 : SCL <= 1'b1;
7'd3 : SCL <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : SCL <= 1'b1;
7'd7 : SCL <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : SCL <= 1'b1;
7'd11: SCL <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: SCL <= 1'b1;
7'd15: SCL <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: SCL <= 1'b1;
7'd19: SCL <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: SCL <= 1'b1;
7'd23: SCL <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: SCL <= 1'b1;
7'd27: SCL <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: SCL <= 1'b1;
7'd31: SCL <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; ack <= sda_in ; // 从机应答
end
7'd33: SCL <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
SCL <= 1'b0; cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1; sda_out <= addr_t[7]; // 字地址
end
7'd1 : SCL <= 1'b1;
7'd3 : SCL <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : SCL <= 1'b1;
7'd7 : SCL <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : SCL <= 1'b1;
7'd11: SCL <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: SCL <= 1'b1;
7'd15: SCL <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: SCL <= 1'b1;
7'd19: SCL <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: SCL <= 1'b1;
7'd23: SCL <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: SCL <= 1'b1;
7'd27: SCL <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: SCL <= 1'b1;
7'd31: SCL <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; ack <= sda_in ; // 从机应答
end
7'd33: SCL <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
SCL <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin // 写数据(8 bit)
case(cnt)
7'd0 :begin
sda_dir <= 1'b1; sda_out <= iic_data_w[7];
end
7'd1 : SCL <= 1'b1;
7'd3 : SCL <= 1'b0;
7'd4 : sda_out <= iic_data_w[6];
7'd5 : SCL <= 1'b1;
7'd7 : SCL <= 1'b0;
7'd8 : sda_out <= iic_data_w[5];
7'd9 : SCL <= 1'b1;
7'd11: SCL <= 1'b0;
7'd12: sda_out <= iic_data_w[4];
7'd13: SCL <= 1'b1;
7'd15: SCL <= 1'b0;
7'd16: sda_out <= iic_data_w[3];
7'd17: SCL <= 1'b1;
7'd19: SCL <= 1'b0;
7'd20: sda_out <= iic_data_w[2];
7'd21: SCL <= 1'b1;
7'd23: SCL <= 1'b0;
7'd24: sda_out <= iic_data_w[1];
7'd25: SCL <= 1'b1;
7'd27: SCL <= 1'b0;
7'd28: sda_out <= iic_data_w[0];
7'd29: SCL <= 1'b1;
7'd31: SCL <= 1'b0;
7'd32: begin
sda_dir <= 1'b0; ack <= sda_in ; // 从机应答
wdata_cnt <= wdata_cnt + 1;
end
7'd33: SCL <= 1'b1;
7'd34: begin
if(wdata_cnt == wdata_num)begin
st_done <= 1'b1; wdata_cnt <= 0;
end
end
7'd35: begin
SCL <= 1'b0; cnt <= 1'b0;
end
default:;
endcase
end
st_addr_rd: begin // 写地址以进行读数据
case(cnt)
7'd0 : begin
sda_dir <= 1'b1; sda_out <= 1'b1;
end
7'd1 : SCL <= 1'b1;
7'd2 : sda_out <= 1'b0; // 重新开始
7'd3 : SCL <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6]; // 传送器件地址
7'd5 : SCL <= 1'b1;
7'd7 : SCL <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : SCL <= 1'b1;
7'd11: SCL <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: SCL <= 1'b1;
7'd15: SCL <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: SCL <= 1'b1;
7'd19: SCL <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: SCL <= 1'b1;
7'd23: SCL <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: SCL <= 1'b1;
7'd27: SCL <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: SCL <= 1'b1;
7'd31: SCL <= 1'b0;
7'd32: sda_out <= 1'b1; // 1:读
7'd33: SCL <= 1'b1;
7'd35: SCL <= 1'b0;
7'd36: begin
sda_dir <= 1'b0; ack <= sda_in ; // 从机应答
end
7'd37: SCL <= 1'b1;
7'd38: st_done <= 1'b1;
7'd39: begin
SCL <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin // 读取数据(8 bit)
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
SCL <= 1'b1; data_r[7] <= sda_in;
end
7'd3: SCL <= 1'b0;
7'd5: begin
SCL <= 1'b1; data_r[6] <= sda_in ;
end
7'd7: SCL <= 1'b0;
7'd9: begin
SCL <= 1'b1; data_r[5] <= sda_in;
end
7'd11: SCL <= 1'b0;
7'd13: begin
SCL <= 1'b1; data_r[4] <= sda_in;
end
7'd15:SCL <= 1'b0;
7'd17: begin
SCL <= 1'b1; data_r[3] <= sda_in;
end
7'd19: SCL <= 1'b0;
7'd21: begin
SCL <= 1'b1; data_r[2] <= sda_in;
end
7'd23: SCL <= 1'b0;
7'd25: begin
SCL <= 1'b1; data_r[1] <= sda_in;
end
7'd27: SCL <= 1'b0;
7'd29: begin
SCL <= 1'b1; data_r[0] <= sda_in;
end
7'd31: begin
SCL <= 1'b0; rdata_cnt <= rdata_cnt + 1; iic_data_r <= data_r;
end
7'd32: begin
if(rdata_cnt < rdata_num)begin
sda_dir <= 1'b1; sda_out <= 1'b0; //应答
end
else begin
sda_dir <= 1'b1; sda_out <= 1'b1; //非应答
end
end
7'd33: SCL <= 1'b1;
7'd35: begin
SCL <= 1'b0;
if(rdata_cnt < rdata_num)
cnt <= 1'b0;
end
7'd36: st_done <= 1'b1;
7'd37: begin
SCL <= 1'b1; cnt <= 1'b0;
if(rdata_cnt == rdata_num) rdata_cnt <= 0;
end
default : ;
endcase
end
st_stop: begin // 结束IIC操作
case(cnt)
7'd0: begin
sda_dir <= 1'b1; // 结束IIC
sda_out <= 1'b0;
end
7'd1 : SCL <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
iic_done <= 1'b1; // 向上层模块传递IIC结束信号
end
default : ;
endcase
end
endcase
end
end
always @(posedge dri_clk or negedge RST_N)
begin
if(!RST_N)
wr_data_vaild_r[0] <= 0;
else if(((cur_state == st_data_wr)&&(cnt == 29))||((cur_state == st_addr8)&&(cnt == 29)&&(wr_flag==1'b0)))
wr_data_vaild_r[0] <= 1;
else
wr_data_vaild_r[0] <= 0;
end
always @(posedge dri_clk or negedge RST_N)
begin
if(!RST_N)
rd_data_vaild_r[0] <= 0;
else if((cur_state == st_data_rd)&&(cnt == 31))
rd_data_vaild_r[0] <= 1;
else
rd_data_vaild_r[0] <= 0;
end
always @(posedge CLK or negedge RST_N)
begin
if(!RST_N)begin
iic_done_r[0] <= 0;
wr_data_vaild_r[1] <= 0;
rd_data_vaild_r[1] <= 0;
end
else begin
iic_done_r[0] <= iic_done;
wr_data_vaild_r[1] <= wr_data_vaild_r[0];
rd_data_vaild_r[1] <= rd_data_vaild_r[0];
end
end
assign wr_data_vaild = ({wr_data_vaild_r[1],wr_data_vaild_r[0]}==2'b01);
assign rd_data_vaild = ({rd_data_vaild_r[1],rd_data_vaild_r[0]}==2'b01);
assign done = ({iic_done_r[0],iic_done}== 2'b01);
endmodule