ZYNQ 7020 之 FPGA知识点重塑笔记一——串口通信

目录

一:串口通信简介

二:三种常见的数据通信方式—RS232串口通信

2.1 实验任务

2.2 串口接收模块的设计

2.2.1 代码设计

2.3 串口发送模块的设计

2.3.1 代码设计

2.4 顶层模块编写

2.4.1 代码设计

2.4.2 仿真验证代码

2.4.3 仿真结果

2.4.4 板上验证

一:串口通信简介

       通信方式一般分为串行通信并行通信并行通信是指多比特数据同时通过并行线进行传送。这种传输方式通信线多、成本高,故不宜进行远距离通信,通常传输距离小于 30 米。串行通信是指数据在一条数据线上,一比特接一比特地按顺序传送的方式。这种运输方式通常节省传输线,大大降低使用成本,但数据传送速度慢。综上可知,串行通信主要应用于长距离、低速率的通信场合。本次实验我们主要讲解下串行通信。

       串行通信一般有 2 种通信方式: 同步串行通信 异步串行通信 同步串行通信需要通信双方在同一时钟的控制下同步传输数据; 异步串行通信是指具有不规则数据段传送特性的串行数据传输。在常见的通信总线协议中,I2C,SPI 属于同步通信而 UART 属于异步通信。同步通信的通信双方必须先建立同步,即双方的时钟要调整到同一个频率,收发双方不停地发送和接收连续的同步比特流。异步通信在发送字符时,发送端可以在任意时刻开始发送字符,所以,在 UART 通信中,数据起始位和停止位是必不可少的。
       UART 串口通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收,如下图所示。对于 PC 来说它的 TX 要和对于 FPGA 来说的 RX 连接,同样 PC RX 要和 FPGA TX 连接,如果是两个TX 或者两个 RX 连接那数据就不能正常被发送出去或者接收到。
UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如下图所示。
起始位:当不传输数据时,UART 数据传输线通常保持高电平。若要开始数据传输,发送 UART 会将传输线从高电平拉到低电平并保持 1 个波特率周期。当接收 UART 检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。
数据帧: 数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是 5 位到 8 位。如果不使用奇偶校验位,数据帧长度可以是 9 位。在很多情况下,数据以最低有效位优先发送。
奇偶校验:奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收 UART 判断传输期间是否有数据发生改变。
停止位 为了表示数据包结束,发送 UART 将数据传输线从低电压驱动到高电压并保持 1 2 位时间。
波特率:即每秒传输的位数 (bit) 。一般选波特率都会有 9600 19200 115200等选项。其实意思就是每秒传输这么多个比特位数(bit)

二:三种常见的数据通信方式—RS232串口通信

rs232通信: RS-232 是单端输入输出,
RS-232 标准的串口最常见的接口类型为 DB9 ,样式如图 27.1.3 所示,工业控制领域中用到的工控机 一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串 口,它们一般通过 USB 转串口线(下图所示 )来实现与外部设备的串口通信。
DB9 接口定义以及各引脚功能说明如下图所示,我们一般只用到其中的 2 RXD )、 3 TXD )、 5 (GND)引脚,其他引脚在普通串口模式下一般不使用,如果大家想了解,可以自行百度下。

2.1 实验任务

      本节实验任务是上位机通过串口调试助手发送数据给zynq开发板,zynq开发板 PL 端通过 USB     
_UART 串口接收数据并将接收到的数据发送给上位机,完成串口数据环回实验。主要模块如下表所示:

2.2 串口接收模块的设计

首先完成串口接收模块的设计,串口接收模块我们的输入信号主要有系统时钟信号、系统复位信号与串口接收端口。当我们将一帧的数据接收完成后,那么要告诉下级模块已经将一帧数据接收完了,所以输出为接收完成标志和串口接收数据信号。 模块接口框图如下所示:
串口接收模块端口与功能描述如下表所示:
绘制波形图:
在绘制波形图之前,我们首先要确定串口通信的数据格式及波特率。在这里我们选择串口比较常用的 一种模式,数据位为 8 位,停止位为 1 位,无校验位,波特率为 115200bps 。则传输一帧数据的时序图如下图所示:

2.2.1 代码设计

