本次所用开发板FPGA芯片型号为:EP4CE6F17C8 EEPROM
芯片型号为:24LC04B UART串口设置为:
波特率11520,无校验位。
本次仅实现了EEPROM的单字节读写。若要实现连续读写可以在下文的EEPROM控制模块设置计数器控制读写字数。
一、I2C协议
1.1 I2C协议简介
①IIC:(Inter-Integrated Circuit)即集成电路总线,是一种两线 式串行总线,由 PHILIPS 公司开发,用于连接微控制器及其外围设备。 多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。
物理层的特点:
(1) 它是一个支持多设备的总线(支持多主机多从机)。
(2) IIC 总线只使用两条总线线路,一条双向串行数据线(SDA) 一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到 IIC 总线的设备都有一个独立的地址,主机可以利用设备独立地址访问不同设备。
(4) IIC 总线通过上拉电阻接到电源。当 IIC 设备空闲时,设备会输出高阻态,当所有设备都空闲,都输出高阻态时,由上拉电阻把 IIC总线拉成高电平。
(5) IIC 总线具有仲裁机制。当多个主机同时发起传输时,触发仲裁机制,最终只给一个主机授权。
(6)具有三种传输模式:
1.3 IIC 协议层:
① IIC 协议空闲状态
② IIC 协议的起始信号
③ IIC 协议的数据传输(数据读写)状态(应答信号(ACK))
④ IIC 协议的停止信号
图中标注的①②③④表示 IIC 协议的 4 个状态,
分别为“总线空闲状态”、“起始信号”、“数据读/写状态”和“停止信号”。
空闲状态:图中标注①表示“总线空闲状态”,在此状态下串口时钟信号SCL和串行数据信号 SDA均保持高电平(都为 1),此时无IIC设备工作。
起始位:
图中标注②表示“起始信号”,在 IIC 总线处于“空闲状态”时, 时钟信号线 SCL 为高电平时,数据信号线 SDA 被拉低(由高电平变为低电平),出现下降沿,表示产生了一个起始信号。起始信号是由主机(本实验中为 FPGA)主动建立的,在建立该信号之前IIC总线必须处于空闲状态。
数据传输:
同时,协议规定在SCL高电平时期SDA数据线必须保持稳定,在SCL低电平时,SDA才允许发生改变。主机进行数据读写时,I2C协议规定,数据传输时先发送寻址字节,即7位从机地址+0/1。其中0表示主机写,1表示主机读。寻址字节发送完成后才是数据字节。
应答位:
I2C协议规定,主机每次向从机传输1字节数据后,需要接收一次从机的应答信号。0为接收成功;1为接受失败,没有响应。
停止位:
当SCL为高电平时,数据线SDA拉高,则认为检测到停止位,一次数据传输结束。
注意:SCL高电平数据必须保持,SCL低电平数据才能改变。
二、EEPROM
2.1 基本信息
EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。本次项目中使用的EEPROM型号为24LC04B。
24LC04B 的存储容量为 4Kbit,其内部有两个 Block,每个Block中有256个字节(一个字节为 8bit)。其读写操作都是以字节(8bit)为基本单位。24LC04B EEPROM存储芯片的器件地址包括厂商设置的高4 位 1010和用户需自主设置的低3位A0、A1、A2。
在IIC主从设备通讯时,主机在发送了起始信号后,接着会向从机发送控制命令。控制命令长度为1个字节,它的高7位为上文讲解的IIC设备的器件地址,最低位为读写控制位。EEPROM储存芯片控制命令格式示意图,具体见下图:
三、IIC协议读写EEPROM
EEPROM的写操作有:
①单字节写(Byte WRITE) ②页写(Page WRITE)
EEPROM的读操作有:
①当前地址读(Current Address READ)
②随机地址读(RANDOM READ)
③顺序地址读(SEQUENTIAL READ)
3.1 EEPROM单字节写(Byte WRITE)操作时序
单字节写操作:在数据信号线SDA上,发起始 -> 控制字节,从机接收到发应答信号 -> 写数据地址,从机接收到发应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到发应答信号 -> 写数据,从机接收到发应答信号 -> 最后发停止位。
3.2 EEPROM页写操作时序
页写(PAGE WRITE)操作:一次写入16个字节(每字节为8bit)数据。
在数据信号线SDA上,发起始位 -> 写控制字节,从机接收到应答信号 -> 写数据地址,从机接收到应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到应答信号 -> 写数据,从机接收到应答信号 -> 继续写数据,直到写完全部的数据 -> 最后发停止位。
当前地址读(Curren Address READ)操作:在数据线SDA上,发起始位 -> 写控制字节,从机接收到应答信号,然后读数据,无应答信号(发No ACK) -> 发停止位。
随机地制读(RANDOM READ)操作:在数据信号线SDA上,发起始位 -> 写控制字节,从机接收到应答信号 -> 再发起始位 -> 读控制字节,从机接收到应答信号 -> 接收读数据 -> 发无应答(No ACK)-> 最后发停止位。
3.5 EEPROM 顺序地址读(SEQUENTIAL READ)操作时序
顺序地址读(RANDOM READ)操作:在数据信号线SDA上,发起始位 -> 写控制字节,从机接收到应答信号 -> 虚写(dummuy write),写数据地址,从机接收到应答信号 -> 再发开始位 -> 读控制字节,从机接收到应答信号 -> 然后接收读数据 -> 发No ACK -> 最后发结束位
顺序地址读实质就是在随机地制读之后
四、状态机
4.1 IIC接口模块
五、程序
5.1、IIC接口模块
/**************************************功能介绍***********************************
Date : 2024年5月20日16:16:29
Author :
Version : 1.0
Description: I2C接口模块
接口命令列表(cmd):
0 1
bit0(起始位) NO YES
bit1(写数据) NO YES
bit2(读数据) NO YES
bit3(停止位) NO YES
bit4(响应位) ACK NO ACK
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module I2C_driver(
input clk ,
input rst_n ,
input [7:0] wr_data ,//要写进的数据
input [4:0] cmd ,//命令信号
input cmd_vld ,//命令信号有效信号
output [7:0] rd_data ,//读出的数据
output rd_data_vld,
output reg rev_ack ,//记响应位为ack还是no ack
output done ,//表示写/读完8bit的数据
output reg scl ,
inout sda
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam IDLE = 7'b0000001,//空闲状态
START = 7'b0000010,//起始位
WR_DATA = 7'b0000100,//写状态(包括写控制字节,写地址和写数据)
RD_DATA = 7'b0001000,//读状态
R_ACK = 7'b0010000,//收ACK(由EEPROM发送给FPGA)
T_ACK = 7'b0100000,//发ACK(由FPGA发送给EEPROM)考虑为ACK还是NO ACK
STOP = 7'b1000000;//结束位
parameter T = 100_000,//速率 100k,400k,3.4M
SCL_MAX = 50_000_000 / T;//速率为100k时,计1bit需要计数500
parameter SCL_LOW_HALF = (SCL_MAX * 1 / 4) - 1,
SCL_HIGH_HALF = (SCL_MAX * 3 / 4) - 1;
`define START_BIT 5'b00001
`define WRITE_BIT 5'b00010
`define READ_BIT 5'b00100
`define STOP_BIT 5'b01000
`define ACK_BIT 5'b10000
`define ACK 0
`define NO_ACK 1
//---------<内部信号定义>-----------------------------------------------------
reg [6:0] cstate ;//现态
reg [6:0] nstate ;//次态
reg [4:0] cmd_r ;//cmd打一拍
reg [7:0] wr_data_r ;
reg [7:0] rd_data_r ;
reg sda_out ;
reg OE ;//三态门使能信号
wire sda_in ;
reg [8:0] cnt_bit ;//计1bit(IIC工作时钟)
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] cnt_num ;//计8bit
wire add_cnt_num ;
wire end_cnt_num ;
reg [3:0] num ;
//状态转移条件
wire IDLE_START ;
wire START_WR_DATA ;
wire WR_DATA_R_ACK ;
wire R_ACK_IDLE ;
wire IDLE_WR_DATA ;
wire R_ACK_STOP ;
wire STOP_IDLE ;
wire IDLE_RD_DATA ;
wire RD_DATA_T_ACK ;
wire T_ACK_IDLE ;
wire T_ACK_STOP ;
//寄存wr_data_r和cmd_r
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_data_r <= 'd0;
cmd_r <= 'd0;
end
else if (cmd_vld) begin
wr_data_r <= wr_data;
cmd_r <= cmd;
end
end
/****************************************************************
计数器
****************************************************************/
//计1bit(IIC工作时钟)
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = cstate != IDLE;
assign end_cnt_bit = add_cnt_bit && cnt_bit == SCL_MAX - 1'd1;
//IIC_SCL
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
scl <= 'd1;
end
else if (cnt_bit == (SCL_MAX - 1 ) >> 1 || STOP_IDLE) begin
scl <= 'd1;
end
else if (end_cnt_bit) begin
scl <= 'd0;
end
end
//8bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num <= 'd0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 'd0;
end
else begin
cnt_num <= cnt_num + 1'd1;
end
end
end
assign add_cnt_num = end_cnt_bit;
assign end_cnt_num = add_cnt_num && cnt_num == num - 1;
//考察计bit数最大值
always @(*) begin
case (cstate)
IDLE : num = 1;
START : num = 1;
WR_DATA : num = 8;
RD_DATA : num = 8;
R_ACK : num = 1;
T_ACK : num = 1;
STOP : num = 1;
default : num = 1;
endcase
end
/****************************************************************
状态机
****************************************************************/
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE : begin
if (IDLE_START) begin
nstate = START;
end
else if (IDLE_WR_DATA) begin
nstate = WR_DATA;
end
else if (IDLE_RD_DATA) begin
nstate = RD_DATA;
end
else begin
nstate = cstate;
end
end
START : begin
if (START_WR_DATA) begin
nstate = WR_DATA;
end
else begin
nstate = cstate;
end
end
WR_DATA : begin
if (WR_DATA_R_ACK) begin
nstate = R_ACK;
end
else begin
nstate = cstate;
end
end
RD_DATA : begin
if (RD_DATA_T_ACK) begin
nstate = T_ACK;
end
else begin
nstate = cstate;
end
end
R_ACK : begin
if (R_ACK_STOP) begin
nstate = STOP;
end
else if (R_ACK_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
T_ACK : begin
if (T_ACK_STOP) begin
nstate = STOP;
end
else if (T_ACK_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
STOP : begin
if (STOP_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = cstate;
endcase
end
assign IDLE_START = (cstate == IDLE) && cmd_vld && (cmd & `START_BIT) ;//存在起始位
assign START_WR_DATA = (cstate == START) && end_cnt_num && (cmd_r & `WRITE_BIT) ;//计1bit起始位并写数据有效
assign WR_DATA_R_ACK = (cstate == WR_DATA) && end_cnt_num ;//计8bit数据位
assign R_ACK_IDLE = (cstate == R_ACK) && end_cnt_num && !(cmd_r & `STOP_BIT) ;//计1bit响应位,没有停止位
assign IDLE_WR_DATA = (cstate == IDLE) && cmd_vld && (cmd & `WRITE_BIT) ;//写数据有效
assign R_ACK_STOP = (cstate == R_ACK) && end_cnt_num && (cmd_r & `STOP_BIT) ;//计1bit响应位,有停止位
assign STOP_IDLE = (cstate == STOP) && end_cnt_num ;//计1bit停止位
assign IDLE_RD_DATA = (cstate == IDLE) && cmd_vld && (cmd & `READ_BIT) ;//读数据有效
assign RD_DATA_T_ACK = (cstate == RD_DATA) && end_cnt_num ;//计8bit数据位
assign T_ACK_IDLE = (cstate == T_ACK) && end_cnt_num && !(cmd_r & `STOP_BIT) ;//计1bit响应位,没有停止位
assign T_ACK_STOP = (cstate == T_ACK) && end_cnt_num && (cmd_r & `STOP_BIT) ;//计1bit响应位,有停止位
/****************************************************************
三态门
****************************************************************/
assign sda = OE ? sda_out : 1'bz;
assign sda_in = sda;
//考察OE
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
OE <= 'b0;
end
else if (IDLE_START || START_WR_DATA || IDLE_WR_DATA || R_ACK_STOP || RD_DATA_T_ACK) begin//FPGA发送,EEPROM接收
OE <= 'b1;
end
else if (IDLE_RD_DATA || WR_DATA_R_ACK || STOP_IDLE) begin//FPGA接收,EEPROM发送
OE <= 'b0;
end
end
/****************************************************************
输出
****************************************************************/
//考察输出
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
sda_out <= 1;
end
else begin
case (cstate)
IDLE :sda_out <= 1;
START :begin//起始位scl一直为高电平,检测sda需要由高电平变低电平
if (cnt_bit == SCL_LOW_HALF) begin
sda_out <= 'b1;
end
else if (cnt_bit == SCL_HIGH_HALF) begin
sda_out <= 'b0;
end
end
WR_DATA :begin//高电平保持,低电平值变化采样
if (cnt_bit == SCL_LOW_HALF) begin
sda_out <= wr_data_r[7 - cnt_num];//高位先发
end
end
T_ACK :begin
if (cnt_bit == SCL_LOW_HALF) begin
if (cmd & `ACK_BIT) begin//命令响应位为1:NO ACK
sda_out <= `NO_ACK;
end
else begin
sda_out <= `ACK;//命令响应位为0:ACK
end
end
end
STOP :begin//检测sda需要由低电平变高电平
if (cnt_bit == SCL_LOW_HALF) begin
sda_out <= 'b0;
end
else if (cnt_bit == SCL_HIGH_HALF) begin
sda_out <= 'b1;
end
end
default: sda_out <= 'b1;
endcase
end
end
/****************************************************************
ACK
****************************************************************/
//数据接收(考察EEPROM发送数据给FPGA的情况)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rev_ack <= 0;
rd_data_r <= 8'b0;
end
else begin
case (cstate)
RD_DATA:begin
if (cnt_bit == SCL_HIGH_HALF) begin//高电平采样
rd_data_r[7-cnt_num] <= sda_in;
end
end
R_ACK :begin
if (cnt_bit == SCL_HIGH_HALF) begin
rev_ack <= sda_in;
end
end
default:;
endcase
end
end
assign done = R_ACK_IDLE || T_ACK_IDLE || STOP_IDLE;
assign rd_data = rd_data_r;
assign rd_data_vld = T_ACK_IDLE || T_ACK_STOP;
endmodule
5.2 IIC控制模块
/**************************************功能介绍***********************************
Date : 2024年5月20日16:19:44
Author :
Version : 1.0
Description: I2C控制模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module I2C_control #(
parameter ADDR_BIT = 8 //从机寄存器地址的位宽
)(
input clk ,
input rst_n ,
input wr_req ,//写使能
input rd_req ,//读使能
input [6:0] device_id ,//从机设备ID
input [ADDR_BIT - 1:0] reg_addr ,//读写地址
input reg_addr_vld,
input [7:0] wr_data ,
input wr_data_vld ,
output [7:0] rd_data ,
output rd_data_vld ,
output ready ,
output scl ,
inout sda
);
//---------<参数定义>---------------------------------------------------------
//接口模块控制命令
`define START_BIT 5'b00001
`define WRITE_BIT 5'b00010
`define READ_BIT 5'b00100
`define STOP_BIT 5'b01000
`define ACK_BIT 5'b10000
//状态机参数定义
localparam IDLE = 6'b000001,//
WR_REQ = 6'b000010,//
WR_WAIT = 6'b000100,//
RD_REQ = 6'b001000,//
RD_WAIT = 6'b010000,//
DONE = 6'b100000;//
localparam WR_CTRL_BYTE = 8'b1010_0000;
localparam RD_CTRL_BYTE = 8'b1010_0001;
//---------<内部信号定义>-----------------------------------------------------
reg [5:0] cstate ;//现态
reg [5:0] nstate ;//次态
wire IDLE_WR_REQ ;
wire IDLE_RD_REQ ;
wire WR_REQ_WR_WAIT ;
wire RD_REQ_RD_WAIT ;
wire WR_WAIT_WR_REQ ;
wire WR_WAIT_DONE ;
wire RD_WAIT_RD_REQ ;
wire RD_WAIT_DONE ;
wire DONE_IDLE ;
reg [2:0] num ;//字节计数器最大值(写3字节,读4字节)
wire done ;
reg [4:0] cmd ;
reg cmd_vld ;
reg [7:0] op_wr_data ;
reg [15:0] addr_r ;
reg [7:0] wr_data_r ;
reg [2:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
//同步数据打拍
reg wr_req_r;
reg rd_req_r;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_req_r <=0;
rd_req_r <= 0;
end
else begin
wr_req_r <= wr_req;
rd_req_r <= rd_req;
end
end
//寄存
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
addr_r <= 'd0;
end
else if (reg_addr_vld) begin
addr_r <= reg_addr;
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_data_r <= 'd0;
end
else if (wr_req) begin
wr_data_r <= wr_data;
end
end
//字节计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 'd0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 1'd1;
end
end
end
assign add_cnt_byte = done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == num - 1;
//考察字节计数器最大值(写3字节,读4字节)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
num <= 1;
end
else if (wr_req) begin
num <= 4;
end
else if (rd_req) begin
num <= 5;
end
else if (end_cnt_byte) begin
num <= 1;
end
end
assign IDLE_WR_REQ = (cstate == IDLE) && wr_req_r;//写使能拉高
assign IDLE_RD_REQ = (cstate == IDLE) && rd_req_r;//读使能拉高
assign WR_REQ_WR_WAIT = (cstate == WR_REQ) && 1;
assign RD_REQ_RD_WAIT = (cstate == RD_REQ) && 1;
assign WR_WAIT_WR_REQ = (cstate == WR_WAIT) && done;//写完1byte
assign WR_WAIT_DONE = (cstate == WR_WAIT) && end_cnt_byte;//写完3byte
assign RD_WAIT_RD_REQ = (cstate == RD_WAIT) && done;//读完1byte
assign RD_WAIT_DONE = (cstate == RD_WAIT) && end_cnt_byte;//读完4byte
assign DONE_IDLE = (cstate == DONE) && 1;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_WR_REQ) begin
nstate = WR_REQ;
end
else if (IDLE_RD_REQ) begin
nstate = RD_REQ;
end
else begin
nstate = cstate;
end
end
WR_REQ :begin
if (WR_REQ_WR_WAIT) begin
nstate = WR_WAIT;
end
else begin
nstate = cstate;
end
end
WR_WAIT :begin
if (WR_WAIT_DONE) begin
nstate = DONE;
end
else if (WR_WAIT_WR_REQ) begin
nstate = WR_REQ;
end
else begin
nstate = cstate;
end
end
RD_REQ :begin
if (RD_REQ_RD_WAIT) begin
nstate = RD_WAIT;
end
else begin
nstate = cstate;
end
end
RD_WAIT :begin
if (RD_WAIT_DONE) begin
nstate = DONE;
end
else if (RD_WAIT_RD_REQ) begin
nstate = RD_REQ;
end
else begin
nstate = cstate;
end
end
DONE :begin
if (DONE_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = cstate;
endcase
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
TX(0,4'h0,8'h00);
end
else begin
case (cstate)
RD_REQ:begin
case (cnt_byte)
0 : TX(1,(`START_BIT | `WRITE_BIT),WR_CTRL_BYTE);
1 : TX(1,(`WRITE_BIT ),addr_r[15:8]);
2 : TX(1,(`WRITE_BIT ),addr_r[7:0] );
3 : TX(1,(`START_BIT | `WRITE_BIT),RD_CTRL_BYTE);
4 : TX(1,(`READ_BIT | `STOP_BIT ),8'h00 );
default: TX(0,cmd,op_wr_data);
endcase
end
WR_REQ:begin
case (cnt_byte)
0 : TX(1,(`START_BIT | `WRITE_BIT),WR_CTRL_BYTE);
1 : TX(1,(`WRITE_BIT ),addr_r[15:8]);
2 : TX(1,(`WRITE_BIT ),addr_r[7:0] );
3 : TX(1,(`WRITE_BIT | `STOP_BIT ),wr_data_r );
default: TX(0,cmd,op_wr_data);
endcase
end
default: TX(0,cmd,op_wr_data);
endcase
end
end
I2C_driver u_I2C_driver(
.clk (clk),
.rst_n (rst_n),
.wr_data (op_wr_data),
.cmd (cmd),
.cmd_vld (cmd_vld),
.rd_data (rd_data),
.rd_data_vld (rd_data_vld),
.done (done),
.scl (scl),
.sda (sda)
);
task TX;
input task_cmd_vld ;
input [3:0] task_cmd ;
input [7:0] task_wr_data ;
begin
cmd_vld = task_cmd_vld ;
cmd = task_cmd ;
op_wr_data = task_wr_data ;
end
endtask
assign ready = cstate == IDLE;
endmodule
5.3按键消抖模块(可以不用)
/**************************************功能介绍***********************************
Date : 2024年5月20日16:33:32
Author :
Version : 1.0
Description: 四位按键消抖模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module key_debounce #(parameter WIDTH = 1,
parameter DELAY_20MS = 1000_000
)(
input clk ,
input rst_n ,
input [WIDTH-1:0] key_in ,
output [WIDTH-1:0] key_out
);
//---------<参数定义>---------------------------------------------------------
reg [WIDTH-1:0] key_out_r;
//状态机参数定义
localparam IDLE = 4'b0001,//空闲状态
FILETER_DOWN = 4'b0010,//按键按下抖动状态
HOLD_DOWN = 4'b0100,//按下稳定按下状态
FILTER_UP = 4'b1000;//按键释放抖动状态
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] cstate ;//现态
reg [3:0] nstate ;//次态
reg [WIDTH-1:0] key_r0 ;//同步打拍
reg [WIDTH-1:0] key_r1 ;
reg [WIDTH-1:0] key_r2 ;
wire [WIDTH-1:0] n_edge ;//下降沿
wire [WIDTH-1:0] p_edge ;//上升沿
reg [19:0] cnt_20ms ;//20ms计数器
wire add_cnt_20ms;
wire end_cnt_20ms;
//状态转移条件定义
wire idle2filter_down ;
wire fiter_down2hold_down ;
wire hold_down2filter_up ;
wire filter_up2idle ;
//****************************************************************
//--cstate
//****************************************************************
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;//复位的初始状态
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE : begin
if(idle2filter_down)begin
nstate = FILETER_DOWN;
end
else begin
nstate = cstate;
// state_n = IDLE;
end
end
FILETER_DOWN : begin
if(fiter_down2hold_down)begin
nstate = HOLD_DOWN;
end
else begin
nstate = cstate;
end
end
HOLD_DOWN : begin
if(hold_down2filter_up)begin
nstate = FILTER_UP;
end
else begin
nstate = cstate;
end
end
FILTER_UP : begin
if(filter_up2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign idle2filter_down = cstate == IDLE && n_edge;
assign fiter_down2hold_down = cstate == FILETER_DOWN && end_cnt_20ms;
assign hold_down2filter_up = cstate == HOLD_DOWN && p_edge;
assign filter_up2idle = cstate == FILTER_UP && end_cnt_20ms;
//****************************************************************
//--n_edge、p_edge
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {WIDTH{1'b1}};
key_r1 <= {WIDTH{1'b1}};
key_r2 <= {WIDTH{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
key_r2 <= key_r1;
end
end
assign n_edge = ~key_r1 & key_r2;
assign p_edge = ~key_r2 & key_r1;
//****************************************************************
//--cnt_20ms
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 'd0;
end
else if(add_cnt_20ms)begin
if(end_cnt_20ms)begin
cnt_20ms <= 'd0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = cstate == FILETER_DOWN || cstate == FILTER_UP;
assign end_cnt_20ms = add_cnt_20ms && cnt_20ms == DELAY_20MS - 1;
//****************************************************************
//--key_out
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= 'd0;
end
else if(hold_down2filter_up)begin
key_out_r <= ~key_r2;
end
else begin
key_out_r <= 'd0;
end
end
assign key_out = key_out_r;
endmodule
5.4串口接收模块(非必要)
/**************************************功能介绍***********************************
Date : 22024年5月20日16:38:29
Author :
Version : 1.0
Description: FPGA收上位机发来的数据【1bit(波形)变8bit】
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module uart_rx(
input clk ,
input rst_n ,
input rx ,
output rx_data_vld,
output [7:0] rx_data
);
//---------<参数定义>---------------------------------------------------------
parameter MAX_BPS = 115200;
parameter CLOCK = 50_000_000;
parameter MAX_1bit = CLOCK/MAX_BPS;//1bit要计434次
parameter CHECK_BIT = "None";//None无校验,Odd奇校验,Even偶校验
//状态机参数定义
localparam IDLE = 'b0001,//空闲状态
START = 'b0010,//起始位
DATA = 'b0100,//数据位
CHECK = 'b1000;
//---------<内部信号定义>-----------------------------------------------------
reg [3:0] cstate ;//现态
reg [3:0] nstate ;//次态
wire IDLE_START;
wire START_DATA;
wire DATA_IDLE;
wire DATA_CHECK;
wire CHECK_IDLE;
reg [8:0] cnt_baud ;//波特计数器,波特率115200
wire add_cnt_baud ;
wire end_cnt_baud ;
reg [2:0] cnt_bit ;//bit计数器,起始位1bit,数据位8bit,结束位1bit
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max;//bit最大值,复用需要考察每个状态的bit值
reg [7:0] rx_temp;
reg rx_check;
wire check_val;
reg rx_r1;
reg rx_r2;
wire rx_nege;
//打两拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_r1 <= 1;
rx_r2 <= 1;
end
else begin
rx_r1 <= rx;
rx_r2 <= rx_r1;
end
end
assign rx_nege = ~rx_r1 && rx_r2;
//计434次
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_baud <= 'd0;
end
else if(add_cnt_baud)begin
if(end_cnt_baud)begin
cnt_baud <= 'd0;
end
else begin
cnt_baud <= cnt_baud + 1'd1;
end
end
end
assign add_cnt_baud = cstate != IDLE;
assign end_cnt_baud = add_cnt_baud && cnt_baud == MAX_1bit - 1'd1;
//bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = end_cnt_baud;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max -1'd1;
//计数器复用
always @(*)begin
case (cstate)
IDLE :bit_max = 'd0;
START:bit_max = 'd1;//起始位1bit
DATA :bit_max = 'd8;//数据位8bit
CHECK:bit_max = 'd1;
default: bit_max = 'd0;
endcase
end
assign IDLE_START = (cstate == IDLE) && rx_nege;//识别到起始位0
assign START_DATA = (cstate == START) && end_cnt_bit;//计1bit数据
assign DATA_IDLE = (cstate == DATA) && end_cnt_bit && CHECK_BIT == "None";//计8bit数据
assign DATA_CHECK = (cstate == DATA) && end_cnt_bit;
assign CHECK_IDLE = (cstate == CHECK) && end_cnt_bit;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_START) begin
nstate = START;
end
else begin
nstate = cstate;
end
end
START :begin
if (START_DATA) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA :begin
if (DATA_IDLE) begin
nstate = IDLE;
end
else if (DATA_CHECK) begin
nstate = CHECK;
end
else begin
nstate = cstate;
end
end
CHECK:begin
if (CHECK_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
//接受校验位
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_check <= 0;
end
else if (cstate == CHECK && cnt_baud == MAX_1bit >>1) begin
rx_check <= rx_r1;
end
end
//计算校验位
assign check_val = (CHECK_BIT == "Odd") ? ~^rx_temp : ^rx_temp;
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_temp <= 0;
end
else if (cstate == DATA && cnt_baud == MAX_1bit >> 1) begin//电平中间值采样,边沿采样容易出错
rx_temp[cnt_bit] <= rx_r1;
end
else begin
rx_temp <= rx_temp;
end
end
assign rx_data = rx_temp;
assign rx_data_vld = (CHECK_BIT == "None") ? DATA_IDLE
:(CHECK_IDLE && (check_val == rx_check)) ? 1
: 0;
endmodule
5.5串口发送模块(非必要)
/**************************************功能介绍***********************************
Date : 2024年5月20日16:40:06
Author :
Version : 2.0
Description: FPGA向上位机发送数据【8bit变1bit(波形)】
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module uart_tx(
input clk ,
input rst_n ,
input [7:0] tx_data ,
input tx_data_vld,
output ready ,
output reg tx
);
//---------<参数定义>---------------------------------------------------------
parameter MAX_BPS = 115200;
parameter CLOCK = 50_000_000;
parameter MAX_1bit = CLOCK/MAX_BPS;//1bit要计434次
parameter CHECK_BIT = "None";//None无校验,Odd奇校验,Even偶校验
//状态机参数定义
localparam IDLE = 'b00001,//空闲状态
START = 'b00010,//起始位
DATA = 'b00100,//数据位
CHECK = 'b01000,//校验位
STOP = 'b10000;//停止位
//---------<内部信号定义>-----------------------------------------------------
reg [4:0] cstate ;//现态
reg [4:0] nstate ;//次态
wire IDLE_START;
wire START_DATA;
wire DATA_CHECK;
wire CHECK_STOP;
wire STOP_IDLE;
reg [8:0] cnt_baud ;//波特计数器,波特率115200
wire add_cnt_baud ;
wire end_cnt_baud ;
reg [2:0] cnt_bit ;//bit计数器,起始位1bit,数据位8bit,结束位1bit
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max;//bit最大值,复用需要考察每个状态的bit值
reg [7:0] tx_data_r;
wire check_val;
//计434次
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_baud <= 'd0;
end
else if(add_cnt_baud)begin
if(end_cnt_baud)begin
cnt_baud <= 'd0;
end
else begin
cnt_baud <= cnt_baud + 1'd1;
end
end
end
assign add_cnt_baud = cstate != IDLE;
assign end_cnt_baud = add_cnt_baud && cnt_baud == MAX_1bit - 1'd1;
//bit计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 'd0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 'd0;
end
else begin
cnt_bit <= cnt_bit + 1'd1;
end
end
end
assign add_cnt_bit = end_cnt_baud;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max -1'd1;
//计数器复用
always @(*)begin
case (cstate)
IDLE :bit_max = 'd0;
START:bit_max = 'd1;//起始位1bit
DATA :bit_max = 'd8;//数据位7bit
CHECK:bit_max = 'd1;//校验位1bit
STOP :bit_max = 'd1;//结束位1bit
default: bit_max = 'd0;
endcase
end
assign IDLE_START = (cstate == IDLE) && tx_data_vld;//考察到开始传输信号
assign START_DATA = (cstate == START) && end_cnt_bit;//计1bit数据
assign DATA_STOP = (cstate == DATA) && end_cnt_bit && CHECK_BIT == "None";
assign DATA_CHECK = (cstate == DATA) && end_cnt_bit;//计8bit数据
assign CHECK_STOP = (cstate ==CHECK) && end_cnt_bit;//计1bit数据
assign STOP_IDLE = (cstate == STOP) && end_cnt_bit;//计1bit数据
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_START) begin
nstate = START;
end
else begin
nstate = cstate;
end
end
START :begin
if (START_DATA) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA :begin
if (DATA_CHECK) begin
nstate = CHECK;
end
else if (DATA_STOP) begin
nstate = STOP;
end
else begin
nstate = cstate;
end
end
CHECK :begin
if (CHECK_STOP) begin
nstate = STOP;
end
else begin
nstate = cstate;
end
end
STOP :begin
if (STOP_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = cstate;
endcase
end
//寄存一拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_data_r <= 'd0;
end
else if (tx_data_vld) begin
tx_data_r <= tx_data;
end
else begin
tx_data_r <= tx_data_r;
end
end
assign check_val = (CHECK_BIT == "Odd") ? ~^tx_data_r : ^tx_data_r;
//第三段:描述输出,时序逻辑或组合逻辑皆可
always @(*)begin
case (cstate)
IDLE : tx = 1'b1;
START: tx = 1'b0;//起始位为0
DATA : tx = tx_data_r[cnt_bit];
CHECK: tx = check_val;
STOP : tx = 1'b1;//结束位为1
default: tx = 1'b1;
endcase
end
assign ready = cstate == IDLE;//当状态为IDLE时,表示tx端可以接收数据
endmodule
5.6顶层模块:
/**************************************功能介绍***********************************
Date : 2024年5月20日16:54:32
Author :
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
input key_in ,
input rx ,
output tx ,
output scl ,
inout sda
);
//---------<参数定义>---------------------------------------------------------
wire rd_req;
wire wr_req;
wire [7:0] wr_data;
wire [7:0] rd_data;
wire rd_data_vld;
//---------<内部信号定义>-----------------------------------------------------
key_debounce u_key_debounce(
.clk (clk),
.rst_n (rst_n),
.key_in (key_in),
.key_out(rd_req)
);
uart_rx u_uart_rx(
.clk (clk),
.rst_n (rst_n),
.rx (rx),
.rx_data_vld (wr_req),
.rx_data (wr_data)
);
uart_tx u_uart_tx(
.clk (clk),
.rst_n (rst_n),
.tx_data (rd_data),
.tx_data_vld(rd_data_vld),
.ready (),
.tx (tx)
);
I2C_control u_I2C_control(
.clk (clk),
.rst_n (rst_n),
.wr_req (wr_req),
.rd_req (rd_req),
.device_id (7'b1010000),
.reg_addr (8'h03),
.reg_addr_vld(1'b1),
.wr_data (wr_data),
.wr_data_vld (wr_req),
.rd_data (rd_data),
.rd_data_vld (rd_data_vld),
.ready (),
.scl (scl),
.sda (sda)
);
endmodule