基于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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/98473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux CentOS7 yum仓库

在windows下安装一个软件很轻松&#xff0c;只要双击setup或者.exe的文件&#xff0c;安装提示连续“下一步”即可&#xff0c;然而linux系统下安装一个软件似乎并不那么轻松&#xff0c;因为我们不是在图形界面下。 本文我们将讨论如何在linux下安装一个软件。 一、linux软件…

混淆技术研究笔记(二)yGuard入门

yGuard官方文档地址&#xff1a;https://yworks.github.io/yGuard/index.html yGuard官方文档包含了比较全面的内容&#xff0c;由于文档是英文的&#xff0c;而且文档翻译后的浏览效果不是特别好&#xff0c;所以看文档入门有点难度。 这个系列的重点是混淆&#xff0c;所以…

4.物联网射频识别,RFID开发【智能门禁项目】

补充&#xff1a;学习路径 一。项目介绍及需求分析 1.酒店智能门禁使用场景介绍 1.客人入住 客人在前台办理入住手续&#xff0c;前台管理员通过门禁管理系统为客户开一张门禁卡 客户持卡到相应客房&#xff0c;用IC 卡刷卡开门 客人过了入住时间后&#xff0c;卡自动失效&a…

Linux自用笔记

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Linux相关 ✨特色专栏&#xff1a; My…

【网络安全 ---- 靶场搭建】凡诺企业网站管理系统靶场详细搭建过程(asp网站,练习sql注入)

一&#xff0c;资源下载 百度网盘资源下载链接&#xff1a;百度网盘 请输入提取码百度网盘为您提供文件的网络备份、同步和分享服务。空间大、速度快、安全稳固&#xff0c;支持教育网加速&#xff0c;支持手机端。注册使用百度网盘即可享受免费存储空间https://pan.baidu.com…

HDMI简介

VGA接口 VGA传输红绿蓝模拟信号和同步信号。因传输的模拟信号&#xff0c;易受干扰&#xff0c;因此&#xff0c;在高分辨率下字体容易虚&#xff0c;信号线长的话&#xff0c;图像有拖尾现象。目前一些显示器已经不带VGA接口&#xff0c;取而代之的是HDMI和DP接口。 如下图所示…

Python元组解密:不可变的数据之美

更多资料获取 &#x1f913; 作者主页&#xff1a;涛哥聊Python &#x1f4da; 个人网站&#xff1a;涛哥聊Python 元组是Python中一种有用的数据类型&#xff0c;用于存储不可变的有序集合。 本文将带您深入了解Python元组&#xff0c;包括定义、特点、创建、基本操作、不可…

第86步 时间序列建模实战:Transformer回归建模

基于WIN10的64位系统演示 一、写在前面 这一期&#xff0c;我们介绍Transformer回归。 同样&#xff0c;这里使用这个数据&#xff1a; 《PLoS One》2015年一篇题目为《Comparison of Two Hybrid Models for Forecasting the Incidence of Hemorrhagic Fever with Renal Sy…

机器学习必修课 - 交叉验证 Cross-Validation

想象一下你有一个包含5000行数据的数据集。通常情况下&#xff0c;你会将约20%的数据保留作为验证数据集&#xff0c;即1000行。但这会在确定模型得分时引入一些随机性。也就是说&#xff0c;一个模型可能在一组1000行数据上表现良好&#xff0c;即使在另一组1000行数据上表现不…

cv2.split函数与cv2.merge函数

split函数用于图像BGR通道的分离 merge函数用于可将分开的图像通道合并到一起 1.split函数的使用 这是原图&#xff0c;我们使用split函数对其三个通道进行分离。 注意&#xff1a;split函数分离通道的顺序是B、G、R。 以下方法是将三个通道的值都设置为与某一个通道相同。…

【Mysql】 blob 转text

有个数据表字段存储的字段类型是blob&#xff0c;想查看字段内容。 blob是二进制的无法直接查看怎么办&#xff1f; 写sql&#xff0c;blob 转text SELECT CONVERT(content USING utf8) FROM article_content ; 我想把原来content字段完全转成text 新建 text 类型字段conten…

k8s-8 ingress-nginx

nodeport 默认端口 nodeport默认端口是30000-32767&#xff0c;超出会报错 添加如下参数&#xff0c;端口范围可以自定义 externalname ingress-nginx 通过一个外部的vip 地址 访问到集群内的多个service 一种全局的、为了代理不同后端 Service 而设置的负载均衡服务&…

uniapp:swiper-demo效果

单元格轮播 <swiper class"swiper1" :circular"true" :autoplay"true" interval"3000" previous-margin"195rpx" next-margin"195rpx"><swiper-item v-for"(item,index) in 5" :key"inde…

虹科方案 | 车载以太网解决方案

全文导读&#xff1a;针对车载以太网&#xff0c;虹科为您提供转换器/交换机/捕捉模块/测试分析软件等相关专业工具、一体化培训以及“交钥匙”的解决方案&#xff0c;助力您的车载以太网数据传输、远程诊断、及时检测等项目。 文章目录 一、车载以太网的重要性&#xff1f;二、…

webstorm自定义文件模板(Vue + Scss)

最终效果如下&#xff1a; 具体配置如下&#xff1a; 新增文件代码如下&#xff1a; <!--* Description: ${COMPONENT_NAME} 页面* Author: mhf* Date: ${DATE} --> <template><div>${COMPONENT_NAME} </div> </template><script&g…

TensorFlow入门(十三、动态图Eager)

一个图(Graph)代表一个计算任务,且在模型运行时,需要把图放入会话(session)里被启动。一旦模型开始运行,图就无法修改了。TensorFlow把这种图一般称为静态图。 动态图是指在Python中代码被调用后,其操作立即被执行的计算。 它与静态图最大的区别是不需要使用session来建立会话…

Scala第二十章节

Scala第二十章节 scala总目录 文档资料下载 章节目标 理解Akka并发编程框架简介掌握Akka入门案例掌握Akka定时任务代码实现掌握两个进程间通信的案例掌握简易版spark通信框架案例 1. Akka并发编程框架简介 1.1 Akka概述 Akka是一个用于构建高并发、分布式和可扩展的基于事…

[MongoDB]-权限验证管理

[MongoDB]-权限验证管理 senge | 2023年9月 背景说明&#xff1a;现有两套MongoDB副本集群给开发人员使用时未开启认证。 产生影响&#xff1a;用户若输入账号以及密码则会进行校验&#xff0c;但用户可以在不输入用户名和密码的情况下也可直接登录。 倘若黑客借此进行攻击勒索…

ubuntu增加内存

文章目录 1、硬盘操作步骤第二步:点击【扩展】(必须关闭ubuntu电源才能修改)第三步:修改【最大磁盘容量大小】1、硬盘操作步骤 最近发现Ubuntu空间不足,怎么去扩容呢? 第一步:点击【硬盘】 第二步:点击【扩展】(必须关闭ubuntu电源才能修改) 第三步:修改【最大磁…

Git 学习笔记 | Git 项目创建及克隆

Git 学习笔记 | Git 项目创建及克隆 Git 学习笔记 | Git 项目创建及克隆创建工作目录与常用指令本地仓库搭建克隆远程仓库 Git 学习笔记 | Git 项目创建及克隆 创建工作目录与常用指令 工作目录&#xff08;WorkSpace)一般就是你希望Git帮助你管理的文件夹&#xff0c;可以是…