详解UART通信协议以及FPGA实现

文章目录

  • 一、UART概述
  • 二、UART协议帧格式
    • 2.1 波特率
    • 2.2 奇校验ODD
    • 2.3 偶校验EVEN
  • 三、UART接收器设计
    • 3.1 接收时序图
    • 3.2 Verilog代码
    • 3.3 仿真文件测试
    • 3.4 仿真结果
    • 3.5 上版测试
  • 四、UART发送器设计
    • 4.1 发送时序图
    • 4.2 Verilog代码
    • 4.3 仿真文件测试
    • 4.4 仿真结果
    • 4.5 上板测试
  • 五、UART回环测试
    • 5.1 系统框图
    • 5.2 Verilog代码
    • 5.3 仿真文件测试
    • 5.4 仿真结果
    • 5.5 上板测试
  • 六、参考


一、UART概述

  从《浅谈UART,TTL,RS-232,RS-485的区别》这篇文章,我们知道了UART是一种串行、异步、全双工的通信协议,属于协议层;传输过程一般采用RS-232,RS-485电平标准,将所需传输的数据一位接一位地传输;整体传输框架如下:

在这里插入图片描述
  串口通信由发送端和接收端构成,两根信号线实现,一根数据发送,一根数据接收。

二、UART协议帧格式

  在串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。根据双方协议好的波特率,接收端即可对发送端的数据进行采样。整个传输流程如下:

  1. 传输线空闲时为高电平,然后拉低一个码元时间表示起始位
  2. 接着传输 8 个数据位,先传输低位,再传输高位
  3. 然后传输一个校验位,用来检测本次传输的数据是否正确
  4. 最后输出高电平表示停止位,可以是 1 位、1.5 位、2 位的高电平,并且进入空闲状态,等待下一次的数据传输

协议帧格式如下:
在这里插入图片描述

2.1 波特率

  根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。如果波特率为 9600也就是相当于每秒中划分成了 9600 等份的码元时间。常见的波特率标准为 300bps,600bps,800bps,9600bps,19200bps,38400bps,115200bps 等。通常对串口进行数据采样,采用更高频的时钟。
  一个码元的计算方法:例如我们采样时钟为 50M ,所使用的波特率为 115200,传输 1个码元需要的时间( 1 / 115200 ) = 8680ns

2.2 奇校验ODD

  使完整编码(有效位和校验位)中的 “1” 的个数为奇数个。如果原来信息中 1 的个数为奇数个,则校验位为 0,这样所有信息中1的个数还是奇数 ;如果原来信息中 1 的个数为偶数个,则校验位为 1,这样所有信息中1的个数还是奇数。
  常见的串口通信格式是(8 位数据位+1 位奇数校验位)。以发送字符:10010101和11010101 为例

数据第0位数据第1位数据第2位数据第3位数据第4位数据第5位数据第6位数据第7位校验位
101010011
101010110

2.3 偶校验EVEN

  使完整编码(有效位和校验位)中的 “1” 的个数为偶数个。如果原来信息中 1 的个数为奇数个,则校验位为 1,这样所有信息中1的个数还是偶数 ;如果原来信息中 1 的个数为偶数个,则校验位为 0,这样所有信息中1的个数还是偶数。
  常见的串口通信格式是(8 位数据位+1 位奇数校验位)。以发送字符:10010101和11010101 为例

数据第0位数据第1位数据第2位数据第3位数据第4位数据第5位数据第6位数据第7位校验位
101010010
101010111

三、UART接收器设计

开发平台:vivado2021.1
开发芯片:xc7a100tfgg484-2

3.1 接收时序图

  本次实现11500波特率,无奇偶校验位,时钟频率为50M,则每个码元计数次数为50000000/115200=434。时序图如下:
在这里插入图片描述

3.2 Verilog代码