//串转并
module zdyz_rs232_rx(input sys_clk , //系统时钟input sys_rst_n , //系统复位,低有效input uart_rxd , //UART 接收端口output reg uart_rx_done, //UART 接收完成信号output reg [7:0] uart_rx_data //UART 接收到的数据
);parameter CLK_FREQ = 5000_0000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次reg uart_rxd_d0;
reg uart_rxd_d1;//定义两个D触发器进行异步打拍处理
reg rx_flag ; //接收过程标志信号
reg [3:0] rx_cnt ; //接收数据位计数器    
reg [15:0] baud_cnt ; //波特率计数器(位宽为16,防止溢出)
reg [7:0 ] rx_data_t ; //接收数据寄存器wire start_flag;//开始接收的标志,下降沿到来。//打两拍:波特率时钟和系统时钟不同步,为异步信号,所以要进行打拍处理,防止产生亚稳态  
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n) beginuart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0;endelse beginuart_rxd_d0 <= uart_rxd;uart_rxd_d1 <= uart_rxd_d0;end
end  assign start_flag = (uart_rxd_d0 == 0)&&(uart_rxd_d1 == 1);//下降沿到来的表示方法
// rx_flag接收信号的拉高与拉低   
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)rx_flag <= 1'b0;else if(start_flag) //检测到起始位rx_flag <= 1'b1; //接收过程中,标志信号 rx_flag 拉高//在停止位一半的时候,即接收过程结束,标志信号 rx_flag 拉低else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))//rx_flag 要提前拉低,防止其影响下一帧数据的接收rx_flag <= 1'b0;elserx_flag <= rx_flag;end 
//波特率的计数器计数逻辑 
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) baud_cnt <= 0;      else if(rx_flag)beginif(baud_cnt == BAUD_CNT_MAX - 1)baud_cnt <= 0;elsebaud_cnt <= baud_cnt + 1;       endelsebaud_cnt <= 0;
end 
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)rx_cnt <= 0;   else if(rx_flag)beginif(baud_cnt == BAUD_CNT_MAX - 1)rx_cnt <= rx_cnt + 1;        elserx_cnt <= rx_cnt;endelserx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end
//根据 rx_cnt 来寄存 rxd 端口的数据
always @(posedge sys_clk or negedge sys_rst_n) beginif(!sys_rst_n)rx_data_t <= 0;    else if(rx_flag)begin   //系统处于接收过程时if(baud_cnt == BAUD_CNT_MAX/2 - 1)begin//判断 baud_cnt 是否计数到数据位的中间 case(rx_cnt)1:rx_data_t[0] <= uart_rxd_d1; //寄存数据的最低位2:rx_data_t[1] <= uart_rxd_d1;3:rx_data_t[2] <= uart_rxd_d1;4:rx_data_t[3] <= uart_rxd_d1;5:rx_data_t[4] <= uart_rxd_d1;6:rx_data_t[5] <= uart_rxd_d1;7:rx_data_t[6] <= uart_rxd_d1;8:rx_data_t[7] <= uart_rxd_d1;//寄存数据的高低位default:rx_data_t <= rx_data_t; endcaseendelse rx_data_t <= rx_data_t;endelserx_data_t <= 0; 
end
//给接收完成信号和接收到的数据赋值    
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)begin    uart_rx_done <= 0;   uart_rx_data <= 0;end//当接收数据计数器计数到停止位,且 baud_cnt 计数到停止位的中间时else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1))beginuart_rx_done <= 1; //拉高接收完成信号uart_rx_data <= rx_data_t;//并对 UART 接收到的数据进行赋值end    else beginuart_rx_done <= 0; uart_rx_data <= uart_rx_data;     end
end    
endmodule

2.3 串口发送模块的设计

串口发送模块我们的输入信号主要有系统时钟信号、系统复位信号以及发送使能信号和待发送数据, 输出信号主要有发送忙状态标志和串口发送端口。模块接口框图如下所示:
串口发送模块端口与功能描述如下表所示:

2.3.1 代码设计

