目录
三、I2C协议的FPGA实现
1. I2C接口设计
2. 仿真验证
本专题EEPROM读写系统(在下一篇博客讲解,包含本篇内容)整体功能实现的工程下载链接如下:
https://download.csdn.net/download/qq_33231534/12503289
上一篇博客对I2C总线协议进行了大体的讲解,以及对I2C总线器件EEPROM(AT24C64)读写时序进行详细阐述,下边就要对EEPROM器件在FPGA上如何进行读写,以及接口设计和调试系统进行具体叙述。本实验平台使用的是小梅哥的AC620开发板,FPGA芯片是cyclone IV EP4CE10F17C8N。
根据上方4个读写时序图,编写I2C接口时序,I2C协议接口设计,这里采用状态机的方式,将整个设计为9个状态,分别为IDLE 、WR_START 、WR_DEVICE 、WR_ADDR 、WR_DATA 、RD_START 、RD_DEVICE、RD_DATA 、STOP 。其状态转移图如下:
其各个状态表示的含义以及该模块接口信号的名称及功能如下思维导图:
这里将接口信号用表格写一下,看的更加清晰一点。如下:
信号名称 | I/O | 位数 | 功能描述 |
clk | I | 1 | 系统时钟50MHz |
rst_n | I | 1 | 系统复位 |
rd_data_num | I | 6 | 读数据个数,最大32 |
wr_data_num | I | 6 | 写数据个数,最大32 |
word_addr | I | 16 | 数据字地址,AT24C64是16位地址 |
device_addr | I | 3 | 设备可变地址,由硬件连接决定,本实验平台为3‘b001 |
wr_en | I | 1 | 写数据使能 |
wr_data | I | 8 | 写数据 |
rd_en | I | 1 | 读数据使能 |
wr_data_vaild | O | 1 | 写数据有效位,读多个数据就是读完每个数据时产生一个有效位 |
rd_data | O | 8 | 读数据 |
rd_data_vaild | O | 1 | 读数据有效位,写多个数据就是写完每个数据时产生一个有效位 |
done | O | 1 | 读写操作结束标志 |
scl | O | 1 | 串行时钟线 |
sda | IO | 1 | 串行数据线 |
直接上代码:
//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN
//File name: I2C_ctrl.v
//Last modified Date: 2020/6/6
//Last Version:
//Descriptions: EEPROM(AT24C64)接口设计
//-------------------------------------------------------------------
module I2C_ctrl(
input clk ,//系统时钟50MHz
input rst_n ,//系统复位
input [ 5: 0] rd_data_num ,//读数据个数,最大32
input [ 5: 0] wr_data_num ,//写数据个数,最大32
input [15: 0] word_addr ,//数据字地址,AT24C64是16位地址
input [ 2: 0] device_addr ,//设备可变地址,由硬件连接决定,本实验平台为3‘b001
input wr_en ,//写数据使能
input [ 7: 0] wr_data ,//写数据
input rd_en ,//读数据使能
output reg wr_data_vaild ,//写数据有效位,读多个数据就是读完每个数据时产生一个有效位
output reg [7:0] rd_data ,//读数据
output reg rd_data_vaild ,//读数据有效位,写多个数据就是写完每个数据时产生一个有效位
output reg done ,//读写操作结束标志
output reg scl ,//串行时钟线
inout sda //串行数据线
);
parameter SYS_CLK = 50_000_000 ;//系统时钟频率
parameter SCLK = 100_000 ;//串行时钟线时钟频率
localparam SCLK_CNT = SYS_CLK/SCLK;//产生时钟线的时钟计数次数
//状态机状态定义
parameter IDLE = 4'd0 ;//空闲状态
parameter WR_START = 4'd1 ;//写数据启动状态
parameter WR_DEVICE = 4'd2 ;//发送设备地址状态
parameter WR_ADDR = 4'd3 ;//发送数据字地址状态
parameter WR_DATA = 4'd4 ;//发送数据状态
parameter RD_START = 4'd5 ;//读数据启动
parameter RD_DEVICE = 4'd6 ;//读操作设备地址状态
parameter RD_DATA = 4'd7 ;//读数据状态
parameter STOP = 4'd8 ;//停止状态
reg [ 3: 0] state_c ;//状态机改变后的状态,时序逻辑
reg [ 3: 0] state_n ;//状态机当前状态,组合逻辑
reg add_flag ;//计数条件标志
reg [ 8: 0] sclk_cnt ;//sclk时钟信号计数
reg scl_high ;//scl信号高电平中间信号,1个时钟周期
reg scl_low ;//scl信号低电平中间信号,1个时钟周期
reg wr_flag ;//写标志
reg rd_flag ;//读标志
reg ack ;//响应信号
reg waddr_cnt ;//数据字地址字节计数,共2字节
reg [ 5: 0] wdata_cnt ;//写数据字节计数,最大32个
reg [ 5: 0] rdata_cnt ;//读数据字节计数,最大32个
reg [ 4: 0] bit_cnt ;//对传输的8位数据中的scl_highhe scl_low信号计数
reg sda_en ;//三态信号sda使能
reg sda_reg ;//三态信号sda寄存数据
reg [ 7: 0] device_addr_a ;//传输的8位设备地址
reg [ 2: 0] sda_reg_cnt ;//传输的8位数据传递数
wire [ 7: 0] word_addr_high;//数据字地址高8位
wire [ 7: 0] word_addr_low ;//数据字地址低8位
assign word_addr_high = word_addr[15:8];
assign word_addr_low = word_addr[ 7:0];
assign sda = sda_en ? sda_reg : 1'bz;
//sclk计数器计数标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
add_flag <= 0;
end
else if(wr_en || rd_en) begin
add_flag <= 1;
end
else if(done)begin
add_flag <= 0;
end
end
//scl信号计数器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sclk_cnt <= 0;
end
else if(add_flag) begin
if((add_flag && sclk_cnt==SCLK_CNT-1) || done)
sclk_cnt <= 0;
else
sclk_cnt <= sclk_cnt + 1'b1;
end
end
//scl信号生成
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl <= 1;
end
else if(sclk_cnt==SCLK_CNT/2-1) begin
scl <= 0;
end
else if(sclk_cnt==1'b0)begin
scl<= 1;
end
else begin
scl <= scl;
end
end
//scl_high信号:scl信号高电平正中间脉冲
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_high <= 0;
end
else if(sclk_cnt==SCLK_CNT*1/4-1) begin
scl_high <= 1;
end
else begin
scl_high <= 0;
end
end
//scl_low信号:scl信号低电平正中间脉冲
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
scl_low <= 0;
end
else if(sclk_cnt==SCLK_CNT*3/4-1) begin
scl_low <= 1;
end
else begin
scl_low <= 0;
end
end
//写信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_flag <= 0;
end
else if(wr_en) begin
wr_flag <= 1;
end
else if(done) begin
wr_flag <= 0;
end
end
//读信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_flag <= 0;
end
else if(rd_en) begin
rd_flag <= 1;
end
else if(done) begin
rd_flag <= 0;
end
end
//状态机第一段
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机第二段
always @(*)begin
case(state_c)
IDLE :begin
if(wr_en || rd_en)
state_n = WR_START;
else
state_n = state_c;
end
WR_START :begin
if(scl==0 && scl_low)
state_n = WR_DEVICE;
else
state_n = state_c;
end
WR_DEVICE :begin
if(ack==1 && scl_low)
state_n = WR_ADDR;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_ADDR :begin
if(wr_flag && ack==1 && waddr_cnt==1 && scl_low)
state_n = WR_DATA;
else if(rd_flag && ack==1 && waddr_cnt==1 &&scl_low)
state_n = RD_START;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
WR_DATA :begin
if(wdata_cnt==wr_data_num-1 && ack==1 && wr_flag==1 && scl_low)
state_n = STOP;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_START :begin
if(scl==0 && scl_low)
state_n = RD_DEVICE;
else
state_n = state_c;
end
RD_DEVICE :begin
if(ack==1 && scl_low)
state_n = RD_DATA;
else if(ack==0 && scl_low && bit_cnt==17)
state_n = IDLE;
else
state_n = state_c;
end
RD_DATA :begin
if(rdata_cnt==rd_data_num-1 && rd_flag==1 && bit_cnt==17 && scl_low)
state_n = STOP;
else
state_n = state_c;
end
STOP :begin
if(scl_high)
state_n = IDLE;
else
state_n = state_c;
end
endcase
end
//在传输设备地址、字地址和数据时要传输8位数据,这里对其计数,计数scl_low和scl_high
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
bit_cnt <= 0;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(scl_high || scl_low)begin
if(bit_cnt==17 && scl_low)
bit_cnt <= 0;
else
bit_cnt <= bit_cnt + 1'b1;
end
end
else begin
bit_cnt <= bit_cnt;
end
end
//ack响应信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
ack <= 0;
end
else if(scl_high && bit_cnt==16 && sda==0) begin
ack <= 1;
end
else if(bit_cnt==17 && scl_low)begin
ack <= 0;
end
else begin
ack <= ack;
end
end
//2个字地址计数器
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
waddr_cnt <= 0;
end
else if(state_c==WR_ADDR && bit_cnt==17 && scl_low) begin
if(waddr_cnt==1)
waddr_cnt <= 0;
else
waddr_cnt <= waddr_cnt + 1'b1;
end
else begin
waddr_cnt <= waddr_cnt;
end
end
//写数据个数计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wdata_cnt <= 0;
end
else if(state_c==WR_DATA && bit_cnt==17 && scl_low) begin
if(wdata_cnt==wr_data_num-1)
wdata_cnt <= 0;
else
wdata_cnt <= wdata_cnt + 1'b1;
end
else begin
wdata_cnt <= wdata_cnt;
end
end
//读数据个数计数
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rdata_cnt <= 0;
end
else if(state_c==RD_DATA && bit_cnt==17 && scl_low) begin
if(rdata_cnt==rd_data_num-1)
rdata_cnt <= 0;
else
rdata_cnt <= rdata_cnt + 1'b1;
end
else begin
rdata_cnt <= rdata_cnt;
end
end
//sda_en信号输出
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_en <= 0;
end
else begin
case(state_c)
IDLE:sda_en <= 0;
WR_START,RD_START,STOP:sda_en <= 1;
WR_DEVICE,WR_ADDR,WR_DATA,RD_DEVICE:
begin
if(bit_cnt<16)
sda_en <= 1;
else
sda_en <= 0;
end
RD_DATA:
//sda_en <= 0;
begin
if(bit_cnt<16)
sda_en <= 0;
else
sda_en <= 1;
end
default:sda_en <= 0;
endcase
end
end
//wr_data_vaild信号:写数据有效信号标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
wr_data_vaild <= 0;
end
else if(state_c==WR_DATA && ack==1 && scl_low && bit_cnt==17) begin
wr_data_vaild <= 1;
end
else begin
wr_data_vaild <= 0;
end
end
//rd_data信号:读出8位数据
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data <= 0;
end
else if(state_c==RD_DATA && bit_cnt<15 && scl_high) begin
rd_data <= {rd_data[6:0],sda};
end
else begin
rd_data <= rd_data;
end
end
//rd_data_vaild信号:读出8位数据有效信号
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
rd_data_vaild <= 0;
end
else if(state_c==RD_DATA && bit_cnt==15 && scl_low) begin
rd_data_vaild <= 1;
end
else begin
rd_data_vaild <= 0;
end
end
//done:I2C工作结束标志
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
done <= 0;
end
else if(state_c==STOP && scl_high) begin
done <= 1;
end
else begin
done <= 0;
end
end
//设备地址
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
device_addr_a <= 0;
end
else if(rd_flag && (state_c==RD_DEVICE || state_c==RD_START || state_c==RD_DATA))begin
device_addr_a <= {4'b1010,device_addr,1'b1};
end
else begin
device_addr_a <= {4'b1010,device_addr,1'b0};
end
end
//sda_reg_cnt
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg_cnt <= 7;
end
else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
if(bit_cnt<16 && scl_low)begin
if(sda_reg_cnt==0)
sda_reg_cnt <= 7;
else
sda_reg_cnt <= sda_reg_cnt - 1'b1;
end
end
else begin
sda_reg_cnt <= sda_reg_cnt;
end
end
//sda_reg
always @(posedge clk or negedge rst_n)begin
if(rst_n==1'b0)begin
sda_reg <= 1;
end
else begin
case(state_c)
WR_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
WR_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
WR_ADDR:begin
if(waddr_cnt==0)
sda_reg <= word_addr_high[sda_reg_cnt];
else if(waddr_cnt==1)
if(bit_cnt<16)
sda_reg <= word_addr_low[sda_reg_cnt];
else
sda_reg <= 1;
end
WR_DATA:begin
sda_reg <= wr_data[sda_reg_cnt]; //输入信号wr_data新数据应该wr_data_vaild时给出
end
RD_START:begin
if(scl_high)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end
RD_DEVICE:begin
sda_reg <= device_addr_a[sda_reg_cnt];
end
RD_DATA:/*begin
if(bit_cnt==16 || bit_cnt==17)
sda_reg <= 0;
else
sda_reg <= sda_reg;
end*/
if(rdata_cnt==rd_data_num-1 && bit_cnt>15)
sda_reg <= 1;
else
sda_reg <= 0;
STOP:begin
sda_reg <= 0;
/*if(scl_high)
sda_reg <= 1;
else
sda_reg <= sda_reg;*/
end
default: sda_reg <= 1;
endcase
end
end
endmodule
这里采用的EEPROM仿真模型进行仿真验证,使用的是镁光的EEPROM仿真模型。需要的人可以从我的工程里边下载,完整工程源码看上方下载链接,这里不多赘述。测试代码如下:
`timescale 1 ns/ 1 ns
module I2C_ctrl_tb();
// test vector input registers
reg clk;
//reg [2:0] device_addr;
//reg [5:0] rd_data_num;
reg rd_en;
reg rst_n;
//reg treg_sda;
reg [15:0] word_addr;
reg [7:0] wr_data;
//reg [5:0] wr_data_num;
reg wr_en;
// wires
wire done;
wire [7:0] rd_data;
wire rd_data_vaild;
wire scl;
wire sda;
wire wr_data_vaild;
pullup(sda);
// assign statements (if any)
//assign sda = treg_sda;
parameter clk_period = 20;
I2C_ctrl i1 (
// port map - connection between master ports and signals/registers
.clk(clk),
.device_addr(3'b000),
.done(done),
.rd_data(rd_data),
.rd_data_num(6'd4),
.rd_data_vaild(rd_data_vaild),
.rd_en(rd_en),
.rst_n(rst_n),
.scl(scl),
.sda(sda),
.word_addr(word_addr),
.wr_data(wr_data),
.wr_data_num(6'd4),
.wr_data_vaild(wr_data_vaild),
.wr_en(wr_en)
);
M24LC64 u_M24LC64(
.A0(1'b0),
.A1(1'b0),
.A2(1'b0),
.WP(1'b0),
.SDA(sda),
.SCL(scl),
.RESET(!rst_n)
);
initial clk = 0;
always #(clk_period/2) clk=~clk;
initial begin
#2;
rst_n = 0;
word_addr = 0;
wr_en = 0;
wr_data = 0;
rd_en = 0;
#(clk_period*200);
rst_n = 1;
#(clk_period*20);
//写入20组数据
word_addr = 0;
wr_data = 0;
repeat(20)begin
wr_en = 1;
#(clk_period);
wr_en = 0;
repeat(4)begin
@(posedge wr_data_vaild)
wr_data = wr_data + 1;
end
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
//读出刚写入的20组数据
word_addr = 0;
repeat(20)begin
rd_en = 1;
#(clk_period);
rd_en = 0;
@(posedge done);
#(clk_period*200);
word_addr = word_addr + 4;
end
#(clk_period*500);
$stop;
end
endmodule
下图是对AT24C64型号EEPROM仿真结果:
下图是对AT24C64型号EEPROM写时序仿真结果:
下图是对AT24C64型号EEPROM读时序仿真结果:
仿真结果符合预期效果。下一篇将会在对I2C接口模块进行板级调试,设计了一个EEPROM读写系统实验,以验证设计的正确性。