文章目录
- 前言
- 一、I2C协议
- 1.1 I2C协议简介
- 1.2 物理层
- 1.3 协议层
- 二、EEPROM
- 2.1 型号及硬件规格
- 2.2 各种读写时序
- 三、状态机设计
- 四、项目源码:
- 五、实现效果
- 参考资料
前言
本次项目所用开发板FPGA芯片型号为:EP4CE6F17C8 EEPROM芯片型号为:24LC04B UART串口设置为:波特率115200
无校验位本次项目仅实现了EEPROM的单字节读写。若要实现连续读写可以在下文的EEPROM控制模块设置计数器控制读写字数。
一、I2C协议
1.1 I2C协议简介
I2C总线是Philips公司在八十年代初推出的一种同步、串行、半双工 的总线,主要用于近距离、低速的芯片之间的通信;I2C总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步;I2C总线硬件结构简单,简化了PCB布线,降低了系统成本,提高了系统可靠性,因此在各个领域得到了广泛应用。
I2C是一种多主机多从机总线,通过呼叫应答的方式实现主机与从机间的通信。每一个I2C设备都有一个唯一的7位地址,也就是说同一根线上最多挂载127个I2C从机设备(主机自己占一个地址)。主机有权发起和结束一次通信,从机只能被动呼叫;当总线上有多个主机同时启用总线时,I2C也具备冲突检测和仲裁的功能来防止错误产生。
1.2 物理层
-
I2C支持多主机多从机
-
I2C有两条线,一条SCL串行时钟线,用于数据收发同步;一条SDA串行数据线,用来表示数据
-
每个连接到IIC总线的设备都有一个唯一的地址(ID),主机可以通过地址与不同的从机建立连接
-
I2C 总线通过上拉电阻接到电源。当 IIC 设备空闲时,设备会输出高阻态,当所有设备都空闲,都输出高阻态时,由上拉电阻把 IIC总线拉成高电平。
-
I2C总线有仲裁机制:当多个主机同时发起传输时,触发仲裁机制,最终只给一个主机授权。
-
具有三种传输模式,每种传输模式对应的传输速率不同,具体速率如下:
有些I2C变种还包含了快速+(1Mbit/s)和超高速(5Mbit/s 单向传输)模式。
1.3 协议层
- 空闲状态:I2C协议规定,在空闲状态下SDA数据线和SCL时钟线均处于高电平。
- 开始位:当SCL处于高电平时,SDA数据线被拉低,则认为检测到起始位,一次数据传输开始。
- 数据传输:同时,协议规定在SCL高电平时期SDA数据线必须保持稳定,在SCL低电平时,SDA才允许发生改变。主机进行数据读写时,I2C协议规定,数据传输时先发送寻址字节,即7位从机地址+0/1。其中0表示主机写,1表示主机读。寻址字节发送完成后才是数据字节
- 应答位:I2C协议规定,主机每次向从机传输1字节数据后,需要接收一次从机的应答信号。0为接收成功;1为接受失败,没有响应。
- 停止位:当SCL为高电平时,数据线SDA拉高,则认为检测到停止位,一次数据传输结束。
二、EEPROM
2.1 型号及硬件规格
EEPROM的全称是“电可擦除可编程只读存储器”,即Electrically Erasable Programmable Read-Only Memory。本次项目中使用的EEPROM型号为24LC04B。
由手册可以看出,24LC04B支持的最大时钟频率为400KHz。
由手册得出该型号EEPROM共有两个Block 每个Block的存储容量为256×8bit。
由手册得出,设备地址为1010xxB0共七位(xx为dont care B0为块选择),1为读操作,0为写操作。
2.2 各种读写时序
单字节写
- 先写开始位
- 然后写控制字节,从机接收到发应答信号
- 然后写数据地址,从机接收到发应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到发应答信号
- 然后写数据,从机接收到发应答信号
- 最后写结束位
页写
- 页节写先开始位
- 然后写控制字节,从机接收到应答信号
- 然后写数据地址,从机接收到应答信号,如果数据地址是2位的话,就继续写数据地址,从机接收到应答信号
- 然后写数据,从机接收到应答信号
- 然后继续写数据,直到写完全部的数据
- 最后是结束位
当前地址读
- 当前地址读先开始位
- 然后写控制字节,从机接收到应答信号,然后读数据,无应答信号
- 最后结束位
随机地址读和顺序读
- 随机读先写开始位
- 然后写控制字节,从机接收到应答信号
- 然后dummuy write 虚写,写数据地址,从机接收到应答信号
- 然后开始位
- 然后读控制字节,从机接收到应答信号
- 然后读数据
- 最后结束位
三、状态机设计
I2C接口模块:
EEPROM控制模块:
四、项目源码:
EEPROM驱动模块:
/**************************************功能介绍***********************************
Date : 2023年8月28日
Author : majiko
Version : 1.0
Description: eeprom驱动模块
cmd 0 1
开始位 NO YES
写数据 NO YES
读数据 NO YES
停止位 NO YES
应答位 ACK NO_ACK
*********************************************************************************/
module eeprom_driver(
input wire clk ,
input wire rst_n ,
input wire [7:0] wr_data ,
input wire [4:0] cmd ,
input wire cmd_vld ,
inout wire i2c_sda ,
output reg i2c_scl ,
output wire [7:0] rd_data ,
output wire rd_data_vld ,
output reg done ,
output reg rev_ack
);
//内部参数定义
//命令宏定义
`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
//状态定义
parameter IDLE = 7'b000_0001,
START = 7'b000_0010,
WR_DATA = 7'b000_0100,
R_ACK = 7'b000_1000,
STOP = 7'b001_0000,
RD_DATA = 7'b010_0000,
T_ACK = 7'b100_0000;
//i2c速率以及采样点定义
parameter SCL_MAX = 50_000_000/100_000; //100K速率
parameter SCL_1_4 = SCL_MAX/4;
parameter SCL_3_4 = SCL_MAX*3/4;
//内部信号定义
//状态寄存器
reg [6:0] cstate ;
reg [6:0] nstate ;
reg [7:0] wr_data_r ;//输入数据寄存
reg [4:0] cmd_r ;//输入命令寄存
reg [7:0] rev_data ;
//跳转条件定义
wire idle2start ;
wire idle2wr_data ;
wire idle2rd_data ;
wire start2wr_data ;
wire wr_data2r_ack ;
wire r_ack2stop ;
wire r_ack2idle ;
wire stop2idle ;
wire rd_data2t_ack ;
wire t_ack2stop ;
wire t_ack2idle ;
//bit计数器
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;
//i2c分频计数器
reg [8:0] cnt_scl ;
wire add_cnt_scl ;
wire end_cnt_scl ;
//三态门信号定义
reg sda_out ;
wire sda_in ;
reg sda_en ;
//****************************************************************
//--数据寄存 命令寄存
//****************************************************************
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data_r <= 1'b0;
end
else if(cmd_vld)begin
wr_data_r <= wr_data;
end
else begin
wr_data_r <= wr_data_r;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cmd_r <= 1'b0;
end
else if(cmd_vld)begin
cmd_r <= cmd;
end
else begin
cmd_r <= cmd_r;
end
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(idle2start)begin
nstate = START;
end
else if(idle2wr_data)begin
nstate = WR_DATA;
end
else if(idle2rd_data)begin
nstate = RD_DATA;
end
else begin
nstate = cstate;
end
end
START : begin
if(start2wr_data)begin
nstate = WR_DATA;
end
else begin
nstate = cstate;
end
end
WR_DATA : begin
if(wr_data2r_ack)begin
nstate = R_ACK;
end
else begin
nstate = cstate;
end
end
R_ACK : begin
if(r_ack2stop)begin
nstate = STOP;
end
else if(r_ack2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
STOP : begin
if(stop2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
RD_DATA : begin
if(rd_data2t_ack)begin
nstate = T_ACK;
end
else begin
nstate = cstate;
end
end
T_ACK : begin
if(t_ack2stop)begin
nstate = STOP;
end
else if(t_ack2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default: nstate = IDLE;
endcase
end
assign idle2start = (cstate == IDLE) && cmd_vld && (cmd & `START_BIT);
assign idle2wr_data = (cstate == IDLE) && cmd_vld && (cmd & `WRITE_BIT);
assign idle2rd_data = (cstate == IDLE) && cmd_vld && (cmd & `READ_BIT);
assign start2wr_data = (cstate == START ) && end_cnt_bit && (cmd_r & `WRITE_BIT);
assign wr_data2r_ack = (cstate == WR_DATA) && end_cnt_bit;
assign r_ack2stop = (cstate == R_ACK) && end_cnt_bit && (cmd_r & `STOP_BIT);
assign r_ack2idle = (cstate == R_ACK) && end_cnt_bit && !(cmd_r & `STOP_BIT);
assign stop2idle = (cstate == STOP ) && end_cnt_bit;
assign rd_data2t_ack = (cstate == RD_DATA) && end_cnt_bit;
assign t_ack2stop = (cstate == T_ACK) && end_cnt_bit && (cmd_r & `STOP_BIT);
assign t_ack2idle = (cstate == T_ACK) && end_cnt_bit && !(cmd_r & `STOP_BIT);
//****************************************************************
//--i2c时钟分频
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_scl <= 'd0;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 'd0;
end
else begin
cnt_scl <= cnt_scl + 1'b1;
end
end
end
assign add_cnt_scl = cstate != IDLE;
assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1'b1;
//assign i2c_scl = (cnt_scl == (SCL_MAX - 1'b1) >> 1'b1 || stop2idle) ? 1'b1 : 1'b0;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
i2c_scl <= 1'b1;
end
else if(cnt_scl == (SCL_MAX - 1) >> 1'b1 || stop2idle)begin
i2c_scl <= 1'b1;
end
else if(end_cnt_scl)begin
i2c_scl <= 1'b0;
end
else begin
i2c_scl <= i2c_scl;
end
end
//****************************************************************
//--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'b1;
end
end
end
assign add_cnt_bit = end_cnt_scl;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;
always @(*)begin
if(cstate == WR_DATA || cstate == RD_DATA)begin
bit_max = 'd8;
end
else begin
bit_max = 'd1;
end
end
//****************************************************************
//--三态门控制
//****************************************************************
assign sda_in = i2c_sda;
assign i2c_sda = sda_en ? sda_out : 1'bz;
//使能信号开启
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_en <= 1'b0;
end
else if(wr_data2r_ack || stop2idle || idle2rd_data)begin
sda_en <= 1'b0;
end
else if(idle2wr_data || idle2start || rd_data2t_ack || r_ack2stop || start2wr_data)begin
sda_en <= 1'b1;
end
else begin
sda_en <= sda_en;
end
end
//****************************************************************
//--数据发送
//****************************************************************
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1'b1;
end
else begin
case(cstate)
IDLE : sda_out <= 1'b1;
START : begin
if(cnt_scl == SCL_1_4)begin
sda_out <= 1'b1;
end
else if(cnt_scl == SCL_3_4)begin
sda_out <= 1'b0;
end
else begin
sda_out <= sda_out;
end
end
WR_DATA : begin
if(cnt_scl == SCL_1_4)begin
sda_out <= wr_data_r[7-cnt_bit];
end
else begin
sda_out <= sda_out;
end
end
T_ACK : begin
if(cnt_scl == SCL_1_4)begin
if(cmd & `ACK_BIT)begin
sda_out <= `NO_ACK;
end
else begin
sda_out <= `ACK;
end
end
else begin
sda_out <= sda_out;
end
end
STOP : begin
if(cnt_scl == SCL_1_4)begin
sda_out <= 1'b0;
end
else if(cnt_scl == SCL_3_4)begin
sda_out <= 1'b1;
end
else begin
sda_out <= sda_out;
end
end
default : sda_out <= 1'b1;
endcase
end
end
//****************************************************************
//--数据接收
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rev_ack <= 1'b0;
rev_data <= 1'b0;
end
else begin
case(cstate)
R_ACK : begin
if(cnt_scl == SCL_3_4)begin
rev_ack <= sda_in;
end
else begin
rev_ack <= rev_ack;
end
end
RD_DATA : begin
if(cnt_scl == SCL_3_4)begin
rev_data[7-cnt_bit] <= sda_in;
end
else begin
rev_data <= rev_data;
end
end
default : ;
endcase
end
end
//****************************************************************
//--接口输出
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
done <= 1'b0;
end
else begin
done <= t_ack2idle || r_ack2idle || stop2idle;
end
end
//assign done = (t_ack2idle || r_ack2idle || stop2idle) ? 1'b1 : 1'b0;
assign rd_data = (t_ack2idle || t_ack2stop) ? rev_data : 1'b0;
assign rd_data_vld = t_ack2idle || t_ack2stop;
endmodule
EEPROM控制模块:
/**************************************功能介绍***********************************
Date : 2023年8月30日
Author : majiko
Version : 1.0
Description: EEPROM控制模块
*********************************************************************************/
module eeprom_control
#(
parameter ADDR_BIT = 8 //从机寄存器地址
)
(
input wire clk ,
input wire rst_n ,
input wire [6:0] device_id ,//i2c从机设备地址
input wire wr_req ,
input wire rd_req ,
input wire [ADDR_BIT - 1:0] reg_addr ,//配置寄存器地址
input wire reg_addr_vld ,
input wire [7:0] wr_data ,
input wire wr_data_vld ,
output wire [7:0] rd_data ,
output wire rd_data_vld ,
output wire ready ,
output wire i2c_scl ,
inout wire i2c_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
parameter IDLE = 6'b000_001,
WR_REQ = 6'b000_010,
WR_WAIT = 6'b000_100,
RD_REQ = 6'b001_000,
RD_WAIT = 6'b010_000,
DONE = 6'b100_000;
parameter WR_CTRL_BYTE = 8'b1010_0000,
RD_CTRL_BYTE = 8'b1010_0001;
//内部信号定义
//状态寄存器
reg [5:0] cstate ;
reg [5:0] nstate ;
//跳转条件定义
wire idle2rd_req ;
wire idle2wr_req ;
wire wr_req2wr_wait ;
wire wr_wait2wr_req ;
wire wr_wait2done ;
wire rd_req2rd_wait ;
wire rd_wait2done ;
wire rd_wait2rd_req ;
wire done2idle ;
wire done ;
reg [4:0] cmd ;
reg cmd_vld ;
reg [7:0] op_wr_data ;
reg [7:0] addr_r ;
reg [7:0] wr_data_r ;
//字节计数器
reg [2:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [2:0] byte_mawr_addr ;
//****************************************************************
//--寄存输入数据
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
addr_r <= 1'b0;
end
else if(reg_addr_vld)begin
addr_r <= reg_addr;
end
else begin
addr_r <= addr_r;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data_r <= 1'b0;
end
else if(wr_data_vld)begin
wr_data_r <= wr_data;
end
else begin
wr_data_r <= wr_data_r;
end
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(idle2rd_req)begin
nstate = RD_REQ;
end
else if(idle2wr_req)begin
nstate = WR_REQ;
end
else begin
nstate = cstate;
end
end
WR_REQ : begin
if(wr_req2wr_wait)begin
nstate = WR_WAIT;
end
else begin
nstate = cstate;
end
end
WR_WAIT : begin
if(wr_wait2done)begin
nstate = DONE;
end
else if(wr_wait2wr_req)begin
nstate = WR_REQ;
end
else begin
nstate = cstate;
end
end
RD_REQ : begin
if(rd_req2rd_wait)begin
nstate = RD_WAIT;
end
else begin
nstate = cstate;
end
end
RD_WAIT : begin
if(rd_wait2done)begin
nstate = DONE;
end
else if(rd_wait2rd_req)begin
nstate = RD_REQ;
end
else begin
nstate = cstate;
end
end
DONE : begin
if(done2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : ;
endcase
end
assign idle2rd_req = cstate == IDLE && rd_req;
assign idle2wr_req = cstate == IDLE && wr_req;
assign wr_req2wr_wait = cstate == WR_REQ && 1'b1;
assign wr_wait2wr_req = cstate == WR_WAIT && done;
assign wr_wait2done = cstate == WR_WAIT && end_cnt_byte;
assign rd_req2rd_wait = cstate == RD_REQ && 1'b1;
assign rd_wait2done = cstate == RD_WAIT && end_cnt_byte;
assign rd_wait2rd_req = cstate == RD_WAIT && done;
assign done2idle = cstate == DONE && 1'b1;
//****************************************************************
//--字节计数器
//****************************************************************
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'b1;
end
end
end
assign add_cnt_byte = done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_mawr_addr - 1'b1;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
byte_mawr_addr <= 1'b1;
end
else if(wr_req)begin
byte_mawr_addr <= 'd3;
end
else if(rd_req)begin
byte_mawr_addr <= 'd4;
end
else if(end_cnt_byte)begin
byte_mawr_addr <= 1'b1;
end
else begin
byte_mawr_addr <= byte_mawr_addr;
end
end
//****************************************************************
//--接口模块控制
//****************************************************************
task TX;
input task_cmd_vld;
input [4: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
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
TX(0,5'h0,8'h00);
end
else begin
case(cstate)
RD_REQ : case(cnt_byte)
0 : TX(1,(`START_BIT | `WRITE_BIT),WR_CTRL_BYTE);//写控制字节
1 : TX(1,(`WRITE_BIT),addr_r[7:0]);//写地址
2 : TX(1,(`START_BIT | `WRITE_BIT),RD_CTRL_BYTE);//读控制字节
3 : TX(1,(`ACK_BIT| `READ_BIT | `STOP_BIT),8'h00);//读数据
default : TX(0,cmd,op_wr_data);
endcase
WR_REQ : case(cnt_byte)
0 : TX(1,(`START_BIT | `WRITE_BIT),WR_CTRL_BYTE);//写控制字节
1 : TX(1,(`WRITE_BIT),addr_r[7:0]);//写地址
2 : TX(1,(`WRITE_BIT | `STOP_BIT),wr_data_r);//写数据
default : TX(0,cmd,op_wr_data);
endcase
default : TX(0,cmd,op_wr_data);
endcase
end
end
eeprom_driver u_eeprom_driver(
/*input wire */.clk (clk ),
/*input wire */.rst_n (rst_n ),
/*input wire [7:0] */.wr_data (op_wr_data ),
/*input wire [4:0] */.cmd (cmd ),
/*input wire */.cmd_vld (cmd_vld ),
/*inout wire */.i2c_sda (i2c_sda ),
/*output wire */.i2c_scl (i2c_scl ),
/*output wire [7:0] */.rd_data (rd_data ),
/*output wire */.rd_data_vld (rd_data_vld ),
/*output wire */.done (done ),
/*output reg */.rev_ack ()
);
// iic_interface iic_interface_inst(
// /* input */.clk (clk ),
// /* input */.rst_n (rst_n ),
// /* input [4:0] */.cmd (cmd ),
// /* input */.cmd_vld (cmd_vld ),
// /* output */.done (done ), //操作结束
// /* output reg */.rev_ack ( ), //接收到的响应信号
// /* input [7:0] */.wr_data (op_wr_data ), //伴随cmd_vld信号一起接收写数据
// /* output [7:0] */.rd_data (rd_data ),
// /* output */.rd_data_vld (rd_data_vld),
// /* output reg */.iic_scl (i2c_scl ),
// /* inout */.iic_sda (i2c_sda )
// );
assign ready = cstate == IDLE;
endmodule
UART RX模块:
//****************************************************************
//--majiko 2023-8-15 UART RX模块
//****************************************************************
module uart_rx
#(
parameter BORT = 20'd115200,
parameter CLOCK = 50_000_000,
parameter CHECK_BIT = "None" //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
)
(
input wire clk ,
input wire rst_n ,
input wire ready ,//准备接受数据信号
input wire rx ,
output wire rx_data_vld ,//数据有效信号
output wire [7:0] rx_data
);
//参数定义
parameter TIME_BORT = CLOCK/BORT;
//状态机状态定义
parameter IDLE = 4'b0001,//空闲状态电平默认拉高
START = 4'b0010,//起始位赋值
DATA = 4'b0100,//数据位赋值
CHECK = 4'b1000;
//波特率115200 1bit传输时间计数器参数
//内部信号定义
//状态寄存器
reg [3:0] cstate ;
reg [3:0] nstate ;
//状态跳转条件
wire idle2start ;
wire start2data ;
wire data2idle ;
wire data2check ;
wire check2idle ;
//1Baud时间计数器
reg [8:0] cnt_bort ;
wire add_cnt_bort ;
wire end_cnt_bort ;
//比特计数器(复用)
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;//计数器复用信号
reg [7:0] rx_temp ;
reg check_bit ;
wire check_temp ;
reg rx_r1 ;
reg rx_r2 ;
wire rx_nedge ;
//rx同步至时钟域 并检测下降沿
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_r1 <= 1'b1;
rx_r2 <= 1'b1;
end
else begin
rx_r1 <= rx;
rx_r2 <= rx_r1;
end
end
assign rx_nedge = ~rx_r1 && rx_r2;
//三段式状态机
//第一段 时序逻辑
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(idle2start)begin
nstate = START;
end
else begin
nstate = cstate;
end
end
START: begin
if(start2data)begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA : begin
if(data2idle)begin
nstate = IDLE;
end
else if(data2check)begin
nstate = CHECK;
end
else begin
nstate = cstate;
end
end
CHECK: begin
if(check2idle)begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign idle2start = cstate == IDLE && rx_nedge;//检测到开始位,结束空闲状态,开始接收数据
assign start2data = cstate == START && end_cnt_bit;
assign data2idle = cstate == DATA && end_cnt_bit && CHECK_BIT == "None";
assign data2check = cstate == DATA && end_cnt_bit;
assign check2idle = cstate == CHECK && end_cnt_bit;
//1Baud所需时间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bort <= 'd0;
end
else if(add_cnt_bort)begin
if(end_cnt_bort)begin
cnt_bort <= 'd0;
end
else begin
cnt_bort <= cnt_bort + 1'b1;
end
end
end
assign add_cnt_bort = cstate != IDLE;
assign end_cnt_bort = add_cnt_bort && cnt_bort == TIME_BORT - 1'b1;
//8bit共需时间
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'b1;
end
end
end
assign add_cnt_bit = end_cnt_bort;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;
//bit计数器复用
always@(*)begin
case(cstate)
IDLE : bit_max = 1'b0;
START : bit_max = 1'b1;
DATA : bit_max = 4'd8;//8位数据位
CHECK : bit_max = 1'b1;//校验位
default : bit_max = 1'b0;
endcase
end
//状态输出
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_temp <= 1'b0;
end
else if(cstate == DATA && cnt_bort == (TIME_BORT >> 1))begin
rx_temp[cnt_bit] <= rx_r1;
end
else begin
rx_temp <= rx_temp;
end
end
assign rx_data = rx_temp;
//校验位
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
check_bit <= 1'b0;
end
else if(cstate == CHECK && cnt_bort == (TIME_BORT >> 1))begin
check_bit <= rx;
end
else begin
check_bit <= check_bit;
end
end
assign check_temp = CHECK_BIT == "Odd" ? ~^rx_data : ^rx_data;
assign rx_data_vld = CHECK_BIT == "None" ? data2idle
: (check2idle && check_temp == check_bit) ? 1
: 0;
endmodule
UART TX模块:
//****************************************************************
//--majiko 2023-8-15 UART TX模块
//****************************************************************
module uart_tx
#(
parameter BORT = 20'd115200,//波特率
parameter CLOCK = 50_000_000,//系统时钟参数
parameter CHECK_BIT = "None" //"None" 无校验,"Odd" 奇校验,"Even" 偶校验
)
(
input wire clk ,
input wire rst_n ,
input wire [7:0] tx_data ,
input wire tx_data_vld ,//数据有效信号
output wire ready ,//准备接受数据信号
output reg tx
);
//参数定义
parameter TIME_BORT = CLOCK/BORT;
//状态机状态定义
parameter IDLE = 5'b00001,//空闲状态电平默认拉高
START = 5'b00010,//起始位赋值
DATA = 5'b00100,//数据位赋值
CHECK = 5'b01000,//奇偶校验位
STOP = 5'b10000;//停止位赋值
//波特率115200 1bit传输时间计数器参数
//内部信号定义
//状态寄存器
reg [4:0] cstate ;
reg [4:0] nstate ;
//状态跳转条件
wire idle2start ;
wire start2data ;
wire data2check ;
wire data2stop ;
wire check2stop ;
wire stop2idle ;
//1Baud时间计数器
reg [8:0] cnt_bort ;
wire add_cnt_bort ;
wire end_cnt_bort ;
//比特计数器(复用)
reg [2:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;//计数器复用信号
reg [7:0] tx_data_r ;//寄存数据
wire check_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(idle2start)begin
nstate <= START;
end
else begin
nstate <= cstate;
end
end
START: begin
if(start2data)begin
nstate <= DATA;
end
else begin
nstate <= cstate;
end
end
DATA : begin
if(data2check)begin
nstate <= CHECK;
end
else if(data2stop)begin
nstate <= STOP;
end
else begin
nstate <= cstate;
end
end
CHECK: begin
if(check2stop)begin
nstate <= STOP;
end
else begin
nstate <= cstate;
end
end
STOP : begin
if(stop2idle)begin
nstate <= IDLE;
end
else begin
nstate <= cstate;
end
end
default : nstate <= IDLE;
endcase
end
assign idle2start = cstate == IDLE && tx_data_vld;//数据有效信号拉高,结束空闲状态,开始接收数据
assign start2data = cstate == START && end_cnt_bit;
assign data2check = cstate == DATA && end_cnt_bit;
assign data2stop = cstate == DATA && end_cnt_bit && CHECK_BIT == "None";
assign check2stop = cstate == CHECK && end_cnt_bit;
assign stop2idle = cstate == STOP && end_cnt_bit;
//1Baud所需时间
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bort <= 'd0;
end
else if(add_cnt_bort)begin
if(end_cnt_bort)begin
cnt_bort <= 'd0;
end
else begin
cnt_bort <= cnt_bort + 1'b1;
end
end
end
assign add_cnt_bort = cstate != IDLE;
assign end_cnt_bort = add_cnt_bort && cnt_bort == TIME_BORT - 1'b1;
//不同状态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'b1;
end
end
end
assign add_cnt_bit = end_cnt_bort;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;
//bit计数器复用
always@(*)begin
case(cstate)
IDLE : bit_max = 1'b0;
START : bit_max = 1'b1;//1位起始位数据
DATA : bit_max = 4'd8;//8位数据位
CHECK : bit_max = 1'b1;//1位奇偶校验位
STOP : bit_max = 1'b1;//1位停止位数据
default : bit_max = 1'b0;
endcase
end
//状态输出
//输入数据寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tx_data_r <= 1'b0;
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_bit = CHECK_BIT == "Odd" ? ~^tx_data_r : ^tx_data_r;
//数据输出
always@(*)begin
case(cstate)
IDLE : tx = 1'b1;//空闲时为高电平
START : tx = 1'b0;//起始位低电平
DATA : tx = tx_data_r[cnt_bit];//数据位从低位开始发送
CHECK : tx = check_bit;
STOP : tx = 1'b1;//停止位高电平
default : tx = 1'b1;
endcase
end
assign ready = cstate == IDLE;
endmodule
顶层模块:
module top(
input wire clk ,
input wire rst_n ,
input wire rx ,
input wire key_in ,
inout wire i2c_sda ,
output wire i2c_scl ,
output wire tx
);
wire ready ;
wire [7:0] rx_data ;
wire [7:0] tx_data ;
wire rx_data_vld ;
wire tx_data_vld ;
wire key_out ;
//模块例化
eeprom_control u_eeprom_control
(
/*input wire */.clk (clk ),
/*input wire */.rst_n (rst_n ),
/*input wire [6:0] */.device_id (7'b1010_000 ),
/*input wire */.wr_req (rx_data_vld ),
/*input wire */.rd_req (key_out ),
/*input wire [ADDR_BIT - 1:0] */.reg_addr (0),
/*input wire */.reg_addr_vld (rx_data_vld ),
/*input wire [7:0] */.wr_data (rx_data ),
/*input wire */.wr_data_vld (rx_data_vld ),
/*output wire [7:0] */.rd_data (tx_data ),
/*output wire */.rd_data_vld (tx_data_vld ),
/*output wire */.ready (),
/*output wire */.i2c_scl (i2c_scl ),
/*inout wire */.i2c_sda (i2c_sda )
);
// iic_control u_eeprom_control2
// (
// /*input wire */.clk (clk ),
// /*input wire */.rst_n (rst_n ),
// /*input wire [6:0] */.device_id (7'b1010_000 ),
// /*input wire */.wr_req (rx_data_vld ),
// /*input wire */.rd_req (key_out ),
// /*input wire [ADDR_BIT - 1:0] */.addr (0),
// /*input wire */.addr_vld (rx_data_vld ),
// /*input wire [7:0] */.wr_data (rx_data ),
// /*input wire */.wr_data_vld (rx_data_vld ),
// /*output wire [7:0] */.rd_data (tx_data ),
// /*output wire */.rd_data_vld (tx_data_vld ),
// /*output wire */.ready (),
// /*output wire */.iic_scl (i2c_scl ),
// /*inout wire */.iic_sda (i2c_sda )
// );
uart_rx u_uart_rx
(
.clk (clk ),
.rst_n (rst_n ),
.ready ( ),
.rx (rx ),
.rx_data_vld (rx_data_vld ),
.rx_data (rx_data )
);
uart_tx u_uart_tx
(
.clk (clk ),
.rst_n (rst_n ),
.tx_data (tx_data ),
.tx_data_vld (tx_data_vld ),
.ready (ready ),
.tx (tx )
);
// ctrl u_ctrl (
// /*input wire */.clk (clk ),
// /*input wire */.rst_n (rst_n ),
// /*input wire */.rx_data_vld (rx_data_vld ),
// /*input reg [7:0] */.rx_data (rx_data ),
// /*input wire */.ready (ready ),
// /*output wire [7:0] */.tx_data (),
// /*output wire */.tx_data_vld ()
// );
key_filter#(.WIDTH(1)) u_key_filter
(
/*input wire */.clk (clk ),
/*input wire */.rst_n (rst_n ),
/*input wire [WIDTH - 1:0] */.key_in (key_in ),
/*output reg [WIDTH - 1:0] */.key_out (key_out )
);
endmodule
按键消抖模块:
module key_filter#(parameter WIDTH = 4) //参数化按键位宽
(
input wire clk ,
input wire rst_n ,
input wire [WIDTH - 1:0] key_in ,//按键输入信号
output reg [WIDTH - 1:0] key_out //输出稳定的脉冲信号
);
parameter MAX = 20'd1_000_000;
reg [19:0] cnt_delay ; //20ms延时计数寄存器
wire add_cnt_delay ; //开始计数的标志
wire end_cnt_delay ; //结束计数的标志
reg [WIDTH - 1:0] key_r0 ; //同步
reg [WIDTH - 1:0] key_r1 ; //打一拍
reg [WIDTH - 1:0] key_r2 ; //打两拍
wire [WIDTH - 1:0] nedge ; //下降沿寄存器
//同步打拍
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
//20ms计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 1'b0;
end
else if(add_cnt_delay )begin
if(nedge)begin //检测到下降沿从0开始计数
cnt_delay <= 1'b0;
end
else if(cnt_delay == MAX - 1'b1)begin
cnt_delay <= cnt_delay; //计数计满结束后保持,避免产生多个输出脉冲
end
else begin
cnt_delay <= cnt_delay + 1'b1;
end
end
else begin
cnt_delay <= 1'b0;
end
end
assign nedge = ~key_r1 & key_r2; //下降沿检测
assign add_cnt_delay = 1'b1;
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX - 1'b1;
//key_out脉冲信号赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out <= 'd0;
end
else if(cnt_delay == MAX - 2'd2)begin //计数计满前一个脉冲时产生按键脉冲
key_out <= ~key_in;
end
else begin
key_out <= 'd0;
end
end
endmodule
五、实现效果
可以看出,能够正常进行单个字节的收发。
参考资料
https://blog.csdn.net/zhangduang_KHKW/article/details/121953275