module zdyz_rs232_tx(input sys_clk , //系统时钟input sys_rst_n , //系统复位,低有效input uart_tx_en , //UART 的发送使能(发送是有标志起始时间的,接收是根据下降沿到来就开始进入起始位)input [7:0] uart_tx_data, //UART 要发送的数据output reg uart_txd , //UART 发送端口 output reg uart_tx_busy //发送忙状态信号 
);//parameter defineparameter CLK_FREQ = 50000000; //系统时钟频率parameter UART_BPS = 115200 ; //串口波特率localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数 BPS_CNT 次    reg [7:0] tx_data_t; //发送数据寄存器 reg [3:0] tx_cnt ; //发送数据位计数器 reg [15:0] baud_cnt ; //波特率计数器 //当 uart_tx_en 为高时,寄存输入的并行数据(防止发生变化影响发送),并拉高 BUSY 信号  
always @(posedge sys_clk or negedge sys_rst_n) begin   if(!sys_rst_n)begin  tx_data_t <= 0;   uart_tx_busy <= 0;            endelse if(uart_tx_en)begintx_data_t <= uart_tx_data;   uart_tx_busy <= 1;end/*为了确保环回实验的成功,在程序的 36 行我们将 uart_tx_busy 提前 1/16 个停止位拉低。尽管串口发送数据只是接收数据的反过程,理论上在传输的时间上是一致的,但是考虑到我们模块里计算波特率会有较小的偏差,并且串口对端的通信设备(如电脑等)收发数据的波特率同样可能会出现较小的偏差,因此为了确保环回实验的成功,这里将发送模块的停止位略微提前结束。需要说明的是,较小偏差的波特率在串口通信时是允许的,同样可以保证数据可靠稳定的传输*/
else if((tx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16))beginuart_tx_busy <= 0; tx_data_t <= 0;end
else beginuart_tx_busy <= uart_tx_busy; tx_data_t <= tx_data_t;    end         
end //波特率的计数器计数逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n) baud_cnt <= 0;      else if(uart_tx_busy)begin //当处于发送过程时,波特率计数器(baud_cnt)进行循环计数if(baud_cnt == BAUD_CNT_MAX - 1)baud_cnt <= 0; //计数达到一个波特率周期后清零elsebaud_cnt <= baud_cnt + 1;       endelsebaud_cnt <= 0;//发送过程结束时计数器清零
end
//位计数实现逻辑
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)tx_cnt <= 0;   else if(uart_tx_busy)begin//处于发送过程时 tx_cnt 才进行计数if(baud_cnt == BAUD_CNT_MAX - 1)//当波特率计数器计数到一个波特率周期时tx_cnt <= tx_cnt + 1;   //发送数据计数器加 1     elsetx_cnt <= tx_cnt;endelsetx_cnt <= 0;//其他情况下都为0,所以不用担心计数超过9,且其计数也不会超过9,当rx_flag为0时就不计数了
end//根据 tx_cnt 来给 uart 发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n)begin if(!sys_rst_n)uart_txd <= 1 ;//空闲态为1else if(uart_tx_busy)begincase(tx_cnt)4'd0 : uart_txd <= 1'b0 ; //起始位4'd1 : uart_txd <= tx_data_t[0]; //数据位最低位4'd2 : uart_txd <= tx_data_t[1];4'd3 : uart_txd <= tx_data_t[2];4'd4 : uart_txd <= tx_data_t[3];4'd5 : uart_txd <= tx_data_t[4];4'd6 : uart_txd <= tx_data_t[5];4'd7 : uart_txd <= tx_data_t[6];4'd8 : uart_txd <= tx_data_t[7]; //数据位最高位4'd9 : uart_txd <= 1'b1 ; //停止位            default:uart_txd <= 1'b1 ;endcaseendelseuart_txd <= 1 ; //空闲时发送端口为高电平
end 
endmodule 

2.4 顶层模块编写

       在顶层模块中完成了对其余各个子模块的例化。本次实验我们需要设置 2 个参数变量,分别是系统时钟频率 CLK_FREQ 与串口波特率 UART_BPS ,大家使用时可以根据不同的系统时钟频率以及所需要的串口波特率设置这两个变量。zynq7020开发板上的系统时钟为 50MHz ,所以这里将 CLK_FREQ 参数设为50000000。在前文我们说过本次实验的波特率设为 115200 ,所以这里的参数 UART_BPS 就设为 115200 。我们可以尝试将串口波特率 UART_BPS 设置为其他值(如 9600 ),在模块例化时会将这个变量传递到串口接收与发送模块中,从而实现不同速率的串口通信。

