IIC总线是嵌入式领域较为重要的器件间通信总线,同样,FPGA也能通过模块的形式实现IIC的功能,其原理和STM32的模拟IIC总线一致,就是控制每个时间点的SCL、SDA总线电平。
IIC总线需要对应的从机机通信器件进行通信,故目前使用AT24C64【IIC总线的EEPROM】作为从机,通过实现对EEPROM的读、写,来学习IIC协议在FPGA的实现。
硬件原理
由于IIC本质就是2线通信方式,以从机地址作为鉴别依据,在IIC总线上进行点对点通信,故不再对细节的硬件进行描述,使用拓扑图即可说明问题,再IIC总线上,EEPROM的从机地址为7‘h50,也就是二进制的0b1010000。
IIC总线的通信波形图我就不过多赘述,大致电文思路就是:
开始–>器件地址输出–>读写功能输出–>等待反馈–>数据地址(读/写)–>等待反馈–>数据与内容(读/写)–>等待反馈–>结束
相关线路的引脚如下:
时序分为写时序、读时序,具体内容如下:
写时序:
连续写就是将8bit的数据与往后继续发送;
读时序:
若要实现连续读,将最后一个主机非应答变为主机应答即可,需要读几个就应答几次。
设计目标
设计目标为将EEPROM中,地址从0开始到255顺序写入0 ~ 255的数值,写入完成后,将地址0 ~ 255的数据读取出来,并检查是否地址与数据相对应,若数据与地址完全一致,则使红色PL_LED闪烁、否则PL_LED常亮。
使用到的资源为:
1、IIC的SCL线和SDA线
2、用于指示的LED灯
3、复位和系统时钟
模块分析
由于需要使用到EEPROM外设,而外设使用IIC总线进行通信,故首先需要实现IIC总线驱动模块;
输入:
位控制信号【数据地址可能位0 ~ 255(8bit)或0 ~ 65535(16bit)】
系统时钟
IIC的数据地址
IIC需要写入的数据【8bit】
IIC使能信号
IIC读写控制信号
模块复位信号
输出:
上层模块的控制时钟【用于让上层模块使用匹配的频率向该模块发送数据】
IIC是否收到应答的标志
IIC读取到的数据【8bit】
IIC当前操作完成标志
IIC总线的SCL信号
IIC总线的SDA信号
当有了IIC的驱动模块后,上层的功能只需要将对应的地址、数据按一定的节拍发送给IIC驱动模块,即可实现IIC的通信,故需要实现EEPROM的读写给,需要有一个读写流程的逻辑模块实现这一功能;
输入:
IIC操作时钟
IIC是否收到应答的标志
IIC读取到的数据【8bit】
IIC当前操作完成标记
复位
输出:
需要操作的IIC地址
IIC写入的数据
IIC驱动模块的使能信号
IIC的读写标记
读写完成标记【用于驱动LED模块】
读写结果【用于驱动LED模块】
最后就是之前提到的用于描述实验是否成功的LED模块,若写入和读取内容一致,则LED闪烁,若不一致则LED长亮;
输入:
运行时钟
复位
读写完成标记
读写结果
输出:
LED灯控制信号
之后使用上层模块将上述子模块例化,即可得到整体设计;
输入:
系统时钟
复位
输出:
IIC_CLK
IIC_SDA
LED
模块原理图如下所示:
代码编辑
Verilog语言的代码包括以下几个部分:
一个用于例化子模块的顶层模块;
- IIC驱动模块【i2c_driver.v】
- EEPROM读写控制模块【eeprom_rw.v】
- led报警模块【led_alarm.v】
顶层模块相关代码
顶层模块代码(eeprom_top.v):
`timescale 1ns / 1ps
module eeprom_top(
input sys_clk ,
input sys_rst_n ,
output iic_scl ,
inout iic_sda ,
output led
);
parameter SLAVE_ADDR = 7'b1010000 ;
parameter BIT_CTRL = 1'b1 ;
parameter CLK_FREQ = 26'd50_000_000 ;
parameter I2C_FREQ = 18'd250_000 ;
parameter L_TIME = 17'd125_000 ;
wire dri_clk ;
wire i2c_exec ;
wire [15:0] i2c_addr ;
wire [ 7:0] i2c_data_w;
wire i2c_done ;
wire i2c_ack ;
wire i2c_rh_wl ;
wire [ 7:0] i2c_data_r;
wire rw_done ;
wire rw_result ;
eeprom_rw u_eeprom_rw(
.clk (dri_clk ),
.rst_n (sys_rst_n ),
.i2c_exec (i2c_exec ),
.i2c_rh_wl (i2c_rh_wl ),
.i2c_addr (i2c_addr ),
.i2c_data_w (i2c_data_w),
.i2c_data_r (i2c_data_r),
.i2c_done (i2c_done ),
.i2c_ack (i2c_ack ),
.rw_done (rw_done ),
.rw_result (rw_result )
);
i2c_driver #(
.SLAVE_ADDR (SLAVE_ADDR),
.CLK_FREQ (CLK_FREQ ),
.I2C_FREQ (I2C_FREQ )
) u_i2c_driver(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.i2c_exec (i2c_exec ),
.bit_ctrl (BIT_CTRL ),
.i2c_rh_wl (i2c_rh_wl ),
.i2c_addr (i2c_addr ),
.i2c_data_w (i2c_data_w),
.i2c_data_r (i2c_data_r),
.i2c_done (i2c_done ),
.i2c_ack (i2c_ack ),
.scl (iic_scl ),
.sda (iic_sda ),
.dri_clk (dri_clk )
);
led_alarm #(.L_TIME(L_TIME )
) u_led_alarm(
.clk (dri_clk ),
.rst_n (sys_rst_n ),
.rw_done (rw_done ),
.rw_result (rw_result ),
.led (led )
);
endmodule
顶层约束文件(eeprom_top.xdc):
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]
set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports iic_scl]
set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports iic_sda]
set_property -dict {PACKAGE_PIN H15 IOSTANDARD LVCMOS33} [get_ports led]
下层功能模块代码
IIC驱动模块,负责将数据与地址操作变为IIC上的电平信号【i2c_driver.v】
该模块将IIC总线输出信号使用复杂的三段式状态机表述【同步时序表述状态转移、组合逻辑判断状态转移、各状态下正常功能执行】,具体含义可见代码注释表述;
`timescale 1ns / 1ps
module i2c_driver
#(
parameter SLAVE_ADDR = 7'b1010000 ,
parameter CLK_FREQ = 26'd50_000_000,
parameter I2C_FREQ = 18'd250_000
)
(
input clk ,
input rst_n ,
input i2c_exec ,
input bit_ctrl ,
input i2c_rh_wl ,
input [15:0] i2c_addr ,
input [ 7:0] i2c_data_w ,
output reg [ 7:0] i2c_data_r ,
output reg i2c_done ,
output reg i2c_ack ,
output reg scl ,
inout sda ,
output reg dri_clk
);
localparam st_idle = 8'b0000_0001;
localparam st_sladdr = 8'b0000_0010;
localparam st_addr16 = 8'b0000_0100;
localparam st_addr8 = 8'b0000_1000;
localparam st_data_wr = 8'b0001_0000;
localparam st_addr_rd = 8'b0010_0000;
localparam st_data_rd = 8'b0100_0000;
localparam st_stop = 8'b1000_0000;
reg sda_dir ;
reg sda_out ;
reg st_done ;
reg wr_flag ;
reg [ 6:0] cnt ;
reg [ 7:0] cur_state ;
reg [ 7:0] next_state;
reg [15:0] addr_t ;
reg [ 7:0] data_r ;
reg [ 7:0] data_wr_t ;
reg [ 9:0] clk_cnt ;
wire sda_in ;
wire [8:0] clk_divide ;
assign sda = sda_dir ? sda_out : 1'bz ;
assign sda_in = sda ;
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
dri_clk <= 1'b0;
clk_cnt <= 10'd0;
end
else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
clk_cnt <= 10'd0;
dri_clk <= ~dri_clk;
end
else
clk_cnt <= clk_cnt + 1'b1;
end
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n)
cur_state <= st_idle;
else
cur_state <= next_state;
end
always @(*) begin
next_state = st_idle;
case(cur_state)
st_idle: begin
if(i2c_exec) begin
next_state = st_sladdr;
end
else
next_state = st_idle;
end
st_sladdr: begin
if(st_done) begin
if(bit_ctrl)
next_state = st_addr16;
else
next_state = st_addr8 ;
end
else
next_state = st_sladdr;
end
st_addr16: begin
if(st_done) begin
next_state = st_addr8;
end
else begin
next_state = st_addr16;
end
end
st_addr8: begin
if(st_done) begin
if(wr_flag==1'b0)
next_state = st_data_wr;
else
next_state = st_addr_rd;
end
else begin
next_state = st_addr8;
end
end
st_data_wr: begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_wr;
end
st_addr_rd: begin
if(st_done) begin
next_state = st_data_rd;
end
else begin
next_state = st_addr_rd;
end
end
st_data_rd: begin
if(st_done)
next_state = st_stop;
else
next_state = st_data_rd;
end
st_stop: begin
if(st_done)
next_state = st_idle;
else
next_state = st_stop ;
end
default: next_state= st_idle;
endcase
end
always @(posedge dri_clk or negedge rst_n) begin
if(!rst_n) begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done <= 1'b0;
i2c_ack <= 1'b0;
cnt <= 1'b0;
st_done <= 1'b0;
data_r <= 1'b0;
i2c_data_r<= 1'b0;
wr_flag <= 1'b0;
addr_t <= 1'b0;
data_wr_t <= 1'b0;
end
else begin
st_done <= 1'b0 ;
cnt <= cnt +1'b1 ;
case(cur_state)
st_idle: begin
scl <= 1'b1;
sda_out <= 1'b1;
sda_dir <= 1'b1;
i2c_done<= 1'b0;
cnt <= 7'b0;
if(i2c_exec) begin
wr_flag <= i2c_rh_wl ;
addr_t <= i2c_addr ;
data_wr_t <= i2c_data_w;
i2c_ack <= 1'b0;
end
end
st_sladdr: begin
case(cnt)
7'd1 : sda_out <= 1'b0;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b0;
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr16: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[15];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[14];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[13];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[12];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[11];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[10];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[9];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[8];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr8: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1 ;
sda_out <= addr_t[7];
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= addr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= addr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= addr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= addr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= addr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= addr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= addr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_wr: begin
case(cnt)
7'd0: begin
sda_out <= data_wr_t[7];
sda_dir <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= data_wr_t[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= data_wr_t[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= data_wr_t[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= data_wr_t[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= data_wr_t[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= data_wr_t[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= data_wr_t[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_addr_rd: begin
case(cnt)
7'd0 : begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd1 : scl <= 1'b1;
7'd2 : sda_out <= 1'b0;
7'd3 : scl <= 1'b0;
7'd4 : sda_out <= SLAVE_ADDR[6];
7'd5 : scl <= 1'b1;
7'd7 : scl <= 1'b0;
7'd8 : sda_out <= SLAVE_ADDR[5];
7'd9 : scl <= 1'b1;
7'd11: scl <= 1'b0;
7'd12: sda_out <= SLAVE_ADDR[4];
7'd13: scl <= 1'b1;
7'd15: scl <= 1'b0;
7'd16: sda_out <= SLAVE_ADDR[3];
7'd17: scl <= 1'b1;
7'd19: scl <= 1'b0;
7'd20: sda_out <= SLAVE_ADDR[2];
7'd21: scl <= 1'b1;
7'd23: scl <= 1'b0;
7'd24: sda_out <= SLAVE_ADDR[1];
7'd25: scl <= 1'b1;
7'd27: scl <= 1'b0;
7'd28: sda_out <= SLAVE_ADDR[0];
7'd29: scl <= 1'b1;
7'd31: scl <= 1'b0;
7'd32: sda_out <= 1'b1;
7'd33: scl <= 1'b1;
7'd35: scl <= 1'b0;
7'd36: begin
sda_dir <= 1'b0;
sda_out <= 1'b1;
end
7'd37: scl <= 1'b1;
7'd38: begin
st_done <= 1'b1;
if(sda_in == 1'b1)
i2c_ack <= 1'b1;
end
7'd39: begin
scl <= 1'b0;
cnt <= 1'b0;
end
default : ;
endcase
end
st_data_rd: begin
case(cnt)
7'd0: sda_dir <= 1'b0;
7'd1: begin
data_r[7] <= sda_in;
scl <= 1'b1;
end
7'd3: scl <= 1'b0;
7'd5: begin
data_r[6] <= sda_in ;
scl <= 1'b1 ;
end
7'd7: scl <= 1'b0;
7'd9: begin
data_r[5] <= sda_in;
scl <= 1'b1 ;
end
7'd11: scl <= 1'b0;
7'd13: begin
data_r[4] <= sda_in;
scl <= 1'b1 ;
end
7'd15: scl <= 1'b0;
7'd17: begin
data_r[3] <= sda_in;
scl <= 1'b1 ;
end
7'd19: scl <= 1'b0;
7'd21: begin
data_r[2] <= sda_in;
scl <= 1'b1 ;
end
7'd23: scl <= 1'b0;
7'd25: begin
data_r[1] <= sda_in;
scl <= 1'b1 ;
end
7'd27: scl <= 1'b0;
7'd29: begin
data_r[0] <= sda_in;
scl <= 1'b1 ;
end
7'd31: scl <= 1'b0;
7'd32: begin
sda_dir <= 1'b1;
sda_out <= 1'b1;
end
7'd33: scl <= 1'b1;
7'd34: st_done <= 1'b1;
7'd35: begin
scl <= 1'b0;
cnt <= 1'b0;
i2c_data_r <= data_r;
end
default : ;
endcase
end
st_stop: begin
case(cnt)
7'd0: begin
sda_dir <= 1'b1;
sda_out <= 1'b0;
end
7'd1 : scl <= 1'b1;
7'd3 : sda_out <= 1'b1;
7'd15: st_done <= 1'b1;
7'd16: begin
cnt <= 1'b0;
i2c_done <= 1'b1;
end
default : ;
endcase
end
endcase
end
end
endmodule
LED的报警模块【led_alarm.v】
`timescale 1ns / 1ps
module led_alarm #(parameter L_TIME = 25'd25_000_000)
(
input clk ,
input rst_n ,
input rw_done ,
input rw_result ,
output reg led
);
reg rw_done_flag;
reg [24:0] led_cnt ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
rw_done_flag <= 1'b0;
else if(rw_done)
rw_done_flag <= 1'b1;
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
led_cnt <= 25'd0;
led <= 1'b0;
end
else begin
if(rw_done_flag)begin
if(rw_result)
led <= 1'b1;
else begin
led_cnt <= led_cnt + 25'd1;
if(led_cnt == L_TIME - 1'b1)begin
led_cnt <= 25'd0;
led <= ~led;
end
end
end
else
led <= 1'b0;
end
end
endmodule
EEPROM的读写判断模块【eeprom_rw.v】
该部分逻辑较为简单,使用单段式状态机实现(状态跳转及状态动作在单段代码实现)。
`timescale 1ns / 1ps
module eeprom_rw(
input clk ,
input rst_n ,
output reg i2c_rh_wl ,
output reg i2c_exec ,
output reg [15:0] i2c_addr ,
output reg [ 7:0] i2c_data_w ,
input [ 7:0] i2c_data_r ,
input i2c_done ,
input i2c_ack ,
output reg rw_done ,
output reg rw_result
);
parameter WR_WAIT_TIME = 14'd5000;
parameter MAX_BYTE = 16'd256 ;
reg [1:0] flow_cnt ;
reg [13:0] wait_cnt ;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
flow_cnt <= 2'b0;
i2c_rh_wl <= 1'b0;
i2c_exec <= 1'b0;
i2c_addr <= 16'b0;
i2c_data_w <= 8'b0;
wait_cnt <= 14'b0;
rw_done <= 1'b0;
rw_result <= 1'b0;
end
else begin
i2c_exec <= 1'b0;
rw_done <= 1'b0;
case(flow_cnt)
2'd0 : begin
wait_cnt <= wait_cnt + 1'b1;
if(wait_cnt == WR_WAIT_TIME - 1'b1) begin
wait_cnt <= 1'b0;
if(i2c_addr == MAX_BYTE) begin
i2c_addr <= 1'b0;
i2c_rh_wl <= 1'b1;
flow_cnt <= 2'd2;
end
else begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
end
end
2'd1 : begin
if(i2c_done == 1'b1) begin
flow_cnt <= 2'd0;
i2c_addr <= i2c_addr + 1'b1;
i2c_data_w <= i2c_data_w + 1'b1;
end
end
2'd2 : begin
flow_cnt <= flow_cnt + 1'b1;
i2c_exec <= 1'b1;
end
2'd3 : begin
if(i2c_done == 1'b1) begin
if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
rw_done <= 1'b1;
rw_result <= 1'b1;
end
else if(i2c_addr == MAX_BYTE - 1'b1) begin
rw_done <= 1'b1;
rw_result <= 1'b0;
end
else begin
flow_cnt <= 2'd2;
i2c_addr <= i2c_addr + 1'b1;
end
end
end
default : ;
endcase
end
end
endmodule
完成代码编辑后,将代码编译为二进制流,并下载到FPGA上。
实验结果
按下复位后,经过一小段时间,EEPROM读写模块校正成功,LED开始闪烁
至此,实验成功,IIC功能模块能够移植使用。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)