`timescale 1ns/1ns
module uart_rx#
(parameter CLK_FREQ = 'd50_000_000,  //时钟频率parameter UART_BPS = 'd115200       //波特率
) 
(input                                               clk ,       input                                               rst_n   ,   input                                               rx  ,       //uart_rxoutput  reg     [7:0]                               rx_data ,   //uart_rx_dataoutput  reg                                         rx_data_valid  //uart_rx_data_valid    
);localparam                                          BAUD_CNT_MAX    = CLK_FREQ/UART_BPS ;//一个码元的长度localparam                                          BAUD_CNT_MAX_div2   = BAUD_CNT_MAX/2 ;  reg                                                 rx_reg1 ;reg                                                 rx_reg2 ;reg                                                 rx_reg3 ;reg                                                 rx_flag ;reg             [23:0]                              baud_cnt    ;reg             [3:0]                               bit_cnt ;reg                                                 bit_flag    ;//打两拍,消除亚稳态
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)beginrx_reg1 <= 1'b1;rx_reg2 <= 1'b1;endelse beginrx_reg1 <= rx;rx_reg2 <= rx_reg1;end
end//再打一拍用于判断RX下降沿
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)rx_reg3 <= 1'b1;elserx_reg3 <= rx_reg2;end//接收范围
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)rx_flag <= 1'b0;else if (rx_reg3 == 1'b1 && rx_reg2 == 1'b0)rx_flag <= 1'b1;else if(bit_cnt == 'd8 && bit_flag ==1'b1)rx_flag <= 1'b0;elserx_flag <= rx_flag;
end//baud_cnt:波特率计数器计数,从 0 计数到 BAUD_CNT_MAX
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)baud_cnt <= 'd0;else if(baud_cnt == BAUD_CNT_MAX || rx_flag == 1'b0)baud_cnt <= 'd0;else if(rx_flag == 1'b1)baud_cnt <= baud_cnt + 1'b1;elsebaud_cnt <= baud_cnt;
end//bit_flag: baud_cnt 计数器计数到码元中间,时采样的数据最稳定
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)bit_flag <= 1'b0;else if(baud_cnt == BAUD_CNT_MAX_div2)bit_flag <= 1'b1;elsebit_flag <= 1'b0;end//bit_cnt:有效数据计数器, 8 个有效数据(不含起始位和停止位)
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)bit_cnt <= 'd0;else if(bit_cnt == 'd8 && bit_flag == 1'b1)bit_cnt <= 'd0;else if(bit_flag == 1'b1)bit_cnt <= bit_cnt + 1'b1;elsebit_cnt <= bit_cnt;
end//输出数据
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)rx_data <= 'd0;else if(bit_cnt >= 'd1 && bit_flag == 1'b1)rx_data <= {rx_reg3,rx_data [7:1]};elserx_data <= rx_data ;
end//输出数据有效信号,当接受完8个数据位后拉高
always @(posedge clk, negedge rst_n) beginif(rst_n == 1'b0)rx_data_valid  <= 1'b0;else if(bit_cnt == 'd8 && bit_flag == 1'b1)rx_data_valid  <= 1'b1;elserx_data_valid  <= 1'b0;
endendmodule

3.3 仿真文件测试

这次仿真文件使用task任务来模拟uart发送数据

`timescale 1ns/1ns
module tb_uart_rx();reg                                                 clk ;reg                                                 rst_n   ;reg                                                 rx  ;wire            [7:0]                               rx_data ;wire                                                rx_data_valid  ;initial beginclk = 0;rst_n = 0;rx = 1;#100rst_n = 1;end//调用task函数,连续发送八个数据initial begin#300tx_data(8'h6);tx_data(8'h6);tx_data(8'h1);tx_data(8'hA);tx_data(8'hB);tx_data(8'hC);tx_data(8'hD);tx_data(8'hE);tx_data(8'hF);end//定义task函数task tx_data(input   [7:0] tx_data);integer  i;for (i=0; i<10; i=i+1 ) begincase(i)0:  rx <= 1'b0;1:  rx <= tx_data[0];2:  rx <= tx_data[1];3:  rx <= tx_data[2];4:  rx <= tx_data[3];5:  rx <= tx_data[4];6:  rx <= tx_data[5];7:  rx <= tx_data[6];8:  rx <= tx_data[7];9:  rx <= 1'b1;endcase#(434*20);//每发送完一个bit数据后,延迟一个码元的时间endendtaskalways #10 clk = ~ clk;
uart_rx#(.CLK_FREQ ( 'd50_000_000 	),.UART_BPS ( 'd115200 		)
)u1_uart_rx
(.clk      		( clk      			),.rst_n    		( rst_n    			),.rx       		( rx       			),.rx_data		( rx_data			),.rx_data_valid  ( rx_data_valid  	)
);endmodule

3.4 仿真结果

在这里插入图片描述

  由仿真结果可以看出,我们接收到的依次是(661ABCDEF)8个数据,和仿真文件给的一致。

3.5 上版测试

  调用ila核,准备抓取rx_data,rx_data_valid信号。通过上位机发送数据,观察rx_data是否正确
