基于FPGA的UART接口设计

article/2025/11/7 2:47:10

 一、顶层设计思路:

        UART即通用异步收发传输接口(Universal Asynchronous Receiver/Transmitter),简称串口,是一种常用的通信接口,其协议原理就不赘述了,不了解的可以自己查阅资料。(不赘述不代表不重要,相反,对于每一个FPGA设计,充分理解原理是基础和前提,而FPGA和Verilog只是工具。)用FPGA来实现UART,关键就是要将UART收发数据时的时序用Verilog描述出来。

        根据UART协议的原理,可以将整个UART分为两个模块:串口接收模块“UART_RX”和串口发送模块“UART_TX”,前者将接收到的1位串行数据“uart_rxd”转化为8位并行数据“data[7:0]”,后者又将8位并行数据“data[7:0]”转化回1位串行数据“uart_txd”输出,最终实现串行数据的收发。UART顶层功能框图如图1所示:

图1 

二、串口接收模块设计思路:

        该模块实现将接收到的1位串行数据转化为8位并行数据,而只有当有数据输入时,该模块才去工作,所以需要一个使能信号“rxd_en”来控制该模块,当“rxd_en”有效时,该模块才工作。根据UART协议原理,接收到的1位串行数据最开始的一位为起始位(“0”),而“uart_rxd”在空闲时为“1”,故刚开始接收到串行数据时,“uart_rxd”必定会产生一个下降沿,所以可以检测这个下降沿,每当下降沿到来的同时将“rxd_en”置为有效,从而使该模块开始工作;每当所有串行数据都被接收完毕时,再把“rxd_en”置为无效,同时发出接收完成标志“rx_done”,从而关闭该模块。(关于在Verilog里怎么实现实现边沿检测,可以看我写的关于DDS信号发生器的那篇博文。)

        在数据接收的过程中,速率以串口波特率为准,常用的串口波特率有:1200、2400、4800、9600、14400、57600、115200等等。所以需要一个波特率计数器“baud_cnt”在特定的波特率下对主时钟进行计数,每当这个波特率计数器计满时,接收一位数据。例如,主时钟为50MHz,当波特率为9600时,波特率计数器的最大值应该为:50000000/9600-1=5207,此时,每当波特率计数器计到5207时就清零,同时接收一位串行数据。

        在本次设计中,每个数据的数据位共有10位(1位起始位、8位数据位、1位停止位),故在接收过程中,还需要一个位计数器“bit_cnt”来对每个串行数据的数据位进行计数,具体操作为:每当波特率计数器计满时,位计数器就自增1,直到位计数器的值为9时清零。

        在接收过程中,为了接收到稳定的串行数据,本设计在每一位串行数据的中间对其进行采样和接收,具体操作为:每当波特率计数器计到最大值的一半时,就对当前的串行数据进行采样,然后根据位计数器的值,将采样后的值赋给相应的并行数据位。此外,为了消除亚稳态,待接收的串行数据应该先通过一个两位的寄存器进行缓冲后再进行边沿检测。

        根据以上分析,做出串口接收模块的时序图如图2所示(“uart_rxd_r”是“uart_rxd”消除亚稳态后再通过边沿检测寄存器后的信号):

图2

        根据时序图,就可以进行串口接收模块的RTL描述,编写的Verilog代码如下:

