IIC物理层框图如下图所示。
(1) 它是一个支持多设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。
IIC协议层时序图如下图所示。
(1) 图中标注①表示“总线空闲状态”,在此状态下串口时钟信号 SCL 和串行数据信号 SDA 均保持高电平,此时无 I2C 设备工作。
(2) 图中标注②表示“起始信号”,在 I2C 总线处于“空闲状态”时,SCL 依旧保持高电平时, SDA 出现由高电平转为低电平的下降沿,产生一个起始信号,此时与总线相连的所有 I2C 设备在检测到起始信号后,均跳出空闲状态,等待控制字节的输入。
(3) 图中标注③表示“数据读/写状态”,“数据读/写状态”时序图具体见下图。
I2C 通讯设备的通讯模式是主从通讯模式,通讯双方有主从之分。
当主机向从机进行指令或数据的写入时,串行数据线 SDA 上的数据在串行时钟 SCL为高电平时写入从机设备,每次只写入一位数据;串行数据线 SDA 中的数据在串行时钟SCL 为低电平时进行数据更新,以保证在 SCL 为高电平时采集到 SDA 数据的稳定状态。
当一个完整字节的指令或数据传输完成,从机设备正确接收到指令或数据后,会通过拉低 SDA 为低电平,向主机设备发送单比特的应答信号,表示数据或指令写入成功。若从机正确应答,可以结束或开始下一字节数据或指令的传输,否则表明数据或指令写入失败,主机就可以决定是否放弃写入或者重新发起写入。
(4) 图中标注④表示“停止信号”,完成数据读写后,串口时钟 SCL 保持高电平,当串口数据信号 SDA 产生一个由低电平转为高电平的上升沿时,产生一个停止信号,I2C 总线跳转回“总线空闲状态”。
IIC器件地址与存储地址
每个IIC通讯的器件都有自己的地址,这在出厂就被设定了,用户无法更改,这个器件地址一般是7位,像0V5640等。这边EEPROM的器件地址是4位,为1010A2A1A0,其中A2A1A0是用户自己根据电平高低设置的,这边开发板都是拉低,所以EEPROM器件地址是1010000,外加写控制0或者读控制1,构成完整的一个字节控制信号。
存储地址更具寄存器或者存储大小而决定,AT24C64因为有64k的存储空间,需要两个字节的存储地址才可以。
IIC单字节写操作
对传入从机的控制命令最低位读写控制位写入不同数据值,主机可实现对从机的读/写操作,读写控制位为 0 时,表示主机要对从机进行数据写入操作;读写控制位为 1 时,表示主机要对从机进行数据读出操作。对于 I2C 协议的读/写操作,我们将其分为读操作和写操作两部分进行讲解。
如下图所示分别是单字节存储的时序图,分别画了单字节存储地址和双字节存储地址。
(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
(3) 先向从机写入高 8 位地址,且高位在前低位在后;
(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位在后,若为 2字节地址,跳转到步骤(6);
(5) 按高位在前低位在后的顺序写入单字节存储地址;
(6) 地址写入完成,主机接收到从机回传的应答信号后,开始单字节数据的写入;
(7) 单字节数据写入完成,主机接收到应答信号后,向从机发送停止信号,单字节数据写入完成。
IIC随机读操作
同样读操作的时序图如下图所示,分别进行单字节读操作和双字节读操作。
(1) 主机产生并发送起始信号到从机,将控制命令写入从机设备,读写控制位设置为低电平,表示对从机进行数据写操作,控制命令的写入高位在前低位在后;
(2) 从机接收到控制指令后,回传应答信号,主机接收到应答信号后开始存储地址的写入。若为 2 字节地址,顺序执行操作;若为单字节地址跳转到步骤(5);
(3) 先向从机写入高 8 位地址,且高位在前低位在后;
(4) 待接收到从机回传的应答信号,再写入低 8 位地址,且高位在前低位后,若为 2字节地址,跳转到步骤(6);
(5) 按高位在前低位在后的顺序写入单字节存储地址;
(6) 地址写入完成,主机接收到从机回传的应答信号后,主机再次向从机发送一个起始信号;
(7) 主机向从机发送控制命令,读写控制位设置为高电平,表示对从机进行数据读操作;
(8) 主机接收到从机回传的应答信号后,开始接收从机传回的第一个单字节数据;
(9) 数据接收完成后,主机产生应答信号回传给从机,从机接收到应答信号开始下一字节数据的传输,若数据接收完成,执行下一操作步骤;若数据接收未完成,在此执行步骤(9);
(10) 主机产生一个时钟的高电平无应答信号;
(11) 主机向从机发送停止信号,顺序读操作完成。
按键滤波模块
按键消抖模块主要作用就是接收外部按键按下,之后延时20ms后,判断按键按下信息,之后给出标志位信号。其框图和时序图如下图所示。
直接给出其源代码如下图,因为模块比较简单,这边就不在进行仿真。
module key_control(
input clk,
input rst_n,
input key_in,
output reg key_flag
);
parameter CNT_20ms = 20'd999_999 ;
reg [19:0] cnt;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt<=20'd0;
end
else if(key_in==1'b0 && cntb1;
end
else if(key_in==1'b1)begin
cnt<=1'b0;
end
else cnt=cnt;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
key_flag<=1'b0;
end
else if(cnt==20'd999_998)begin
key_flag<=1'b1;
end
else key_flag<=1'b0;
end
endmodule
数码管显示模块
数码管显示模块主要就是接收从eeprom读出的数据,送到fifo中进行缓存,之后在数码管显示。主要包含了时钟、复位、数据、数码管位选、数码管信号这些端口。
数码管的代码如下图所示,这边也不进行仿真,只做一些讲解。cnt进行周期的计数,计数到4999后输出一个flag标志位高电平信号。6位的位选信号收到flag标志位信号后进行移位,对数码管进行动态刷新。
将接收到的数据赋值给number,之后将number数据对应赋给8位的数码管seg。
module smg #(
parameter W = 4'd8
)
(
input clk,
input rst_n,
input [W-1:0] data,
output reg[5:0] sel,
output reg[7:0] seg
);
reg [3:0] number;
wire [3:0] data0;
wire [3:0] data1;
assign data0=data[3:0];
assign data1=data[7:4];
reg [12:0] cnt;
reg flag;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt<=13'd0;
end
else if(cnt==13'd4999)begin
cnt<=13'd0;
end
else cnt<=cnt+1'b1;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
flag<=1'b0;
end
else if(cnt==13'd4999)begin
flag<=1'b1;
end
else flag<=1'b0;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
sel<=6'b111_110;
end
else if(flag==1)begin
sel<={sel[4:0],sel[5]};
end
else sel<=sel;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
number<=4'd0;
end
else begin
case(sel)
6'b111_110:number<=data0;
6'b111_101:number<=data1;
6'b111_011:number<=4'd0;
6'b110_111:number<=4'd0;
6'b101_111:number<=4'd0;
6'b011_111:number<=4'd0;
default:number<=4'd0;
endcase
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
seg<=8'd0;
end
else begin
case(number)
4'd0:seg<=8'b1100_0000;
4'd1:seg<=8'b1111_1001;
4'd2:seg<=8'b1010_0100;
4'd3:seg<=8'b1011_0000;
4'd4:seg<=8'b1001_1001;
4'd5:seg<=8'b1001_0010;
4'd6:seg<=8'b1000_0010;
4'd7:seg<=8'b1111_1000;
4'd8:seg<=8'b1000_0000;
4'd9:seg<=8'b1001_0000;
4'd10:seg<=8'b1000_1000;
4'd11:seg<=8'b1000_0011;
4'd12:seg<=8'b1100_0110;
4'd13:seg<=8'b1010_0001;
4'd14:seg<=8'b1000_0110;
4'd15:seg<=8'b1000_1110;
default:seg<=8'b1100_0000;
endcase
end
end
endmodule
IIC控制模块
该模块主要包含了8个输入信号,5个输出信号。
IIC读写操作设计到流程控制,这边利用状态机来控制整个流程,状态装换图如下图所示。
给出IIC控制模块读写操作的时序图如下图所示。首先是写操作控制流程图。具体流程:1、cnt_clk进行0-24的循环计数,实现对50M时钟的50分频,得到1MHz的时钟信号,就是i2c_clk。2、cnt_i2c_clk_en信号是时钟计数控制的使能信号,为下面控制SCL和SDA做准备,在接收到i2c_start信号后拉高使能信号。当状态机状态为STOP并且cnt_i2c_clk等于3,并且cnt_bit等于3时拉低使能信号。3、cnt_i2c_clk在cnt_i2c_clk_en使能信号拉高时进行计数,计数到3后清零,反复进行。4、cnt_bit是对写入的位数进行计数,具体操作可以看书序图。5、state是状态机的状态,这部分跳转比较复杂,可以对照代码和时序图查看。6、ACK是应答信号,在发送完一个字节数据时会接收到一位的低电平信号,在ACK1-5时进行判断,其他情况都是高电平。7、iic_sda_reg和rd_data_reg主要是对信号进行缓存,这部分可以对照波形图和代码查看。8、最后完成sda和scl信号的代码编写。
最后的iic控制模块代码如下图所示。
module i2c_control
#(
parameter DEVICE_ADDR = 7'b1010_000,
parameter SYS_FREQ=26'd50_000_000,
parameter I2C_FREQ=18'd250_000
)
(
input clk,
input rst_n,
input wr_en,
input rd_en,
input i2c_start,
input addr_num,
input [15:0] byte_addr,
input [7:0] wr_data,
output reg i2c_clk,
output reg i2c_end,
output reg [7:0] rd_data,
output reg i2c_scl,
inout wire i2c_sda
);
localparam IDLE=4'd0;
localparam START_1=4'd1;
localparam SEND_D_ADDR=4'd2;
localparam ACK_1=4'd3;
localparam SEND_B_ADDR_H=4'd4;
localparam ACK_2=4'd5;
localparam SEND_B_ADDR_L=4'd6;
localparam ACK_3=4'd7;
localparam WR_DATA=4'd8;
localparam ACK_4=4'd9;
localparam STOP=4'd10;
localparam START_2=4'd11;
localparam SEND_RD_ADDR=4'd12;
localparam ACK_5=4'd13;
localparam RD_DATA=4'd14;
localparam N_ACK=4'd15;
reg [7:0] cnt_clk;
reg cnt_i2c_clk_en;
reg [3:0] state;
reg [1:0] cnt_i2c_clk;
reg [2:0] cnt_bit;
reg ack;
reg i2c_sda_reg;
reg [7:0] rd_data_reg;
wire sda_en;
wire sda_in;
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt_clk<=8'd0;
end
else if(cnt_clk==((SYS_FREQ/I2C_FREQ)>>2'd3)-1'b1)begin
cnt_clk<=8'd0;
end
else cnt_clk<=cnt_clk+1'b1;
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
i2c_clk<=1'b0;
end
else if(cnt_clk==((SYS_FREQ/I2C_FREQ)>>2'd3)-1'b1)begin
i2c_clk<=~i2c_clk;
end
else i2c_clk<=i2c_clk;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
cnt_i2c_clk_en<=1'b0;
end
else if(i2c_start==1'b1)begin
cnt_i2c_clk_en<=1'b1;
end
else if(state==STOP && cnt_i2c_clk==2'd3 && cnt_bit==3'd3)begin
cnt_i2c_clk_en<=1'b0;
end
else cnt_i2c_clk_en<=cnt_i2c_clk_en;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
cnt_i2c_clk<=2'd0;
end
else if(cnt_i2c_clk_en==1'b1)begin
if(cnt_i2c_clk==2'd3)begin
cnt_i2c_clk<=2'd0;
end
else cnt_i2c_clk<=cnt_i2c_clk+1'b1;
end
else cnt_i2c_clk<=2'd0;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
cnt_bit<=3'd0;
end
else if(state==IDLE || state== START_1 ||
state==START_2 || state==ACK_1 || state==ACK_2 ||
state==ACK_3 || state==ACK_4 || state==ACK_5 || state==N_ACK)begin
cnt_bit<=3'd0;
end
else if(cnt_bit==3'd7 && cnt_i2c_clk==2'd3)begin
cnt_bit<=3'd0;
end
else if(cnt_i2c_clk==3'd3)begin
cnt_bit<=cnt_bit+1'b1;
end
end
always@(posedge i2c_clk or negedge rst_n)begin
if(rst_n == 1'b0)
state <= IDLE;
else case(state)
IDLE:
if(i2c_start == 1'b1)
state <= START_1;
else
state <= state;
START_1:
if(cnt_i2c_clk == 3)
state <= SEND_D_ADDR;
else
state <= state;
SEND_D_ADDR:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_1;
else
state <= state;
ACK_1:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
begin
if(addr_num == 1'b1)
state <= SEND_B_ADDR_H;
else
state <= SEND_B_ADDR_L;
end
else
state <= state;
SEND_B_ADDR_H:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_2;
else
state <= state;
ACK_2:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= SEND_B_ADDR_L;
else
state <= state;
SEND_B_ADDR_L:
if((cnt_bit == 3'd7) && (cnt_i2c_clk == 3))
state <= ACK_3;
else
state <= state;
ACK_3:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
begin
if(wr_en == 1'b1)
state <= WR_DATA;
else if(rd_en == 1'b1)
state <= START_2;
else
state <= state;
end
else
state <= state;
WR_DATA:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_4;
else
state <= state;
ACK_4:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= STOP;
else
state <= state;
START_2:
if(cnt_i2c_clk == 3)
state <= SEND_RD_ADDR;
else
state <= state;
SEND_RD_ADDR:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= ACK_5;
else
state <= state;
ACK_5:
if((cnt_i2c_clk == 3) && (ack == 1'b0))
state <= RD_DATA;
else
state <= state;
RD_DATA:
if((cnt_bit == 3'd7) &&(cnt_i2c_clk == 3))
state <= N_ACK;
else
state <= state;
N_ACK:
if(cnt_i2c_clk == 3)
state <= STOP;
else
state <= state;
STOP:
if((cnt_bit == 3'd3) &&(cnt_i2c_clk == 3))
state <= IDLE;
else
state <= state;
default: state <= IDLE;
endcase
end
always@(*)begin
case (state)
IDLE,START_1,SEND_D_ADDR,SEND_B_ADDR_H,SEND_B_ADDR_L,
WR_DATA,START_2,SEND_RD_ADDR,RD_DATA,N_ACK,STOP:
ack <= 1'b1;
ACK_1,ACK_2,ACK_3,ACK_4,ACK_5:
if(cnt_i2c_clk == 2'd0)
ack <= sda_in;
else
ack <= ack;
default: ack <= 1'b1;
endcase
end
always@(*)begin
case (state)
IDLE:
i2c_scl <= 1'b1;
START_1:
if(cnt_i2c_clk == 2'd3)
i2c_scl <= 1'b0;
else
i2c_scl <= 1'b1;
SEND_D_ADDR,ACK_1,SEND_B_ADDR_H,ACK_2,SEND_B_ADDR_L,
ACK_3,WR_DATA,ACK_4,START_2,SEND_RD_ADDR,ACK_5,RD_DATA,N_ACK:
if((cnt_i2c_clk == 2'd1) || (cnt_i2c_clk == 2'd2))
i2c_scl <= 1'b1;
else
i2c_scl <= 1'b0;
STOP:
if((cnt_bit == 3'd0) &&(cnt_i2c_clk == 2'd0))
i2c_scl <= 1'b0;
else
i2c_scl <= 1'b1;
default: i2c_scl <= 1'b1;
endcase
end
always@(*)begin
case (state)
IDLE:
begin
i2c_sda_reg <= 1'b1;
rd_data_reg <= 8'd0;
end
START_1:
if(cnt_i2c_clk <= 2'd0)
i2c_sda_reg <= 1'b1;
else
i2c_sda_reg <= 1'b0;
SEND_D_ADDR:
if(cnt_bit <= 3'd6)
i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
else
i2c_sda_reg <= 1'b0;
ACK_1:
i2c_sda_reg <= 1'b1;
SEND_B_ADDR_H:
i2c_sda_reg <= byte_addr[15 - cnt_bit];
ACK_2:
i2c_sda_reg <= 1'b1;
SEND_B_ADDR_L:
i2c_sda_reg <= byte_addr[7 - cnt_bit];
ACK_3:
i2c_sda_reg <= 1'b1;
WR_DATA:
i2c_sda_reg <= wr_data[7 - cnt_bit];
ACK_4:
i2c_sda_reg <= 1'b1;
START_2:
if(cnt_i2c_clk <= 2'd1)
i2c_sda_reg <= 1'b1;
else
i2c_sda_reg <= 1'b0;
SEND_RD_ADDR:
if(cnt_bit <= 3'd6)
i2c_sda_reg <= DEVICE_ADDR[6 - cnt_bit];
else
i2c_sda_reg <= 1'b1;
ACK_5:
i2c_sda_reg <= 1'b1;
RD_DATA:
if(cnt_i2c_clk == 2'd2)
rd_data_reg[3'd7 - cnt_bit] <= sda_in;
else
rd_data_reg <= rd_data_reg;
N_ACK:
i2c_sda_reg <= 1'b1;
STOP:
if((cnt_bit == 3'd0) && (cnt_i2c_clk < 2'd3))
i2c_sda_reg <= 1'b0;
else
i2c_sda_reg <= 1'b1;
default:
begin
i2c_sda_reg <= 1'b1;
rd_data_reg <= rd_data_reg;
end
endcase
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
rd_data<=8'd0;
end
else if(state==RD_DATA && cnt_bit==3'd7 && cnt_i2c_clk==3'd3)begin
rd_data<=rd_data_reg;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
i2c_end<=1'b0;
end
else if(state==STOP && cnt_i2c_clk==2'd3 && cnt_bit==3'd3)begin
i2c_end<=1'b1;
end
else i2c_end<=1'b0;
end
assign sda_en = ((state == RD_DATA) || (state == ACK_1) || (state == ACK_2)
|| (state == ACK_3) || (state == ACK_4) || (state == ACK_5))
? 1'b0 : 1'b1;
assign i2c_sda = (sda_en == 1'b1) ? i2c_sda_reg : 1'bz;
assign sda_in = i2c_sda;
endmodule
编写完控制代码后对其进行modelsim仿真控制。仿真激励文件如下图所示,以及仿真结果图如下图所示,对照仿真结果和画的时序图,测试结果是没问题的。蓝色线是高阻态,可以看做是应答信号。
`timescale 1ns / 1ps
module tb_i2c_control;
// i2c_control Parameters
parameter PERIOD = 10 ;
parameter DEVICE_ADDR = 7'b1010_000 ;
parameter SYS_FREQ = 26'd50_000_000;
parameter I2C_FREQ = 18'd250_000 ;
// i2c_control Inputs
reg clk = 0 ;
reg rst_n = 0 ;
reg wr_en = 1 ;
reg rd_en = 0 ;
reg i2c_start = 0 ;
reg addr_num = 1 ;
reg [15:0] byte_addr = 16'h005a ;
reg [7:0] wr_data = 8'haa ;
// i2c_control Outputs
wire i2c_clk ;
wire i2c_end ;
wire [7:0] rd_data ;
wire i2c_scl ;
// i2c_control Bidirs
wire i2c_sda ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 1;
end
initial
begin
#(PERIOD*10)
i2c_start=1;
#(PERIOD*100)
i2c_start=0;
end
i2c_control #(
.DEVICE_ADDR ( DEVICE_ADDR ),
.SYS_FREQ ( SYS_FREQ ),
.I2C_FREQ ( I2C_FREQ )
)
u_i2c_control (
.clk ( clk ),
.rst_n ( rst_n ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.i2c_start ( i2c_start ),
.addr_num ( addr_num ),
.byte_addr ( byte_addr [15:0] ),
.wr_data ( wr_data [7:0] ),
.i2c_clk ( i2c_clk ),
.i2c_end ( i2c_end ),
.rd_data ( rd_data [7:0] ),
.i2c_scl ( i2c_scl ),
.i2c_sda ( i2c_sda )
);
initial
begin
end
endmodule
IIC读写控制模块
IIC读写控制模块主要包括了7个输入信号和6个输出信号。
下图所示是IIC读写控制模块的具体时序图。
1、cnt_wr和cnr_rd分别是写和读的计数器,其主要作用是模块接收到,read和write信号后,延时一段时间,输出一个较长时间的写和读控制信号write_vaild和read_vaild。这边加延迟是考虑到按键模块使用的时钟是50M,而读I2C控制是1M的时钟,所以延时一段时间使得1M时钟可以读取到写标志和读标志。
2、wr_en和rd_en分别是写使能和读使能,写使能和读使能在接收到标志信号高电平时拉高。写使能在i2c_end等于1且wr_i2c_data_num等于9时拉低,表示写操作结束,读操作也是类似的。
3、cnt_start是计数模块,控制每一位读取的控制间隔,这边让他在读使能有效或者写使能有效时进行累加至4999后清零。
4、wr_i2c_data_num写入的数据计数,当写使能有效并且i2c_end等于1时,代表写入一个字节完成,计数加1。
5、i2c_start是和i2c_end相对应,代表了一个字节信号开始传输。当写使能或者读使能有效并且cnt_start计数到4999时拉高,其他时间为0。
6、wr_data和byte_addr分别是要写入的数据和地址,这个和wr_i2c_data_num是对应的。用组合逻辑控制实现亦可。
7、fifo_rd_vaild是读fifo的使能信号。当data_num计数为10时,信号拉高,当data_num等于0并且cnt_wait计数到最大值时信号拉低,读结束。
8、cnt_wait是在读取fifo过程中的延时,其主要作用是让fifo读的慢一些,能看出在数码管显示的变化。
9、fifo_rd_en是读fifo的标志位,在拉高时进行读。
10、详细介绍可以看野火的文档。
最后根据上面的时序图可以编写verilog代码。
module i2c_rw_control(
input clk,
input rst_n,
input i2c_clk,
input write,
input read,
input i2c_end,
input [7:0] rd_data,
output reg wr_en,
output reg rd_en,
output reg i2c_start,
output reg [15:0] byte_addr,
output reg [7:0] wr_data,
output wire [7:0] fifo_rd_data
);
parameter DATA_NUM = 8'd10 ,
CNT_START_MAX = 13'd5000 ,
CNT_WR_RD_MAX = 8'd200 ,
CNT_WAIT_MAX = 28'd500_000 ;
reg [7:0] cnt_wr;
reg [7:0] cnt_rd;
reg write_vaild;
reg read_vaild;
reg fifo_rd_vaild;
reg [7:0] wr_i2c_data_num;
reg [7:0] rd_i2c_data_num;
reg [12:0] cnt_start;
wire [7:0] data_num;
reg [27:0] cnt_wait;
reg fifo_rd_en;
reg [7:0] rd_data_num;
//写操作
/*
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt_wr<=8'd0;
end
else if(write==1'b1 || cnt_wr!=8'd0)begin
cnt_wr<=cnt_wr+1'b1;
end
else if(cnt_wr==CNT_WR_RD_MAX)begin
cnt_wr<=8'd0;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
write_vaild<=1'b0;
end
else if(cnt_wr>8'd0 && cnt_wrb1;
end
else write_vaild<=1'b0;
end
*/
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt_wr<=8'd0;
end
else if(write_vaild==1'b1)begin
cnt_wr<=cnt_wr+1'b1;
end
else if(write_vaild==1'b0)begin
cnt_wr<=8'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
write_vaild<=1'b0;
end
else if(cnt_wr == (CNT_WR_RD_MAX-1'b1))begin
write_vaild<=1'b0;
end
else if(write==1'b1)begin
write_vaild<=1'b1;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
wr_en<=1'b0;
end
else if(write_vaild==1'b1)begin
wr_en<=1'b1;
end
else if(i2c_end==1'b1 && wr_i2c_data_num==DATA_NUM-1'b1 && wr_en==1'b1)begin
wr_en<=1'b0;
end
else wr_en<=wr_en;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
cnt_start<=13'd0;
end
else if(wr_en==1'b1 || rd_en==1'b1)begin
if(cnt_start==CNT_START_MAX-1'b1)begin
cnt_start<=13'd0;
end
else cnt_start<=cnt_start+1'b1;
end
else cnt_start<=13'd0;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
wr_i2c_data_num<=8'd0;
end
else if(wr_en==1'b1)begin
if(i2c_end==1'b1)begin
wr_i2c_data_num<=wr_i2c_data_num+1'b1;
end
end
else wr_i2c_data_num<=8'd0;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
i2c_start<=1'b0;
end
else if(rd_en==1'b1 || wr_en==1'b1)begin
if(cnt_start==CNT_START_MAX-1'b1)begin
i2c_start<=1'b1;
end
else i2c_start<=1'b0;
end
else i2c_start<=1'b0;
end
always @(*) begin
if(wr_en==1'b1)begin
case(wr_i2c_data_num)
8'd0:wr_data<=8'ha5;
8'd1:wr_data<=8'ha6;
8'd2:wr_data<=8'ha7;
8'd3:wr_data<=8'ha8;
8'd4:wr_data<=8'ha9;
8'd5:wr_data<=8'haa;
8'd6:wr_data<=8'hab;
8'd7:wr_data<=8'hac;
8'd8:wr_data<=8'had;
8'd9:wr_data<=8'hae;
default:wr_data<=8'ha5;
endcase
end
else wr_data<=8'ha5;
end
always @(*) begin
if(wr_en==1'b1)begin
case(wr_i2c_data_num)
8'd0:byte_addr<=16'h005a;
8'd1:byte_addr<=16'h005b;
8'd2:byte_addr<=16'h005c;
8'd3:byte_addr<=16'h005d;
8'd4:byte_addr<=16'h005e;
8'd5:byte_addr<=16'h005f;
8'd6:byte_addr<=16'h0060;
8'd7:byte_addr<=16'h0061;
8'd8:byte_addr<=16'h0062;
8'd9:byte_addr<=16'h0063;
default:byte_addr<=16'h005a;
endcase
end
else if(rd_en==1'b1)begin
case(rd_i2c_data_num)
8'd0:byte_addr<=16'h005a;
8'd1:byte_addr<=16'h005b;
8'd2:byte_addr<=16'h005c;
8'd3:byte_addr<=16'h005d;
8'd4:byte_addr<=16'h005e;
8'd5:byte_addr<=16'h005f;
8'd6:byte_addr<=16'h0060;
8'd7:byte_addr<=16'h0061;
8'd8:byte_addr<=16'h0062;
8'd9:byte_addr<=16'h0063;
default:byte_addr<=16'h005a;
endcase
end
else byte_addr<=16'h005a;
end
//读操作
/*
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt_rd<=8'd0;
end
else if(read==1'b1 || cnt_rd!=8'd0)begin
cnt_rd<=cnt_rd+1'b1;
end
else if(cnt_rd==CNT_WR_RD_MAX)begin
cnt_rd<=8'd0;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
read_vaild<=1'b0;
end
else if(cnt_rd>8'd0 && cnt_rd<CNT_WR_RD_MAX)begin
read_vaild<=1'b1;
end
else read_vaild<=1'b0;
end
*/
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
cnt_rd<=8'd0;
end
else if(read_vaild==1'b1)begin
cnt_rd<=cnt_rd+1'b1;
end
else if(read_vaild==1'b0)begin
cnt_rd<=8'd0;
end
end
always @(posedge clk or negedge rst_n) begin
if(~rst_n)begin
read_vaild<=1'b0;
end
else if(cnt_rd == (CNT_WR_RD_MAX-1'b1))begin
read_vaild<=1'b0;
end
else if(read==1'b1)begin
read_vaild<=1'b1;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
rd_en<=1'b0;
end
else if(read_vaild==1'b1)begin
rd_en<=1'b1;
end
else if(i2c_end==1'b1 && rd_i2c_data_num==DATA_NUM-1'b1 && rd_en==1'b1)begin
rd_en<=1'b0;
end
else rd_en<=rd_en;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
rd_i2c_data_num<=8'd0;
end
else if(rd_en==1'b1)begin
if(i2c_end==1'b1)begin
rd_i2c_data_num<=rd_i2c_data_num+1'b1;
end
end
else rd_i2c_data_num<=8'd0;
end
/*
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
fifo_rd_vaild<=1'b0;
end
else if(data_num==DATA_NUM-1'b1 && i2c_end==1'b1)begin
fifo_rd_vaild<=1'b1;
end
else if(data_num==8'd0 && cnt_wait==CNT_WAIT_MAX-1'b1)begin
fifo_rd_vaild<=1'b0;
end
else fifo_rd_vaild<=fifo_rd_vaild;
end
*/
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
fifo_rd_vaild<=1'b0;
end
else if(data_num==DATA_NUM)begin
fifo_rd_vaild<=1'b1;
end
else if(data_num==8'd0 && cnt_wait==CNT_WAIT_MAX-1'b1)begin
fifo_rd_vaild<=1'b0;
end
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
cnt_wait<=28'd0;
end
else if(fifo_rd_vaild==1'b1)begin
if(cnt_wait==CNT_WAIT_MAX-1'b1)begin
cnt_wait<=28'd0;
end
else cnt_wait<=cnt_wait+1'b1;
end
else cnt_wait<=28'd0;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
fifo_rd_en<=1'b0;
end
else if(cnt_wait==CNT_WAIT_MAX-1'b1 && rd_data_num<DATA_NUM)begin
fifo_rd_en<=1'b1;
end
else fifo_rd_en<=1'b0;
end
always @(posedge i2c_clk or negedge rst_n) begin
if(~rst_n)begin
rd_data_num<=8'd0;
end
else if(fifo_rd_vaild==1'b1)begin
if(fifo_rd_en==1'b1)begin
rd_data_num<=rd_data_num+1'b1;
end
else rd_data_num<=rd_data_num;
end
else rd_data_num<=8'd0;
end
i2c_fifo u_i2c_fifo(
.clock ( i2c_clk ),
.data ( rd_data ),
.rdreq ( fifo_rd_en && fifo_rd_vaild ),
.wrreq ( rd_en && i2c_end),
.q ( fifo_rd_data ),
.usedw ( data_num )
);
endmodule
不在对这个模块进行仿真验证,直接编写顶层模块来例化上面四个模块,顶层模块代码如下图所示。
module i2c_eeprom(
input clk,
input rst_n,
input key_wr,
input key_rd,
output scl,
inout sda,
output wire [5:0] sel,
output wire [7:0] seg
);
wire write;
wire read;
wire i2c_clk;
wire i2c_end;
wire [7:0] rd_data;
wire wr_en;
wire rd_en;
wire i2c_start;
wire [15:0] byte_addr;
wire [7:0] wr_data;
wire [7:0] fifo_rd_data;
key_control u_key_control_wr(
.clk ( clk ),
.rst_n ( rst_n ),
.key_in ( key_wr ),
.key_flag ( write )
);
key_control u_key_control_rd(
.clk ( clk ),
.rst_n ( rst_n ),
.key_in ( key_rd ),
.key_flag ( read )
);
i2c_rw_control u_i2c_rw_control(
.clk ( clk ),
.rst_n ( rst_n ),
.i2c_clk ( i2c_clk ),
.write ( write ),
.read ( read ),
.i2c_end ( i2c_end ),
.rd_data ( rd_data ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.i2c_start ( i2c_start ),
.byte_addr ( byte_addr ),
.wr_data ( wr_data ),
.fifo_rd_data ( fifo_rd_data )
);
i2c_control#(
.DEVICE_ADDR ( 7'b1010_000 ),
.SYS_FREQ ( 26'd50_000_000 ),
.I2C_FREQ ( 18'd250_000 )
)u_i2c_control(
.clk ( clk ),
.rst_n ( rst_n ),
.wr_en ( wr_en ),
.rd_en ( rd_en ),
.i2c_start ( i2c_start ),
.addr_num ( 1'b1 ),
.byte_addr ( byte_addr ),
.wr_data ( wr_data ),
.i2c_clk ( i2c_clk ),
.i2c_end ( i2c_end ),
.rd_data ( rd_data ),
.i2c_scl ( scl ),
.i2c_sda ( sda )
);
smg#(
.W ( 4'd8 )
)u_smg(
.clk ( clk ),
.rst_n ( rst_n ),
.data ( fifo_rd_data ),
.sel ( sel ),
.seg ( seg )
);
endmodule
编写测试的testbench,testbench如下图所示。这边要说明的是,仿真文件需要AT24C64的文件,可以在野火官网找到,以及添加altera_mf文件。仿真中也改变了一些设置。将CNT_WAIT_MAX改为1000,CNT_START_MAX改为1500,是为了让仿真更快一些。
`timescale 1ns / 1ps
module tb_i2c_eeprom;
// i2c_eeprom Parameters
parameter PERIOD = 20;
// i2c_eeprom Inputs
reg clk = 0 ;
reg rst_n = 0 ;
reg key_wr = 0 ;
reg key_rd = 0 ;
// i2c_eeprom Outputs
wire scl ;
wire [5:0] sel ;
wire [7:0] seg ;
// i2c_eeprom Bidirs
wire sda ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 1;
end
i2c_eeprom u_i2c_eeprom (
.clk ( clk ),
.rst_n ( rst_n ),
.key_wr ( key_wr ),
.key_rd ( key_rd ),
.scl ( scl ),
.sel ( sel [5:0] ),
.seg ( seg [7:0] ),
.sda ( sda )
);
M24LC64 M24lc64_inst
(
.A0 (1'b0 ), //器件地址
.A1 (1'b0 ), //器件地址
.A2 (1'b0 ), //器件地址
.WP (1'b0 ), //写保护信号,高电平有效
.RESET (~rst_n ), //复位信号,高电平有效
.SDA (sda ), //串行数据
.SCL (scl ) //串行时钟
);
initial
begin
key_wr <= 1'b1 ;
key_rd <= 1'b1 ;
#(PERIOD*100)
key_wr <= 1'b0 ;
key_rd <= 1'b1 ;
#(PERIOD*200)
key_wr <= 1'b1 ;
key_rd <= 1'b1 ;
#(PERIOD*200_000*15)
key_wr <= 1'b1 ;
key_rd <= 1'b0 ;
#(PERIOD*200)
key_wr <= 1'b1 ;
key_rd <= 1'b1 ;
#(PERIOD*200_000*15)
$stop;
end
endmodule
仿真的结果图如下图所示,结果也是没有问题的。可以进行正常的读写操作,至于AT24C64那边读出的数据都是高阻态,我看了官方仿真结果也是这样的,可能给的仿真文件也有问题。