在这里插入图片描述
  上位机没发送数据时候,ila采集不到rx_data_valid信号拉高。
在这里插入图片描述
  上位机发送一个9F时,ila采集到rx_data_valid信号拉高,并且输出rx_data为9F

四、UART发送器设计

4.1 发送时序图

在这里插入图片描述

4.2 Verilog代码

`timescale 1ns / 1ps
module uart_tx#
(parameter CLK_FREQ = 'd50_000_000,  //时钟频率parameter UART_BPS = 'd115200       //波特率
)
(input                                               clk ,input                                               rst_n   ,input           [7:0]                               tx_data ,       //发送数据input                                               tx_data_en  ,   //发送数据有效信号output  reg                                         tx  
);localparam                                          BAUD_CNT_MAX    = CLK_FREQ/UART_BPS;//待计数的码元周期最大值reg             [7:0]                               tx_data_reg ;reg             [23:0]                              baud_cnt    ;reg             [3:0]                               bit_cnt ;reg                                                 bit_flag    ;reg                                                 tx_flag ;//将待发送的数据缓存起来
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)tx_data_reg <= 'd0;else if(tx_data_en == 1'b1)tx_data_reg <= tx_data;elsetx_data_reg <= tx_data_reg;
end//tx_flag拉高时刻
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)tx_flag <= 1'b0;else if(tx_data_en == 1'b1)tx_flag <= 1'b1;else if(bit_cnt == 'd9 && bit_flag == 1'b1)tx_flag <= 1'b0;elsetx_flag <= tx_flag;
end//baud_cnt 计数器
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)baud_cnt <= 'd0;else if(baud_cnt == BAUD_CNT_MAX)baud_cnt <= 'd0;else if(tx_flag == 1'b1)baud_cnt <= baud_cnt + 1'b1;elsebaud_cnt <= 'd0;
end
//bit_flag拉高时刻
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)bit_flag <= 1'b0;else if(baud_cnt == 'd1)bit_flag <= 1'b1;elsebit_flag <= 1'b0;
end//bit_cnt 计数器
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)bit_cnt <= 'd0;else if(bit_cnt == 'd9 && bit_flag == 1'b1)bit_cnt <= 'd0;else if(bit_flag == 1'b1)bit_cnt <= bit_cnt + 1'b1;elsebit_cnt <= bit_cnt;
end//移位data,由低到高发送至tx
always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)tx <= 1'b1;else if(bit_flag == 1'b1)case(bit_cnt)0:  tx <= 1'b0;1:  tx <= tx_data_reg[0];2:  tx <= tx_data_reg[1];3:  tx <= tx_data_reg[2];4:  tx <= tx_data_reg[3];5:  tx <= tx_data_reg[4];6:  tx <= tx_data_reg[5];7:  tx <= tx_data_reg[6];8:  tx <= tx_data_reg[7];9:  tx <= 1'b1;default:tx <= 1'b1;endcase
endendmodule

4.3 仿真文件测试

  模拟两个数据(1A,3B)的输入,让tx模块发送

`timescale 1ns / 1ns
module tb_uart_tx();reg                                                 clk ;reg                                                 rst_n   ;wire                                                tx  ;reg             [7:0]                               tx_data ;reg                                                 tx_data_en  ;initial beginclk = 0;rst_n = 0;tx_data = 8'd0;tx_data_en = 0;#100rst_n = 1;#300tx_data = 8'h1a;tx_data_en = 1;#20 tx_data_en = 0;#(434*20*10+100)//等待10个码元周期时间再等待100nstx_data = 8'h3b;tx_data_en = 1;#20 tx_data_en = 0;#(434*20*10+100)$stop;endalways #10 clk = ~clk;uart_tx#
(.CLK_FREQ    ( 'd50_000_000 ),.UART_BPS    ( 'd115200     )
)
u1_uart_tx
(.clk         ( clk         ),.rst_n       ( rst_n       ),.tx_data     ( tx_data     ),.tx_data_en  ( tx_data_en  ),.tx          ( tx          )
);endmodule

4.4 仿真结果

在这里插入图片描述
  因为发送数据是(1A:00011010)uart先发送起始位,再发送数据位从低到高,最后拉高一个停止位。理论上tx发送的顺序就是0_01011000_1
  从仿真结果来看,tx线上的顺序和理论一致,3B数据同理。