`timescale 1ns / 1psmodule UART_RX(input clk, //主时钟,50MHzinput rst, //复位,高电平有效input uart_tx_data, //发送给串口的串行数据output reg [7:0] uart_rx_data, //串口接收后的并行数据output reg rx_done //接收完成标志);parameter CLK_F = 50000000; //主时钟频率parameter UART_B = 9600; //串口波特率parameter B_CNT = CLK_F / UART_B; //波特率计数器的最大值reg [1:0] uart_tx_data_r1; //用于消除输入串行数据的亚稳态reg [1:0] uart_tx_data_r2; //输入串行数据边沿检测寄存器reg rxd_en; //接收使能信号reg [15:0] baud_cnt = 16'd0; //115200波特率计数器reg [3:0] bit_cnt = 4'd0; //位计数器reg [7:0] rx_data; //接收数据寄存器/***************消除输入串行数据亚稳态*************/always @ (posedge clk)beginuart_tx_data_r1 <= {uart_tx_data_r1[0] , uart_tx_data};end/***************输入串行数据边沿检测*************/always @ (posedge clk)beginuart_tx_data_r2 <= {uart_tx_data_r2[0] , uart_tx_data_r1[1]};end/***************接收使能信号控制*************/always @ (posedge clk or negedge rst)beginif (rst)rxd_en <= 1'd0;elsebegin if (uart_tx_data_r2 == 2'b10)rxd_en <= 1'd1;else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))rxd_en <= 1'd0;else rxd_en <= rxd_en;endend/***************波特率计数器控制*************/always @ (posedge clk or negedge rst)beginif (rst)baud_cnt <= 16'd0;else if (rxd_en)beginif (baud_cnt == B_CNT - 1)baud_cnt <= 16'd0;elsebaud_cnt <= baud_cnt + 1'b1;endelsebaud_cnt <= 16'd0;end/***************位计数器控制*************/always @ (posedge clk or negedge rst)beginif (rst)bit_cnt <= 4'd0;else if (rxd_en)beginif (baud_cnt == B_CNT - 1)bit_cnt <= bit_cnt + 1'b1;elsebit_cnt <= bit_cnt;endelsebit_cnt <= 4'd0;end/***************接收缓存*************/always @ (posedge clk or negedge rst)beginif (rst)rx_data <= 8'd0;else if (rxd_en)beginif (baud_cnt == B_CNT / 2)begincase (bit_cnt)4'd1:rx_data[0] <= uart_tx_data_r2[1];4'd2:rx_data[1] <= uart_tx_data_r2[1];4'd3:rx_data[2] <= uart_tx_data_r2[1];4'd4:rx_data[3] <= uart_tx_data_r2[1];4'd5:rx_data[4] <= uart_tx_data_r2[1];4'd6:rx_data[5] <= uart_tx_data_r2[1];4'd7:rx_data[6] <= uart_tx_data_r2[1];4'd8:rx_data[7] <= uart_tx_data_r2[1];default:;endcaseendelserx_data <= rx_data;endelserx_data <= 8'd0;end/***************接收*************/always @ (posedge clk or negedge rst)beginif (rst)uart_rx_data <= 8'd0;else if (bit_cnt == 4'd9)uart_rx_data <= rx_data;elseuart_rx_data <= 8'd0;end/***************接收完成标志控制*************/always @ (posedge clk or negedge rst)beginif (rst)rx_done <= 1'd0;else if (bit_cnt == 4'd9)rx_done <= 1'd1;elserx_done <= 1'd0;endendmodule

        编写的testbeach如下:

`timescale 1ns / 1psmodule tb_uart_rx();reg clk; //主时钟,50MHzreg rst; //复位,低电平有效reg uart_tx_data; //发送给串口的串行数据wire [7:0] uart_rx_data; //串口接收后的并行数据wire rx_done; //接收完成标志/***************模块例化*************/ UART_RX tb_uart_rx(.clk(clk),      .rst(rst),     .uart_tx_data(uart_tx_data), .uart_rx_data(uart_rx_data), .rx_done(rx_done)  );/***************产生主时钟*************/always #10 clk = ~clk;/***************初始化*************/initial beginclk = 1'd0;rst = 1'd0;uart_tx_data = 1'b1;#1000000uart_tx_data = 1'b0;#200000uart_tx_data = 1'b1;#200000uart_tx_data = 1'b0;#200000uart_tx_data = 1'b1;endendmodule

         仿真结果如图3所示,可以看到接收功能已实现:

图3 

三、串口发送模块设计思路:

        该模块实现将8位并行数据转化回1位串行数据输出,与接收模块一样,串口发送模块也需要一个使能信号“txd_en”来控制,当“txd_en”有效时,模块才工作。根据UART原理,只有接收模块接收完成后,发送模块才能开始工作,故接收模块里的接收完成标志即是发送模块的发送开始标志“txd_start”,所以可以通过检测发送开始标志的上升沿来使能“txd_en”,从而使能发送模块。

        根据以上分析,做出串口发送模块的时序图如图4所示:

 图4

        根据时序图,编写的Verilog代码如下:

`timescale 1ns / 1psmodule UART_TX(input clk, //主时钟,50MHzinput rst, //复位,高电平有效input txd_start, //发送开始标志input [7:0] uart_rx_data, //串口接收到的并行数据output reg uart_tx_data //串口发送的串行数据);parameter CLK_F = 50000000; //主时钟频率parameter UART_B = 9600; //串口波特率parameter B_CNT = CLK_F / UART_B; //波特率计数器的最大值reg [1:0] txd_start_r; //发送开始标志边沿检测寄存器 reg txd_en; //发送使能信号reg [15:0] baud_cnt = 16'd0; //115200波特率计数器reg [3:0] bit_cnt = 4'd0; //位计数器reg [7:0] tx_data; //发送数据寄存器/***************发送开始标志边沿检测*************/always @ (posedge clk)begintxd_start_r <= {txd_start_r[0] , txd_start};end/***************发送使能信号控制*************/always @ (posedge clk or negedge rst)beginif (rst)txd_en <= 1'd0;elsebegin if (txd_start_r == 2'b01)txd_en <= 1'd1;else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))txd_en <= 1'd0;else txd_en <= txd_en;endend/***************发送缓存*************/always @ (posedge clk or negedge rst)beginif (rst)tx_data <= 8'd0;elsebegin if (txd_start_r == 2'b01)tx_data <= uart_rx_data;else if ((bit_cnt == 4'd9) && (baud_cnt == B_CNT / 2))tx_data <= 8'd0;else tx_data <= tx_data;endend/***************波特率计数器控制*************/always @ (posedge clk or negedge rst)beginif (rst)baud_cnt <= 16'd0;else if (txd_en)beginif (baud_cnt == B_CNT - 1)baud_cnt <= 16'd0;elsebaud_cnt <= baud_cnt + 1'b1;endelsebaud_cnt <= 16'd0;end/***************位计数器控制*************/always @ (posedge clk or negedge rst)beginif (rst)bit_cnt <= 4'd0;else if (txd_en)beginif (baud_cnt == B_CNT - 1) bit_cnt <= bit_cnt + 1'b1;elsebit_cnt <= bit_cnt;endelsebit_cnt <= 4'd0;end/***************发送*************/always @ (posedge clk or negedge rst)beginif (rst)uart_tx_data <= 1'd1;else if (txd_en)begincase (bit_cnt)4'd0:uart_tx_data <= 1'd0;4'd1:uart_tx_data <= tx_data[0];4'd2:uart_tx_data <= tx_data[1];4'd3:uart_tx_data <= tx_data[2];4'd4:uart_tx_data <= tx_data[3];4'd5:uart_tx_data <= tx_data[4];4'd6:uart_tx_data <= tx_data[5];4'd7:uart_tx_data <= tx_data[6];4'd8:uart_tx_data <= tx_data[7];4'd9:uart_tx_data <= 1'd1;default:;endcaseendelseuart_tx_data <= 1'd1;endendmodule

        编写的testbeach如下:

`timescale 1ns / 1psmodule tb_uart_tx();reg clk; //主时钟,50MHzreg rst; //复位,低电平有效reg txd_start; //发送开始标志reg [7:0] uart_rx_data; //串口接收到的并行数据wire uart_tx_data; //串口发送的串行数据/***************模块例化*************/ UART_TX tb_uart_tx(.clk(clk),      .rst(rst),     .txd_start(txd_start),    .uart_rx_data(uart_rx_data), .uart_tx_data(uart_tx_data));/***************产生主时钟*************/always #10 clk = ~clk;/***************初始化*************/initial beginclk = 1'd0;rst = 1'd0;txd_start = 1'd0;uart_rx_data = 8'h5a;#20txd_start = 1'd1;#20txd_start = 1'd0;#100000uart_rx_data = 8'h3f;#100000uart_rx_data = 8'he6;endendmodule

          仿真结果如图5所示,可以看到发送功能已实现:

图5

四、顶层代码及其上板调试:

        两个子模块设计完成后,按照顶层功能框图可以编写顶层的RTL描述,编写的Verilog代码如下(由于我的开发板上的时钟是差分时钟,故需要调用一个差分信号转单端信号的设计原语“IBUFDS”,该原语的使用很简单,在这里就不专门介绍了,不了解的可以自己查阅资料):

