串口收发UART(Verilog HDL)
UART(Universal Asynchronous Receiver Transmitter,通用异步收发器)是一种异步串行通信协议,主要用于计算机和嵌入式系统之间的数据交换。
实现UART通信的接口规范和总线标准包括RS-232、RS449、RS423和RS485等,接口标准规定了通信标准的电气特性、传输速率、连接特性和机械特性。
文章摘要:本篇文章目标设计一个格式为起始位+8位数据(无校验)+停止位的串口收发,接收PC上位机RS232总线信号后,重新打包转发至PC端显示(形成回环),数据完整无错码情况。
关键词: 异步时钟;亚稳态;异步串行通信;Verilog HDL
目标设计框图:
【串行帧格式】
UART通信使用两条信号线进行数据传输:发送数据线(TX)和接收数据线(RX)。数据以数据帧的形式传输,每个数据帧由起始位、数据位(通常为5到9位)、校验位(可选)和停止位(通常为1到2位)组成。
发送方在发送数据之前,会先发送一个起始位(通常为低电平),然后按照设定的数据位和校验位发送数据,最后发送一个或多个停止位(通常为高电平)以标识数据传输的结束。接收方在检测到起始位后,会开始接收数据,直到检测到停止位为止(起止式协议)。同时,接收方会根据约定的校验方式对数据进行校验。
1、起始位(Start Bit):通常是一个单独的位,逻辑值为0(低电平),表示字符开始,接收端同步到发送端的数据流。
2、数据位(Data Bits):帧的主体部分,包含传输信息,数据位数量可以根据需要进行配置,常见的有5位、6位、7位、8位等。
3、校验位(Parity Bit):可选位,校验位可以是偶校验(Even Parity)、奇校验(Odd Parity)或无校验(No Parity),偶校验意味着数据位中1的个数(包括校验位)应该是偶数;奇校验则是奇数。
4、停止位(Stop Bits):标志字符的结束,并允许接收器在接收下一个字符之前有时间复位。停止位的数量可以是1位、1.5位或2位。
5、空闲位(Idle Line):在两个字符之间,线路通常保持在高电平状态(逻辑值为1)称为空闲线状态,它允许接收端在字符之间有时间来检测和响应线路状态的变化。
数据传输中,波特率(Baud rate)是一个重要的参数,表示单位时间内传输的码元符号的个数(通常指二进制位)。波特率并不直接等同于比特率(bit rate),因为每个码元符号可能包含多个比特。但在许多情况下,特别是在串行通信中,一个码元符号就是一个比特,此时波特率就等于比特率。
传送速率为960字符/秒,而每个字符为10位(可能是1个起始位、8个数据位和1个停止位)。总的二进制位数传送速率是960字符/秒 × 10位/字符 = 9600位/秒,所以波特率就是9600。
【异步传输存在亚稳态】
在数字电路设计中同步建立至关重要,异步时钟域之间的信号通信不可避免存在亚稳态问题。UART接收过程,起始位的检测尤为关键,错误的起始位检测可能导致整个数据包的接收失败。为了避免因亚稳态导致的采样错误和电路故障,设计者必须在接口处采取可靠的同步化措施。
亚稳态:简单来说,亚稳态是指触发器(Flip-Flop)或其他数字电路元件无法在某个规定的时间段内达到一个可确认的稳定状态。
产生原因:主要由于违反了触发器的建立和保持时间(Setup and Hold Time)要求。在时钟上升沿前后的特定时间窗口内,如果数据输入端口上的数据发生变化,就会产生时序违规,从而导致亚稳态的出现,如下图。
表现特征:在稳定期间,触发器可能输出一些中间级电平,或者处于振荡状态。并且,这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去,导致整个系统的不稳定。
减少亚稳态影响:
亚稳态震荡时间(Tmet)关系到后级寄存器的采集稳定问题,Tmet影响因素包括器件的生产工艺、温度、环境等。
当Tmet1时间长到大于一个采样周期后,那第二级寄存器就会采集到亚稳态,但是从第二级寄存器输出的信号就是相对稳定的了。由于寄存器本身就有减小Tmet时间让数据快速稳定的作用,第二级寄存器的Tmet2的持续时间继续延长到大于一个采样周期这种情况虽然会存在,但是其概率是极小的。在第三级寄存器时,数据传输几乎达到稳态。
单比特信号从慢速时钟域同步到快速时钟域需要使用打两拍的方式消除亚稳态。第一级寄存器产生亚稳态并经过自身后可以稳定输出的概率为70%~80%,第二级寄存器可以稳定输出的概率为99%左右,再多加寄存器的级数改善效果就不明显了,Verilog HDL程序如下。
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg1 <= 1'b1;
else rx_reg1 <= rx;
end
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg2 <= 1'b1;
else rx_reg2 <= rx_reg1;
end
always @(posedge sys_clk or negedge sys_rst)begin
if(!sys_rst) rx_reg3 <= 1'b1;
else rx_reg3 <= rx_reg2;
end
【接收端处理逻辑】
在本篇中,通信格式:起始位+8位数据+无校检+1位停止位。sys_clk
为系统时钟(在这里仅作为时钟参考)设置为50Mhz(20ns),异步通信的波特率设定为9600Baud,则波特率计数器Baud_cnt
最大值 = 1/9600x50_000_000 ≈ 5208。
bit_flag
和 bit_cnt
:位标志和位计数器,用于跟踪当前接收到的位的数量和状态。rx_reg1
, rx_reg2
, rx_reg3
:接收寄存器,用于暂存接收到的数据,rx_flag
和 po_flag
:接收和发送的标志,用于指示接收或发送过程的状态。rx
和 rx_data_out
:接收到的数据和输出数据。具体UART接收端时序图如下。
数据流:当start
信号被触发时,UART开始接收数据,并使能work_en
信号,数据位(XData[0]
到 XData[7]
)被连续接收并存储在rx_reg1
、rx_reg2
、rx_reg3
等寄存器中。在数据接收过程中,Baud_cnt[12:0]
、bit_flag
和 bit_cnt
用于确保数据以正确的波特率接收,并跟踪当前位的状态。一旦所有数据位和停止位(Xstop
)都被接收,UART将rx_data_out
设置为接收到的数据,并可能设置rx_flag
以指示数据已准备好。
依据时序图,接续完成其他类型信号时序程序逻辑:
always @(posedge sys_clk or negedge sys_rst)begin//处理start_flag逻辑
if(!sys_rst) start_flag <= 1'b0;
else if((rx_reg3 == 1'b1)&&(rx_reg2 == 1'b0)&&(work_en == 1'b0))start_flag <= 1'b1;
else start_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理work_en逻辑
if(!sys_rst) work_en <= 1'b0;
else if(start_flag) work_en <= 1'b1;
else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1)) work_en <= 1'b0;
else work_en <= work_en;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理baud_cnt逻辑
if(!sys_rst) baud_cnt <= 13'd0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(work_en == 1'b0))baud_cnt <= 13'd0;
else baud_cnt <= baud_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理bit_flag逻辑
if(!sys_rst) bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) bit_flag <= 1'b1;
else bit_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理bit_cnt逻辑
if(!sys_rst) bit_cnt <= 4'd0;
else if((bit_cnt == 4'd8)&&(bit_flag == 1'b1)) bit_cnt <= 4'd0;
else if(bit_flag) bit_cnt <= bit_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理rx_data逻辑
if(!sys_rst) rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt<= 4'd8)&&(bit_flag))
rx_data <= {rx_reg3,rx_data[7:1]};
end
always @(posedge sys_clk or negedge sys_rst)begin//处理rx_flag逻辑
if(!sys_rst) rx_flag <= 1'd0;
else if((bit_cnt == 4'd8)&&(bit_flag)) rx_flag <= 1'b1;
else rx_flag <= 1'd0;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理po_data逻辑
if(!sys_rst) po_data <= 8'd0;
else if(rx_flag) po_data <= rx_data;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理po_flag逻辑
if(!sys_rst) po_flag <= 1'b0;
else po_flag <= rx_flag;
end
完备后,编写了一个简单的仿真程序,验证接收的时序情况。从下图可知,三级寄存器依次往后延迟了一个时钟周期,即20ns,这实现了目标期望,用于减小亚稳态影响,保证时序准确。
再看接收rx信号线数据的处理情况,待一轮字符数据处理完成后,po_data
得到了正确的串行输入数据。波特率计数器在计数到13’d5027后归零,等待使能信号拉高。
【发送端处理逻辑】
较于接收端,发送端逻辑比较简单。pi_data
:输入的8位数据信号,pi_flag
:输入信号标志。Baud_cnt
为波特率计数器。输入发送标志为高电平,发送端将准备的数据,按位计数输出到串行信号总线tx
,依次拉低信号线,发送起始标志+8位数据信号。比特计数器计数到4'd9拉低使能线,表示单个字符数据输出完毕,拉高信号线标志停止至空闲状态。
always @(posedge sys_clk or negedge sys_rst)begin//处理work_en逻辑
if(!sys_rst) work_en <= 1'b0;
else if((bit_cnt == 4'd9)&&bit_flag) work_en <= 1'b0;
else if(pi_flag) work_en <= 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理baud_cnt逻辑
if(!sys_rst) baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1'b1)||(!work_en)) baud_cnt <= 13'b0;
else if(work_en) baud_cnt <= baud_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理bit_cnt逻辑
if(!sys_rst) bit_cnt <= 4'd0;
else if((bit_cnt == 4'd9)&&(bit_flag == 1'b1)) bit_cnt <= 4'd0;
else if((work_en == 1'b1)&&(bit_flag == 1'b1)) bit_cnt <= bit_cnt + 1'b1;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理bit_flag逻辑
if(!sys_rst) bit_flag <= 1'b0;
else if(baud_cnt == 13'd1) bit_flag <= 1'd1;
else bit_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst)begin//处理tx发送逻辑
if(!sys_rst) tx <= 1'b0;
else if(bit_flag == 1'b1)begin
case(bit_cnt)
0 :tx <= 1'b0;
1 :tx <= pi_data[0];
2 :tx <= pi_data[1];
3 :tx <= pi_data[2];
4 :tx <= pi_data[3];
5 :tx <= pi_data[4];
6 :tx <= pi_data[5];
7 :tx <= pi_data[6];
8 :tx <= pi_data[7];
9 :tx <= 1'b1;
default: tx<= 1'b1;
endcase
end
end
完备后,编写了仿真程序,验证发送的时序情况。检测到输入标志为高电平,work_en
使能有效,波特率计数器开始计数,比特位计数器待其计数到13'd1时计数加1,这样避免比特错判。
从下面仿真图可以看到,tx发送输出的串行信号时序是与pi_data
保持一致的。
【整体验证UART】
首先,建立五组任意字符数据,经UART发送端向UART接收端转发。通过仿真程序进行分析,由于接收端与PC源数据相比,对数据的处理要迟后一个字符周期,即5208x20x10ns,从下图可得到,数据处理串行数据tran_data
后保持正确,并且输出数据out_data
与源数据保持一致,即可验证模块设计暂时是没有大问题。
最后,将UART接收端和发送端模块实例化为文章开头的设计框架。在PC端发送任意三组数据后,如下图COM11端口接收数据并且正确,设计到此验证成功,满足期望。
文献参考:
[1] 野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com);
[2] FPGA中亚稳态——让你无处可逃 - 屋檐下的龙卷风 - 博客园 (cnblogs.com);
[3] ww1.microchip.com/downloads/en/DeviceDoc/70000582e.pdf;
[4]万敏. 异步时序电路中的亚稳态设计与分析[D]. 上海:上海交通大学,2008.
本篇文章中使用的Verilog程序模块,若有需见网页左栏Gitee仓库链接:憨大头的妙妙屋: 真奇妙! (gitee.com)
串口收发UART(Verilog HDL)的更多相关文章
- [ZigBee] 8、ZigBee之UART剖析·二(串口收发)
前言:上一节讲UART基本知识介绍完了,并深入剖析了一个串口发送工程,本节将进一步介绍串口收发! 1.初始化 在串口初始化部分,和上一节不同的地方是: 51 U0CSR |= 0x40; //允许接收 ...
- 基于Verilog HDL 各种实验
菜鸟做的的小实验链接汇总: 1.基于Verilog HDL 的数字时钟设计 2.乘法器 3.触发器(基本的SR触发器.同步触发器.D触发器) 4.基于Verilog HDL的ADC ...
- STM32串口通信UART使用
STM32串口通信UART使用 uart使用的过程为: 1. 使能GPIO口和UART对应的总线时钟 2. 配置GPIO口的输出模式 3. 配置uart口相关的基本信息 4. 使能uart口的相关的中 ...
- STM32F103C8T6-CubeMx串口收发程序详细设计与测试(1)——CubeMx生成初始代码
STM32F103C8T6-CubeMx串口收发程序详细设计与测试(1)--CubeMx生成初始代码 关键词:STM32F103C8T6 CubeMX UART 详细程序设计 1.开发环境 (1)ST ...
- 【STM32】串口收发驱动Drv_Uart|学习笔记
一.什么事串口? 大家常说串口,其实串口有很多种UART,SPI,IIC都是串口,一般大家口中的串口就是UART(Universal Asynchronous Receiver/Transmitter ...
- 基于Verilog HDL整数乘法器设计与仿真验证
基于Verilog HDL整数乘法器设计与仿真验证 1.预备知识 整数分为短整数,中整数,长整数,本文只涉及到短整数.短整数:占用一个字节空间,8位,其中最高位为符号位(最高位为1表示为负数,最高位为 ...
- 关于初次使用Verilog HDL语言需要懂的基本语法
关于初次使用Verilog HDL语言需要懂的基本语法 1.常量 数字表达式全面的描述方式为:<位宽><进制><数字> 8’b10101100,表示位宽为8的二进制 ...
- FPGA Verilog HDL 系列实例--------步进电机驱动控制
[连载] FPGA Verilog HDL 系列实例 Verilog HDL 之 步进电机驱动控制 步进电机的用途还是非常广泛的,目前打印机,绘图仪,机器人等等设备都以步进电机为动力核心.那么,下面我 ...
- Verilog HDL基础语法讲解之模块代码基本结构
Verilog HDL基础语法讲解之模块代码基本结构 本章主要讲解Verilog基础语法的内容,文章以一个最简单的例子"二选一多路器"来引入一个最简单的Verilog设计文件的 ...
- Verilog HDL模块的结构
一个设计是由一个个模块(module)构成的.一个模块的设计如下: 1.模块内容是嵌在module 和endmodule两个语句之间.每个模块实现特定的功能,模块可进行层次的嵌套,因此可以将大型的数字 ...
随机推荐
- Apache RocketMQ 的 Service Mesh 开源之旅
作者 | 凌楚 阿里巴巴开发工程师 导读:自 19 年底开始,支持 Apache RocketMQ 的 Network Filter 历时 4 个月的 Code Review(Pull Reque ...
- 大数据时代下,App数据隐私安全你真的了解么?
简介:你是否有过这样的经历:你和朋友聊天表达你近期想要购买某件商品,第二天当你打开某购物软件时,平台向你推送的商品正是你想要购买的:或者,你是否接到过陌生来电,他们准确的报出了你的名字和年龄.... ...
- 行业实战 | 5G+边缘计算+“自由视角” 让体育赛事更畅快
简介: 世界本是多维的.进入5G时代,观众对多维度视觉体验的需求日益增长,5G MEC网络与边缘计算的结合,具备大带宽.低延迟特性,使视频多维视觉呈现成为现实.在第二十三届CUBA中国大学生篮球联赛期 ...
- 代码安全无忧—云效Codeup代码加密技术发展之路
简介: 从代码服务及代码安全角度出发,看看云效代码加密技术如何解决这一问题 代码数据存在云端,如何保障它的安全? 部分企业管理者对于云端代码托管存在一丝担心:我的代码存在云端服务器,会不会被泄露? 接 ...
- 走近Quick Audience,了解消费者运营产品的发展和演变
简介: Quick Audience产品是一款云原生面向消费者的营销产品,自诞生以来,经历了三个发展阶段.每个阶段的转变,都与互联网环境和消费者行为的变迁有着极大的关联. Quick Audien ...
- Python编程的若干个经典小技巧
1. 原地交换两个数字 Python 提供了一个直观的在一行代码中赋值与交换(变量值)的方法,请参见下面的示例: x,y= 10,20 print(x,y) x,y= y,x print(x,y) # ...
- 9.prometheus监控--监控springboot2.x(Java)
一.环境部署 yum search java | grep jdk yum install -y java-11-openjdk-devel 二.监控java应用(tomcat/jar) JMX ex ...
- 如何用python运用ocr技术来识别文字
要先安装ocr技术,也就是光学符号识别,通过扫描等光学输入方式将各种票据.报刊.书籍.文稿及其他印刷品的文字转化为图像信息,再利用文字识别技术将图像信息转化为可以使用的文本的技术(我在百度百科抄的), ...
- Java 集合类 List 的那些坑
现在的一些高级编程语言都会提供各种开箱即用的数据结构的实现,像 Java 编程语言的集合框架中就提供了各种实现,集合类包含 Map 和 Collection 两个大类,其中 Collection 下面 ...
- gin-vue-admin 03 项目打包上线
目录 作者视频 思路 环境要求 1. 配置nginx 2.打包前台vue代码 3.打包后台go代码 4. 上传代码到服务器 5. 后台运行power 6. 访问后台 开发场景: 1. nginx 配置 ...