IIC协议读写EEPROM
- 一、功能分析/模块划分
- 二、状态转移图
- 1、EEPROM读写控制状态转移图
- 2、IIC接口驱动状态转移图
- 三、工程代码实现
- 1、顶层模块
- 2、EEPROM读写控制模块
- 3、IIC接口驱动模块
- 4、参数配置
- 5、其他模块
- 四、仿真测试
- 五、上板验证
写在前面
FPGA实现IIC协议读写EEPROM相关文章:
IIC通信协议
【FPGA】FPGA实现IIC协议读写EEPROM(一) ----- IIC接口驱动实现
【FPGA】FPGA实现IIC协议读写EEPROM(二) -----EEPROM读写控制模块实现
【FPGA】FPGA实现IIC协议读写EEPROM(三) ----- 汇总篇
在前面几篇文章中介绍了IIC通信协议的相关时序、IIC接口驱动以及EEPROM读写控制模块的实现,在本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),EEPROM型号:24LC04B,本文将对IIC协议读写EEPROM工程进行综合。
一、功能分析/模块划分
下面介绍此工程需要实现哪些功能,以此进行功能模块划分。
- 通过按键发送读/写请求给EEPROM读写控制模块控制读写时序。
- 通过串口发送和接收读写数据。
- 通过数码管显示正在进行操作。
- 编写IIC接口驱动实现EEPROM和控制模块的通信时序。
- 编写EEPROM读写控制模块,实现读写时序控制。
通过上述功能分析,我们不难把工程进行模块划分,系统框图划分如下图所示:
二、状态转移图
1、EEPROM读写控制状态转移图
EEPROM读写控制状态机实现参考文章:EEPROM读写控制状态机
2、IIC接口驱动状态转移图
IIC接口驱动状态机参考文章:IIC接口驱动状态机
三、工程代码实现
1、顶层模块
顶层模块verilog代码如下:
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: i2c_eeprom
// Module Name: top
// Target Device: Cyclone IV E (EP4CE6F17C8), EEPROM(24LC04B)
// Tool versions: Quartus Prime 18.1
// Description: I2C读写EEPROM工程顶层设计模块
// **************************************************************
module top (
input clk ,
input rst_n ,
// key_filter
input [1:0] key_in ,
// input wr_req ,
// input rd_req ,
// uart
input uart_rxd ,
output uart_txd ,
// eeprom
inout sda ,
output scl ,
// 数码管
output [5:0] seg_sel ,
output [7:0] seg_dig
);
wire wr_req ;
wire rd_req ;
wire req ;
wire [7:0] wr_dout ;
wire [3:0] cmd ;
wire [7:0] rdout ;
wire rdout_vld ;
wire rw_done ;
wire sda_in ;
wire sda_out ;
wire sda_out_en ;
wire [1:0] rw_flag ;
// uart_rx
wire [7:0] rx_dout ;
wire rx_dout_vld ;
// uart_tx
wire [7:0] tx_din ;
wire tx_din_vld ;
wire ready ;
assign sda = sda_out_en ? sda_out : 1'bz ;
assign sda_in = sda ;
key_filter u_key_filter1 (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.key_in (key_in[0] ),
/*output reg */.key_out (wr_req )
);
key_filter u_key_filter2 (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.key_in (key_in[1] ),
/*output reg */.key_out (rd_req )
);
uart_rx u_uart_rx (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input */.uart_rx (uart_rxd ), // 接收到的串口数据
/**/
/*output [7:0] */.rx_dout (rx_dout ), // 串行数据转换成并行数据后输出给control模块
/*output */.rx_dout_vld (rx_dout_vld)
);
uart_tx u_uart_tx (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// control*/
/*input [7:0] */.tx_din (tx_din ),
/*input */.tx_din_vld (tx_din_vld),
/*output */.ready (ready ), // 给control模块的握手信号,表示可以接收数据进行发送
/*// 上位机*/
/*output */.uart_tx (uart_txd )
);
eeprom_ctrl eeprom_ctrl (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// key_filter*/
/*input */.wr_req (wr_req ), // 读请求
/*input */.rd_req (rd_req ), // 写请求
/*// uart_rx*/
/*input [7:0] */.rx_din (rx_dout ), // uart接收到的要发送的数据
/*input */.rx_din_vld (rx_dout_vld ), // 数据有效标志
/*// uart_tx*/
/*output [7:0] */.tx_dout (tx_din ), // 读回的数据给到串口发送模块
/*output */.tx_dout_vld (tx_din_vld ), // 数据有效
/*input m */.ready (ready ),
/*// i2c_interface*/
/*input [7:0] */.rdin (rdout ), // 从I2C接口读回的数据
/*input */.rdin_vld (rdout_vld ),
/*input */.rw_done (rw_done ), // 读写一字节数据完成标志
/*output */.req (req ), // 读写请求
/*output [7:0] */.wr_dout (wr_dout ), // 要发送的数据
/*output [3:0] */.cmd (cmd ),
/*output [1:0] */.rw_flag (rw_flag ) // 读写标志 00:空闲 01:写 10:读
);
i2c_interface u_i2c_interface (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// eeprom_ctrl*/
/*input */.req (req ), // 读写请求
/*input [7:0] */.wr_din (wr_dout ), // 需要发送的一字节数据
/*input [3:0] */.cmd (cmd ), // 控制命令组合
/*output [7:0] */.rdout (rdout ), // 读取的数据
/*output */.rdout_vld (rdout_vld ), // 读取数据有效标志
/*output */.rw_done (rw_done ), // 读写一字节完成标志
/*// EEPROM*/
/*input */.sda_in (sda_in ),
/*output */.sda_out (sda_out ),
/*output */.sda_out_en (sda_out_en ),
.scl (scl )
);
seg_driver u_seg_driver (
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*// eeprom_ctrl*/
/*// input [7:0]din , () // 要显示的数据*/
/*// input din_vld , () // 显示数据有效标志*/
/*input [1:0] */.rw_flag (rw_flag ), // 读写标志
/*// 数码管*/
/*output [5:0] */.seg_sel (seg_sel ),
/*output [7:0] */.seg_dig (seg_dig )
);
endmodule
2、EEPROM读写控制模块
EEPROM读写控制模块verilog代码如下:
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: i2c_eeprom
// Module Name: eeprom_ctrl
// Target Device: Cyclone IV E (EP4CE6F17C8) , EEPROM(24LC04B)
// Tool versions: Quartus Prime 18.1
// Description: 使用I2C协议读写EEPROM驱动控制模块
// **************************************************************
module eeprom_ctrl (
input clk ,
input rst_n ,
// key_filter
input wr_req , // 写请求
input rd_req , // 读请求
// uart_rx
input [7:0] rx_din , // uart接收到的要发送的数据
input rx_din_vld , // 数据有效标志
// uart_tx
output [7:0] tx_dout , // 读回的数据给到串口发送模块
output tx_dout_vld , // 数据有效
input ready ,
// i2c_interface
input [7:0] rdin , // 从I2C接口读回的数据
input rdin_vld ,
input rw_done , // 读写一字节数据完成标志
output req , // 读写请求
output [7:0] wr_dout , // 要发送的数据
output [3:0] cmd ,
// seg_driver
output [1:0] rw_flag // 读写标志 00:空闲 01:写 10:读
);
// 参数定义
// 状态机参数定义
localparam IDLE = 7'b000_0001 ,
SEND_WRREQ = 7'b000_0010 , // 发送写请求
SEND_RDREQ = 7'b000_0100 , // 发送读请求
WAIT_WRDONE = 7'b000_1000 , // 等待写数据完成
WAIT_RDDONE = 7'b001_0000 , // 等待读数据完成
WR_DONE = 7'b010_0000 , // 写数据完成
RD_DONE = 7'b100_0000 ; // 读数据完成
// 信号定义
reg [6:0] state_c ;
reg [6:0] state_n ;
reg [3:0] byte_num ; // 字节数
reg [3:0] cnt_byte ; // 字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [7:0] wrdata ; // 寄存将要发送的数据
reg [3:0] command ; // 寄存将要发送的命令
reg rw_req ;
reg [1:0] rwflag ;
// 状态转移条件
wire idle2sendwrreq ;
wire idle2sendrdreq ;
wire sendwrreq2waitwrdone ;
wire sendrdreq2waitrddone ;
wire waitwrdone2wrdone ;
wire waitwrdone2sendwrreq ;
wire waitrddone2rddone ;
wire waitrddone2sendrdreq ;
wire wrdone2idle ;
wire rddone2idle ;
reg [7:0] rxdata ; // 串口接收数据寄存
reg [7:0] txdata ; // 串口发送数据寄存
// wrfifo
// wire wrfifo_empty ;
// wire wrfifo_full ;
// wire [2:0] wrfifo_usedw ;
// wire [7:0] wrfifo_q ;
// wire wrfifo_rdreq ;
// // rdfifo
// wire rdfifo_empty ;
// wire rdfifo_full ;
// wire [2:0] rdfifo_usedw ;
// wire [7:0] rdfifo_q ;
// wire rdfifo_rdreq ;
// 状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
IDLE: begin
if(idle2sendwrreq)
state_n = SEND_WRREQ ;
else if(idle2sendrdreq)
state_n = SEND_RDREQ ;
else
state_n = state_c ;
end
SEND_WRREQ: begin
if(sendwrreq2waitwrdone)
state_n = WAIT_WRDONE ;
else
state_n = state_c ;
end
SEND_RDREQ: begin
if(sendrdreq2waitrddone)
state_n = WAIT_RDDONE ;
else
state_n = state_c ;
end
WAIT_WRDONE: begin
if(waitwrdone2wrdone)
state_n = WR_DONE ;
else if(waitwrdone2sendwrreq)
state_n = SEND_WRREQ ;
else
state_n = state_c ;
end
WAIT_RDDONE: begin
if(waitrddone2rddone)
state_n = RD_DONE ;
else if(waitrddone2sendrdreq)
state_n = SEND_RDREQ ;
else
state_n = state_c ;
end
WR_DONE: begin
if(wrdone2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
RD_DONE: begin
if(rddone2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移
assign idle2sendwrreq = state_c == IDLE && wr_req ;
assign idle2sendrdreq = state_c == IDLE && rd_req ;
assign sendwrreq2waitwrdone= state_c == SEND_WRREQ && (1'b1) ;
assign sendrdreq2waitrddone= state_c == SEND_RDREQ && (1'b1) ;
assign waitwrdone2wrdone = state_c == WAIT_WRDONE && end_cnt_byte ;
assign waitwrdone2sendwrreq= state_c == WAIT_WRDONE && (cnt_byte <= 2) && rw_done ;
assign waitrddone2rddone = state_c == WAIT_RDDONE && end_cnt_byte ;
assign waitrddone2sendrdreq= state_c == WAIT_RDDONE && (cnt_byte <= 3) && rw_done ;
assign wrdone2idle = state_c == WR_DONE && (1'b1) ;
assign rddone2idle = state_c == RD_DONE && (1'b1) ;
// byte_num
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
byte_num <= 0 ;
end
else if(wr_req)begin
byte_num <= 3 ;
end
else if(rd_req)begin
byte_num <= 4 ;
end
// else if(wrdone2idle | rddone2idle)begin
// byte_num <= 0 ;
// end
end
// cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0 ;
end
else begin
cnt_byte <= cnt_byte + 1 ;
end
end
end
assign add_cnt_byte = rw_done ;
assign end_cnt_byte = add_cnt_byte && (cnt_byte == byte_num - 1) ;
// rw_req
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rw_req <= 1'b0 ;
end
else if(idle2sendwrreq | idle2sendrdreq | sendwrreq2waitwrdone | sendrdreq2waitrddone)begin
rw_req <= 1'b1 ;
end
else begin
rw_req <= 1'b0 ;
end
end
// wrdata command
always @(*)begin
if(state_c == SEND_WRREQ || state_c == WAIT_WRDONE)begin
case (cnt_byte)
0: begin wrdata = 8'b1010_0000 ;command = 4'b1010 ; end // 写控制字
1: begin wrdata = 8'b1101_1001 ;command = 4'b1000 ; end // 写地址
2: begin wrdata = rxdata ;command = 4'b1001 ; end // 写数据
default: begin wrdata = 8'b0 ;command = 4'b0 ;end
endcase
end
else if(state_c == SEND_RDREQ || state_c == WAIT_RDDONE)begin
case (cnt_byte)
0: begin wrdata = 8'b1010_0000 ;command = 4'b1010 ; end // 写控制字
1: begin wrdata = 8'b1101_1001 ;command = 4'b1000 ; end // 读地址
2: begin wrdata = 8'b1010_0001 ;command = 4'b1010 ; end // 写读控制字
3: begin wrdata = 8'b0 ;command = 4'b0101 ; end // 读数据
default: begin wrdata = 8'b0 ;command = 4'b0 ;end
endcase
end
else begin
wrdata = wrdata ;
command = command ;
end
end
// rwflag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rwflag <= 0 ;
end
else if(wr_req)begin
rwflag <= 2'b01 ;
end
else if(rd_req)begin
rwflag <= 2'b10 ;
end
end
// rxdata
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rxdata <= 0 ;
end
else if(rx_din_vld)begin
rxdata <= rx_din ;
end
end
// txdata
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
txdata <= 0 ;
end
else if(rdin_vld)begin
txdata <= rdin ;
end
end
// wrfifo例化
// wrfifo wrfifo_inst (
// .aclr ( ~rst_n ),
// .clock ( clk ),
// .data ( rx_din ),
// .rdreq ( wrfifo_rdreq ),
// .wrreq ( rx_din_vld ),
// .empty ( wrfifo_empty ),
// .full ( wrfifo_full ),
// .q ( wrfifo_q ),
// .usedw ( wrfifo_usedw )
// );
// assign wrfifo_rdreq = wr_req && ~wrfifo_empty ;
// // rdfifo例化
// rdfifo rdfifo_inst (
// .aclr ( ~rst_n ),
// .clock ( clk ),
// .data ( rdin ),
// .rdreq ( rdfifo_rdreq ),
// .wrreq ( rdin_vld ),
// .empty ( rdfifo_empty ),
// .full ( rdfifo_full ),
// .q ( rdfifo_q ),
// .usedw ( rdfifo_usedw )
// );
// assign rdfifo_rdreq = rddone2idle && ~rdfifo_empty && ready;
// 输出
assign req = rw_req ;
assign cmd = command ;
assign wr_dout = wrdata ;
assign rw_flag = rwflag ;
assign tx_dout = txdata ;
assign tx_dout_vld = rddone2idle ;
endmodule
3、IIC接口驱动模块
EEPROM读写控制模块verilog代码如下:
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: i2c_eeprom
// Module Name: i2c_interface
// Target Device: Cyclone IV E (EP4CE6F17C8), EEPROM(24LC04B)
// Tool versions: Quartus Prime 18.1
// Description: I2C接口驱动模块
// **************************************************************
`include "param.v"
module i2c_interface (
input clk ,
input rst_n ,
// eeprom_ctrl
input req , // 读写请求
input [7:0] wr_din , // 需要发送的一字节数据
input [3:0] cmd , // 控制命令组合
output [7:0] rdout , // 读取的数据
output rdout_vld , // 读取数据有效标志
output rw_done , // 读写一字节完成标志
// EEPROM
input sda_in ,
output sda_out ,
output sda_out_en ,
output scl
);
// 参数定义
localparam IDLE = 7'b000_0001 ,
START = 7'b000_0010 ,
WR_DATA = 7'b000_0100 ,
RD_DATA = 7'b000_1000 ,
REC_ACK = 7'b001_0000 ,
SEND_ACK= 7'b010_0000 ,
STOP = 7'b100_0000 ;
// 信号定义
reg [6:0] state_c ;
reg [6:0] state_n ;
reg [7:0] cnt_scl ; // scl周期计数器
wire add_cnt_scl ;
wire end_cnt_scl ;
reg [3:0] bit_num ; // bit数
reg [3:0] cnt_bit ; // bit计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [7:0] dout_data ; // 发送数据寄存
reg [3:0] command ; // 控制命令寄存
reg scl_dout ; // SDA寄存
reg sda_dout ;
reg sda_dout_en ;
reg [7:0] rx_data ; // 接收读回的数据
reg ack_flag ; // ack响应标志
// 状态转移条件
wire idle2start ;
wire idle2wrdata ;
wire idle2rddata ;
wire start2wrdata ;
wire start2rddata ;
wire wrdata2recack ;
wire rddata2sendack ;
wire recack2idle ;
wire recack2stop ;
wire sendack2idle ;
wire sendack2stop ;
wire stop2idle ;
// 状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
IDLE: begin
if(idle2start)
state_n = START ;
else if(idle2wrdata)
state_n = WR_DATA ;
else if(idle2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
START: begin
if(start2wrdata)
state_n = WR_DATA ;
else if(start2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
WR_DATA: begin
if(wrdata2recack)
state_n = REC_ACK ;
else
state_n = state_c ;
end
RD_DATA: begin
if(rddata2sendack)
state_n = SEND_ACK ;
else
state_n = state_c ;
end
REC_ACK: begin
if(recack2idle)
state_n = IDLE ;
else if(recack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
SEND_ACK: begin
if(sendack2idle)
state_n = IDLE ;
else if(sendack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
STOP: begin
if(stop2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移条件
assign idle2start = state_c == IDLE && req && (cmd & `STA) ;
assign idle2wrdata = state_c == IDLE && req && (cmd & `WRITE) ;
assign idle2rddata = state_c == IDLE && req && (cmd & `READ) ;
assign start2wrdata = state_c == START && end_cnt_bit && (command & `WRITE) ;
assign start2rddata = state_c == START && end_cnt_bit && (command & `READ) ;
assign wrdata2recack = state_c == WR_DATA && end_cnt_bit ;
assign rddata2sendack = state_c == RD_DATA && end_cnt_bit ;
assign recack2idle = state_c == REC_ACK && end_cnt_bit && (command & `STO) == 0 ;
assign recack2stop = state_c == REC_ACK && end_cnt_bit && (command & `STO) ;
assign sendack2idle = state_c == SEND_ACK && end_cnt_bit && (command & `STO) == 0 ;
assign sendack2stop = state_c == SEND_ACK && end_cnt_bit && (command & `STO) ;
assign stop2idle = state_c == STOP && end_cnt_bit ;
// cnt_scl 200KHz 一个SCL周期为250个系统时钟周期
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_scl <= 0 ;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 0 ;
end
else begin
cnt_scl <= cnt_scl + 1 ;
end
end
end
assign add_cnt_scl = state_c != IDLE ;
assign end_cnt_scl = add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;
// bit_num
always @(*)begin
if(state_c == RD_DATA || state_c == WR_DATA)begin
bit_num = 8 ;
end
else begin
bit_num = 1 ;
end
end
// cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0 ;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0 ;
end
else begin
cnt_bit <= cnt_bit + 1 ;
end
end
end
assign add_cnt_bit = end_cnt_scl ;
assign end_cnt_bit = add_cnt_bit && (cnt_bit == bit_num - 1) ;
// dout_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_data <= 0 ;
end
else if(req)begin
dout_data <= wr_din ;
end
end
// command
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
command <= 0 ;
end
else if(req)begin
command <= cmd ;
end
end
// scl_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
scl_dout <= 1'b1 ;
end
else if(idle2start | idle2wrdata | idle2rddata)begin
scl_dout <= 1'b0 ;
end
else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
scl_dout <= 1'b1 ;
end
else if(end_cnt_scl && ~stop2idle)begin
scl_dout <= 1'b0 ;
end
end
// sda_dout_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout_en <= 1'b0 ;
end
else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
sda_dout_en <= 1'b1 ;
end
else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
sda_dout_en <= 1'b0 ;
end
end
// sda_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout <= 1'b1 ;
end
else if(state_c == START)begin
if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b0 ;
end
else if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b1 ;
end
end
else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
sda_dout <= dout_data[7 - cnt_bit] ;
end
else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
end
else if(state_c == STOP)begin
if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b0;
end
else if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b1;
end
end
else if(wrdata2recack | rddata2sendack)begin
sda_dout <= 1'b1;
end
end
// rx_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0 ;
end
else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
rx_data[7 - cnt_bit] <= sda_in ;
end
end
// ack_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ack_flag <= 1'b0 ;
end
else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
ack_flag <= ~sda_in ;
end
else begin
ack_flag <= 1'b0 ;
end
end
// 输出
assign rdout = rx_data ;
assign rdout_vld = rddata2sendack ;
assign rw_done = stop2idle | sendack2idle | recack2idle ;
assign sda_out = sda_dout ;
assign sda_out_en = sda_dout_en ;
assign scl = scl_dout ;
endmodule
4、参数配置
参数配置文件param.v如下:
// IIC时钟参数
`define SCL_PERIOD 250
`define SCL_HALF 125
`define LOW_HALF 65
`define HIGH_HALF 190
// 控制命令
`define WRITE 4'b1000 // 写
`define READ 4'b0100 // 读
`define STA 4'b0010 // 起始位
`define STO 4'b0001 // 停止位
// UART参数配置文件
`define BAUD_RATE_115200
`define CLK_FREQ 50_000_000
`ifdef BAUD_RATE_9600
`define BAUD `CLK_FREQ / 9600
`elsif BAUD_RATE_19200
`define BAUD `CLK_FREQ / 19200
`elsif BAUD_RATE_38400
`define BAUD `CLK_FREQ / 38400
`elsif BAUD_RATE_57600
`define BAUD `CLK_FREQ / 57600
`elsif BAUD_RATE_115200
`define BAUD `CLK_FREQ / 115200
`endif
5、其他模块
按键消抖、数码管显示模块比较简单,这里就不过多赘述。
串口数据收发参考文章:UART串口通信
四、仿真测试
1、仿真文件如下:
`timescale 1ns/1ps
module test_tb ();
reg tb_clk ;
reg tb_rst_n ;
reg [1:0] key_in;
wire sda ;
wire scl ;
wire [7:0] seg_dig ;
wire [5:0] seg_sel ;
reg [7:0] data ;
reg data_vld ;
wire ready ;
// PC
wire uart_txd ;
wire uart_rxd ;
uart_tx uart_tx_pc (
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*// control*/
/*input [7:0] */.tx_din (data ),
/*input */.tx_din_vld (data_vld),
/*output */.ready (ready ), // 给control模块的握手信号,表示可以接收数据进行发送
/*// 上位机*/
/*output */.uart_tx (uart_txd )
);
top u_top (
/*input */.clk (tb_clk ),
/*input */.rst_n (tb_rst_n ),
/*// key_filter*/
//input */.wr_req (wr_req ),
//input */.rd_req (rd_req ),
/*input [1:0] */.key_in (key_in ),
// uart
/*input */.uart_rxd (uart_txd ),
/*input */.uart_txd (uart_rxd ),
/*// eeprom*/
/*inout */.sda (sda ),
/*output */.scl (scl ),
// 数码管
/*output [5:0] */.seg_sel (seg_sel ),
/*output [7:0] */.seg_dig (seg_dig )
);
M24LC04B u_M24LC04B(
.A0 (0 ),
.A1 (0 ),
.A2 (0 ),
.WP (0 ),
.SDA (sda ),
.SCL (scl ),
.RESET (~tb_rst_n )
);
parameter CYCLE = 20;
defparam u_top.u_key_filter1.DELAY = 10 ;
defparam u_top.u_key_filter2.DELAY = 10 ;
always #(CYCLE / 2) tb_clk = ~tb_clk;
initial begin
tb_clk = 1'b1 ;
tb_rst_n = 1'b1 ;
key_in = 0 ;
data = 0 ;
data_vld = 1'b0 ;
#(CYCLE * 2);
#2;
tb_rst_n = 1'b0 ;
#(CYCLE * 2);
tb_rst_n = 1'b1 ;
#(CYCLE * 20);
Send(8'ha9);
key_in[0] = 1'b1 ;
#(CYCLE * 20) ;
key_in[0] = 1'b0 ;
#(CYCLE * 100 * 100 * 30) ;
key_in[1] = 1'b1 ;
#(CYCLE * 20) ;
key_in[1] = 1'b0 ;
#(CYCLE * 100 * 100 * 10) ;
$stop ;
end
task Send;
input [7:0] send_data ;
begin
data = send_data ;
data_vld = 1'b1 ;
# CYCLE ;
data_vld = 1'b0 ;
// @(posedge ready)
# (CYCLE*10) ;
end
endtask
endmodule
2、仿真结果
注意:
1. 24LC04B读数据只能读到0,无法读1,当读1的时候显示的是高阻状态。
2. 仿真的时候写操作完成后,延时5ms再开始读操作。
五、上板验证
总结:此工程主要实现IIC单字节读写EEPROM的操作,感兴趣的小伙伴可以尝试一下页写和其他几种方式的读操作。