当前位置: 首页 > news >正文

【UART】Verilog实现UART接收和发送模块

目录

写在前面

UART 工作原理

UART 接收部分

UART RX 模块图

UART RX 时序图

Verilog 实现 UART RX 模块

UART 发送部分

UART TX 模块图

UART TX 时序图

Verilog 实现 UART TX 模块

总结


写在前面

UART协议在之前的一篇博客中有介绍,直达链接如下:

【总线】一文看懂 UART 通信协议

这里只是做简单的介绍,重点在 Verilog 实现部分。

UART 工作原理

将要传输数据的UART从数据总线接收数据。数据总线用于通过另一个设备(如CPU,内存或微控制器)将数据发送到UART。数据以并行形式从数据总线传输到传输UART。在发送UART从数据总线获取并行数据后,它会添加一个起始位、一个奇偶校验位和一个停止位,从而创建数据包。接下来,数据包在Tx引脚上逐位串行输出。接收UART在其Rx引脚上逐位读取数据包。然后,接收的UART将数据转换回并行形式,并删除起始位、奇偶校验位和停止位。最后,接收UART将数据包并行传输到接收端的数据总线:

UART传输的数据被组织成数据包。每个数据包包含 1 个起始位、5 到 9 个数据位(取决于 UART)、一个可选的奇偶校验位以及 1 个或 2 个停止位:

UART 接收部分

UART RX 模块图

UART 接受模块的作用是将接受到的串行数据转换成并行数据。

由于我做的实验工程是将 UART 和 RS485 共同使用,所以需要一个信号 dir 控制RS485的传输方向,如果不用 RS485 的话可以将此信号忽略。

UART 的接受模块分为六个信号,三个输入信号:时钟信号clk、复位信号(低有效)rst_n、接受串行数据信号;三个输出信号:并行数据po_data、输出并行数据的同步标志信号pi_flag、控制方向信号 dir。

UART RX 时序图

时序图清晰的描述了数据是如何变化的。

Verilog 实现 UART RX 模块

