Chapter007-FPGA学习之IIC总线EEPROM读取
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
//
// Company:
// Engineer:
//
// Create Date: 2022/07/21 21:29:06
// Design Name:
// Module Name: eeprom_top
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//module eeprom_top(input sys_clk , //系统时钟input sys_rst_n , //系统复位//eeprom interfaceoutput iic_scl , //eeprom的时钟线sclinout iic_sda , //eeprom的数据线sda//user interfaceoutput led //led显示);//parameter define
parameter SLAVE_ADDR = 7'b1010000 ; //器件地址(SLAVE_ADDR)
parameter BIT_CTRL = 1'b1 ; //字地址位控制参数(16b/8b)
parameter CLK_FREQ = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter I2C_FREQ = 18'd250_000 ; //I2C的SCL时钟频率
parameter L_TIME = 17'd125_000 ; //led闪烁时间参数//wire define
wire dri_clk ; //I2C操作时钟
wire i2c_exec ; //I2C触发控制
wire [15:0] i2c_addr ; //I2C操作地址
wire [ 7:0] i2c_data_w; //I2C写入的数据
wire i2c_done ; //I2C操作结束标志
wire i2c_ack ; //I2C应答标志 0:应答 1:未应答
wire i2c_rh_wl ; //I2C读写控制
wire [ 7:0] i2c_data_r; //I2C读出的数据
wire rw_done ; //E2PROM读写测试完成
wire rw_result ; //E2PROM读写测试结果 0:失败 1:成功 //*****************************************************
//** main code
//*****************************************************
//e2prom读写测试模块
eeprom_rw u_eeprom_rw(.clk (dri_clk ), //时钟信号.rst_n (sys_rst_n ), //复位信号//i2c interface.i2c_exec (i2c_exec ), //I2C触发执行信号.i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号.i2c_addr (i2c_addr ), //I2C器件内地址.i2c_data_w (i2c_data_w), //I2C要写的数据.i2c_data_r (i2c_data_r), //I2C读出的数据.i2c_done (i2c_done ), //I2C一次操作完成.i2c_ack (i2c_ack ), //I2C应答标志 //user interface.rw_done (rw_done ), //E2PROM读写测试完成.rw_result (rw_result ) //E2PROM读写测试结果 0:失败 1:成功
);//i2c驱动模块
i2c_driver #(.SLAVE_ADDR (SLAVE_ADDR), //EEPROM从机地址.CLK_FREQ (CLK_FREQ ), //模块输入的时钟频率.I2C_FREQ (I2C_FREQ ) //IIC_SCL的时钟频率
) u_i2c_driver(.clk (sys_clk ), .rst_n (sys_rst_n ), //i2c interface.i2c_exec (i2c_exec ), //I2C触发执行信号.bit_ctrl (BIT_CTRL ), //器件地址位控制(16b/8b).i2c_rh_wl (i2c_rh_wl ), //I2C读写控制信号.i2c_addr (i2c_addr ), //I2C器件内地址.i2c_data_w (i2c_data_w), //I2C要写的数据.i2c_data_r (i2c_data_r), //I2C读出的数据.i2c_done (i2c_done ), //I2C一次操作完成.i2c_ack (i2c_ack ), //I2C应答标志.scl (iic_scl ), //I2C的SCL时钟信号.sda (iic_sda ), //I2C的SDA信号//user interface.dri_clk (dri_clk ) //I2C操作时钟
);//led指示模块
led_alarm #(.L_TIME(L_TIME ) //控制led闪烁时间
) 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
//
// Company:
// Engineer:
//
// Create Date: 2022/07/21 21:36:55
// Design Name:
// Module Name: i2c_driver
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//module i2c_driver#(parameter SLAVE_ADDR = 7'b1010000 , //EEPROM从机地址parameter CLK_FREQ = 26'd50_000_000, //模块输入的时钟频率parameter I2C_FREQ = 18'd250_000 //IIC_SCL的时钟频率)(input clk , input rst_n , //i2c interface input i2c_exec , //I2C触发执行信号input bit_ctrl , //字地址位控制(16b/8b)input i2c_rh_wl , //I2C读写控制信号input [15:0] i2c_addr , //I2C器件内地址input [ 7:0] i2c_data_w , //I2C要写的数据output reg [ 7:0] i2c_data_r , //I2C读出的数据output reg i2c_done , //I2C一次操作完成output reg i2c_ack , //I2C应答标志 0:应答 1:未应答output reg scl , //I2C的SCL时钟信号inout sda , //I2C的SDA信号//user interface output reg dri_clk //驱动I2C操作的驱动时钟);//localparam define
localparam st_idle = 8'b0000_0001; //空闲状态
localparam st_sladdr = 8'b0000_0010; //发送器件地址(slave address)
localparam st_addr16 = 8'b0000_0100; //发送16位字地址
localparam st_addr8 = 8'b0000_1000; //发送8位字地址
localparam st_data_wr = 8'b0001_0000; //写数据(8 bit)
localparam st_addr_rd = 8'b0010_0000; //发送器件地址读
localparam st_data_rd = 8'b0100_0000; //读数据(8 bit)
localparam st_stop = 8'b1000_0000; //结束I2C操作//reg define
reg sda_dir ; //I2C数据(SDA)方向控制
reg sda_out ; //SDA输出信号
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 ; //I2C需写的数据的临时寄存
reg [ 9:0] clk_cnt ; //分频时钟计数//wire define
wire sda_in ; //SDA输入信号
wire [8:0] clk_divide ; //模块驱动时钟的分频系数//*****************************************************
//** main code
//*****************************************************//SDA控制
assign sda = sda_dir ? sda_out : 1'bz ; //SDA数据输出或高阻
assign sda_in = sda ; //SDA数据输入
assign clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2 ; //模块驱动时钟的分频系数//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) beginif(!rst_n) begindri_clk <= 1'b0;clk_cnt <= 10'd0;endelse if(clk_cnt == clk_divide[8:1] - 1'd1) beginclk_cnt <= 10'd0;dri_clk <= ~dri_clk;endelseclk_cnt <= clk_cnt + 1'b1;
end//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) beginif(!rst_n)cur_state <= st_idle;elsecur_state <= next_state;
end//组合逻辑判断状态转移条件
always @(*) beginnext_state = st_idle;case(cur_state)st_idle: begin //空闲状态if(i2c_exec) beginnext_state = st_sladdr;endelsenext_state = st_idle;endst_sladdr: beginif(st_done) beginif(bit_ctrl) //判断是16位还是8位字地址next_state = st_addr16;elsenext_state = st_addr8 ;endelsenext_state = st_sladdr;endst_addr16: begin //写16位字地址if(st_done) beginnext_state = st_addr8;endelse beginnext_state = st_addr16;endendst_addr8: begin //8位字地址if(st_done) beginif(wr_flag==1'b0) //读写判断next_state = st_data_wr;elsenext_state = st_addr_rd;endelse beginnext_state = st_addr8;endendst_data_wr: begin //写数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_wr;endst_addr_rd: begin //写地址以进行读数据if(st_done) beginnext_state = st_data_rd;endelse beginnext_state = st_addr_rd;endendst_data_rd: begin //读取数据(8 bit)if(st_done)next_state = st_stop;elsenext_state = st_data_rd;endst_stop: begin //结束I2C操作if(st_done)next_state = st_idle;elsenext_state = st_stop ;enddefault: next_state= st_idle;endcase
end//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin//复位初始化if(!rst_n) beginscl <= 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; //开始I2C7'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; //0:写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 //写数据(8 bit)case(cnt) 7'd0: begin sda_out <= data_wr_t[7]; //I2C写8位数据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; //1:读7'd33: scl <= 1'b1; 7'd35: scl <= 1'b0; 7'd36: begin sda_dir <= 1'b0; sda_out <= 1'b1; end7'd37: scl <= 1'b1;7'd38: begin //从机应答st_done <= 1'b1; if(sda_in == 1'b1) //高电平表示未应答i2c_ack <= 1'b1; //拉高应答标志位 end 7'd39: beginscl <= 1'b0;cnt <= 1'b0;enddefault : ;endcaseendst_data_rd: begin //读取数据(8 bit)case(cnt)7'd0: sda_dir <= 1'b0;7'd1: begindata_r[7] <= sda_in;scl <= 1'b1;end7'd3: scl <= 1'b0;7'd5: begindata_r[6] <= sda_in ;scl <= 1'b1 ;end7'd7: scl <= 1'b0;7'd9: begindata_r[5] <= sda_in;scl <= 1'b1 ;end7'd11: scl <= 1'b0;7'd13: begindata_r[4] <= sda_in;scl <= 1'b1 ;end7'd15: scl <= 1'b0;7'd17: begindata_r[3] <= sda_in;scl <= 1'b1 ;end7'd19: scl <= 1'b0;7'd21: begindata_r[2] <= sda_in;scl <= 1'b1 ;end7'd23: scl <= 1'b0;7'd25: begindata_r[1] <= sda_in;scl <= 1'b1 ;end7'd27: scl <= 1'b0;7'd29: begindata_r[0] <= sda_in;scl <= 1'b1 ;end7'd31: scl <= 1'b0;7'd32: beginsda_dir <= 1'b1; sda_out <= 1'b1;end7'd33: scl <= 1'b1;7'd34: st_done <= 1'b1; //非应答7'd35: beginscl <= 1'b0;cnt <= 1'b0;i2c_data_r <= data_r;enddefault : ;endcaseendst_stop: begin //结束I2C操作case(cnt)7'd0: beginsda_dir <= 1'b1; //结束I2Csda_out <= 1'b0;end7'd1 : scl <= 1'b1;7'd3 : sda_out <= 1'b1;7'd15: st_done <= 1'b1;7'd16: begincnt <= 1'b0;i2c_done <= 1'b1; //向上层模块传递I2C结束信号enddefault : ;endcaseendendcaseend
end endmodule
LED的报警模块【led_alarm.v】
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/07/21 22:03:45
// Design Name:
// Module Name: led_alarm
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//module led_alarm #(parameter L_TIME = 25'd25_000_000)(input clk , //时钟信号input rst_n , //复位信号input rw_done , //错误标志input rw_result , //E2PROM读写测试完成output reg led //E2PROM读写测试结果 0:失败 1:成功);//reg define
reg rw_done_flag; //读写测试完成标志
reg [24:0] led_cnt ; //led计数//*****************************************************
//** main code
//***************************************************** always @(posedge clk or negedge rst_n)beginif(!rst_n)rw_done_flag <= 1'b0;else if(rw_done)rw_done_flag <= 1'b1;
end //错误标志为1时PL_LED0闪烁,否则PL_LED0常亮
always @(posedge clk or negedge rst_n) beginif(!rst_n)beginled_cnt <= 25'd0;led <= 1'b0;endelse beginif(rw_done_flag)beginif(rw_result)led <= 1'b1;else beginled_cnt <= led_cnt + 25'd1;if(led_cnt == L_TIME - 1'b1)beginled_cnt <= 25'd0;led <= ~led;endendendelseled <= 1'b0;end
endendmodule
EEPROM的读写判断模块【eeprom_rw.v】
该部分逻辑较为简单,使用单段式状态机实现(状态跳转及状态动作在单段代码实现)。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/07/21 21:34:30
// Design Name:
// Module Name: eeprom_rw
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//module eeprom_rw(input clk , //时钟信号input rst_n , //复位信号//i2c interfaceoutput reg i2c_rh_wl , //I2C读写控制信号output reg i2c_exec , //I2C触发执行信号output reg [15:0] i2c_addr , //I2C器件内地址output reg [ 7:0] i2c_data_w , //I2C要写的数据input [ 7:0] i2c_data_r , //I2C读出的数据input i2c_done , //I2C一次操作完成input i2c_ack , //I2C应答标志//user interfaceoutput reg rw_done , //E2PROM读写测试完成output reg rw_result //E2PROM读写测试结果 0:失败 1:成功);//parameter define
//EEPROM写数据需要添加间隔时间,读数据则不需要
parameter WR_WAIT_TIME = 14'd5000; //写入间隔时间
parameter MAX_BYTE = 16'd256 ; //读写测试的字节个数//reg define
reg [1:0] flow_cnt ; //状态流控制
reg [13:0] wait_cnt ; //延时计数器//*****************************************************
//** main code
//*****************************************************
//EEPROM读写测试,先写后读,并比较读出的值与写入的值是否一致
always @(posedge clk or negedge rst_n) beginif(!rst_n) beginflow_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; endelse begini2c_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 //EEPROM写操作延时完成wait_cnt <= 1'b0;if(i2c_addr == MAX_BYTE) begin //256个字节写入完成i2c_addr <= 1'b0;i2c_rh_wl <= 1'b1;flow_cnt <= 2'd2;endelse beginflow_cnt <= flow_cnt + 1'b1;i2c_exec <= 1'b1;endendend2'd1 : beginif(i2c_done == 1'b1) begin //EEPROM单次写入完成flow_cnt <= 2'd0;i2c_addr <= i2c_addr + 1'b1; //地址0~255分别写入i2c_data_w <= i2c_data_w + 1'b1; //数据0~255end end2'd2 : begin flow_cnt <= flow_cnt + 1'b1;i2c_exec <= 1'b1;end 2'd3 : beginif(i2c_done == 1'b1) begin //EEPROM单次读出完成//读出的值错误或者I2C未应答,读写测试失败if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) beginrw_done <= 1'b1;rw_result <= 1'b1;endelse if(i2c_addr == MAX_BYTE - 1'b1) begin //读写测试成功rw_done <= 1'b1;rw_result <= 1'b0;end else beginflow_cnt <= 2'd2;i2c_addr <= i2c_addr + 1'b1;endend enddefault : ;endcase end
end endmodule
完成代码编辑后,将代码编译为二进制流,并下载到FPGA上。
实验结果
按下复位后,经过一小段时间,EEPROM读写模块校正成功,LED开始闪烁
至此,实验成功,IIC功能模块能够移植使用。
Chapter007-FPGA学习之IIC总线EEPROM读取相关推荐
- IIC总线随机读VHDL实现FIFO实现乒乓操作HM62256测试定制IP核
博客简介 本博客是本人大二上学期数字系统实验硬件描述3的内容,在此记录以防丢失.目录如下: IIC串行总线时序分析 VHDL编程设计专门状态机与2片异步FIFO来实现乒乓操作 设计HM62256测试电 ...
- 【五一特刊】FPGA零基础学习:IIC协议驱动设计
本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的"傻瓜式"讲解,让电子.信息.通信类专业学生.初入职场小白及打算进阶提升的职 ...
- 用Proteus学习51单片机之I2C(IIC)总线
最近刚做好一个站,基于rails 3,教程为主,大家捧场看看,谢谢!www.yo945.com 在学习单片机的过程中,我常有这样的烦恼:随随便便一个芯片,少则占用三五个IO口,一般的就占用8个,稍微想 ...
- fpga驱动oled iic显示代码_【接口时序】6、IIC总线的原理与Verilog实现
欢迎FPGA工程师加入官方微信技术群 点击蓝字关注我们FPGA之家-中国最好最大的FPGA纯工程师社群 一. 软件平台与硬件平台 软件平台: 1.操作系统:Windows-8.1 2.开发套件:ISE ...
- 【乌拉喵.教程】IIC总线介绍及FPGA编程
最近将多年来收集到的教学视频.国内外图书.源码等整理整合拿出来,涉及arm.Linux.python.信号完整性.FPFA.DSP.算法.stm32.单片机.制图.电子模块.kali.出版社图书等.资 ...
- IIC总线通讯协议、EEPROM芯片
EEPROM芯片: 掉电不会丢失数据,可以保存数据. IIC串行总线的组成及工作原理: IIC总线传输协议 IIC产生起始与终止信号: IIC字节的传送与应答: 应答位作用: 数据帧格式: 总线寻址 ...
- FPGA实现IIC协议(一)----初识IIC总线
写在前面 IIC协议系列博文: FPGA实现IIC协议(一)----初识IIC总线 FPGA实现IIC协议(二)----IIC总线的FPGA实现(单次读写驱动) 1.什么是IIC协议 IIC通讯协议( ...
- iic获取salve设备地址_Linux下使用IIC总线读写EEPROM(读写i2c从设备通用程序)
Linux 下使用IIC总线 读写 EEPROM by 韩大卫 @吉林师范大学 handawei@jusontech.com 转载请务必表明出处 ******************* ******* ...
- FPGA零基础学习:IIC协议驱动设计
FPGA零基础学习:IIC协议驱动设计 本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的"傻瓜式"讲解,让电子.信息.通信类 ...
最新文章
- 剑指offer六十一之序列化二叉树(待补充)
- 在centos上搭建svn服务器
- Winform中连接Mysql8并查询表中数据进行显示
- c语言程序朴素贝叶斯分类器,生成式学习算法(四)之----朴素贝叶斯分类器
- 无法对 null 引用执行运行时绑定_你真的懂this吗?聊聊默认绑定,隐式绑定,显示绑定,new绑定...
- Facebook为Messenger应用添加群组付款功能
- Magicodes.IE之快速导出Excel
- HDU 3966 树链剖分后线段树维护
- 十年WEB技术发展历程
- 《兔兔公司的历史》那些年,百度的荣耀和沉沦
- 天池 在线编程 推荐朋友(哈希)
- Apache 查看连接数
- c语言程序设计的实验仪器和设备,C语言程序设计实验.doc
- go开发属于自己的日志库-日志库易用性封装
- 云计算相关的一些概念Baas、Saas、Iaas、Paas
- 如何学习单片机?单片机c语言编程入门教程
- PS4在Jetson nano下的配对使用,并用ROS接口来控制
- 产品经理进修第六天 产品经理面试
- kafka consumer 如何设置每次重启时从最新数据开始读取
- UART数据发送和接收(Verilog)