`timescale 1ns / 1psmodule UART_TOP(input clk_p, //差分主时钟正端,50MHzinput clk_n, //差分主时钟负端,50MHzinput rst, //复位,高电平有效input uart_rxd, //接收端output uart_txd //发送端);parameter  CLK_FREQ = 50000000; //主时钟频率parameter  UART_BPS = 460800; //串口波特率wire clk; //主时钟,50MHzwire [7:0] uart_rx_data_w; //串口里的并行数据线wire rx_done_w; //接收完成信号线/***************差分时钟转单端时钟*************/IBUFDS #(.DIFF_TERM("FALSE"), .IBUF_LOW_PWR("TRUE"), .IOSTANDARD("DEFAULT") ) IBUFDS_inst (.O(clk), .I(clk_p), .IB(clk_n));/***************调用串口接收模块*************/UART_RX #(.CLK_F(CLK_FREQ),.UART_B(UART_BPS))uut_rxd(.clk(clk),.rst(rst),.uart_tx_data(uart_rxd),.uart_rx_data(uart_rx_data_w),.rx_done(rx_done_w));/***************调用串口发送模块*************/UART_TX #(.CLK_F(CLK_FREQ), .UART_B(UART_BPS))uut_txd(.clk(clk),.rst(rst),.txd_start(rx_done_w),.uart_rx_data(uart_rx_data_w),.uart_tx_data(uart_txd) );endmodule

        综合、实现后,进行上板调试,为了简单起见,本设计采用回环的方式来调试验证,即PC发送数据到FPGA上,FPGA通过串口接收数据后再通过串口发送回PC。调试结果如图6所示,可以看到,所设计的串口工作正常:

图6

        至此,本设计完成。 


http://chatgpt.dhexx.cn/article/2IsvuyGZ.shtml

相关文章

单片机通信接口:UART、I2C、SPI、TTL、RS232、RS422、RS485、CAN、USB

参考资料&#xff1a; 这些单片机接口&#xff0c;一定要熟悉&#xff1a;UART、I2C、SPI、TTL、RS232、RS422、RS485、CAN、USB、SD卡 秒懂所有USB接口类型&#xff0c;USB接口大全 1. UART UART(通用异步收发器)指的是一种物理接口形式(硬件)。 UART是异步&#xff0c;全双…

通信接口:UART、I2C、SPI、TTL、RS232、RS422、RS485、CAN、USB

1. UART UART(通用异步收发器)指的是一种物理接口形式(硬件)。 UART是异步&#xff0c;全双工串口总线。它比同步串口复杂很多。有两根线&#xff0c;一根TXD用于发送&#xff0c;一根RXD用于接收。 UART的串行数据传输不需要使用时钟信号来同步传输&#xff0c;而是依赖于发送…

USB,串口(RS232、RS485),UART接口

USB转串口即实现计算机USB接口到通用串口之间的转换。为没有串口的计算机提供快速的通道&#xff0c;而且&#xff0c;使用USB转串口设备等于将传统的串口设备变成了即插即用的USB设备。作为应用最广泛的USB接口&#xff0c;每台电脑必不可少的通讯接口之一&#xff0c;它的最大…

Uart接口的详细解释

我面试的时候一般喜欢问应聘者一个问题&#xff1a;UART与RS232/RS485的区别与联系&#xff1f;很多人对于这个问题答得都不是很好。还有些人压根就没有想过这个问题&#xff0c;一直认为他们是同一个东西&#xff0c;就是咱们俗称的串口。 我刚入嵌入式的大门时&#xff0c;对…

UART接口说明

逼近年关事情多&#xff0c;少了更新。今天冒个泡。说下UART通信接口。 UART扫盲 前面做了SPI和I2C&#xff0c;前两者一个是摩托&#xff0c;一个是飞利浦背书&#xff0c;简单好理解。这个UART就相对复杂一点&#xff0c;全称universal Asynchronous Receiver/Transmitter …

UART接口详解

文章目录 简介硬件接线RS232RS485RS232和RS485比较 通信原理uart和usart的区别实例针对STM32的串口数据位特点&#xff0c;改成对应PC的串口数据校验当使用9600波特率的时候&#xff0c;通讯稳定&#xff0c;当使用115200波特率的时候&#xff0c;通讯变得不稳定。 简介 UART全…

UART接口介绍

0 Preface/Foreword UART是Universal Asynchronous Receiver and Transmitter简称&#xff0c;中文为 通用异步接收和发送器&#xff0c;是常用的串行通讯接口。 RS-232&#xff1a;RS-232标准接口&#xff08;aka. EIA RS-232&#xff09;是常用的串行通信接口标准之一&#…

一文理解UART通信

还记得当年的打印机&#xff0c;鼠标和调制解调器吗?他们都有巨大笨重的连接器和粗电缆&#xff0c;并且必须拧到你的电脑上。这些设备正是使用UART协议与计算机进行通信。虽然USB几乎完全取代了旧的电缆和连接器&#xff0c;但UART绝对没有过时。您会发现目前许多项目中使用U…

Ajax传json对象(jQuery)

Ajax传json对象 相信很多小伙伴想要通过Ajax传输json数据给后端&#xff0c;本来直接发送一个data: JSON.stringify(obj)就可以了&#xff0c;但是发现后端的请求参数中有一个参数需要int类型&#xff0c;这个时候就需要用到对象了。 封装对象 首先得到数据并封装对象 &…

十分钟搞懂JSON(JSON对象---JSON字符串---对象 之间的区别)

好记性不如烂笔头&#xff0c;相信我&#xff0c;看了之后你会彻底搞懂JSON 前言&#xff1a;前天被JSON对象&#xff0c;JSON字符串&#xff0c;JAVA对象搞混了&#xff0c;不知道各自代表的意思&#xff0c;我就查了资料&#xff0c;总结为一篇博文。 另外我想List<User&g…

【python】将json字符串转化为json对象

首先导入python自带的json模块 import json loads方法将json<key,value>字符串转化为dict形式 info {"name":"Lucy","age":22,"addr": "China"} print(type(info)) info_dict json.loads(info) print(info_dict)…

将js对象转化为json对象

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script type"text/javascript">//编写 一个JavaScript对象&#xff0c;var user {name: "asdas",age…

Mysql JSON对象和JSON数组查询

文章目录 1. 函数说明2. JSON对象3. 字符串JSON数组3.1 AND关系3.2 OR关系 4. 对象数组5. 集成 Mybatis plus6. 模糊查询7. json_table7.1 分组计算总数7.2 对象去重 file_type可以是 varchar&#xff0c;也可以是 json类型 1. 函数说明 JSON_CONTAINS(json_doc, val[, path…

Json对象和Json字符串的区别

Json对象和Json字符串的区别 </h1><div class"clear"></div><div class"postBody"><div id"cnblogs_post_body" class"blogpost-body blogpost-body-html">1、Json概念 概念&#xff1a;JSON (JavaSc…

获取json对象的key

获取json对象的key值 我有一个json对象&#xff0c;我想获取每一个学生的信息&#xff0c;但是每次拿到的数据最外层的key值是随学生人数增加而变化的&#xff0c;并不是固定的。 所以我的思路为&#xff1a;先获取最外层的key&#xff0c;然后根据key获取value。 实现过程如下…

js操作json对象

工作中做了几款自动化测试工具都是跟日志读取相关&#xff0c;日志格式又大多数都是JSON&#xff0c;所以这里把常用的JS操作JSON的方法做了总结&#xff1a; 一、概念简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;采用完全独立于语言的文本格…

嵌套 JSON 对象中的数组

JSON 对象中数组可以包含另外一个数组&#xff0c;或者另外一个 JSON 对象&#xff1a; var myObj {"name":"网站","num":3,"sites": [{ "name":"Google", "info":[ "Android", "Googl…

Js 给JSON对象排序

众所周知&#xff0c;json对象是没有顺序的。只有数组才有排序功能。 但我们遇到的业务场景里面&#xff0c;不仅仅需要对数组排序&#xff0c;也有需要对对象排序的情况。 例如下面这种数据&#xff1a; let data {zhangsan: {age: 18, height: 189}, lisi: {age: 18, hei…

嵌套 json对象

什么是嵌套json对象&#xff1f; ---- JSON 对象中可以包含另外一个 JSON 对象 你可以使用点号(.)或者中括号([])来访问嵌套的 JSON 对象。 <script>// 嵌套 json对象var myjson {"name":"老李","keywords":[狗,熊,李],"site"…

JSON对象

开发工具与关键技术&#xff1a;VS MVC 作者&#xff1a;冉冉 撰写时间&#xff1a;2019年05月21日Json是JavaScript对象方法它的全称为&#xff1a;JavaScript Object Notation。Json是存储和交换文本信息的语法&#xff1a; 1、 JSON是轻量级的文本数据交换格式&#xff1b; …