基于FPGA的I2C读写EEPROM
文章目录
- 前言
- 一、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)beginif(!rst_n)beginwr_data_r <= 1'b0;endelse if(cmd_vld)beginwr_data_r <= wr_data;endelse beginwr_data_r <= wr_data_r;endendalways@(posedge clk or negedge rst_n)beginif(!rst_n)begincmd_r <= 1'b0;endelse if(cmd_vld)begincmd_r <= cmd;endelse begincmd_r <= cmd_r;endend
//****************************************************************
//--读写状态机
//****************************************************************//第一段 状态定义always @(posedge clk or negedge rst_n) beginif(!rst_n)begincstate <= IDLE;endelse begincstate <= nstate;endend//第二段 状态转移规律always@(*)begincase (cstate)IDLE : beginif(idle2start)beginnstate = START;endelse if(idle2wr_data)beginnstate = WR_DATA;endelse if(idle2rd_data)beginnstate = RD_DATA;endelse beginnstate = cstate;endendSTART : beginif(start2wr_data)beginnstate = WR_DATA;endelse beginnstate = cstate;endendWR_DATA : beginif(wr_data2r_ack)beginnstate = R_ACK;endelse beginnstate = cstate;endend R_ACK : beginif(r_ack2stop)beginnstate = STOP;endelse if(r_ack2idle)beginnstate = IDLE;endelse beginnstate = cstate;endendSTOP : beginif(stop2idle)beginnstate = IDLE;endelse beginnstate = cstate;endendRD_DATA : beginif(rd_data2t_ack)beginnstate = T_ACK;endelse beginnstate = cstate;endendT_ACK : beginif(t_ack2stop)beginnstate = STOP;endelse if(t_ack2idle)beginnstate = IDLE;endelse beginnstate = cstate;endend default: nstate = IDLE;endcaseendassign 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)begincnt_scl <= 'd0;end else if(add_cnt_scl)begin if(end_cnt_scl)begin cnt_scl <= 'd0;endelse begin cnt_scl <= cnt_scl + 1'b1;end endend 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)beginif(!rst_n)begini2c_scl <= 1'b1;endelse if(cnt_scl == (SCL_MAX - 1) >> 1'b1 || stop2idle)begini2c_scl <= 1'b1;endelse if(end_cnt_scl)begini2c_scl <= 1'b0;endelse begini2c_scl <= i2c_scl;endend//****************************************************************
//--bit计数器
//****************************************************************always @(posedge clk or negedge rst_n)begin if(!rst_n)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end endend assign add_cnt_bit = end_cnt_scl;assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;always @(*)beginif(cstate == WR_DATA || cstate == RD_DATA)beginbit_max = 'd8;endelse beginbit_max = 'd1;endend//****************************************************************
//--三态门控制
//****************************************************************assign sda_in = i2c_sda;assign i2c_sda = sda_en ? sda_out : 1'bz;//使能信号开启always @(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_en <= 1'b0;endelse if(wr_data2r_ack || stop2idle || idle2rd_data)beginsda_en <= 1'b0;endelse if(idle2wr_data || idle2start || rd_data2t_ack || r_ack2stop || start2wr_data)beginsda_en <= 1'b1;endelse beginsda_en <= sda_en;endend
//****************************************************************
//--数据发送
//****************************************************************always@(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_out <= 1'b1;endelse begincase(cstate)IDLE : sda_out <= 1'b1;START : beginif(cnt_scl == SCL_1_4)beginsda_out <= 1'b1;endelse if(cnt_scl == SCL_3_4)beginsda_out <= 1'b0;endelse beginsda_out <= sda_out;endendWR_DATA : beginif(cnt_scl == SCL_1_4)beginsda_out <= wr_data_r[7-cnt_bit];endelse beginsda_out <= sda_out;endendT_ACK : beginif(cnt_scl == SCL_1_4)beginif(cmd & `ACK_BIT)beginsda_out <= `NO_ACK;endelse beginsda_out <= `ACK;endendelse beginsda_out <= sda_out;endendSTOP : beginif(cnt_scl == SCL_1_4)beginsda_out <= 1'b0;endelse if(cnt_scl == SCL_3_4)beginsda_out <= 1'b1;endelse beginsda_out <= sda_out;endenddefault : sda_out <= 1'b1;endcaseendend//****************************************************************
//--数据接收
//****************************************************************always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrev_ack <= 1'b0;rev_data <= 1'b0;endelse begincase(cstate)R_ACK : beginif(cnt_scl == SCL_3_4)beginrev_ack <= sda_in;endelse beginrev_ack <= rev_ack;endendRD_DATA : beginif(cnt_scl == SCL_3_4)beginrev_data[7-cnt_bit] <= sda_in;endelse beginrev_data <= rev_data;endenddefault : ;endcaseendend//****************************************************************
//--接口输出
//****************************************************************always @(posedge clk or negedge rst_n)beginif(!rst_n)begindone <= 1'b0;endelse begindone <= t_ack2idle || r_ack2idle || stop2idle;endend//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'b10000parameter 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)beginif(!rst_n)beginaddr_r <= 1'b0;endelse if(reg_addr_vld)beginaddr_r <= reg_addr;endelse beginaddr_r <= addr_r;endendalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginwr_data_r <= 1'b0;endelse if(wr_data_vld)beginwr_data_r <= wr_data;endelse beginwr_data_r <= wr_data_r;endend//****************************************************************
//--状态机
//****************************************************************always @(posedge clk or negedge rst_n)beginif(!rst_n)begincstate <= IDLE;endelse begincstate <= nstate;endendalways @(*)begincase(cstate)IDLE : beginif(idle2rd_req)beginnstate = RD_REQ;endelse if(idle2wr_req)beginnstate = WR_REQ;endelse beginnstate = cstate;endendWR_REQ : beginif(wr_req2wr_wait)beginnstate = WR_WAIT;endelse beginnstate = cstate;endendWR_WAIT : beginif(wr_wait2done)beginnstate = DONE;endelse if(wr_wait2wr_req)beginnstate = WR_REQ;endelse beginnstate = cstate;endendRD_REQ : beginif(rd_req2rd_wait)beginnstate = RD_WAIT;endelse beginnstate = cstate;endendRD_WAIT : beginif(rd_wait2done)beginnstate = DONE;endelse if(rd_wait2rd_req)beginnstate = RD_REQ;endelse beginnstate = cstate;endendDONE : beginif(done2idle)beginnstate = IDLE;endelse beginnstate = cstate;endenddefault : ;endcaseendassign 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)begincnt_byte <= 'd0;end else if(add_cnt_byte)begin if(end_cnt_byte)begin cnt_byte <= 'd0;endelse begin cnt_byte <= cnt_byte + 1'b1;end endend 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) beginif(!rst_n)beginbyte_mawr_addr <= 1'b1;endelse if(wr_req)beginbyte_mawr_addr <= 'd3;endelse if(rd_req)beginbyte_mawr_addr <= 'd4;endelse if(end_cnt_byte)beginbyte_mawr_addr <= 1'b1;endelse beginbyte_mawr_addr <= byte_mawr_addr;endend//****************************************************************
//--接口模块控制
//****************************************************************task TX;input task_cmd_vld;input [4:0] task_cmd;input [7:0] task_wr_data;begincmd_vld = task_cmd_vld;cmd = task_cmd;op_wr_data = task_wr_data;endendtaskalways @(posedge clk or negedge rst_n) beginif(!rst_n)beginTX(0,5'h0,8'h00);endelse begincase(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);endcaseWR_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);endcasedefault : TX(0,cmd,op_wr_data);endcaseendendeeprom_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)beginif(!rst_n)beginrx_r1 <= 1'b1;rx_r2 <= 1'b1;endelse beginrx_r1 <= rx;rx_r2 <= rx_r1;endendassign rx_nedge = ~rx_r1 && rx_r2;//三段式状态机//第一段 时序逻辑always @(posedge clk or negedge rst_n) beginif(!rst_n)begincstate <= IDLE;endelse begincstate <= nstate;endend//第二段 组合逻辑always @(*)begincase (cstate)IDLE : beginif(idle2start)beginnstate = START;endelse beginnstate = cstate;endendSTART: beginif(start2data)beginnstate = DATA;endelse beginnstate = cstate;endendDATA : beginif(data2idle)beginnstate = IDLE;endelse if(data2check)beginnstate = CHECK;endelse beginnstate = cstate;endendCHECK: beginif(check2idle)beginnstate = IDLE;endelse beginnstate = cstate;endenddefault : nstate = IDLE;endcaseendassign 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)begincnt_bort <= 'd0;end else if(add_cnt_bort)begin if(end_cnt_bort)begin cnt_bort <= 'd0;endelse begin cnt_bort <= cnt_bort + 1'b1;end endend 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)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end endend assign add_cnt_bit = end_cnt_bort;assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;//bit计数器复用always@(*)begincase(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;endcaseend//状态输出always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_temp <= 1'b0;endelse if(cstate == DATA && cnt_bort == (TIME_BORT >> 1))beginrx_temp[cnt_bit] <= rx_r1;endelse beginrx_temp <= rx_temp;endendassign rx_data = rx_temp;//校验位always @(posedge clk or negedge rst_n)beginif(!rst_n)begincheck_bit <= 1'b0;endelse if(cstate == CHECK && cnt_bort == (TIME_BORT >> 1))begincheck_bit <= rx;endelse begincheck_bit <= check_bit;endendassign 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) beginif(!rst_n)begincstate <= IDLE;endelse begincstate <= nstate;endend//第二段 组合逻辑always @(*)begincase (cstate)IDLE : beginif(idle2start)beginnstate <= START;endelse beginnstate <= cstate;endendSTART: beginif(start2data)beginnstate <= DATA;endelse beginnstate <= cstate;endendDATA : beginif(data2check)beginnstate <= CHECK;endelse if(data2stop)beginnstate <= STOP;endelse beginnstate <= cstate;endendCHECK: beginif(check2stop)beginnstate <= STOP;endelse beginnstate <= cstate;endendSTOP : beginif(stop2idle)beginnstate <= IDLE;endelse beginnstate <= cstate;endenddefault : nstate <= IDLE;endcaseendassign 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)begincnt_bort <= 'd0;end else if(add_cnt_bort)begin if(end_cnt_bort)begin cnt_bort <= 'd0;endelse begin cnt_bort <= cnt_bort + 1'b1;end endend 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)begincnt_bit <= 'd0;end else if(add_cnt_bit)begin if(end_cnt_bit)begin cnt_bit <= 'd0;endelse begin cnt_bit <= cnt_bit + 1'b1;end endend assign add_cnt_bit = end_cnt_bort;assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1'b1;//bit计数器复用always@(*)begincase(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;endcaseend//状态输出//输入数据寄存always@(posedge clk or negedge rst_n)beginif(!rst_n)begintx_data_r <= 1'b0;endelse if(tx_data_vld)begintx_data_r <= tx_data;endelse begintx_data_r <= tx_data_r;endend//奇偶校验位assign check_bit = CHECK_BIT == "Odd" ? ~^tx_data_r : ^tx_data_r;//数据输出always@(*)begincase(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;endcaseendassign 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) beginif(!rst_n)beginkey_r0 <= {WIDTH{1'b1}};key_r1 <= {WIDTH{1'b1}};key_r2 <= {WIDTH{1'b1}};endelse beginkey_r0 <= key_in; //同步key_r1 <= key_r0; //寄存一拍key_r2 <= key_r1; //寄存两拍end
end//20ms计数器
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_delay <= 1'b0;endelse if(add_cnt_delay )beginif(nedge)begin //检测到下降沿从0开始计数cnt_delay <= 1'b0;endelse if(cnt_delay == MAX - 1'b1)begincnt_delay <= cnt_delay; //计数计满结束后保持,避免产生多个输出脉冲endelse begincnt_delay <= cnt_delay + 1'b1;endendelse begincnt_delay <= 1'b0;end
endassign 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)beginif(!rst_n)beginkey_out <= 'd0;endelse if(cnt_delay == MAX - 2'd2)begin //计数计满前一个脉冲时产生按键脉冲key_out <= ~key_in;endelse beginkey_out <= 'd0;end
endendmodule
五、实现效果
可以看出,能够正常进行单个字节的收发。
参考资料
https://blog.csdn.net/zhangduang_KHKW/article/details/121953275
相关文章:

基于FPGA的I2C读写EEPROM
文章目录 前言一、I2C协议1.1 I2C协议简介1.2 物理层1.3 协议层 二、EEPROM2.1 型号及硬件规格2.2 各种读写时序 三、状态机设计四、项目源码:五、实现效果参考资料 前言 本次项目所用开发板FPGA芯片型号为:EP4CE6F17C8 EEPROM芯片型号为:24L…...
Viva Employee Communications Communities部署方案
目录 Viva Employee Communications & Communities产品介绍 1. 沟通中心(Communications Center) 2. 新闻和公告(News and Announcements)...

WPF向Avalonia迁移(三、项目结构)
前提: Avalonia版本11.0.0 1.配置文件 1.1 添加配置文件 1.2 读取配置文件 添加System.Configuration.ConfigurationManager using Avalonia.Controls; using System.Configuration;namespace AvaloniaApplication7.Views {public partial class MainWindow : W…...

cvpr24写作模板pdfLaTex编译器注意点小结
文章目录 1 更改作者显示 Anonymous CVPR submission2 \label标签3 换行符// 与换列符&4 \medskip5 首行缩进6 插入图片6.1 单幅图片6.2 并排显示\hfill Reference https://cvpr.thecvf.com/Conferences/2024 1 更改作者显示 Anonymous CVPR submission 这一行开头加上% …...
windows版php扩展包下载
php8有些扩展需自己下载,像redis 看下phpinfo中的PHP Extension Build,确定自己的php版本 windows.php.net - /downloads/pecl/releases/...

计算机竞赛 题目:基于深度学习的中文汉字识别 - 深度学习 卷积神经网络 机器视觉 OCR
文章目录 0 简介1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 简介 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的中文汉字识别 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! &a…...
Django跨域访问 nginx转发 开源浏览器
Django跨域访问 https://blog.csdn.net/lonelysnowman/article/details/128086205 nginx转发 https://blog.csdn.net/faye0412/article/details/75200607/ 开源浏览器 https://www.oschina.net/p/chromiumengine 浏览器油猴开发 https://blog.csdn.net/mukes/article/detail…...

Docker Alist 在线网盘部署
文章目录 拉取镜像创建并运行查看容器自动生成的密码在浏览器中进行访问 挂载本地磁盘 拉取镜像 docker pull xhofe/alist-aria2创建并运行 # -v /data/alist:/opt/alist/data 挂载本地目录 docker run -d --restartalways -v /data/alist:/opt/alist/data -p 5244:5244 -e P…...

Jmeter吞吐量控制器使用小结
吞吐量控制器(Throughput Controller)场景: 在同一个线程组里, 有10个并发, 7个做A业务, 3个做B业务,要模拟这种场景,可以通过吞吐量模拟器来实现.。 添加吞吐量控制器 用法1: Percent Executions 在一个线程组内分别建立两个吞吐量控制器, 分别放业务A和业务B 吞吐量控制器采…...

3分钟轻松实现网关网口连接罗克韦尔AB CompactLogix系列PLC
目录 EG网关网口连接罗克韦尔AB CompactLogix系列PLC 一. 准备工作 1.1 在对接前我们需准备如下物品 1.2 EG20网关准备工作 1.3 PLC准备工作 二. EMCP平台设置 2.1 新增EG设备 2.2 远程配置网关 2.3 网关绑定 2.4 通讯参数设置 2.5 创建设备驱动 2.5.1 添加变量 2.…...

vscode刷leetcode使用Cookie登录
1、打开vscode,选择扩展,搜索leetcode,选择第一个,带有中文力扣字样,安装后重启 2、选择这个小球,切换中文版本,切换后,会显示一个打勾 3、选择小球旁边的有箭头的小门࿰…...
每次启动Docker容器指定IP、hosts和端口
每次启动Docker容器指定IP、hosts和端口 1问题描述1解决办法1.1启动容器指定好IP、hostname、端口等信息1.2通过docker-compose启动容器,可以配置extra_hosts属性1.3通过k8s来管理容器,则在可以在创建pod的yaml文件通过hostAliases添加域名IP映射 2问题描…...
PL/SQL增量同步
PL/SQL全量同步_枯河垂钓的博客-CSDN博客 目录 增量同步 增量同步存储过程 ORACEL 独有的 增量更新 merge into 增量同步 逻辑: 用源表的数据更新目标表,数据存在则更新,数据不存在,则插入 实现的逻辑: 1. 首先判断目标表是否有源表的数据 2. 如果有,则用源表的数据更新…...

C++——多态底层原理
虚函数表 先来看这个问题: class Base { public: virtual void Func1() { cout << "Func1()" << endl; } private: int _b 1; }; sizeof(Base)是多少? 答案是:8 因为Base中除了成员变量_b,还有一个虚函数表_vfp…...
asdTools-ReID热力图可视化
文章首发见博客:https://mwhls.top/4869.html。 无图/格式错误/后续更新请见首发页。 更多更新请到mwhls.top查看 欢迎留言提问或批评建议,私信不回。 Github - 开源代码及Readme Blog - 工具介绍 摘要:基于TorchCam实现ReID的热力图可视化的…...

CSS学习笔记
目录 1.CSS简介1.什么是CSS2.为什么使用CSS3.CSS作用 2.基本用法1.CSS语法2.CSS应用方式1. 内部样式2.行内样式3.外部样式1.使用 link标签 链接外部样式文件2.import 指令 导入外部样式文件3.使用举例 3.选择器1.基础选择器1.标签选择器2.类选择器3.ID选择器4.使用举例 2.复杂选…...
linux操作命令
VMware版本: 17.0 ubantu版本:22.04.3 命令: # 查看当前目录文件 ls# 切换路径 []内是路径 cd [snap/] cd ../ #返回上一层# 创建文件 []内是文件名 touch [test.txt]# 创建文件夹 []内是文件夹名 mkdir [myself]# 测试一下网络 ping www.b…...
猜数字游戏(Python)
一、猜数字游戏是一个古老的密码破译类、益智类小游戏,通常由两个人参与,一个人设置一个数字,一个人猜数字,当猜数字的人说出一个数字,由出数字的人告知是否猜中:若猜测的数字大于设置的数字,出…...
可视化模块
目录 可视化送入网络的图片可视化网络层的热力图 可视化送入网络的图片 送入的数据为imgs,其大小为(8,3,256,256),并以2行8列进行展示 import matplotlib.pyplot as plt import numpy as np# 假设你的张量名为 tensor,形状为 (8, 3, 256, 2…...
MyBatis insert标签
<insert id"addWebsite" parameterType"string">insert into website(name)values(#{name}) </insert> 在 WebsiteMapper 接口中定义一个 add() 方法 public int addWebsite(String name); 参数为 Sting 类型的字符串;返回值为 …...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
BLEU评分:机器翻译质量评估的黄金标准
BLEU评分:机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域,衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标,自2002年由IBM的Kishore Papineni等人提出以来,…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...