基于I2C协议的EEPROM 驱动控制
yummy
阅读:758
2022-04-07 14:56:47
评论:0
I2C通讯协议
I2C通讯协议(Inter-Integrated Circuit)是由Philips公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。
I2C通讯协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行AD,图像处理领域的摄像头配置,工业控制领域的X射线管配置等等。除之之外,由于I2C占用引脚特别少,硬件实现简单,可扩展性强,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
I2C物理层
I2C_SCL:串行时钟线,用于同步通讯数据。
I2C_SDA:双向串行数据线,传输通讯数据
I2C协议层
①总线空闲状态 ②起始信号 ③数据读写状态 ④停止信号
器件地址
I2C读写操作--单字节数据写操作
页写操作--单字节和2字节
数据读操作--随机读操作和顺序读操作
顺序读操作
EEPROM 24C64 -- 器件地址1010011
子功能模块
顶层
系统框图
状态转移图
I2C控制模块
写波形图
读波形
代码部分
module i2c_ctrl
#(parameter SYS_CLK_FREQ = 'd50_000_000 , //系统时钟参数
SCL_FREQ = 'd250_000 , //串行时钟参数
DEVICE_ADDER = 7'd1010_011 //器件地址
)
(
input wire sys_clk , //系统时钟,50MHZ
input wire sys_rst_n , //系统复位信号,低电平有效
input wire i2c_start , //I2C开始信号
input wire wr_en , //写数据使能
input wire rd_en , //读数据使能
input wire [15:0] byte_addr , //字节地址
input wire [7:0] wr_data , //写数据
input wire add_num , //地址字节控制,低电平表示存储单字节地址
output reg [7:0] rd_data , //读数据
output reg i2c_clk , //I2C时钟,1MHZ,I2C_ctrl模块时钟频率是I2C_scl的4倍,作为读写模块的时钟
output reg i2c_scl , //串行时钟信号
output reg i2c_sda , //串行数据信号
output reg i2c_end //I2C结束信号
);
reg [7:0] cnt_clk ; //时钟分频计数器
reg [2:0] i2c_state ; //I2C状态
reg [1:0] cnt_i2c_clk ; //I2C时钟计数器
reg cnt_i2c_clk_en ; //I2C时钟计数器使能信号
reg [3:0] cnt_bit ; //sda bit计数器
reg sda_out ; //输出数据寄存
wire sda_en ; //sda使能信号
reg ack ; //应答信号
wire sda_in ; //输入数据寄存
reg [7:0] rd_data_reg ; //读数据寄存器
parameter IDLE = 4'b0000 , //空闲状态
START = 4'b0001 , //开始状态
SEND_D_A = 4'b0010 , //发送读控制指令
ACK_1 = 4'b0011 , //响应1
SEND_B_H_A = 4'b0100 , //发送地址高八位
ACK_2 = 4'b0101 , //响应2
SEND_B_L_A = 4'b0110 , //发送地址第八位
ACK_3 = 4'b0111 , //响应3
WR_DATA = 4'b1000 , //写数据状态
ACK_4 = 4'b1001 , //响应4
START_2 = 4'b1010 , //开始2状态
SEND_RD_A = 4'b1011 , //发送数据读取控制字
ACK_5 = 4'b1100 , //响应5
RD_DATA = 4'b1101 , //读数据状态
N_ACK = 4'b1110 , //无响应状态
STOP = 4'b1111 ; //停止状态
parameter CNT_CLK_MAX = (SYS_CLK_FREQ / SCL_FREQ) >>3 ; //计数最大值
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
cnt_clk <= 8'd0 ; //如果复位信号有效,时钟分频计数器清零
end
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
begin
cnt_clk <= 8'd0 ; //如果时钟分频计数器计数到最大值,清零
end
else
begin
cnt_clk <= cnt_clk + 1'b1 ; //其他情况下,计数继续
end
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
i2c_clk <= 1'b0 ; //如果复位信号有效,I2C时钟拉低
end
else if(cnt_clk == CNT_CLK_MAX - 1'b1)
begin
i2c_clk <= ~i2c_clk ; //如果时钟分频计数器计数到最大值,I2C时钟信号取反
end
else
begin
i2c_clk <= i2c_clk ; //否则保持不变
end
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
i2c_state <= IDLE ; //如果复位信号有效,I2C状态为空闲状态
end
else
begin
case(i2c_state)
IDLE :
if(i2c_start == 1'b1)
begin
i2c_state <= START ; //如果I2C开始信号拉高,状态切换到开始状态
end
else
begin
i2c_state <= i2c_state ; //否则保持当前状态
end
START :
if(cnt_i2c_clk == 2'd3)
begin
i2c_state <= SEND_D_A ; //如果I2C时钟计数器计到3,状态切换到发送读控制指令
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
SEND_D_A :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= ACK_1 ; //如果I2C时钟计数器计到3并且bit计数器计到7,状态切换到响应1状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
ACK_1 :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0)) //如果I2C时钟计数器计到3,并且响应信号拉低
begin
if(add_num == 1'b1)
begin
i2c_state <= SEND_B_H_A ; //如果地址字节为高,状态跳转到发送高八位状态
end
else
begin
i2c_state <= SEND_B_L_A ; //否则跳转到发送第八位地址状态
end
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
SEND_B_H_A :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= ACK_2 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应2状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
ACK_2 :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
begin
i2c_state <= SEND_B_L_A ; //如果I2C时钟计数器计到3,响应信号拉低,状态跳转到发送低字节状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
SEND_B_L_A :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= ACK_3 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应3状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
ACK_3 :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0)) //如果I2C时钟计数器计到3,响应信号拉低
begin
if(wr_en == 1'b1)
begin
i2c_state <= WR_DATA ; //如果写使能有效,状态跳转到写状态
end
else if(rd_en == 1'b1)
begin
i2c_state <= START_2 ; //如果读使能有效,状态跳转到开始2状态
end
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
WR_DATA :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= ACK_4 ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应4状态
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
ACK_4 :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
begin
i2c_state <= STOP ;//如果I2C时钟计数器计到3,响应信号拉低,状态跳转到停止状态
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
START_2 :
if(cnt_i2c_clk == 2'd3)
begin
i2c_state <= SEND_RD_A ;//如果I2C时钟计数器计到3,状态跳转到发送读数据状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
SEND_RD_A :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= ACK_5 ;//如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到响应5状态
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
ACK_5 :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
begin
i2c_state <= RD_DATA ; //如果I2C时钟计数器计到3,响应信号拉低,状态跳转到读数据状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
RD_DATA :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
i2c_state <= N_ACK ; //如果I2C时钟计数器计到3,并且bit计数器计到7,状态跳转到无响应状态
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
N_ACK :
if((cnt_i2c_clk == 2'd3)&&(ack == 1'b0))
begin
i2c_state <= STOP ;//如果I2C时钟计数器计到3,响应信号拉低,状态跳转到停止状态
end
else
begin
i2c_state <= i2c_state ;// 否则保持当前状态
end
STOP :
if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd3))
begin
i2c_state <= IDLE ;//如果I2C时钟计数器计到3,并且bit计数器计到3,状态跳转到空闲状态
end
else
begin
i2c_state <= i2c_state ; // 否则保持当前状态
end
default : i2c_state <= IDLE ; //其他情况下,状态保持空闲状态
endcase
end
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
cnt_i2c_clk <= 2'd0 ; //如果复位信号有效,I2C时钟计数器清零
end
else if(cnt_i2c_clk_en == 1'b1)
begin
cnt_i2c_clk <= cnt_i2c_clk + 1'b1 ; //如果I2C时钟计数器使能,I2C时钟计数器继续计数
end
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
cnt_i2c_clk_en <= 1'b0 ; //如果复位信号有效,I2C时钟计数器使能信号拉低
end
else if(i2c_start ==1'b1)
begin
cnt_i2c_clk_en <= 1'b1 ; //如果I2C开始信号拉高,I2C时钟计数器使能信号拉高
end
else if((cnt_i2c_clk == 2'd3)&&(i2c_state == STOP)&&(cnt_bit == 3'd3))
begin
cnt_i2c_clk_en <= 1'b0 ; //如果I2C时钟计数器计到3,并且I2C保持停止状态,bit计数器计到3,I2C时钟计数器使能信号拉低
end
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
cnt_bit <= 3'd0 ; //如果复位信号有效,bit计数器清零
end
else if((i2c_state == IDLE)||(i2c_state == START)||(i2c_state == ACK_1)||(i2c_state == ACK_2)||(i2c_state == ACK_3)||(i2c_state == ACK_4)||(i2c_state == ACK_5)||(i2c_state == N_ACK)||(i2c_state ==START_2))
begin
cnt_bit <= 3'd0 ; //如果状态为空闲状态,或者开始状态,或者响应1状态、或者响应2状态、或者响应3状态、或者响应4状态、或者响应5状态、或者无响应状态、或者开始2状态
end
else if((cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
cnt_bit <= 1'b0 ; //如果I2C计数器计数到3,并且bit计数计到7,bit计数器清零
end
else if((cnt_i2c_clk == 2'd3)||(i2c_state != IDLE ))
begin
cnt_bit <= cnt_bit + 1'b1 ; //如果I2C时钟计数器计到3,或者当前状态不是空闲状态,bit计数器继续计数
end
end
always @(*) begin
case (i2c_state)
IDLE :
sda_out <= 1'b1 ; //空闲状态,输出数据寄存器拉高
START :
if(cnt_i2c_clk == 2'd0)
begin
sda_out <= 1'b1 ; //I2C时钟计数器计到0,输出数据寄存器拉高
end
else
begin
sda_out <= 1'b0 ;//否则输出数据寄存器拉低
end
SEND_D_A:
if(cnt_bit <= 3'd6)
begin
sda_out <= DEVICE_ADDER[6 - cnt_bit] ; //如果bit计数到6,把器件地址寄存到输出数据寄存器中
end
else
begin
sda_out <= 1'b0 ; //否则输出数据寄存器拉低
end
ACK_1,ACK_2,ACK_3,ACK_4,ACK_5,N_ACK,RD_DATA :
sda_out <= 1'b1 ; //当前状态为响应1,响应2,响应3,响应4,响应5,无响应,读数据状态,输出数据寄存器拉高
SEND_B_H_A :
sda_out <= byte_addr[15-cnt_bit] ; //当前状态为发送高字节地址状态,把地址的高八位寄存到输出数据寄存器中
SEND_B_L_A :
sda_out <= byte_addr[7-cnt_bit] ; //当前状态为发送低字节地址状态,把地址的低八位寄存到输出数据寄存器中
WR_DATA :
sda_out <= wr_data[7-cnt_bit] ; // 当前状态为读数据状态,把写数据地址低八位寄存到输出数据寄存器中
START_2 :
if(cnt_i2c_clk <= 2'd1)
begin
sda_out <= 1'b1 ; //I2C时钟计数器计到1,输出数据寄存器拉高
end
else
begin
sda_out <= 1'b0 ; //否则输出数据寄存器拉低
end
SEND_RD_A :
if(cnt_bit <= 3'd6)
begin
sda_out <= DEVICE_ADDER[6 - cnt_bit] ; //bit计数器计到6,把器件地址寄存到输出数据寄存器中
end
else
begin
sda_out <= 1'b1 ; //否则输出数据寄存器拉高
end
RD_DATA :
sda_out <= 1'b1; //输出数据寄存器拉高
STOP :
if((cnt_i2c_clk <= 2'd3)&&(cnt_bit == 3'd0))
begin
sda_out <= 1'b0 ; //I2C时钟计数器计数到3,bit计数器计到0
end
else
begin
sda_out <= 1'b1 ; //输出数据寄存器拉高
end
default: sda_out <= 1'b1 ;//输出数据寄存器拉高
endcase
end
assign sda_en = ((i2c_state==RD_DATA)||(i2c_state==ACK_1)||(i2c_state== ACK_2)||(i2c_state==ACK_3)||(i2c_state==ACK_4)||(i2c_state==ACK_5)||(i2c_state==N_ACK))?1'b0:1'b1;
//当前状态为读数据状态时,或者响应1状态、或者响应2状态、或者响应3状态、或者响应4状态、或者响应5状态、或者无响应状态、或者开始2状态,数据输出使能拉高,或者拉低
always @(*) begin
case(i2c_state)
ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
begin
if(cnt_i2c_clk == 2'd0)
begin
ack <= sda_in ; //在响应1,2,3,4,5,状态下,I2C时钟计数器计到0,把数据输入寄存器赋值给响应信号
end
else
begin
ack <= ack ; // 否则保持不变
end
end
default : ack <= 1'b1 ; //其他情况下,响应信号拉高
endcase
end
assign sda_in = i2c_sda ; //I2C串行数据赋值给数据输入寄存器
always @(*) begin
case (i2c_state)
IDLE :
i2c_scl <= 1'b1 ; //空闲状态下,I2C串行时钟信号拉高
START:
if(cnt_i2c_clk == 2'd3)
begin
i2c_scl <= 1'b0 ; //开始状态下,I2C时钟计数器计到3,I2C串行时钟信号拉低
end
else
begin
i2c_scl <= 1'b1 ; //否则I2C串行时钟信号拉高
end
SEND_D_A ,ACK_1 ,SEND_B_H_A, ACK_2 ,SEND_B_L_A ,ACK_3 ,WR_DATA ,ACK_4 ,START_2 ,SEND_RD_A ,ACK_5 ,RD_DATA ,N_ACK ://在发送读控制指令、响应1,2,3,4,5,发送高字节地址、发送低字节地址、写数据、开始2、发送读控制指令、读数据、无响应状态下。
if((cnt_i2c_clk == 2'd1)||(cnt_i2c_clk == 2'd2))
begin
i2c_scl <= 1'b1 ; //如果I2C时钟计数器计到1或者计到2,I2C串行时钟信号拉高
end
else
begin
i2c_scl <= 1'b0 ;//否则I2C串行时钟信号拉低
end
STOP :
if((cnt_i2c_clk ==2'd0)&&(cnt_bit==3'd0))
begin
i2c_scl <= 1'b0 ;//如果I2C时钟计数器计到0,并且bit计数器计到0,I2C串行时钟信号拉低
end
else
begin
i2c_scl <= 1'b1 ; //否则I2C串行时钟信号拉高
end
default : i2c_scl <= 1'b1 ;//其他状态下,I2C串行时钟信号拉高
endcase
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
i2c_sda <= 1'b1 ; // 1'如果复位信号有效,串行数据信号拉高
end
else begin
case (i2c_state)
IDLE,START,SEND_D_A,SEND_B_H_A,SEND_B_L_A,WR_DATA,START_2,SEND_RD_A,STOP: //空闲状态、开始状态、发送读控制指令、发送高字节地址、发送低字节地址、写数据状态、开始2状态、发送写控制指令、停止状态下
i2c_sda <= sda_out ; //数据输出寄存器赋值给串行数据信号
ACK_1,ACK_2 ,ACK_3 ,ACK_4 ,ACK_5 ,N_ACK :
i2c_sda <= 1'b0 ; //响应1、2、3、4、5,无响应状态下,串行数据信号拉低
RD_DATA :
i2c_sda <= rd_data[7-cnt_bit] ; //读数据状态下,读数据赋值给串行数据信号
default : i2c_sda <= 1'b1 ; //其他状态下串行数据信号拉高
endcase
end
end
always @(*) begin
case(i2c_state)
IDLE : rd_data_reg <= 8'd0 ; //空闲状态下,读数据寄存器清零
RD_DATA : rd_data_reg[7-cnt_bit] <= sda_in ; //读数据状态下,数据输入赋值给读数据寄存器
default : rd_data_reg <= rd_data_reg; //其他情况下,读数据寄存器保持不变
endcase
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if (~sys_rst_n) begin
rd_data <= 8'd0 ; //如果复位信号有效,读数据清零
end
else if((i2c_state == RD_DATA)&&(cnt_i2c_clk == 2'd3)&&(cnt_bit == 3'd7))
begin
rd_data <= rd_data_reg ; //如果I2C时钟计数器计到3,并且bit计数器计到7,当前状态为读数据系统下,读数据寄存器的数据赋值给读数据
end
end
always @(posedge i2c_clk or negedge sys_rst_n) begin
if(~sys_rst_n)
begin
i2c_end <= 1'b0 ; //如果复位信号有效,I2C结束信号拉低
end
else if((i2c_state == STOP)&&(cnt_bit == 3'd3)&&(cnt_i2c_clk == 2'd3))
begin
i2c_end <= 1'b1 ; //如果当前状态为停止状态,bit计数器计到3,并且I2C时钟计数器计到3,I2C结束信号拉高
end
else
begin
i2c_end <= i2c_end ; // 否则保持不变
end
end
endmodule
I2C读写数据模块
读写按键的波形图
写操作波形图
数据读操作波形
数据发送模块波形
本文 zblog模板 原创,转载保留链接!网址:http://www.xn--zqqs03dbu6a.cn/?id=26
声明
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。