【FPGA】八、UART串口通信
文章目录
前言
一、UART简介
1、基本概念
2、UART协议
3、波特率简介
二、UART串口回环实验
1、设计思路
2、程序代码
① 串口接收模块
② 串口发送模块
③ 串口顶层模块
④ 串口仿真模块
3、仿真验证
总结
前言
在我们进行FPGA设计时,常常会用到一些数据通信接口,这些通信接口都是有着特定的功能以及协议的,其中最常见的莫过于串口uart了,它对于每一个做硬件和嵌入式软件的人来说,几乎就是一个必备的工具,用来调试一个带MCU或者CPU的系统。
串口uart是一种非常通用的设备接口,可以实现不同硬件间的通信,对于FPGA开发来说,串口也同样可以实现FPGA开发板与电脑PC端的通信,下面我们就来简单介绍一下串口uart的基本协议及功能。
一、UART简介
1、基本概念
通用异步收发传输器,英文全称为Universal Asynchronous Receiver/Transmitter,简称UART,是一种异步收发传输器,在发送数据通过将并行数据转换成串行数据进行传输,在接收数据时将串行数据转换成并行数据。
串行通信分为同步串行通信和异步串行通信。同步串行通信即需要时钟的参与,通信双方需要在同一时钟的控制下,同步传输数据;异步串行通信则不需要时钟的干预,通信双方使用各种的时钟来控制数据的发送和接收。uart属于异步串行通信,即没有时钟信号来同步或验证从发送器发送并由接收器接收的数据,这就要求发送器和接收器必须事先就时序参数达成一致。
UART是通用异步收发器的简称,它包括了RS232、RS422、RS423、RS449以及RS485等接口标准和总线规范标准,UART是异步串行通信接口的总称。而RS323、RS422、RS423、RS449和RS485等是对应各种异步串行通信的接口标准和总线标准,它规定了通信接口的电器特性、传输速率以及接口的机械特性等内容。
2、UART协议
UART串口通信需要两根信号线来实现,一根用于串口发送数据,一根用于串口接收数据。UART串口传输的数据被组织成数据包,每个数据包包含了一个起始位,5至9个数据位,可选的奇偶校验位和1或者2个停止位,如下图所示。
UART串口协议规定,当总线处于空闲状态时信号线的状态为高电平,表示当前线路上没有数据传输。
起始位:开始进行数据传输时发送方要先发送一个低电平来表示传输字符的开始。
数据位:起始位之后就需要传输数据,数据位可以是5~9位,构成一个字符,一般是8位,先发送最低位后发送最高位。
奇偶校验位:奇偶校验位是用来检验数据在传输过程中是否出错。在奇校验时,发送方应使数据位中1的个数与校验位中1的个数为奇数,接收方在接收数据时,对1的个数进行检测,若1的个数不为奇数个,则说明数据在传输过程中存在差错。偶校验则相反。
停止位:数据结束标志,可以是1位或者2位的高电平。由于数据在传输线上是定时传输的,并且每一个设备有自己的时钟,很可能在通信中两台设备之间出现了小小的不同步,因此停止位不仅仅是表示数据传输的结束,并且提供计算机校正时钟的机会。停止位越多,数据传输月稳定,但是数据传输速度越慢。
3、波特率简介
在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。
波特率表示每秒钟传送码元符号的个数,它是对符号传输速率的一种度量,用单位时间内载波调制状态改变的次数来表示,1波特指每秒传输1个字符。
数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率有9600、19200、38400、57600、115200等。例如将串口波特率设置位115200bps,那么传输一个bit需要的时间是1/115200 ≈ 8.68us。
二、UART串口回环实验
1、设计思路
实验任务:通过电脑端的串口调试助手向FPGA发送数据,FPGA通过串口接收数据并将接受到的数据发送给上位机,实现串口回环功能。
接收模块(RX):通过检测起始位来表示数据传输的开始,在波特率中间时刻去采样总线上的数据,最后将数据进行串并转换。
发送模块(TX):将并行数据转换成串行数据,然后在串行数据帧头加上起始位,帧尾加上停止位,发送给上位机。
2、程序代码
本次程序设计中没有用到奇偶校验位,一帧数据为8bit,停止位为1位,波特率可供选择(代码为115200),FPGA的系统晶振时钟为50MHZ。
① 串口接收模块
/*===============================*filename : uart_rx.vdescription : 串口接收模块time : 2022-12-22 author : 卡夫卡与海
*================================*/module uart_rx(input clk ,//时钟 50MHZinput rst_n ,//复位input uart_rx ,//rx数据线input [2:0] baud_sel ,//波特率选择output reg [7:0] po_data ,//接收的数据output reg po_flag //数据使能
);//参数定义
parameter SCLK = 50_000_000;//系统时钟 50MHZ//波特率选择
parameter BAUD_9600 = SCLK/9600 ,BAUD_19200 = SCLK/19200 ,BAUD_38400 = SCLK/38400 ,BAUD_57600 = SCLK/57600 ,BAUD_115200 = SCLK/115200;//信号定义
reg uart_rx_1 ;//同步、打拍
reg uart_rx_2 ;
wire rx_nedge ;//下降沿检测reg start_flag ;//起始标志
reg work_en ;//工作使能reg [15:0] cnt_baud ;//波特率计数器
reg [15:0] BAUD_NUM ;reg bit_flag ;//接收数据使能
reg [3:0] cnt_bit ;//bit计数器reg [7:0] rx_data ;//数据
reg rx_flag ;//数据标志//同步、打拍 检测下降沿
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginuart_rx_1 <= 1'b1;uart_rx_2 <= 1'b1;endelse beginuart_rx_1 <= uart_rx;uart_rx_2 <= uart_rx_1;end
end
assign rx_nedge = uart_rx_2 && ~uart_rx_1;//start_flag
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginstart_flag <= 1'b0;endelse if(rx_nedge && work_en == 1'b0)beginstart_flag <= 1'b1;endelse beginstart_flag <= 1'b0;end
end//work_en
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginwork_en <= 1'b0;endelse if(start_flag == 1'b1)beginwork_en <= 1'b1;endelse if(cnt_bit == 4'd8 && bit_flag == 1'b1)beginwork_en <= 1'b0;endelse beginwork_en <= work_en;end
end//cnt_baud
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_baud <= 16'd0;endelse if((cnt_baud == BAUD_NUM - 1) || (work_en == 1'b0))begincnt_baud <= 16'd0;endelse if(work_en == 1'b1)begincnt_baud <= cnt_baud + 1'b1;end
end//BAUD_NUM
always @(*)begincase(baud_sel)3'd0 : BAUD_NUM = BAUD_9600 ;3'd1 : BAUD_NUM = BAUD_19200 ;3'd2 : BAUD_NUM = BAUD_38400 ;3'd3 : BAUD_NUM = BAUD_57600 ;3'd4 : BAUD_NUM = BAUD_115200;default : BAUD_NUM = BAUD_115200;endcase
end//bit_flag
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginbit_flag <= 1'b0;endelse if(cnt_baud == (BAUD_NUM >> 1) - 1)beginbit_flag <= 1'b1;endelse beginbit_flag <= 1'b0;end
end//cnt_bit
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_bit <= 4'd0;endelse if((cnt_bit == 4'd8) && (bit_flag == 1'b1))begincnt_bit <= 4'd0;endelse if(bit_flag == 1'b1)begincnt_bit <= cnt_bit + 1'b1;end
end//rx_data
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_data <= 8'b0;endelse if((cnt_bit>=4'd1)&&(cnt_bit<=4'd8)&&(bit_flag==1'b1))beginrx_data <= {uart_rx_2,rx_data[7:1]};end
end//rx_flag
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_flag <= 1'b0;endelse if((cnt_bit == 4'd8) && (bit_flag == 1'b1))beginrx_flag <= 1'b1;endelse beginrx_flag <= 1'b0;end
end//输出
//po_data
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginpo_data <= 8'b0;endelse if(rx_flag == 1'b1)beginpo_data <= rx_data;end
end//po_flag
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginpo_flag <= 1'b0;endelse beginpo_flag <= rx_flag;end
endendmodule
② 串口发送模块
/*===============================*filename : uart_tx.vdescription : 串口发送模块time : 2022-12-22 author : 卡夫卡与海
*================================*/module uart_tx(input clk ,//时钟 50MHZinput rst_n ,//复位input [2:0] baud_sel ,//波特率选择input [7:0] pi_data ,//数据input pi_flag ,//数据使能output reg uart_tx //tx数据线
);//参数定义
parameter SCLK = 50_000_000;//系统时钟 50MHZ//波特率选择
parameter BAUD_9600 = SCLK/9600 ,BAUD_19200 = SCLK/19200 ,BAUD_38400 = SCLK/38400 ,BAUD_57600 = SCLK/57600 ,BAUD_115200 = SCLK/115200;//信号定义
reg work_en ;//工作使能reg [15:0] cnt_baud ;//波特率计数器
reg [15:0] BAUD_NUM ;reg bit_flag ;//bit标志信号
reg [3:0] cnt_bit ;//bit计数器//work_en
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginwork_en <= 1'b0;endelse if(pi_flag == 1'b1)beginwork_en <= 1'b1;endelse if((cnt_bit == 4'd9) && (bit_flag == 1'b1))beginwork_en <= 1'b0;end
end//cnt_baud
always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_baud <= 16'd0;endelse if((work_en == 1'b0) || (cnt_baud == BAUD_NUM - 1))begincnt_baud <= 16'd0;endelse if(work_en == 1'b1)begincnt_baud <= cnt_baud + 1'b1;end
end//BAUD_NUM
always @(*)begincase(baud_sel)3'd0 : BAUD_NUM = BAUD_9600 ;3'd1 : BAUD_NUM = BAUD_19200 ;3'd2 : BAUD_NUM = BAUD_38400 ;3'd3 : BAUD_NUM = BAUD_57600 ;3'd4 : BAUD_NUM = BAUD_115200;default : BAUD_NUM = BAUD_115200;endcase
end//bit_flag
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginbit_flag <= 1'b0;endelse if(cnt_baud == 16'd1)beginbit_flag <= 1'b1;endelse beginbit_flag <= 1'b0;end
end//cnt_bit
always @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 4'd0;endelse if((cnt_bit == 4'd9)&&(bit_flag == 1'b1))begincnt_bit <= 4'd0;endelse if((work_en == 1'b1)&&(bit_flag == 1'b1))begincnt_bit <= cnt_bit + 1'b1;end
end//输出 uart_tx
always @(posedge clk or negedge rst_n)beginif(!rst_n)beginuart_tx <= 1'b1;endelse if(bit_flag == 1'b1)begincase(cnt_bit)0 : uart_tx <= 1'b0;//起始位1 : uart_tx <= pi_data[0];2 : uart_tx <= pi_data[1];3 : uart_tx <= pi_data[2];4 : uart_tx <= pi_data[3];5 : uart_tx <= pi_data[4];6 : uart_tx <= pi_data[5];7 : uart_tx <= pi_data[6];8 : uart_tx <= pi_data[7];9 : uart_tx <= 1'b1;//停止位default : uart_tx <= 1'b1;endcaseend
endendmodule
③ 串口顶层模块
/*==============================*filename : uart_top.vdescription : 串口顶层模块time : 2022-12-22 author : 卡夫卡与海
*================================*/module uart_top(input clk ,input rst_n ,input uart_rx ,output uart_tx
);
//波特率选择
/*常用波特率选择:
baud_sel == 3'd0 :波特率为:9600
baud_sel == 3'd1 :波特率为:19200
baud_sel == 3'd2 :波特率为:38400
baud_sel == 3'd3 :波特率为:57600
baud_sel == 3'd4 :波特率为:115200 */
wire [2:0] baud_sel ;//波特率选择assign baud_sel = 3'd4;//波特率 = 115200//信号定义
wire [7:0] data ;
wire flag ;//模块例化
//串口接收模块
uart_rx u_uart_rx(/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input */.uart_rx (uart_rx ),/*input [2:0] */.baud_sel (baud_sel),/*output reg [7:0] */.po_data (data ),/*output reg */.po_flag (flag )
);//串口发送模块
uart_tx u_uart_tx(/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input [2:0] */.baud_sel (baud_sel),/*input [7:0] */.pi_data (data ),/*input */.pi_flag (flag ),/*output reg */.uart_tx (uart_tx )
);endmodule
④ 串口仿真模块
/*========================================*filename : uart_top_tb.vdescription : 串口顶层模块仿真文件time : 2022-12-22 author : 卡夫卡与海
*========================================*/
`timescale 1ns/1ns module uart_top_tb();reg clk ;reg rst_n ;reg rx ;wire tx ;//产生时钟
initial beginclk = 1'b1;rx = 1'b1;forever #10clk = ~clk;
end//产生复位
initial beginrst_n = 1'b0;#20;rst_n = 1'b1;
end//产生激励
task rx_bit(input [7:0] data
);integer i;
for(i = 0 ; i < 10 ; i = i + 1)begincase(i)0:rx <= 1'b0;//起始位1:rx <= data[0];2:rx <= data[1];3:rx <= data[2];4:rx <= data[3];5:rx <= data[4];6:rx <= data[5];7:rx <= data[6];8:rx <= data[7];9:rx <= 1'b1;//停止位endcase#(434*20);//根据不同的波特率,延时的时间不同
end
endtasktask rx_byte();integer j;for(j = 0 ; j < 8 ; j = j + 1)rx_bit(j);
endtaskinitial begin#200rx_byte();
end//模块例化
uart_top u_uart_top(/*input */.clk (clk ),/*input */.rst_n (rst_n),/*input */.uart_rx (rx ),/*output */.uart_tx (tx )
);endmodule
3、仿真验证
通过对我们项目工程的仿真来看,显示接收到的数据为0~7共八个字节的数据,发送给上位机的数据也为0~7,说明我们的功能能够正确的接收并发送数据。
通过放大波形图可以看到,波特率计数器总共计数434次,波特率为115200,系统时钟为50MHZ,则50_000_000/115200≈434,波特率计数器正确。我们的采样时刻是在波特率计数器的中间时刻去采样,这样能够保证采样到的数据的稳定性,这里的bit标志信号在波特率计数器计数到217时拉高,表示此刻进行数据的采样操作。
最后就是进行串并转换了,接收模块将串行数据转换成并行数据传给发送模块,而发送模块将接收模块传入的并行数据转换成串行数据通过uart_tx信号线发送给上位机,而上位机通过串口调试助手将接收到的数据串并转换并打印出来显示在屏幕上。
总结
UART串口通信协议还是比较简单的,在编写代码时最重要的是思路的完整性,要有一个全局的概念,不能写了一部分就不知道接下来要干嘛了。实现UART串口通信的思路还有很大,也可以通过前面讲过的状态机的方法来实现,还可以在中间调用FIFO IP核,我这里这个算是一个简单的串口回环实现方式了,在后续的项目中还得加以改进和优化。
【FPGA】八、UART串口通信相关推荐
- 【FPGA】UART串口通信
目录 前言 一丶通信方式 1.串行通信 2.并行通信 二丶UART 串口通信 三丶模块设计 四丶发送模块 1.代码 2.仿真 五丶接收模块 1.代码 2.仿真 六丶顶层模块 1.代码 2.模块原理图 ...
- 基于FPGA的UART串口通信实验(VHDL语言实现)
一.前言: 最近在做UART串口通信的相关实验时,在网上查了很多资料,发现网上的大部分文章只注重理论,不注重代码,很多代码有错误不说,而且难以理解.故在完成此实验后,起了写一篇博客的心思,以供有想做相 ...
- 【FPGA】——UART串口通信
UART串口简介 串行通信分为两种方式:同步串行通信和异步串行通信.同步串行通信要求通信双方使用同一时钟,异步则没有这个要求.UART是一种采用异步串行通信方式的通用异步收发传输器(univers ...
- 【FPGA练习】(一): UART串口通信实验
由于之前学习FPGA的过程中,没有做一个良好的记录,以及已学知识的扩展,所以从今天开始每一个实验例程和扩展应用,都要做文档记录.本实验,是基于正点原子达芬奇xc7a35tfgg484-2开发板.开发板 ...
- 基于FPGA Uart串口通信实验
基于FPGA Uart串口通信实验 首先需要了解uart串口通信协议,根据个人专业需求不同,了解的层面可以不同. UART简介 通用异步收发传输器(Universal Asynchronous Rec ...
- NXP(I.MX6uLL) UART串口通信原理————这个未复习
参考:Linux NXP (I.MX6uLL) UART串口通信原理 作者:一只青木呀 发布时间: 2020-09-20 16:48:33 网址:https://blog.csdn.net/weixi ...
- 【蓝桥杯】单片机学习(7)——UART串口通信
UART串口通信 一.基础知识介绍 1.通信方式的分类 2.RS232通信接口 3.UART模块介绍 (1)串口控制寄存器SCON(可位寻址) (2)电源控制寄存器PCON(不可位寻址) (3)辅助寄 ...
- FPGA自学笔记--串口通信发送多字节数据(verilog版)
1.需求分析 关于uart协议实现这部分大家可以参考我上一篇的博客.<FPGA自学笔记--串口通信实现(vivado&verilog版)>.在上一篇博客中,主要实现了将单字节的数据 ...
- 【正点原子MP157连载】 第十六章 UART串口通信实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7
1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...
最新文章
- Python爱好者,这里有一个库可以帮助你作为新手掌握人工智能!
- 如何给上传控件上色【HOW TO SET CSS FOR THE CONTROL OF FILEUPLOAD】
- 图神经网络代码_第一篇:图神经网络(GNN)计算框架绪论
- Android框架攻击之Fragment注入
- sendmessage和postmessage的区别
- J2EE技术(三)——JMS
- PHP7数字三角形代码,倒数字三角
- 面试官系统精讲Java源码及大厂真题 - 33 CountDownLatch、Atomic 等其它源码解析
- ppt2003 未找到 mathpagewll 53_steam每日特惠:《伊拉图斯:死之主》今日史低53元
- Android 模拟器连接异常:Unable to connect to ADB server
- 那些不用上班的老人每天是不是很幸福?
- 测试面试题+测试面试宝典(分类版)
- D-Link DWA-160 wifi抓包
- 重磅!Spring Cloud 生态再添新套件:Spring Cloud Tencent
- CnPack 自动完成 回车时替换整个标识符
- 智启联云GPS定位平台API开发接口HTTP/MQTT
- 浅谈信息学奥赛NOIP
- Android平台的音乐资源管理与播放
- OSPF—DR与BDR知识点及选举
- 离散数学 用c++实现离散数学逻辑推理
热门文章
- 升级鸿蒙系统详细教程,华为鸿蒙系统怎么升级 鸿蒙系统升级方法步骤
- Ubuntu16.04 搭建FTP服务器,设置用户权限为只许上传、禁止下载和删除
- 大数据关键技术——MapReduce
- 费马小定理证明 (copy的,自己捋清楚)
- 最新完整版外卖人8.7完美运营版PHP源码带配送端+修复APK编译包
- 星号表达式(starred expression)或在列表前面加星号(*)的作用
- 如何获得更持久的广告投放,闲鱼程序员的年终奖全靠它。。。
- base64 图片在线解码/编码
- 港交所VS科创板,企业IPO更应该选择谁?
- 中国奇谭小猪妖、女C罗和领域驱动设计伪创新-罗大佑K歌集及溯源(二)(6-7)