前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >FPGA实现uart_FPGA的EMU接口

FPGA实现uart_FPGA的EMU接口

作者头像
全栈程序员站长
发布于 2022-10-05 02:21:31
发布于 2022-10-05 02:21:31
72000
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

一、顶层设计思路:

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代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
`timescale 1ns / 1ps
module UART_RX(
input clk, //主时钟,50MHz
input 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)
begin
uart_tx_data_r1 <= {uart_tx_data_r1[0] , uart_tx_data};
end
/***************输入串行数据边沿检测*************/
always @ (posedge clk)
begin
uart_tx_data_r2 <= {uart_tx_data_r2[0] , uart_tx_data_r1[1]};
end
/***************接收使能信号控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
rxd_en <= 1'd0;
else
begin 
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;
end
end
/***************波特率计数器控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
baud_cnt <= 16'd0;
else if (rxd_en)
begin
if (baud_cnt == B_CNT - 1)
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
else
baud_cnt <= 16'd0;
end
/***************位计数器控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
bit_cnt <= 4'd0;
else if (rxd_en)
begin
if (baud_cnt == B_CNT - 1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
else
bit_cnt <= 4'd0;
end
/***************接收缓存*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
rx_data <= 8'd0;
else if (rxd_en)
begin
if (baud_cnt == B_CNT / 2)
begin
case (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:;
endcase
end
else
rx_data <= rx_data;
end
else
rx_data <= 8'd0;
end
/***************接收*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
uart_rx_data <= 8'd0;
else if (bit_cnt == 4'd9)
uart_rx_data <= rx_data;
else
uart_rx_data <= 8'd0;
end
/***************接收完成标志控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
rx_done <= 1'd0;
else if (bit_cnt == 4'd9)
rx_done <= 1'd1;
else
rx_done <= 1'd0;
end
endmodule

编写的testbeach如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
`timescale 1ns / 1ps
module tb_uart_rx();
reg clk; //主时钟,50MHz
reg 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 
begin
clk = 1'd0;
rst = 1'd0;
uart_tx_data = 1'b1;
#1000000
uart_tx_data = 1'b0;
#200000
uart_tx_data = 1'b1;
#200000
uart_tx_data = 1'b0;
#200000
uart_tx_data = 1'b1;
end
endmodule

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

图3

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

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

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

图4

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
`timescale 1ns / 1ps
module UART_TX(
input clk, //主时钟,50MHz
input 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)
begin
txd_start_r <= {txd_start_r[0] , txd_start};
end
/***************发送使能信号控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
txd_en <= 1'd0;
else
begin 
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;
end
end
/***************发送缓存*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
tx_data <= 8'd0;
else
begin 
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;
end
end
/***************波特率计数器控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
baud_cnt <= 16'd0;
else if (txd_en)
begin
if (baud_cnt == B_CNT - 1)
baud_cnt <= 16'd0;
else
baud_cnt <= baud_cnt + 1'b1;
end
else
baud_cnt <= 16'd0;
end
/***************位计数器控制*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
bit_cnt <= 4'd0;
else if (txd_en)
begin
if (baud_cnt == B_CNT - 1) 
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
else
bit_cnt <= 4'd0;
end
/***************发送*************/
always @ (posedge clk or negedge rst)
begin
if (rst)
uart_tx_data <= 1'd1;
else if (txd_en)
begin
case (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:;
endcase
end
else
uart_tx_data <= 1'd1;
end
endmodule

编写的testbeach如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
`timescale 1ns / 1ps
module tb_uart_tx();
reg clk; //主时钟,50MHz
reg 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 
begin
clk = 1'd0;
rst = 1'd0;
txd_start = 1'd0;
uart_rx_data = 8'h5a;
#20
txd_start = 1'd1;
#20
txd_start = 1'd0;
#100000
uart_rx_data = 8'h3f;
#100000
uart_rx_data = 8'he6;
end
endmodule

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