4.5 上板测试

  我们创建一个顶层,调用uart_tx模块,在顶层上输入一个数据,让发送模块发出,然后在上位机上接送,观察和我们发送的数据是否一致,代码如下:

`timescale 1ns / 1psmodule uart_top(input                                               clk ,input                                               rst_n   ,output                                              tx  );reg             [7:0]                               tx_data ;reg                                                 tx_data_en  ;reg             [1:0]                               cnt ;always @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)cnt <= 'd0;else if(cnt == 'd3)cnt <= cnt;else cnt <= cnt +1'b1;endalways @(posedge clk or negedge rst_n) beginif(rst_n == 1'b0)begintx_data     <= 'd0;tx_data_en  <= 1'b0;endelse if (cnt == 'd2)begintx_data     <= 8'h6D;tx_data_en  <= 1'b1;endelse begintx_data     <= 'd0;tx_data_en  <= 1'b0;endenduart_tx#
(.CLK_FREQ    ( 'd50_000_000 ),.UART_BPS    ( 'd115200 	)
)
u_uart_tx
(.clk         ( clk         ),.rst_n       ( rst_n       ),.tx_data     ( tx_data     ),.tx_data_en  ( tx_data_en  ),.tx          ( tx          )
);endmodule

  我们在顶层模块发送了一个 6D数据。

在这里插入图片描述
上板后,上位机收到了 6D数据

五、UART回环测试

5.1 系统框图

  上面我们测试了UART发送和接收模块都正常,接下来我们将接收模块的输出信号接到发送模块的输入信号,在上位机上面发送数据和接收数据,看是否一致,系统框图如下:
在这里插入图片描述

5.2 Verilog代码

`timescale 1ns / 1psmodule uart_top(input                                               clk ,input                                               rst_n   ,input                                               rx  ,output                                              tx  );wire             [7:0]                               rx_data ;wire                                                 rx_data_valid  ;uart_rx#
(.CLK_FREQ ( 'd50_000_000    ),.UART_BPS ( 'd115200        )    
)
u_uart_rx(.clk            ( clk               ),.rst_n          ( rst_n             ),.rx             ( rx                ),.rx_data        ( rx_data           ),.rx_data_valid  ( rx_data_valid     )
);uart_tx#
(.CLK_FREQ    ( 'd50_000_000 ),.UART_BPS    ( 'd115200     )
)
u_uart_tx
(.clk         ( clk              ),.rst_n       ( rst_n            ),.tx_data     ( rx_data          ),.tx_data_en  ( rx_data_valid    ),.tx          ( tx               )
);endmodule

5.3 仿真文件测试

  我们依然用uart_rx里的测试代码,调用task函数,发送661abcdef

