1.数据总线和时钟线在空闲时都必须拉高
2.开始条件:时钟总线高电平时,拉低数据总线
3.数据允许变化:在时钟总线低电平时
4.数据保持稳定,采样数据:在时钟总线高电平时
5.停止条件:时钟总线高电平时,拉高数据总线
6.ACK:应答是低电平有效,应答由接收方传出
该eeprom的设备地址:1010
每一bit都需要一个sclk周期完成
控制字节
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fc0A9fnC-1642921692513)(C:\Users\DELL\Desktop\Revision\Protocol_Verilog\i2c_eeprom\doc\部分图\image-20220117215006419.png)]
module eeprom_interface (
input clk ,
input rst_n ,
//ctrl模块传入
input [7:0] data_in ,//写入eeprom的八位数据
input req ,//请求一次读/写
input [3:0] command ,//输入的命令:起/读/写/止
//传入ctrl模块
output reg slack ,//从机响应
output done ,//一个字节读/写完成
output [7:0] data_out ,//从EEPROM读取的数据
//与eeprom连接
output reg sclk ,//从机时钟
inout sda //从机数据线
);
/* 参数定义 */
localparam IDLE = 8'b00000_001, //状态参数定义
START = 8'b00000_010,
READ = 8'b00000_100,
WRITE = 8'b00001_000,
ACK = 8'b00010_000,
SACK = 8'b00100_000,
STOP = 8'b01000_000,
DONE = 8'b1000_0000;
localparam CMD_START = 4'b1000,//起/读/写/止
CMD_READ = 4'b0100,
CMD_WRITE = 4'b0010,
CMD_STOP = 4'b0001;
localparam T_CLK = 200_000 ,//从机频率200KHZ
T_CYCLE = 250 ,//从机一个周期的时间
T_CYCLE_HALF = 124 ,
T_LOW_HALF = 65 ,
T_HIGH_HALF = 189 ;
/* 信号定义 */
reg sda_out ;
wire sda_in ;
reg sda_out_en;
reg [7:0] data_out_r;
reg [7:0] state_c;//状态
reg [7:0] state_n;
wire idle_start ;
wire idle_read ;
wire idle_write ;
wire start_read ;
wire start_write;
wire write_sack ;
wire read_ack ;
wire ack_stop ;
wire ack_done ;
wire sack_stop ;
wire sack_done ;
wire stop_done ;
wire done_idle ;
reg [7:0] clk_cnt ;//时钟计数器
wire add_clk_cnt;
wire end_clk_cnt;
reg [3:0] bit_cnt ;//bit计数器
wire add_bit_cnt;
wire end_bit_cnt;
//状态转换
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态转换条件
assign idle_start = state_c == IDLE && req && (command & CMD_START);
assign idle_read = state_c == IDLE && req && (command & CMD_READ);
assign idle_write = state_c == IDLE && req && (command & CMD_WRITE);
assign start_read = state_c == START && (command & CMD_READ) && end_bit_cnt;
assign start_write= state_c == START && (command & CMD_WRITE)&& end_bit_cnt;
assign write_sack = state_c == WRITE && end_bit_cnt;
assign read_ack = state_c == READ && end_bit_cnt;
assign ack_stop = state_c == ACK && (command & CMD_STOP) && end_bit_cnt;
assign ack_done = state_c == ACK && !((command & CMD_STOP))&& end_bit_cnt;//注意位取反(每一位都取反)和逻辑取反(0/1)的区别
assign sack_stop = state_c == SACK && (command & CMD_STOP || ~slack) && end_bit_cnt;
assign sack_done = state_c == SACK && (!(command & CMD_STOP))&& end_bit_cnt && slack ;
assign stop_done = state_c == STOP && end_bit_cnt;
assign done_idle = state_c == DONE && 1'b1;
//状态转移规律
always @(*) begin
case(state_c)
IDLE :
if (idle_start) begin//优先判断起始,否则会与读写冲突
state_n = START;
end
else if (idle_read) begin
state_n = READ;
end
else if (idle_write) begin
state_n = WRITE;
end
else begin
state_n = state_c;
end
START :
if (start_read) begin
state_n = READ;
end
else if (start_write) begin
state_n = WRITE;
end
else begin
state_n = state_c;
end
READ :
if (read_ack) begin
state_n = ACK;
end
else begin
state_n = state_c;
end
WRITE :
if (write_sack) begin
state_n = SACK;
end
else begin
state_n = state_c;
end
ACK :
if (ack_stop) begin
state_n = STOP;
end
else if (ack_done) begin
state_n = DONE;
end
else begin
state_n = state_c;
end
SACK :
if (sack_stop) begin
state_n = STOP;
end
else if (sack_done) begin
state_n = DONE;
end
else begin
state_n = state_c;
end
STOP :
if (stop_done) begin
state_n = DONE;
end
else begin
state_n = state_c;
end
DONE :
if (done_idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
default : state_n <= state_c ;
endcase
end
//创建从机时钟
//时钟计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
clk_cnt <= 0;
end
else if((state_c == IDLE) || (state_c == DONE))begin
clk_cnt <= 0;
end
else if(add_clk_cnt)begin
if(end_clk_cnt)begin
clk_cnt <= 0;
end
else begin
clk_cnt <= clk_cnt + 1;
end
end
else begin
clk_cnt <= clk_cnt;
end
end
assign add_clk_cnt = state_c != IDLE;
assign end_clk_cnt = add_clk_cnt && clk_cnt == T_CYCLE - 1;
//SCLK
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sclk <= 1'b1;
end
else if (idle_start || idle_write || idle_read) begin
sclk <= 1'b0;
end
else if(clk_cnt == T_CYCLE_HALF && add_clk_cnt)begin
sclk <= 1'b1;
end
else if (stop_done || ack_done || sack_done) begin
sclk <= 1'b1;
end
else if (end_clk_cnt) begin
sclk <= 1'b0;
end
end
//位计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bit_cnt <= 0;
end
else if(add_bit_cnt)begin
if(end_bit_cnt)begin
bit_cnt <= 0;
end
else begin
bit_cnt <= bit_cnt + 1;
end
end
else begin
bit_cnt <= bit_cnt;
end
end
assign add_bit_cnt = (state_c == START || state_c == READ || state_c == WRITE || state_c == READ||state_c == ACK||state_c == SACK||state_c == STOP) && end_clk_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == ((state_c == READ || state_c==WRITE)?7:0);
//sda_out
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1'b1;
end
else if(state_c == START && clk_cnt == T_LOW_HALF)begin
sda_out <= 1'b1;
end
else if(state_c == START && clk_cnt == T_HIGH_HALF)begin
sda_out <= 1'b0;
end
else if(state_c == WRITE && clk_cnt == T_LOW_HALF)begin //写数据
sda_out <= data_in[7 - bit_cnt];
end
else if (state_c == ACK && clk_cnt == T_LOW_HALF && !(command &CMD_STOP)) begin
sda_out <= 1'b0;
end
else if (state_c == ACK && clk_cnt == T_LOW_HALF && (command &CMD_STOP)) begin
sda_out <= 1'b1;
end
else if (state_c == STOP && clk_cnt == T_LOW_HALF) begin //防止应答后sdaout最后为1达不到上升沿的效果
sda_out <= 1'b0;
end
else if (state_c == STOP && clk_cnt == T_HIGH_HALF) begin
sda_out <= 1'b1;
end
end
//sda_out_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out_en <= 0;
end
else if(idle_start || idle_write || sack_stop || read_ack)begin //在开始,写,停止,主机应答需要控制数据线
sda_out_en =1'b1;
end
else if(write_sack || idle_read )begin //在从机应答,从eeprom读取数据需要从机控制总线
sda_out_en = 1'b0;
end
else if (ack_done || sack_done || stop_done) begin//释放数据线
sda_out_en = 1'b0;
end
end
//sda_in在SACK和READ状态下,sda低电平响应
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_out_r <= 0;
slack <= 1'b1;
end
else if(state_c == READ && clk_cnt == T_HIGH_HALF)begin
data_out_r[7-bit_cnt] <= sda_in ;
end
else if (state_c == SACK && clk_cnt == T_HIGH_HALF ) begin
slack <= ~sda_in;
end
end
assign sda_in = sda;
assign sda = sda_out_en?sda_out:1'bz;
assign data_out = data_out_r ;
assign done = (stop_done || sack_done || ack_done )?1'b1:1'b0;
endmodule //eeprom_interface
module eeprom_ctrl (
input clk ,
input rst_n ,
input [1:0] key_down ,//控制读写请求
input slack ,
input done ,
input [7:0] data_rec ,//从EEPROM读取的数据
output reg [7:0] data_send ,//写入eeprom的八位数据
output reg req ,//请求一次读/写
output reg [3:0] command //输出的命令:起/读/写/止
);
/* 参数定义 */
localparam IDLE = 4'b0001,
READ = 4'b0010,
WRITE = 4'b0100,
DONE = 4'b1000;
localparam BYTE_WRITE = 3 ,//字节写/读,页写/读
PAGE_WRITE = 18,
BYTE_READ = 4 ,
PAGE_READ = 19;
localparam WORD_ADDR = 8'b0001_0000;
localparam CMD_START = 4'b1010,//起/读/写/止
CMD_READ = 4'b0100,
CMD_WRITE = 4'b0010,
CMD_STOP = 4'b0001;
/* 信号定义 */
reg [7:0] data_rec_r;
reg [3:0] state_c;
reg [3:0] state_n;
reg r_req;
reg w_req;
reg [4:0] byte_cnt;
wire end_byte_cnt;
wire add_byte_cnt;
reg [4:0] byte_sel;
wire idle_read ;
wire idle_write;
wire read_done ;
wire read_idle ;
wire write_done;
wire write_idle;
wire done_idle ;
//状态转换
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态转移条件
assign idle_read = state_c == IDLE && r_req;
assign idle_write = state_c == IDLE && w_req;
assign read_done = state_c == READ && (end_byte_cnt );
assign read_idle = state_c == READ && (~end_byte_cnt && ~slack);//从机不响应会 中止数据传输
assign write_done = state_c == WRITE && (end_byte_cnt );
assign write_idle = state_c == WRITE && (~end_byte_cnt && ~slack);//从机不响应会 中止数据传输
assign done_idle = state_c == DONE && 1'b1;
//状态转移规律
always @(*) begin
case(state_c)
IDLE :
if (idle_read) begin
state_n = READ;
end
else if (idle_write) begin
state_n = WRITE;
end
else begin
state_n = state_c;
end
READ :
if (read_done) begin
state_n = DONE;
end
else if (read_idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
WRITE :
if (write_done) begin
state_n = DONE;
end
else if (write_idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
DONE :
if (done_idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
default : state_n <= state_c ;
endcase
end
//BYTE计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
byte_cnt <= 0;
end
else if(add_byte_cnt)begin
if(end_byte_cnt)begin
byte_cnt <= 0;
end
else begin
byte_cnt <= byte_cnt + 1;
end
end
else begin
byte_cnt <= byte_cnt;
end
end
assign add_byte_cnt = done;
assign end_byte_cnt = add_byte_cnt && byte_cnt ==byte_sel - 1;
//r_req,w_req
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
r_req <= 0;
w_req <= 0;
end
else if(key_down[0])begin
r_req <= 1'b1;
end
else if(key_down[1])begin
w_req <= 1'b1;
end
else begin
r_req <= 0;
w_req <= 0;
end
end
//byte_sel
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
byte_sel <= 0;
end
else if(key_down[0])begin
byte_sel <= BYTE_READ ;
end
else if(key_down[1])begin
byte_sel <= BYTE_WRITE;
end
end
//data_send,req,command
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_send <= 0;
req <= 0;
command <= 0;
end
else if (state_c == IDLE) begin
data_send <= 0;
req <= 0;
command <= 0;
end
else if( end_byte_cnt) begin//读写一次结束
req <= 1'b0;
end
else if(state_c == READ)begin
case(byte_cnt)
0 : //写入设备地址
begin
req <= 1'b1;
data_send <= 8'b1010_1000;
command <= 4'b1010;
end
1 ://写入字节地址
begin
req <= 1'b1;
data_send <= WORD_ADDR;
command <= 4'b0010;
end
2 ://重新写入设备地址和起始位
begin
req <= 1'b1;
data_send <= 8'b1010_1001;
command <= 4'b1010;
end
3 ://最后一位读和发送停止位
begin
req <= 1'b1;
data_rec_r <= data_rec;
command <= 4'b0101;
end
default :
begin
req <= 1'b1;
data_rec_r <= data_rec;
command <= 4'b0100;
end
endcase
end
else if(state_c == WRITE)begin
case(byte_cnt)
0 : //写入设备地址,起始位,存储块位,读写位
begin
req <= 1'b1;
data_send <= 8'b1010_1000;
command <= 4'b1010;
end
1 ://写入字节地址
begin
req <= 1'b1;
data_send <= WORD_ADDR;
command <= 4'b0010;
end
2 ://最后一位发送数据+停止位
begin
req <= 1'b1;
data_send <= 8'b1010_1111;
command <= 4'b0011;
end
default :
begin
req <= 1'b1;
data_send <= 8'b1010_1111;
command <= 4'b0010;
end
endcase
end
end
endmodule
module top (
input clk ,
input rst_n ,
input [1:0] key_in ,
//数码管模块
output [7:0] seg_dig ,
output [5:0] seg_sel ,
//EEPROM模块
output sclk ,//eeprom时钟
inout sda //数据线
);
wire [1:0] key_down;
wire [7:0] data_write;
wire [7:0] data_read;
wire [3:0] command;
wire done ;//一个字节是否读写完
wire req ;//请求一次读写
wire slack ;//从机应答
//eeprom接口模块
eeprom_interface u_eeprom_interface (
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* */ //ctrl模块传入
/* input [7:0] */ .data_in (data_write ) ,//写入eeprom的八位数据
/* input */ .req (req ) ,//请求一次读/写
/* input [3:0] */ .command (command ) ,//输入的命令:起/读/写/止
/* */ //传入ctrl模块
/* output */ .slack (slack ) ,//从机响应
/* output */ .done (done ) ,//一个字节读/写完成
/* output [7:0] */ .data_out (data_read ) ,//从EEPROM读取的数据
/* */ //与eeprom连接
/* output */ .sclk (sclk ) ,//从机时钟
/* inout */ .sda (sda ) //从机数据线
);
//eeprom控制模块
eeprom_ctrl u_eeprom_ctrl(
/* input */ .clk (clk ),
/* input */ .rst_n (rst_n ),
/* input [1:0] */ .key_down (key_down ),
/* input */ .slack (slack ),
/* input */ .done (done ),
/* input [7:0] */ .data_rec (data_read ),//从EEPROM读取的数据
/* output [7:0] */ .data_send (data_write),//写入eeprom的八位数据
/* output */ .req (req ) ,//请求一次读/写
/* output [3:0] */ .command (command ) //输入的命令:起/读/写/止
);
//按键模块
key_debounce u_key_debounce (
/* input */.clk (clk ),
/* input */.rst_n (rst_n ),
/* input [KEY_W-1:0] */ .key_in (key_in ),
/* */
/* output reg [KEY_W-1:0] */ .key_out (key_down) //检测到按下,输出一个周期的高脉冲,其他时刻为0
);
seg u_seg(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input [23:0 */ .data_in ({4'b0,4'b0,4'b0,4'b0,data_read[7:4],data_read[3:0]}) ,//输入的数据
/* output [7:0] */ .seg_dig (seg_dig) ,//数码管段选 + 小数点
/* output [5:0] */ .seg_sel (seg_sel) //数码管位选
);
endmodule //top