`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Linest-5 // Create Date: 2022/04/14 // Design Name: // Module Name: UART_RX // Project Name: // Target Devices: // Tool Versions: // Description: UART 接收模块,将串行的数据转成并行的数据接收 // Dependencies: // Revision: // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module UART_RX #( parameter BAUD_RATE = 'd9600, //波特率 parameter CLK_FREQ = 'd250000000, //时钟周期 parameter BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE ) ( input wire clk, //接收到的数据 input wire rst_n, //复位信号,低电平有效 input wire rx, //输入的串行数据 output reg [7:0] po_data, //输出的并行数据 output reg po_flag //并行数据输出的同步标志信号 ); reg rx_reg1; //打三拍,稳定数据 reg rx_reg2; reg rx_reg3; reg start_flag; //数据稳定信号 reg work_en; //开始提取数据有效信号 reg bit_flag; //数据提取标志信号 reg [3:0] bit_cnt; //数据位计数信号 reg [12:0] baud_cnt; //比特计数信号,一个数据需要多少个时钟周期 reg [7:0] rx_data; //串转并数据 reg rx_flag; //串转并完成信号 //消除亚稳态 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin rx_reg1 <= 'd1; end else begin rx_reg1 <= rx; end end always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin rx_reg2 <= 'd1; end else begin rx_reg2 <= rx_reg1; end end always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin rx_reg3 <= 'd1; end else begin rx_reg3 <= rx_reg2; end end //下降沿检测 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin start_flag <= 'd0; end else if ((rx_reg2 == 0) && (rx_reg3 == 1) && (work_en == 'd0)) begin start_flag <= 'd1; end else begin start_flag <= 'd0; end end //开始数据计数信号 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin work_en <= 'd0; end else if (start_flag == 1) begin work_en <= 'd1; end else if ((bit_flag == 1) && (bit_cnt == 'd8)) begin work_en <= 'd0; end end //波特计数信号,用时钟频率除以波特率即每个数据需要的时钟周期数 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin baud_cnt <= 'd0; end else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 'd0)) begin baud_cnt <= 'd0; end else begin baud_cnt <= baud_cnt + 'd1; end end //数据中间提取最稳定 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin bit_flag <= 'd0; end else if (baud_cnt == BAUD_CNT_MAX/2-1) begin bit_flag <= 'd1; end else begin bit_flag <= 'd0; end end //数据位计数信号,只去有效的数据位,起始位和结束位不要 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin bit_cnt <= 'd0; end else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin bit_cnt <= 'd0; end else if (bit_flag == 'd1) begin bit_cnt <= bit_cnt + 'd1; end end //将提取的数据串转并 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin rx_data <= 'd0; end else if ((bit_flag == 'd1) && (bit_cnt >= 'd1) && (bit_cnt <= 'd8)) begin rx_data <= {rx_reg3,rx_data[7:1]}; end end //完成最后一个数据的拼接之后, 就把此信号拉高 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin rx_flag <= 'd0; end else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin rx_flag <= 'd1; end else begin rx_flag <= 'd0; end end //并行数据输出 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin po_data <= 'd0; end else if (rx_flag == 'd1) begin po_data <= rx_data; end else begin po_data <= po_data; end end //并行数据输出标志信号 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin po_flag <= 'd0; end else begin po_flag <= rx_flag; end end endmodule

UART 发送部分

UART TX 模块图

输入信号:时钟信号clk、复位信号rst_n、输入并行数据、输入并行数据的同步标志信号

输出信号:发送串行数据tx、数据发送完成标志信号tx_done、方向控制信号dir、发哦是那个端空闲标志信号tx_ready

UART TX 时序图

Verilog 实现 UART TX 模块

`timescale 1ns / 1ps ////////////////////////////////////////////////////////////////////////////////// // Company: // Engineer: Linest-5 // Create Date: 2022/04/15 // Design Name: // Module Name: UART_TX // Project Name: // Target Devices: // Tool Versions: // Description: UART 发送模块,将接收到的并行数据转成串行数据并发送 // Dependencies: // Revision: // Additional Comments: // ////////////////////////////////////////////////////////////////////////////////// module UART_TX( input clk, //系统时钟 input rst_n, //系统复位,低电平有效 input valid, //数据有效信号 input [7:0] pi_data, //输入的并行数据 output tx, //并转串的输出数据 output tx_done, //并转串的输出数据发送完成标志信号 output reg dir=1, //控制max3485的dir使能信号 output reg ready, //tx模块可以接收数据信号 output reg ena //使能信号 ); reg [7:0] pi_data_reg; //输入数据寄存 reg work_en; //开始提取数据有效信号 reg [17:0] baud_cnt; //波特计数信号,每个数据需要的波特数 reg [3:0] bit_cnt; //数据位计数信号 reg bit_flag; //数据提取标志信号 reg tx_reg; //发送数据寄存 reg tx_done_reg; //发送数据完成标志信号 //参数定义 parameter CLK_FREQ = 'd50000000; parameter BAUD_RATE = 'd9600; parameter BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE; //检测输入数据的到来,并将数据寄存 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin pi_data_reg <= 'd0; end else if (valid == 'd1) begin pi_data_reg <= pi_data ; end else begin pi_data_reg <= pi_data_reg; end end //工作使能,在数据标志信号为高时拉高,在发送数据完成信号为高时拉低 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin work_en <= 'd0; end else if (valid == 'd1) begin work_en <= 'd1; end else if (tx_done == 'd1) begin work_en <= 'd0; end end //根据不同的波特率,对每个数据需要波特数进行计数 always @(posedge clk or negedge rst_n) begin if(rst_n == 'd0)begin baud_cnt <= 'd0; end else if(work_en == 'd1 && (baud_cnt == BAUD_CNT_MAX - 'd1))begin baud_cnt <= 'd0 ; end else if (work_en == 'd1) begin baud_cnt <= baud_cnt + 'd1 ; end else begin baud_cnt <= 'd0; end end //比特标志信号拉高,在每一个波特计数为1时拉高,相当于对数据的提取标志信号 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin bit_flag <= 'd0; end else if (baud_cnt == 'd1) begin bit_flag <= 'd1; end else begin bit_flag <= 'd0; end end //比特计数信号,在对每一个数据为进行计数 always @(posedge clk or negedge rst_n) begin if(rst_n == 'd0)begin bit_cnt <= 'd0; end else if ((work_en == 'd1) && (bit_flag == 'd1) && (bit_cnt == 'd11)) begin bit_cnt <= 'd0; end else if ((work_en == 'd1) && (bit_flag == 'd1)) begin bit_cnt <= bit_cnt + 'd1; end else if (work_en == 'd0) begin bit_cnt <= 'd0; end end //对并行数据映射到串行数据 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin tx_reg <= 'd0; end else begin case (bit_cnt) 4'd1: tx_reg <= 'd0; 4'd2: tx_reg <= pi_data_reg[0]; 4'd3: tx_reg <= pi_data_reg[1]; 4'd4: tx_reg <= pi_data_reg[2]; 4'd5: tx_reg <= pi_data_reg[3]; 4'd6: tx_reg <= pi_data_reg[4]; 4'd7: tx_reg <= pi_data_reg[5]; 4'd8: tx_reg <= pi_data_reg[6]; 4'd9: tx_reg <= pi_data_reg[7]; 4'd10:tx_reg <= 'd1; 4'd11:tx_reg <= 'd1; default :tx_reg <= 'd1; endcase end end //发送数据完成标志信号 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin tx_done_reg <= 'd0; end else if ((bit_flag == 'd1) && (bit_cnt == 'd10)) begin tx_done_reg <= 'd1; end else begin tx_done_reg <= 'd0; end end //在tx模块发送数据完成时或者空闲状态时拉高,拉高表示可以接收上游传来的数据,在数据并转串时拉低 always @(posedge clk or negedge rst_n) begin if (rst_n == 'd0) begin ready <= 'd1; end else if (tx_done == 'd1) begin ready <= 'd1; end else if (valid == 'd1) begin ready <= 'd0; end end //Bram的使能信号 always @(posedge clk or negedge rst_n) begin if (rst_n) begin ena <= 'd0; end else begin ena <= tx_done; end end assign tx = tx_reg; assign tx_done = tx_done_reg; endmodule