2.4.1 代码设计


module top_uart(input sys_clk , //外部 50MHz 时钟input sys_rst_n, //系外部复位信号,低有效//UART 端口 input uart_rxd , //UART 接收端口(串行数据)output uart_txd //UART 发送端口);parameter CLK_FREQ = 50000000; //定义系统时钟频率parameter UART_BPS = 115200 ; //定义串口波特率//wire definewire uart_rx_done; //UART 接收完成信号wire [7:0] uart_rx_data; //UART 接收数据(并行数据)wire uart_tx_busy;//*****************************************************//**                      main code//***************************************************** //串口接收zdyz_rs232_rx #(.CLK_FREQ (CLK_FREQ),.UART_BPS (UART_BPS))zdyz_rs232_rx(.sys_clk(sys_clk) , //系统时钟.sys_rst_n(sys_rst_n) , //系统复位,低有效.uart_rxd(uart_rxd) , //UART 接收端口.uart_rx_done(uart_rx_done), //UART 接收完成信号.uart_rx_data(uart_rx_data)//UART 接收到的数据
); zdyz_rs232_tx #(.CLK_FREQ (CLK_FREQ),.UART_BPS (UART_BPS))zdyz_rs232_tx(.sys_clk(sys_clk) , //系统时钟.sys_rst_n(sys_rst_n) , //系统复位,低有效.uart_tx_en(uart_rx_done) , //UART 的发送使能.uart_tx_data(uart_rx_data), //UART 要发送的数据.uart_txd(uart_txd) , //UART 发送端口 .uart_tx_busy(uart_tx_busy) //发送忙状态信号 
); endmodule