`timescale 1ns / 1ps
module tb_uart_top();reg                                                 clk ;reg                                                 rst_n   ;reg                                                 rx  ;wire                                                tx  ;initial beginclk = 0;rst_n = 0;rx = 1;#100rst_n = 1;end//调用task函数,连续发送八个数据initial begin#300tx_data(8'h6);tx_data(8'h6);tx_data(8'h1);tx_data(8'hA);tx_data(8'hB);tx_data(8'hC);tx_data(8'hD);tx_data(8'hE);tx_data(8'hF);end//定义task函数task tx_data(input   [7:0] tx_data);integer  i;for (i=0; i<10; i=i+1 ) begincase(i)0:  rx <= 1'b0;1:  rx <= tx_data[0];2:  rx <= tx_data[1];3:  rx <= tx_data[2];4:  rx <= tx_data[3];5:  rx <= tx_data[4];6:  rx <= tx_data[5];7:  rx <= tx_data[6];8:  rx <= tx_data[7];9:  rx <= 1'b1;endcase#(434*20);//每发送完一个bit数据后,延迟一个码元的时间endendtaskalways #10 clk  =~clk;
uart_top u_uart_top(.clk    ( clk    ),.rst_n  ( rst_n  ),.rx     ( rx     ),.tx     ( tx     )
);endmodule

5.4 仿真结果

在这里插入图片描述
可以看出接收后的数据和发送的数据没问题

5.5 上板测试

在这里插入图片描述
可以看到发送和接收都正常

六、参考


UART介绍

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

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

相关文章

CommunityToolkit.Mvvm笔记---AsyncRelayCommand

AsyncRelayCommand 是 CommunityToolkit.Mvvm 中的一个功能&#xff0c;专门设计用来处理异步操作。它是 RelayCommand 的一个变体&#xff0c;提供了对异步任务的支持&#xff0c;允许开发者在 MVVM&#xff08;Model-View-ViewModel&#xff09;模式中方便地实现异步命令。使…

RK3568笔记二十二:基于TACO的垃圾检测和识别

若该文为原创文章&#xff0c;转载请注明原文出处。 基于TACO数据集&#xff0c;使用YOLOv8分割模型进行垃圾检测和识别&#xff0c;并在ATK-RK3568上部署运行。 一、环境 1、测试训练环境&#xff1a;AutoDL. 2、平台&#xff1a;rk3568 3、开发板: ATK-RK3568正点原子板子…

气膜滑冰馆:打破冰雪运动地域限制-轻空间

冰雪运动资源受地域与气候环境的限制&#xff0c;仅能在特定区域和时间段内进行&#xff0c;但随着科技的进步&#xff0c;气膜滑冰馆的出现打破了这一限制&#xff0c;成为了冰雪运动领域的新宠。这种全新的体育场馆让滑冰运动摆脱了地域限制&#xff0c;为运动爱好者提供了更…

[html]一个动态js倒计时小组件

先看效果 代码 <style>.alert-sec-circle {stroke-dasharray: 735;transition: stroke-dashoffset 1s linear;} </style><div style"width: 110px; height: 110px; float: left;"><svg style"width:110px;height:110px;"><cir…

【C++】set 类 和 map 类

1. 关联式容器 关联式容器也是用来存储数据的&#xff0c;与序列式容器不同的是&#xff0c;其里面存储的是<key, value>结构的 键值对&#xff0c;在数据检索时比序列式容器效率更高 2. 键值对 用来表示具有一一对应关系的一种结构&#xff0c;该结构中一般只包含…

Linux-时间同步服务器

1. (问答题) 一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 编写脚本文件 #!/bin/bash hostnamectl hostname ntp_server.example.com cd /etc/NetworkManager/system-connections/ rm -fr * cat > eth0.nmconnection <&…

【STL详解 —— priority_queue的使用与模拟实现】

STL详解 —— priority_queue的使用与模拟实现 priority_queue的使用priority_queue的介绍priority_queue的定义方式priority_queue各个接口的使用 priority_queue的模拟实现仿函数priority_queue的模拟实现 priority_queue的使用 priority_queue的介绍 std::priority_queue 是…

Java面试八股文(JVM篇)(❤❤)

Java面试八股文_JVM篇 1、知识点汇总2、知识点详解&#xff1a;3、说说类加载与卸载11、说说Java对象创建过程12、知道类的生命周期吗&#xff1f;14、如何判断对象可以被回收&#xff1f;17、调优命令有哪些&#xff1f;18、常见调优工具有哪些20、你知道哪些JVM性能调优参数&…

[Algorithm][双指针][查找总价格为目标值的两个商品][三数之和][四数之和]详细解读 + 代码实现

目录 1.查找总价格为目标值的两个商品1.题目链接2.算法原理讲解3.代码实现 2.三数之和1.题目链接2.算法原理讲解3.代码实现 3.四数之和1.题目链接2.算法原理讲解3.代码实现 1.查找总价格为目标值的两个商品 1.题目链接 题目链接 2.算法原理讲解 由于本题数据有序&#xff0c…

【前端】1. HTML【万字长文】

HTML 基础 HTML 结构 认识 HTML 标签 HTML 代码是由 “标签” 构成的. 形如: <body>hello</body>标签名 (body) 放到 < > 中大部分标签成对出现. <body> 为开始标签, </body> 为结束标签.少数标签只有开始标签, 称为 “单标签”.开始标签和…

Transformer的Decoder的输入输出都是什么

目录 1 疑问&#xff1a;Transformer的Decoder的输入输出都是什么 2 推理时Transformer的Decoder的输入输出 2.1 推理过程中的Decoder输入输出 2.2 整体右移一位 3 训练时Decoder的输入 参考文献&#xff1a; 1 疑问&#xff1a;Transformer的Decoder的输入输出都是什么 …

【Golang】并发编程之三大问题:原子性、有序性、可见性

目录 一、前言二、概念理解2.1 有序性2.2 原子性后果1&#xff1a;其它线程会读到中间态结果&#xff1a;后果2&#xff1a;修改结果被覆盖 2.3 可见性1&#xff09;store buffer(FIFO)引起的类似store-load乱序现象2&#xff09;store buffer(非FIFO)引起的类似store-store乱序…