总结

UART 的时序并不复杂,最重要的是理解比特率的概念,以及在计数当中如何设定,这两个模块在实际的工程中可以正常跑通。

http://www.cnnetsun.cn/news/1351757.html

相关文章:

  • 实战案例:使用混合推理打造高性能AI原生应用
  • VMware Horizon 8安装部署(一)AD域的安装
  • 主板STM32,GD32等MCU电路设计思维-状态提示
  • Qt 中多媒体模块的使用
  • 探索音乐创新:PedalinoMini™ —— 蓝牙WiFi MIDI控制器
  • 爬虫 APP 逆向 ---> 粉笔考研
  • AtomPePacker 开源项目教程
  • 北航论文格式零失误:BUAAthesis模板中图表、公式与代码块的规范使用
  • 从明文暴露到安全存储:Keyring彻底解决Python密码管理痛点
  • PDF4QT命令行工具详解:自动化处理PDF文档的实用技巧
  • java基于微信小程序的陕西省红色旅游管理系统_dkv6632x
  • leetcode153.寻找旋转排序数组中的最小值
  • MySQL数据库初体验
  • 【即插即用完整代码】CVPR 2026新方法归一化空间与通道注意力,无额外参数,轻量且高效,超越CBAM,快速涨点,发表论文!
  • BetterNCM 插件导致网易云音乐启动失败问题分析
  • Camera:实时监控与数据交互的智能设备服务
  • 事件日志清理与痕迹擦除:wmiexec-Pro的eventlog模块深度应用
  • 告别厂商限制:Tuya TH05Z温湿度传感器接入Zigbee2MQTT完全指南
  • 复购率不理想如何用产品线组合提升长期价值
  • SimpleMem快速上手指南:5分钟搭建LLM智能体记忆系统
  • 基于LangChain的RAG与Agent智能体开发 - 使用LangChain调用大语言模型
  • MiniChain与Hugging Face Datasets:实现高效文档嵌入与相似度搜索的完整指南
  • Deepagents数据可视化:展示AI代理工作成果的终极指南
  • LDNetDiagnoService_IOS架构解析:Ping与Traceroute原理在iOS中的实现
  • 突破Ebitengine着色器限制:多重赋值问题的优雅解决方案
  • 告别繁琐切换!Micro编辑器智能文件类型识别功能详解:让编码效率提升300%的秘诀
  • STK信号处理秘籍:BiQuad滤波器与Chorus效果的应用技巧
  • Python入门:Python3 Pickle模块全面学习教程
  • Deepagents边境安全:监控边境活动的AI代理
  • 打造个性化观影系统:embyToLocalPlayer高级设置与自定义技巧