2.4.2 仿真验证代码

 `timescale 1ns/1ns //仿真的单位/仿真的精度module tb_uart_loopback();//parameter define
parameter CLK_PERIOD = 20;//时钟周期为 20ns//reg definereg sys_clk ; //时钟信号reg sys_rst_n; //复位信号reg uart_rxd ; //UART 接收端口//wire definewire uart_txd ; //UART 发送端口//*****************************************************//** main code//*****************************************************//发送 8'h55 8'b0101_0101initial beginsys_clk <= 1'b0;sys_rst_n <= 1'b0;uart_rxd <= 1'b1;#200sys_rst_n <= 1'b1; #1000uart_rxd <= 1'b0; //起始位#8680uart_rxd <= 1'b1; //D0#8680uart_rxd <= 1'b0; //D1#8680uart_rxd <= 1'b1; //D2#8680uart_rxd <= 1'b0; //D3#8680uart_rxd <= 1'b1; //D4#8680uart_rxd <= 1'b0; //D5#8680uart_rxd <= 1'b1; //D6#8680uart_rxd <= 1'b0; //D7 #8680uart_rxd <= 1'b1; //停止位#8680uart_rxd <= 1'b1; //空闲状态 end//50Mhz 的时钟,周期则为 1/50Mhz=20ns,所以每 10ns,电平取反一次always #(CLK_PERIOD/2) sys_clk = ~sys_clk;//例化顶层模块top_uart top_uart(.sys_clk (sys_clk ),.sys_rst_n (sys_rst_n),.uart_rxd (uart_rxd ),.uart_txd (uart_txd ));endmodule

2.4.3 仿真结果

2.4.4 板上验证

上述程序已经经过板上验证了,代码正确,注意若发送和接受都选择了16进制的话,那么就输入0~F,如果不选择16进制的话,就可以任意输入阿拉伯数字和26个字母。

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

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

相关文章

阿里云PolarDB数据库优惠价格表11元一天起

阿里云数据库PolarDB租用价格表&#xff0c;云数据库PolarDB MySQL版2核4GB&#xff08;通用&#xff09;、2个节点、60 GB存储空间55元5天&#xff0c;云数据库 PolarDB 分布式版标准版2核16G&#xff08;通用&#xff09;57.6元3天&#xff0c;阿里云百科aliyunbaike.com分享…

uni-app js语法

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

Lesson 06 vector类(上)

C&#xff1a;渴望力量吗&#xff0c;少年&#xff1f; 文章目录 一、vector是什么&#xff1f;二、vector的使用1. 构造函数2. vector iterator3. vector 空间增长问题4. vector增删查改 三、vector实际使用 一、vector是什么&#xff1f; vector是表示可变大小数组的序列容器…

DFS

目录 DFS 实现数字全排列 N 皇后问题 DFS 算法的理解 优先考虑深度&#xff0c;换句话说就是一条路走到黑&#xff0c;直到无路可走的情况下&#xff0c;才会选择回头&#xff0c;然后重新选择一条路。空间复杂度&#xff1a;O&#xff08;h&#xff09;和高度成正比 不具…

设计模式——适配器模式(Adapter Pattern)

概述 适配器模式可以将一个类的接口和另一个类的接口匹配起来&#xff0c;而无须修改原来的适配者接口和抽象目标类接口。适配器模式(Adapter Pattern)&#xff1a;将一个接口转换成客户希望的另一个接口&#xff0c;使接口不兼容的那些类可以一起工作&#xff0c;其别名为包装…

UE蓝图 RPG动作游戏(一) day15

角色状态制作 制作角色动画混合空间 创建一个动混合空间 添加动作在混合空间 动画蓝图 创建一个动画蓝图 先使用混合空间进行移动&#xff0c;后续优化后再使用状态机 编写垂直水平速度逻辑初始化&#xff0c;获取到此动画的角色组件 获取Horizontal与Vertical的速度逻辑 …

C语言之指针和函数

目录 作为函数参数的指针 二值互换 scanf函数和指针 指针的类型 空指针 标量型 在C语言程序中&#xff0c;指针的一个重要作用就是作为函数参数使用&#xff0c;下面我们就来学习作为函数参数的指针的相关内容。 作为函数参数的指针 假如我有一个神奇的能力&#xff0c;能…

【Vue2+3入门到实战】(18)VUE之Vuex状态管理器概述、VueX的安装、核心概念 State状态代码实现 详细讲解

目录 一、[Vuex](https://vuex.vuejs.org/zh/) 概述1.是什么2.使用场景3.优势4.注意&#xff1a; 二、需求: 多组件共享数据1.创建项目2.创建三个组件, 目录如下3.源代码如下 三、vuex 的使用 - 创建仓库1.安装 vuex2.新建 store/index.js 专门存放 vuex3.创建仓库 store/index…

骑砍战团MOD开发(30)-游戏大地图map.txt

骑砍1战团mod开发-大地图制作方法_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1rz4y1c7wH/ 一.骑砍游戏大地图 骑砍RTS视角游戏大地图 大地图静态模型(map.txt) 军团/城镇图标(module_parties.py). 骑砍大地图的战争迷雾和天气通过API进行管理和控制: # Weather-h…

移动端开发框架mui代码在安卓模拟器上运行(HbuilderX连接到模拟器)

开发工具 HBuilder X 3.8.12.20230817 注意&#xff1a;开发工具尽量用最新的或较新的。太旧的版本在开发调试过程中可能会出现莫名其妙的问题。 1、电脑下载安装安卓模拟器 我这里使用的是 夜神模拟器 &#xff0c;也可以选择其他安卓模拟器 夜神模拟器官网&#xff1a;夜神安…

轻松删除文件名中的符号,使用替换功能,让管理文件更加得心应手!

在我们的日常生活和工作中&#xff0c;文件管理是一项必不可少的任务。而一个整洁、有序的文件名系统则有助于我们快速找到所需的文件。如果你发现文件名中存在一些不必要的符号&#xff0c;那么这款文件重命名工具将是你的得力助手。它具备强大的替换功能&#xff0c;可以轻松…

2023 搞懂git 工作目录---暂存区---本地仓库---版本库

最近了解了下git的底层原理&#xff08;大神录制的视频放在最下方&#xff09;&#xff0c;记录下&#xff1a; 工作区 就是存放待提交文件的目录&#xff08;下图图解标注&#xff09;比如pyhon_test目录暂存区 .git目录下的index文件 对应的指令 git add本地仓库 .gi…