当前位置:网站首页>【FPGA】day20-I2C读写EEPROM
【FPGA】day20-I2C读写EEPROM
2022-08-11 03:28:00 【春风浅作序】
目录
一、项目概述
1、实验要求
2、I2C协议
(1)简介
(2)协议流程
(3)时序分析
起始位跟停止位均有主机产生,且在时钟总线高电平时产生
数据在时钟总线低电平时发送
数据传输
应答信号
3、SCCB通信协议
(1)简介
不支持重复起始,例如当读数据时,写传输完了后,要先发送停止位,然后读传输才能发起始位。而I2C支持重复起始,读数据时,写传输完了后,不用发停止位,读传输直接发送起始位。
(2)时序分析
数据传输
(3)写数据
三/四相写传输
起始位
第一相:ID+写命令
第二相:寄存器地址高字节
第三相:寄存器地址低字节
第四相:一字节数据
停止位
(4)读数据
二/三相写传输
二相读传输
4、EEPRO简介
(1)写操作时序
上图为字节写,下图为页写
(2)读操作时序
当前地址读
随机地址读
顺序地址读
顺序读,发起始位—>写写控制字—>接收ACK—>写读地址—>接收ACK—>发起始位—>写读控制字—>接收ACK—>接收读数据—>发ACK—>接收读数据—>发NO ACK—>发停止位
二、项目分析
1、项目功能需求
2、模块设计
通过串口来发送、接收数据
3、时序图
4、状态机设计
右图上半部分为写操作
下半部分为读操作(包含前三字节的虚写)
5、设计思路
状态机设计
三、项目源码
1—4为时序逻辑输出,因等号右边为reg型,此处的assign相当于通过一根连线将寄存器引到端口。
5为组合逻辑输出,因等号右边进行了逻辑运算,没有经过寄存器寄存。
时序逻辑输出更好。
1、顶层模块
`define BUAD_115200 //设置波特率为115200
module iic_eeprom_top (
input clk ,//时钟信号
input rst_n ,//复位信号
input key_in ,//按键输入信号 读数据输出
input rx ,//上位机-->FPGA 串行数据
inout sda ,
output tx ,//FPGA-->上位机 串行数据
output scl
);
//宏定义
`ifdef BUAD_115200
parameter buad_set = 0;
`elsif BUAD_57600
parameter buad_set = 1;
`elsif BUAD_38400
parameter buad_set = 2;
`else
parameter buad_set = 0;
`endif
//信号定义
wire [1:0] set_bps ;
wire key_done ;
//串口接收串行数据转并行数据 写入FIFO
wire [7:0] dout ;
wire dout_vld ;
//eepromFIFO读出数据
wire [7:0] dout1 ;
wire dout_vld1 ;
//从WWPROM读出的数据 写入FIFO
// wire [7:0] din ;
// wire din_vld ;
wire [7:0] dout2 ;
wire done ;
wire ready ;
wire req ;
wire [7:0] rd_data ;
wire [7:0] wr_data ;
wire [3:0] cmd ;
wire sda_in ;
wire sda_o ;
wire sda_oe ;
//设置波特率
assign set_bps = buad_set;
//三态门
assign sda = sda_oe?sda_o:1'bz;
assign sda_in = sda;
//模块例化
key_debounce u_key_debounce(
.clk (clk ) ,//时钟信号
.rst_n (rst_n ) ,//复位信号
.key_in (key_in ) ,//按键输入信号
.key_done (key_done ) //输出信号
);
uart_rx u_uart_rx(
.clk (clk ) ,//时钟信号
.rst_n (rst_n ) ,//复位信号
.rx (rx ) ,//串行数据
.set_bps (set_bps ) ,
.dout (dout ) ,//并行数据
.dout_vld (dout_vld ) //输出数据有效信号
);
uart_tx u_uart_tx(
.clk (clk ) ,//时钟信号
.rst_n (rst_n ) ,//复位信号
.din (dout1 ) ,//输入的并行数据数据
.din_vld (dout_vld1) ,//数据有效标识
.set_bps (set_bps ) ,
.tx (tx ) , //输出的串行数据
.ready (ready )
);
eeprom_ctrl u_eeprom_ctrl(
/*input */.clk (clk ),//系统时钟
/*input */.rst_n (rst_n ),//复位信号
/*input [7:0] */.din (dout ),//串口接受的数据,需要写入fifo 再读出传递给EEPROM
/*input */.din_vld (dout_vld ),//数据有效
/*input [7:0] */.rd_data (dout2 ),//eeprom读出数据存入FIFO
/*input */.rd_en (key_done ),//读使能 按键控制
/*input */.ready (ready ),//串口发送模块准备好
/*input */.done (done ),//1字节处理完毕
/*output [7:0] */.dout (dout1 ),//eepromFIFO读出数据
/*output */.dout_vld (dout_vld1),//读出数据有效
/*output */.req (req ),//请求信号
/*output [7:0] */.wr_data (wr_data ),//写数据内容
/*output [3:0] */.cmd (cmd ) //命令
);
i2c_master u_i2c_master(
/*input */.clk (clk ),//系统时钟
/*input */.rst_n (rst_n ),//复位信号
/*input */.req (req ),//请求
/*input [3:0] */.cmd (cmd ),//命令
/*input [7:0] */.din (wr_data),//接收数据写入eeprom
/*input */.sda_in (sda_in ),//sda总线接受数据 eeprom-->FPGA
/*output */.scl (scl ),//输出时钟信号
/*output */.sda_o (sda_o ),//sda总线发送数据 FPGA-->eeprom
/*output */.sda_oe (sda_oe ),//sda总线发送数据有效
/*output [7:0] */.dout (dout2 ),//总线接受数据 串行转并行
/*output */.done (done ) //一个字节处理完毕
);
endmodule
2、参数模块
//i2c时钟参数
`define SCL 250 //IIC时钟周期
`define SCL_HALF 125
`define LOW_HALF 65 //时钟低电平中间
`define HIGH_HALF 190 //时钟高电平中间
//i2c命令参数
`define CMD_START 4'b0001
`define CMD_WRITE 4'b0010
`define CMD_READ 4'b0100
`define CMD_STOP 4'b1000
//读模式 16字节
//`define CURRENT_READ //从当前地址读
//`define RANDOM_READ //随机读
`define SEQUENCE_READ //顺序读
//写模式 16字节
//`define BYTE_WRITE //字节写
`define PAGE_WRITE //页写
`ifdef BYTE_WRITE
`define WR_BYTE 3
`elsif PAGE_WRITE
`define WR_BYTE 18
`endif
//i2c外设地质参数定义
`define I2C_ADR 6'b1010_00
`define WR_BIT 1'b0
`define RD_BIT 1'b1
`ifdef RANDOM_READ
`define RD_BYTE 4
`elsif SEQUENCE_READ
`define RD_BYTE 19
`endif
3、i2c主机模块
/********************************************************** // Copyright 2022.05-2025.05 // Contact with [email protected] ================ xxx.v ====================== >> Author : lzh >> Date : >> Description : 接口状态机 此状态机根据命令内容执行 >> note : >> : >> V180121 : ************************************************************/
`include "param.v"
module i2c_master(
input clk ,//系统时钟
input rst_n ,//复位信号
input req ,//接收请求信号
input [3:0] cmd ,//接收待执行命令
input [7:0] din ,//接收 串口接收FIFO读出的数据 写入eeprom
input sda_in ,//sda总线接受eeprom数据 eeprom-->FPGA
output scl ,//输出i2c时钟信号
output sda_o ,//sda总线发送数据 FPGA-->eeprom
output sda_oe ,//sda总线发送数据有效
output [7:0] dout ,//输出从eeprom读出的数据
output done //一个字节处理完毕标志
);
//参数定义
localparam //状态机状态
IDLE = 7'b000_0001,//空闲状态
START = 7'b000_0010,//一次读写命令的开始状态
WRITE = 7'b000_0100,//主机写数据状态
RACK = 7'b000_1000,//FPGA等待eeprom应答状态
READ = 7'b001_0000,//主机读数据状态
SACK = 7'b010_0000,//FPGA发送应答状态给eeprom状态
STOP = 7'b100_0000;//一次读写命令的结束状态
//信号定义
reg [6:0] state_c ;//现态
reg [6:0] state_n ;//次态
reg [8:0] cnt_clk ;//时钟计数器 选时钟频率为200kHZ 周期为5*10^(-6) 50_000_000HZ时钟要计数250次才是一个周期
wire add_cnt_clk ;
wire end_cnt_clk ;
reg [3:0] cnt_bit ;//比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_num ;
reg scl_r ;
reg sda_o_r ;
reg sda_oe_r ;
reg [7:0] rx_data ;//读数据
reg rx_ack ;//接收应答
reg [3:0] cmd_r ;
reg [7:0] tx_data ;//写数据
wire idle2start ;
wire idle2write ;
wire idle2read ;
wire start2write ;
wire start2read ;
wire write2rack ;
wire rack2stop ;
wire rack2idle ;
wire read2sack ;
wire sack2stop ;
wire sack2idle ;
wire stop2idle ;
//状态机 描述状态转移
[email protected](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(idle2write)
state_n = WRITE;
else if(idle2read)
state_n = READ;
else
state_n = state_c;
end
START:begin
if(start2write)
state_n = WRITE;
else if(start2read)
state_n = READ;
else
state_n = state_c;
end
WRITE:begin
if(write2rack)
state_n = RACK;
else
state_n = state_c;
end
RACK:begin
if(rack2stop)
state_n = STOP;
else if(rack2idle)
state_n = IDLE;
else
state_n = state_c;
end
READ:begin
if(read2sack)
state_n = SACK;
else
state_n = state_c;
end
SACK:begin
if(sack2stop)
state_n = STOP;
else if(sack2idle)
state_n = IDLE;
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&`CMD_START));//接受请求且接收命令中有开始命令 这里使用的输入的命令是因为每次从IDLE状态跳转都接收新的命令
assign idle2write = state_c == IDLE && (req && (cmd&`CMD_WRITE));//接受请求且接收命令中有写命令 这里使用的输入的命令是因为每次从IDLE状态跳转都接收新的命令
assign idle2read = state_c == IDLE && (req && (cmd&`CMD_READ));//接受请求且接收命令中有读命令 这里使用的输入的命令是因为每次从IDLE状态跳转都接收新的命令
assign start2write = state_c == START && (end_cnt_bit && (cmd_r&`CMD_WRITE));//一个bit结束 开始写数据 在该bit所处时间内在时钟高电平条件下拉低sda总线
assign start2read = state_c == START && (end_cnt_bit && (cmd_r&`CMD_READ));//一个bit结束 开始读数据 在该bit所处时间内在时钟高电平条件下拉低sda总线
assign write2rack = state_c == WRITE && (end_cnt_bit);//一个字节结束 一次写数据结束 在该字节所处时间内,在时钟低电平中间更改传输数据,高电平保持
assign rack2stop = state_c == RACK && (end_cnt_bit && (cmd_r&`CMD_STOP));//一个bit结束 且命令含有结束命令
assign rack2idle = state_c == RACK && (end_cnt_bit && (cmd_r&`CMD_STOP)==0);//一个bit结束 接收非应答或还未接收停止命令
assign read2sack = state_c == READ && (end_cnt_bit);//一个字节结束 一次读数据结束 在该字节所处时间内,在时钟高电平中间读取数据
assign sack2stop = state_c == SACK && (end_cnt_bit && (cmd_r&`CMD_STOP));//一个字节结束 且命令含有结束命令
assign sack2idle = state_c == SACK && (end_cnt_bit && (cmd_r&`CMD_STOP)==0);//一个bit结束 还未接收停止命令
assign stop2idle = state_c == STOP && (end_cnt_bit);//一个bit结束后
//时钟计数器
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_clk <= 0;
end
else if(add_cnt_clk) begin
if(end_cnt_clk)begin
cnt_clk <= 0;
end
else begin
cnt_clk <= cnt_clk + 1;
end
end
end
assign add_cnt_clk = (state_c != IDLE);
assign end_cnt_clk = add_cnt_clk && cnt_clk == `SCL - 1;//250次
[email protected](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_clk);//一个时钟周期对应一个bit
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_num - 1;
[email protected](*)begin
if(state_c == WRITE | state_c == READ)begin//在读写数据状态,比特数为8
bit_num = 8;
end
else begin//其余状态为1 STOP RACK SACK START
bit_num = 1;
end
end
//cmd_r
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
cmd_r <= 0;
end
else if(req)begin//接收到请求 将命令锁存
cmd_r <= cmd;
end
end
//tx_data
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data <= 0;
end
else if(req)begin//接收到请求 将要写入eeprom的数据锁存
tx_data <= din;
end
end
//scl_r
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
scl_r <= 1'b1;
end
else if(idle2start | idle2write | idle2read)begin//开始读写数据 时钟信号拉低 保证时钟先低后高
scl_r <= 1'b0;
end
else if(add_cnt_clk && cnt_clk == `SCL_HALF - 1) begin //计数器记到时钟脉冲中间拉高
scl_r <= 1'b1;
end
else if(end_cnt_clk && ~stop2idle)begin//一次时钟计数器计满且没有从stop回到idle状态
scl_r <= 1'b0;
end
end
//sda_o_r
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_o_r <= 1'b1;
end
//保证起始位能被检测到
else if(state_c == START)begin//发送起始位
if(cnt_clk == `LOW_HALF)begin//时钟电平低拉高sda总线
sda_o_r <= 1'b1;
end
else if(cnt_clk == `HIGH_HALF)begin//时钟电平高拉低sda总线
sda_o_r <= 1'b0;
end
end
else if(state_c == WRITE && cnt_clk == `LOW_HALF)begin
sda_o_r <= tx_data[7-cnt_bit];//在时钟低电平期间更改发送数据 从高字节开始传
end
else if(state_c == SACK && cnt_clk == `LOW_HALF) begin//发送应答
sda_o_r <= (cmd_r&`CMD_STOP)?1'b1:1'b0;//接收结束命令,返回非应答信号
end
//保证停止位能被检测到
else if(state_c == STOP)begin//发送停止位
if(cnt_clk == `LOW_HALF)begin//时钟电平低拉低da总线
sda_o_r <= 1'b0;
end
else if(cnt_clk == `HIGH_HALF)begin//时钟电平高拉高da总线
sda_o_r <= 1'b1;
end
end
end
//sda_oe_r
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_oe_r <= 1'b0;
end
else if(idle2start | idle2write | read2sack | rack2stop)begin
sda_oe_r <= 1'b1;
end
else if(idle2read | start2read | write2rack | stop2idle) begin
sda_oe_r <= 1'b0;
end
end
//rx_data
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0;
end
else if(state_c == READ && cnt_clk == `HIGH_HALF)begin
rx_data[7-cnt_bit] <= sda_in;//sda总线接收串行数据保存为并行数据
end
end
//rx_ack
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_ack <= 1'b1;//高电平非应答
end
else if(state_c == RACK && cnt_clk == `HIGH_HALF)begin
rx_ack <= sda_in;
end
end
//输出信号
assign scl = scl_r ;
assign sda_o = sda_o_r ;
assign sda_oe = sda_oe_r ;
assign dout = rx_data ;
assign done = rack2idle | sack2idle | stop2idle ;
endmodule
4、eeprom控制模块
/********************************************************** // Copyright 2022.05-2025.05 // Contact with [email protected] ================ xxx.v ====================== >> Author : lzh >> Date : 202X/XX/XX >> Description : 控制状态机 此状态机发送命令 不进行具体执行过程 >> note : >> : >> V180121 : ************************************************************/
`include "param.v"
module eeprom_ctrl (
input clk ,//系统时钟
input rst_n ,//复位信号
input [7:0] din ,//串口接受的数据,需要写入wfifo 再读出传递给EEPROM
input din_vld ,//数据有效
input [7:0] rd_data ,//从eeprom读出的数据存入rfifo
input rd_en ,//读使能 按键控制
input ready ,//串口发送模块准备好
input done ,//1字节处理完毕 从状态机返回信号
output [7:0] dout ,//rfifo读出数据 将要通过串口模块发送给上位机
output dout_vld ,//读出数据有效
output req ,//请求信号 该信号给从状态机
output [7:0] wr_data ,//写数据内容
output [3:0] cmd //命令
);
//参数定义
localparam //状态机状态
IDLE = 6'b00_0001 ,//空闲状态
WR_REQ = 6'b00_0010 ,//写请求
WAIT_WR = 6'b00_0100 ,//等待一个字节写完
RD_REQ = 6'b00_1000 ,//读请求
WAIT_RD = 6'b01_0000 ,//等待一个字节读完
DONE = 6'b10_0000 ;//一次读写完成
// parameter WR_LEN = 16,RD_LEN = 8;
//信号定义
reg [5:0] state_c ;//现态
reg [5:0] state_n ;//次态
reg [7:0] cnt_byte ;//字节计数器
wire add_cnt_byte ;
wire end_cnt_byte ;
reg tx_req ;//发送请求
reg [3:0] tx_cmd ;//发送命令
reg [7:0] tx_data ;//发送数据
reg [8:0] wr_addr ;//写地址
reg [8:0] rd_addr ;//读地址
//写FIFO
wire wfifo_rd ;
wire wfifo_wr ;
wire wfifo_empty ;
wire wfifo_full ;
wire [7:0] wfifo_qout ;
wire [5:0] wfifo_usedw ;
//读FIFO
wire rfifo_rd ;
wire rfifo_wr ;
wire rfifo_empty ;
wire rfifo_full ;
wire [7:0] rfifo_qout ;
wire [5:0] rfifo_usedw ;
reg rd_flag ;//rfifo可读标志
reg [7:0] dout_r ;//输出数据寄存器
reg dout_r_vld ;
wire idle2wr_req ;
wire wr_req2wait_wr ;
wire wait_wr2wr_req ;
wire wait_wr2done ;
wire idle2rd_req ;
wire rd_req2wait_rd ;
wire wait_rd2rd_req ;
wire wait_rd2done ;
wire done2idle ;
//描述状态转移
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state_c <= IDLE ;
end
else begin
state_c <= state_n;
end
end
//状态转移条件
[email protected](*)begin
case(state_c)
IDLE :begin
if(idle2wr_req)
state_n = WR_REQ ;
else if(idle2rd_req)
state_n = RD_REQ ;
else
state_n = state_c ;
end
WR_REQ :begin
if(wr_req2wait_wr)
state_n = WAIT_WR ;
else
state_n = state_c ;
end
WAIT_WR :begin
if(wait_wr2wr_req)
state_n = WR_REQ ;
else if(wait_wr2done)
state_n = DONE ;
else
state_n = state_c ;
end
RD_REQ :begin
if(rd_req2wait_rd)
state_n = WAIT_RD ;
else
state_n = state_c ;
end
WAIT_RD :begin
if(wait_rd2rd_req)
state_n = RD_REQ ;
else if(wait_rd2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE :begin
if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default : state_n = IDLE ;
endcase
end
assign idle2wr_req = state_c==IDLE && (wfifo_usedw > `ER_BYTE-2);//当wfifo中的数据位宽大于某个值就开始向从状态机发送写请求
assign wr_req2wait_wr = state_c==WR_REQ && (1'b1);
assign wait_wr2wr_req = state_c==WAIT_WR && (done & cnt_byte < `ER_BYTE-1);//接收到从状态机一字节写完信号 且总字节还未写完 继续下一字节的写(向从状态机发送写请求)
assign wait_wr2done = state_c==WAIT_WR && (end_cnt_byte && done);//字节写完
assign idle2rd_req = state_c==IDLE && (rd_en);//外部给读使能信号 按键 向从状态机发送读请求
assign rd_req2wait_rd = state_c==RD_REQ && (1'b1);
assign wait_rd2rd_req = state_c==WAIT_RD && (done & cnt_byte < `RD_BYTE-1);//接收到从状态机一字节读完信号 且总字节还未读完 继续下一字节的读(向从状态机发送读请求)
assign wait_rd2done = state_c==WAIT_RD && (end_cnt_byte && done);//字节读完
assign done2idle = state_c==DONE && (1'b1);
//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)
cnt_byte <= 0;
else
cnt_byte <= cnt_byte+1 ;
end
end
assign add_cnt_byte = (state_c==WAIT_WR | state_c==WAIT_RD) & done;//读写状态字节计数器
assign end_cnt_byte = add_cnt_byte && cnt_byte == ((state_c==WAIT_WR)?
(`ER_BYTE-1):(`RD_BYTE-1));//读写字节计完
//输出 向从状态机发送 请求、命令、数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
TX(1'b0,4'd0,8'd0);
end
else if(state_c==WR_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{
`CMD_START | `CMD_WRITE},{
`I2C_ADR,wr_addr[8],`WR_BIT});// 第一个字节(写设备地址,块选,写控制字)
1 :TX(1'b1,`CMD_WRITE,wr_addr[7:0]); //第二个字节(写写地址)
`WR_BYTE-1 :TX(1'b1,{
`CMD_WRITE | `CMD_STOP},wfifo_qout); //最后一个字节(带停止位)
default :TX(1'b1,`CMD_WRITE,wfifo_qout); //中间字节(写数据)
endcase
end
else if(state_c==RD_REQ)begin
case(cnt_byte)
0 :TX(1'b1,{
`CMD_START | `CMD_WRITE},{
`I2C_ADR,rd_addr[8],`WR_BIT});// 第一个字节(写设备地址,块选,写控制字)
1 :TX(1'b1,`CMD_WRITE,rd_addr[7:0]); //第二个字节(写读地址)
2 :TX(1'b1,{
`CMD_START | `CMD_WRITE},{
`I2C_ADR,rd_addr[8],`RD_BIT});//发起始位、读控制字
`RD_BYTE-1 :TX(1'b1,{
`CMD_READ | `CMD_STOP},0); //最后一个字节(停止位)
default :TX(1'b1,`CMD_READ,0); //中间字节(读数据)
endcase
end
else begin
TX(1'b0,tx_cmd,tx_data);
end
end
//用task发送请求、命令、数据(地址+数据)
task TX;
input req ;
input [3:0] command ;
input [7:0] data ;
begin
tx_req = req;
tx_cmd = command;
tx_data = data;
end
endtask
//wr_addr rd_addr
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_addr <= 0;
end
else if(wait_wr2done)begin//写完一次 地址自增
wr_addr <= wr_addr + `WR_BYTE - 2;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_addr <= 0;
end
else if(wait_rd2done)begin//读完一次 地址自增
rd_addr <= rd_addr + `RD_BYTE - 3;
end
end
//rd_flag
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_flag <= 1'b0;
end
else if(~rfifo_empty)begin//读FIFO不为空 读标志为1
rd_flag <= 1'b1;
end
else begin
rd_flag <= 1'b0;
end
end
//dout_r dout_r_vld
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
dout_r <= 0;
dout_r_vld <= 0;
end
else begin
dout_r <= rfifo_qout;//取出读FIFO数据
dout_r_vld <= rfifo_rd;//数据有效
end
end
//输出
assign req = tx_req ;
assign cmd = tx_cmd ;
assign wr_data = tx_data;
assign dout = dout_r;//控制器输出数据
assign dout_vld= dout_r_vld;
// assign dout = din;//串口接受数据 存入FIFO之前
// assign dout_vld= din_vld;
// assign dout = wfifo_qout;//串口接受数据 存入FIFO之后取出
// assign dout_vld= wfifo_rd;
// assign dout = tx_data;//写入eeprom数据
// assign dout_vld= req;
// assign dout = rd_data;//eeprom读出数据 存入FIFO之前
// assign dout_vld= rfifo_wr;
// assign dout = rfifo_qout;//eeprom读出数据 存入FIFO之后
// assign dout_vld= rfifo_rd;
//fifo例化
wrfifo u_wrfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (din ),
.rdreq (wfifo_rd ),
.wrreq (wfifo_wr ),
.empty (wfifo_empty),
.full (wfifo_full ),
.q (wfifo_qout ),
.usedw (wfifo_usedw)
);
assign wfifo_rd = ~wfifo_empty && state_c==WAIT_WR && done && cnt_byte > 1;
assign wfifo_wr = ~wfifo_full & din_vld;//fifo非满且输入有效。则写请求
rdfifo u_rdfifo (
.aclr (~rst_n ),
.clock (clk ),
.data (rd_data ),
.rdreq (rfifo_rd ),
.wrreq (rfifo_wr ),
.empty (rfifo_empty),
.full (rfifo_full ),
.q (rfifo_qout ),
.usedw (rfifo_usedw)
);
assign rfifo_wr = ~rfifo_full && state_c==WAIT_RD && cnt_byte > 2 && done;
assign rfifo_rd = ~rfifo_empty && rd_flag && ready;
endmodule
5、按键消抖模块
module key_debounce(
input clk ,//时钟信号
input rst_n ,//复位信号
input key_in ,//按键输入信号
output reg key_done //输出信号
);
//参数定义
parameter TIME_delay = 1000_000;//20ms
//状态机参数定义
parameter IDLE = 4'b0001,//空闲状态 保持高电平
FILTER_DOWN = 4'b0010,//按键按下抖动状态 判断是否按下
HOLD = 4'b0100,//保持状态 保持低电平
FILTER_UP = 4'b1000;//按键释放抖动状态 滤除释放产生的抖动
//信号定义
reg [3:0] state_c ;//现态
reg [3:0] state_n ;//次态
reg key_r0 ;//按键变化同步到时钟上升沿
reg key_r1 ;//保留上个时钟周期key_r0的值
wire nedge ;//下降沿
wire pedge ;//上升沿
reg [19:0] cnt_20ms ;//滤除抖动计数器,延时20ms
wire add_cnt_20ms;
wire end_cnt_20ms;
wire idle2filter_down ;//空闲状态,检测到下降沿,说明有按键按下进入抖动状态 IDLE-->FILTER_DOWN
wire filter_down2idle ;//按键按下出现上升沿,说明在抖动 FILTER_DOWN-->IDLE
wire filter_down2hold ;//某个按键按下 抖动过程的最后一次下降沿进入此状态后不会出现上升沿延时20ms后进入保持状态 FILTER_DOWN-->HOLD
wire hold2filter_up ;//保持状态出现上升沿进入释放抖动状态HOLD-->FILTER_UP
wire filter_up2idle ;//20ms结束,返回IDLE状态FILTER_UP-->IDLE
//状态机第一段 时序逻辑 描述状态的转移
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//状态机状态转移条件 组合逻辑
[email protected](*)begin
case(state_c)
IDLE :begin
if(idle2filter_down)
state_n = FILTER_DOWN;
else
state_n = state_c;
end
FILTER_DOWN :begin
if(filter_down2idle)//有优先级,在按下状态计数器记到20ms之前先出现上升沿,说明在抖动
state_n = IDLE;
else if(filter_down2hold)//计数器记到20ms之前没有出现上升沿,保持状态 稳定
state_n = HOLD;
else
state_n = state_c;
end
HOLD :begin
if(hold2filter_up)//检测到上升沿,进入释放状态
state_n = FILTER_UP;
else
state_n = state_c;
end
FILTER_UP :begin
if(filter_up2idle)//释放状态20ms结束回到空闲状态
state_n = IDLE;
else
state_n = state_c;
end
default :begin
state_n = IDLE;
end
endcase
end
assign idle2filter_down = state_c == IDLE && (nedge != 0) ;//出现下降沿进入抖动状态IDLE-->FILTER_DOWN
assign filter_down2idle = state_c == FILTER_DOWN && (pedge != 0) ;//按键出现抖动FILTER_DOWN-->IDLE
assign filter_down2hold = state_c == FILTER_DOWN && (end_cnt_20ms);//某个按键按下FILTER_DOWN-->HOLD
assign hold2filter_up = state_c == HOLD && (pedge != 0) ;//出现上升沿进入释放抖动状态HOLD-->FILTER_UP
assign filter_up2idle = state_c == FILTER_UP && (end_cnt_20ms);//20ms结束,返回IDLE状态FILTER_UP-->IDLE
//同步打拍
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= 1'b1;
key_r1 <= 1'b1;
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
//判断下降沿及上升沿
assign nedge = key_r1 & ~key_r0;
assign pedge = ~key_r1 & key_r0;
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms) begin
if(end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1;
end
end
end
assign add_cnt_20ms = (state_c == FILTER_DOWN || state_c == FILTER_UP);//按键按下或释放状态就开始计时20ms
assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == TIME_delay - 1 || filter_down2idle);
//key_out
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_done <= 1'b0;
end
else if(filter_down2hold)begin//检测到某按键按下的时刻 输出key的值
key_done <= ~key_r0;
end
else begin
key_done <= 1'b0;
end
end
endmodule
6、串口发送模块
module uart_tx(
input clk ,//时钟信号
input rst_n ,//复位信号
input [7:0] din ,//输入的并行数据数据
input din_vld ,//数据有效标识
input [1:0] set_bps ,
output reg tx , //输出的串行数据
output reg ready
);
//参数定义
parameter BPS_115200 = 434 ,//1bit数据传输所需的时钟周期
BPS_57600 = 868 ,
BPS_38400 = 1302 ;
//信号定义
reg [9:0] data ;//锁存din_vld有效时的输入
reg [10:0] cnt_bps ;//波特率计数器
wire add_cnt_bps ;
wire end_cnt_bps ;
reg flag ;//波特率计数器开启标识
reg [12:0] BPS_set ;
reg [3:0] cnt_bit ;//比特计数器
wire add_cnt_bit ;
wire end_cnt_bit ;
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
data <= 0;
end
else if(din_vld)begin//在数据有效标识为1时锁存数据
data <= {
1'b1,din,1'b0};//为数据拼接上起始位0和停止位1
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx <= 1'b1;
end
else if(flag || din_vld)begin//发送数据
tx <= data[cnt_bit];
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0;
end
else if(din_vld)begin//输入数据有效时开启波特率计数器
flag <= 1'b1;
end
else if(end_cnt_bit) begin//一帧数据(10比特)计数完毕,波特率计数器停止计数
flag <= 1'b0;
end
end
[email protected](posedge clk or negedge rst_n)begin//波特率计数器,计数每bit数据传输时间
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps) begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
end
assign add_cnt_bps = flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == BPS_set - 1;
[email protected](*)begin
case(set_bps)
0: BPS_set = BPS_115200 ;
1: BPS_set = BPS_57600 ;
2: BPS_set = BPS_38400 ;
default: BPS_set = BPS_115200;
endcase
end
[email protected](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_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1;
// [email protected](posedge clk or negedge rst_n)begin
// if(!rst_n)begin
// ready <= 1;
// end
// else if(end_cnt_bit) begin
// ready <= 1;
// end
// else if(din_vld)begin
// ready <= 0;
// end
// end
//assign rdreq
// [email protected](*)begin
// if(!rst_n)begin
// ready = 1;
// end
// else if(flag)begin
// ready = 0;
// end
// else begin
// ready = 1;
// end
// end
[email protected](*)begin
if(!rst_n)begin
ready = 1;
end
else if(din_vld || flag)begin
ready = 0;
end
else begin
ready = 1;
end
end
endmodule
7、串口接收模块
module uart_rx(
input clk ,//时钟信号
input rst_n ,//复位信号
input rx ,//串行数据
input [1:0] set_bps ,
output reg [7:0] dout ,//并行数据
output reg dout_vld //输出数据有效信号
);
//参数定义
parameter BPS_115200 = 434 ,//1bit数据传输所需的时钟周期
BPS_57600 = 868 ,
BPS_38400 = 1302 ;
//信号定义
reg rx_r0 ;
reg rx_r1 ;
reg rx_r2 ;
wire nedge ;
reg flag ;//接受数据标志
reg [12:0] BPS_set ;
reg [10:0] cnt_bps ;
wire add_cnt_bps ;
wire end_cnt_bps ;
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [9:0] data_r ;//数据接收缓存
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_r0 <= 1;
end
else begin //同步rx数据
rx_r0 <= rx;
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_r1 <= 1;
rx_r2 <= 1;
end
else begin //打拍 -- 检测起始信号
rx_r1 <= rx_r0;
rx_r2 <= rx_r1;
end
end
assign nedge = rx_r2 & ~rx_r1;//以r1 r2作为判断下降沿的信号
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 0;
end
else if(nedge)begin
flag <= 1'b1;
end
else if(end_cnt_bit) begin
flag <= 1'b0;
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bps <= 0;
end
else if(add_cnt_bps) begin
if(end_cnt_bps)begin
cnt_bps <= 0;
end
else begin
cnt_bps <= cnt_bps + 1;
end
end
end
assign add_cnt_bps = flag;
assign end_cnt_bps = add_cnt_bps && cnt_bps == BPS_set - 1;
[email protected](*)begin
case(set_bps)
0:BPS_set = BPS_115200 ;
1:BPS_set = BPS_57600 ;
2:BPS_set = BPS_38400 ;
default:BPS_set = BPS_115200;
endcase
end
[email protected](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_bps;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1;
//dout
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_r <= 0;
end
else if(cnt_bps == (BPS_set >> 1))begin//数据采样
data_r[cnt_bit] <= rx_r2;
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout <= 0;
end
else if(end_cnt_bit)begin//截取数据为
dout <= data_r[8:1];
end
end
[email protected](posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_vld <= 0;
end
else if(end_cnt_bit)begin//给发送模块锁存数据产生数据有效信号
dout_vld <= 1'b1;
end
else begin
dout_vld <= 1'b0;
end
end
endmodule
四、项目仿真
1、eeprom控制模块仿真
`timescale 1ns/1ps
module eeprom_ctrl_tb();
//参数定义
parameter CYCLE = 20;
//信号定义
reg clk ;
reg rst_n ;
reg [7:0] din ;
reg din_vld ;
reg ready ;
reg sda_in ;
reg key_done ;
reg [7:0] dout ;
reg dout_vld ;
wire [7:0] dout1 ;
wire dout_vld1 ;
wire [7:0] dout2 ;
wire done ;
wire req ;
wire [7:0] wr_data ;
wire [3:0] cmd ;
wire scl ;
wire sda_o ;
wire sda_oe ;
//模块例化
eeprom_ctrl u_eeprom_ctrl(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [7:0] */.din (dout ),//串口接受的数据,需要写入fifo 再读出传递给I
/*input */.din_vld (dout_vld ),//数据有效
/*input [7:0] */.rd_data (dout2 ),//eeprom读出数据存入FIFO
/*input */.rd_en (key_done ),//读使能 按键控制
/*input */.ready (ready ),//串口发送模块准备好
/*input */.done (done ),//1字节处理完毕
/*output [7:0] */.dout (dout1 ),//eepromFIFO读出数据
/*output */.dout_vld (dout_vld1),//读出数据有效
/*output */.req (req ),//请求信号
/*output [7:0] */.wr_data (wr_data ),//写数据内容
/*output [3:0] */.cmd (cmd ) //命令
);
i2c_master u_i2c_master(
/*input */.clk (clk ),//系统时钟
/*input */.rst_n (rst_n ),//复位信号
/*input */.req (req ),//请求
/*input [3:0] */.cmd (cmd ),//命令
/*input [7:0] */.din (wr_data),//接收数据写入eeprom
/*input */.sda_in (sda_in ),//sda总线接受数据 eeprom-->FPGA
/*output */.scl (scl ),//输出时钟信号
/*output */.sda_o (sda_o ),//sda总线发送数据 FPGA-->eeprom
/*output */.sda_oe (sda_oe ),//sda总线发送数据有效
/*output [7:0] */.dout (dout2 ),//总线接受数据 串行转并行
/*output */.done (done ) //一个字节处理完毕
);
//产生时钟
always #(CYCLE/2) clk = ~clk;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#(CYCLE*2);
rst_n = 1'b0;
din = 1'b0;
din_vld = 1'b0;
ready = 1'b0;
sda_in = 1'b0;
key_done = 1'b0;
#(CYCLE*2);
rst_n = 1'b1;
#(CYCLE*2);
din = 8'hac;
din_vld = 1'b1;
ready = 1'b1;
repeat(8)begin
sda_in = {
$random}%2;
#(CYCLE * 250);
end
key_done = 1'b1;
#(CYCLE*10000);
$stop;
end
endmodule
2、i2c主机模块仿真
`timescale 1ns/1ps
module i2c_master_tb();
//参数定义
parameter CYCLE = 20;
//信号定义
reg clk ;
reg rst_n ;
reg req ;
reg [3:0] cmd ;
reg [7:0] wr_data ;
reg sda_in ;
wire scl ;
wire sda_o ;
wire sda_oe ;
wire [7:0] dout ;
wire done ;
//模块例化
i2c_master u_i2c_master(
/*input */.clk (clk ),//系统时钟
/*input */.rst_n (rst_n ),//复位信号
/*input */.req (req ),//请求
/*input [3:0] */.cmd (cmd ),//命令
/*input [7:0] */.din (wr_data),//接收数据写入eeprom
/*input */.sda_in (sda_in ),//sda总线接受数据 eeprom-->FPGA
/*output */.scl (scl ),//输出时钟信号
/*output */.sda_o (sda_o ),//sda总线发送数据 FPGA-->eeprom
/*output */.sda_oe (sda_oe ),//sda总线发送数据有效
/*output [7:0] */.dout (dout2 ),//总线接受数据 串行转并行
/*output */.done (done ) //一个字节处理完毕
);
//产生时钟
always #(CYCLE/2) clk = ~clk;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#(CYCLE*2);
rst_n = 1'b0;
req = 1'b0;
cmd = 4'd0;
wr_data = 8'd0;
sda_in = 1'b0;
#(CYCLE*2);
rst_n = 1'b1;
#(CYCLE*2);
req = 1'b1;
cmd = 4'b1011;
wr_data = 8'hac;
#(CYCLE);
req = 1'b0;
repeat(8)begin
sda_in = {
$random}%2;
#(CYCLE * 250);
end
#(CYCLE*1000);
req = 1'b1;
cmd = 4'b1101;
wr_data = 8'hac;
#(CYCLE);
req = 1'b0;
#(CYCLE*10000);
$stop;
end
endmodule
3、顶层模块仿真
`timescale 1ns/1ps
module iic_eeprom_top_tb();
//参数定义
defparam u_iic_eeprom_top.u_key_debounce.TIME_delay = 10;
parameter CYCLE = 20;
//信号定义
reg clk ;
reg rst_n ;
reg key_in ;
reg rx ;
wire sda ;
wire tx ;
wire scl ;
//模块例化
iic_eeprom_top u_iic_eeprom_top(
/*input */.clk (clk ),//时钟信号
/*input */.rst_n (rst_n ),//复位信号
/*input */.key_in (key_in ),//按键输入信号 读数据输出
/*input */.rx (rx ),//上位机-->FPGA 串行数据
/*inout */.sda (sda ),
/*output */.tx (tx ),//FPGA-->上位机 串行数据
/*output */.scl (scl )
);
// i2c_master u_i2c_master(
// /*input */.clk (clk ) ,//系统时钟
// /*input */.rst_n (rst_n ) ,//复位信号
// /*input */.req (req ) ,//接收请求信号
// /*input [3:0] */.cmd (cmd ) ,//接收待执行命令
// /*input [7:0] */.din (din ) ,//接收 串口接收FIFO读出的数据 写入eeprom
// /*input */.sda_in (sda_in) ,//sda总线接受eeprom数据 eeprom-->FPGA
// /*output */.scl (scl ) ,//输出i2c时钟信号
// /*output */.sda_o (sda_o ) ,//sda总线发送数据 FPGA-->eeprom
// /*output */.sda_oe (sda_oe) ,//sda总线发送数据有效
// /*output [7:0] */.dout (dout ) ,//输出从eeprom读出的数据
// /*output */.done (done ) //一个字节处理完毕标志
// );
//产生时钟
always #(CYCLE/2) clk = ~clk;
initial begin
clk = 1'b1;
rst_n = 1'b1;
#(CYCLE*2);
rst_n = 1'b0;
key_in = 1'b1;
rx = 1'b0;
#(CYCLE*2);
rst_n = 1'b1;
#(CYCLE*2);
repeat(320)begin
rx = {
$random}%2;
#(CYCLE * 434);
end
#(CYCLE*100000);
key_in = 1'b0;//按键按下 读使能
#(CYCLE*15);
key_in = 1'b1;
#(CYCLE*100000);
$stop;
end
endmodule
4、仿真结果
(1)起始位
(2)顺序读
从状态机第一个字节:
从状态机第二、三字节:
从状态机之间读字节:
从状态机最后一个字节:
顺序读1:
顺序读2:
(3)停止位
(4)页写
页写1:
从状态机第一个字节:
从状态机第二个字节:
从状态机最后一个字节:
五、上板验证
边栏推荐
猜你喜欢
音视频开发,为什么要学习FFmpeg?应该怎么入手FFmpeg学习?
Salesforce disbands the Chinese team, which CRM product is more suitable for the Chinese
多商户商城系统功能拆解26讲-平台端分销设置
What should I do if the channel ServerID is incorrect when EasyCVR is connected to a Hikvision Dahua device and selects another cluster server?
I didn't expect MySQL to ask these...
DNS分离解析和智能解析
索引的创建、查看、删除
浮点数在内存中的存储方式
【Yugong Series】August 2022 Go Teaching Course 036-Type Assertion
rac备库双节点查询到的表最后更新时间不一致
随机推荐
[ADI low-power 2k code] Based on ADuCM4050, ADXL363, TMP75 acceleration, temperature detection and serial port printing, buzzer playing music (lone warrior)
LeetCode热题(12.买卖股票的最佳时机)
KingbaseES有什么办法,默认不读取sys_catalog下的系统视图?
互换性与测量技术-公差原则与选用方法
LeetCode Hot Questions (12. The Best Time to Buy and Sell Stocks)
C language recv() function, recvfrom() function, recvmsg() function
Is there any way for kingbaseES to not read the system view under sys_catalog by default?
Unity2D animation (1) introduction to Unity scheme - animation system composition and the function of use
Roewe imax8ev cube battery security, what blackening and swelling are hidden behind it?
The 125th day of starting a business - a note
flink The object probably contains or references non serializable fields.
什么是三方支付?
添加用户报错useradd: cannot open /etc/passwd
元素的BFC属性
输入起始位置,终止位置截取链表
What should I do if the channel ServerID is incorrect when EasyCVR is connected to a Hikvision Dahua device and selects another cluster server?
this question in js
增加对 Textbundle 的支持
The negative semantic transformation layer
Audio codec, using FAAC to implement AAC encoding