图5

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
`timescale 1ns / 1ps
module UART_TOP(
input clk_p, //差分主时钟正端,50MHz
input clk_n, //差分主时钟负端,50MHz
input rst, //复位,高电平有效
input uart_rxd, //接收端
output uart_txd //发送端
);
parameter  CLK_FREQ = 50000000; //主时钟频率
parameter  UART_BPS = 460800; //串口波特率
wire clk; //主时钟,50MHz
wire [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

至此,本设计完成。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年9月14日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
fpga通过uart向上位机传输ad7606采集数据
前文提供了ad7606的驱动程序,本文通过串口将8路adc采集的数据传输给上位机显示。
FPGA技术江湖
2025/03/14
850
fpga通过uart向上位机传输ad7606采集数据
FPGA大赛【七】具体模块设计--ddr+串口联合测试
DDR端的数据通过AXI总线进行数据传输。在前面章节介绍了DDR数据读写模块的设计(aq_axi_master),本章节中便对这个axi的读写模块进行测试。在测试中,先向ddr的某个地址中写入数据,然后再将该地址的数据读取出来,通过串口将此数据发送到电脑端,以此验证ddr数据的读写是否正确。
数字积木
2021/04/15
5970
uart串口通信编程_verilog调用模块端口对应方式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/10/05
1.2K0
基于FPGA的CAN总线控制器的设计(附代码)
今天给大侠带来基于FPGA的CAN总线控制器的设计,包括CAN 总线协议解析以及 CAN 通信控制器程序基本框架、CAN 通信控制器的具体实现、程序的仿真与测试以及总结。篇幅较长,话不多说,上货。
FPGA技术江湖
2024/11/26
1300
基于FPGA的CAN总线控制器的设计(附代码)
基于FPGA的CAN总线控制器的设计(中)
今天给大侠带来基于FPGA的CAN总线控制器的设计,由于篇幅较长,分三篇。今天带来第二篇,中篇,CAN 通信控制器的具体实现。话不多说,上货。
FPGA技术江湖
2021/05/11
1.2K0
基于FPGA的CAN总线控制器的设计(中)
串口收发模块设计
该串口收发模块有串口发送模块,串口接收模块,波特率生成模块,发送数据fifo模块,接收数据的fifo模块组成。
数字积木
2021/04/15
7960
FPGA零基础学习之Vivado-UART驱动教程
本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
FPGA技术江湖
2023/07/19
6010
FPGA零基础学习之Vivado-UART驱动教程
FPGA零基础学习:UART协议驱动设计
本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
FPGA技术江湖
2021/03/23
8980
FPGA零基础学习:UART协议驱动设计
UART
UARTRS232 RS485 RS422区别RS232物理接口RS485物理接口RS422物理接口UART通信协议UART设计波特率产生模块发送模块接收模块顶层模块串口驱动下载
瓜大三哥
2020/05/29
1.3K0
FPGA综合项目——SDRAM控制器
再者就是通信处理模块,具体的通信设置,发送什么命令是写?什么命令是读?发的什么数据?等等。
全栈程序员站长
2022/09/16
6270
FPGA综合项目——SDRAM控制器
UART接口控制器
主设备与从设备通过总线来进行数据通信,是一个数字系统不可或缺的一部分,本篇讲述一种常见的总线控制器UART串行数据接口,也称为串口。 串口的标准一般有,RS-232、RS-422与RS-485标准,我们讲述的是RS-232接口信号。
全栈程序员站长
2022/10/05
6960
UART接口控制器
串口通信控制器的Verilog HDL实现(一) 顶层模块
本设计采用分层设计思想,主要由顶层模块、波特率发生器、接收模块和发送模块这4个模块组成,强调功能划分明确,便于系统设计和调试。 本系统要求在Xilinx Spartan 3E Starter开发板上实现波特率为9600,停止位为1比特、不带校验位并且具备复位功能的串口通信控制器,并要求和PC机通过超级终端完成双向通信。不仅要求将板极发送数据显示在PC机的超级终端上,还要求用PC发送数据的ASCII码来驱动电路板的8个LED灯。为了便于测试,要求当按下开发板上的button_s时,板级发送的数值恢复到48,对
瓜大三哥
2018/02/24
1.7K0
串口通信控制器的Verilog HDL实现(一) 顶层模块
详解串行通信协议及其FPGA实现(二)
基于Verilog实现标准串口协议发送8位数据:起始位 + 8位数据位 + 校验位 + 停止位 = 11位,每1位的时间是16个时钟周期,所以输入时钟应该为:波特率*16,带Busy忙信号输出。实现方法比较简单,数据帧的拼接、计数器计时钟周期,每16个时钟周期输出一位数据即可。
单片机点灯小能手
2020/07/16
7420
FPGA设计思想(持续更新)
该文介绍了如何利用FPGA实现图像处理算法,包括基于硬件的图像处理算法和可重构的图像处理算法。作者分别介绍了两种算法的实现方式,并通过实例进行对比。最后,作者探讨了在FPGA上实现图像处理算法的优势,包括实现高速处理、降低系统复杂度、提高可扩展性等方面。
NingHeChuan
2018/01/05
9700
FPGA设计思想(持续更新)
FPGA计算3行同列数据之和
本文介绍了如何利用FPGA实现Sobel边缘检测算法,通过仿真实验证明该方法可以大幅提高边缘检测的实时性,从而在嵌入式系统中得到广泛应用。
NingHeChuan
2018/01/05
1.4K0
FPGA计算3行同列数据之和
VCS与Verdi的联合仿真
Verdi主要用于生成fsdb模型,同VCS使用的vcd文件相比,verdi使用的fsdb相当于vcd文件经过霍夫编码压缩之后的精简版,可用于查看fsdb波形并追踪RTL代码。
根究FPGA
2020/07/28
9.3K0
VCS与Verdi的联合仿真
2.3 基于FPGA的UART协议实现(二)简单UART传输FPGA实现
      图2 34 FPGA发送一帧串口数据(考虑波特率)   如果图2 34考虑 115200 的波特率,结果如图2 34所示,每一位数据都保持 434 个时钟,为此 Verilog 可以这样表示,如代码2 11所示:           代码2 11
碎碎思
2020/06/30
8270
FPGA设计中,对SPI进行参数化结构设计
大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。
FPGA技术江湖
2020/12/29
6560
FPGA设计中,对SPI进行参数化结构设计
FPGA基础知识极简教程(6)UART通信与移位寄存器的应用
相关博文1单独介绍了各种类型的移位寄存器,其中就包括串行输入并行输出移位寄存器(SIPO)以及并行输入串行输出移位寄存器 (PISO)。移位寄存器有如下功能:
Reborn Lee
2020/06/29
1.4K0
FPGA零基础学习:IIC协议驱动设计
本系列将带来FPGA的系统性学习,从最基本的数字电路基础开始,最详细操作步骤,最直白的言语描述,手把手的“傻瓜式”讲解,让电子、信息、通信类专业学生、初入职场小白及打算进阶提升的职业开发者都可以有系统性学习的机会。
FPGA技术江湖
2021/03/23
1.3K0
FPGA零基础学习:IIC协议驱动设计
相关推荐
fpga通过uart向上位机传输ad7606采集数据
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验