当前位置:网站首页>FPGA开发(2)——IIC通信
FPGA开发(2)——IIC通信
2022-06-29 23:05:00 【不脱单不改名1】
1、IIC通信理论知识
I2C 通讯协议(Inter-Integrated Circuit)是由 Philips 公司开发的一种简单、双向二线制同步串行总线,只需要两根线即可在连接于总线上的器件之间传送信息。
I2C 通讯协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 I2C 协议占用引脚特别少,硬件实现简单,可扩展型强,现在被广泛地使用在系统内多个集成电路(IC)间的通讯。
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) 主机向从机发送停止信号,顺序读操作完成。
2、IIC实现EEPROM读写
本次实验完成对AT24C64这种EEPROM的读写控制,往EEPROM中写入十个数据,之后读取十个数据到数码管进行显示。整个工程主要包括了以下的模块:按键滤波模块、eeprom读写控制模块、eeprom控制模块、数码管显示模块。我们分别对这几个模块进行设计和验证。


按键滤波模块
按键消抖模块主要作用就是接收外部按键按下,之后延时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 && cnt<CNT_20ms)begin cnt<=cnt+1'b1;
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 [email protected](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
[email protected](*)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 [email protected](*)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
[email protected](*)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_wr<CNT_WR_RD_MAX)begin write_vaild<=1'b1;
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那边读出的数据都是高阻态,我看了官方仿真结果也是这样的,可能给的仿真文件也有问题。
3、上板验证
可以进行正常的数据写入以及读取操作。


边栏推荐
- Hematemesis finishing: a rare map of architects!
- 成为唯一的key
- 股票开户安全吗?上海股票开户。
- 二叉搜索树 230. 二叉搜索树中第K小的元素 1038. 从二叉搜索树到更大和树
- High performance and high availability computing architecture of "Weibo comments"
- 大学里遗憾的事,希望你无怨也无悔
- 剑指 Offer 38. 字符串的排列
- Inspiration collection · evaluation of creative writing software: flomo, obsidian memo, napkin, flowus
- Cacti关于spine轮询的设置
- CE second operation
猜你喜欢

Constexpr function

NRM explanation

分布式消息中间件设计

Incluxdb time series database system

软件测试 接口测试 Jmeter 5.5 安装教程

Leetcode(680)——验证回文字符串 Ⅱ

Status acquisition and control system of on-site express cabinet

Intranet penetration (NC)

软件测试 接口测试 Postman测试工具 接口测试的流程 执行接口测试 接口关联 环境变量和全局变量 内置动态参数以及自动有的动态参数

SQL question brushing 595 Big country
随机推荐
Paper writing tool: latex online website
SQL question brushing 595 Big country
远程沟通高效的自我总结| 社区征文
Solr基础操作1
软件测试 接口测试 Postman测试工具 接口测试的流程 执行接口测试 接口关联 环境变量和全局变量 内置动态参数以及自动有的动态参数
Incluxdb time series database system
JS function related review
2022年PMP项目管理考试敏捷知识点(5)
STM32 basic knowledge points
How tcpdump filters specific TCP flag bits
Solr基础操作5
Software testing interface testing JMeter 5.5 installation tutorial
Gnawing down the big bone - sorting (I)
自己收藏的一些网址
Leetcode(633)——平方数之和
二叉搜索树 230. 二叉搜索树中第K小的元素 1038. 从二叉搜索树到更大和树
大学里遗憾的事,希望你无怨也无悔
Remember the process of checking online MySQL deadlock. You should not only know curd, but also know the principle of locking
Some of my favorite websites
Solution to version conflict of flutter plug-in