ZYNQ初识10(zynq_7010)UART通信实验

基于bi站正点原子讲解视频:

系统框图(基于串口的数据回环)如下:

以下,是串口接收端的波形图,系统时钟和波特率时钟不同,为异步时钟,,需要先延时两拍,将时钟同步过来,取到start_flag信号,由start_flag信号结合clk_cnt、bps_cnt两个计数器取到rx_flag信号,随后在rx_flag高电平时计算clk_cnt以及bps_cnt两个信号。最后两个信号uart_done、uart_data则在串口发送模块有所体现。

实际上,uart_done在串口发送模块中也就是uart_en信号,而uart_data也就是发送模块中的uart_din信号。

复制代码
`timescale 1ns / 1ps
// Create Date: 2025/01/06 09:38:08
// Design Name: 
// Module Name: uart_recv
module uart_recv(
	input              sys_clk   ,    //50Mhz系统时钟
	input              sys_rst_n ,
	input              uart_rxd  ,   //接收到的数据
	
	output  reg [7:0]  uart_data  ,   //输出的并行数据
	output  reg        uart_done     //一帧信号接收完成

    );
    parameter  sys_freq = 50_000_000;
    parameter  uart_bps = 115_200;
    parameter  bps_cnt  = sys_freq/uart_bps - 1;//从0开始计算
    
    reg         uart_rxd_d0;
    reg         uart_rxd_d1;
    wire        start_flag;
     
    reg         rx_flag;
    reg  [15:0] clk_cnt;
    reg  [3:0]  rx_cnt;
    reg  [7:0]  rx_data;//中间变量存储提取到的每个位的数据来实现串口端的串并转换
    
    //由高电平向低电平的跳变(下降沿),相当于d1延时2个时钟周期;d0延时1个时钟周期
    //因此判断d1是否为高且d0是否为低即可。
    assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);
    
    //异步时钟同步化处理
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            begin
                uart_rxd_d0 <= 1'b1;
                uart_rxd_d1 <= 1'b1;
            end
        else 
             begin
                uart_rxd_d0 <= uart_rxd;
                uart_rxd_d1 <= uart_rxd_d0;
             end
    end
  
    //rx_flag
	always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            rx_flag <= 1'b0;
        else if(start_flag == 1'b1)
            rx_flag <= 1'b1;
        else if((rx_cnt == 4'd9) && (clk_cnt == bps_cnt/2 - 1'b1)) 
            //为监测到下一帧数据的起始位留半个周期的时间
            rx_flag <= 1'b0;
        else 
        	rx_flag <= 1'b1;
    end
   //clk_cnt
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            clk_cnt <= 16'd0;
        else if(rx_flag == 1'b1)
//            begin   //效果相同否?
//                if(clk_cnt == uart_bps)
//                    clk_cnt <= 16'd0; 
//                else 
//                    clk_cnt <= clk_cnt + 16'd1;
//            end
              begin                               
                  if(clk_cnt < uart_bps - 16'd1)         
                     clk_cnt <= clk_cnt + 16'd1;           
                  else                            
                      clk_cnt <= 16'd0; 
              end                                                                     
        else 
            clk_cnt <= 16'd0;  
    end
    
    //rx_cnt
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            rx_cnt <= 4'd0;
        else if(rx_flag == 1'b1)
            begin
                if(clk_cnt == uart_bps - 16'd1)
                    rx_cnt <= rx_cnt + 4'd1;
                else 
                    rx_cnt <= rx_cnt;
            end
        else
            rx_cnt <= 4'd0;
    end
    //rx_data
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            rx_data <= 8'd0;
        else if((rx_flag == 1'b1)&&(clk_cnt == uart_bps/2)) 
            begin
                case(rx_cnt)//数据的串转并_uart_rxd是异步信号
                            //所以要需要两拍之后的信号uart_rxd_d1。
                    4'd1: rx_data[0] <= uart_rxd_d1;
                    4'd2: rx_data[1] <= uart_rxd_d1;
                    4'd3: rx_data[2] <= uart_rxd_d1;
                    4'd4: rx_data[3] <= uart_rxd_d1;
                    4'd5: rx_data[4] <= uart_rxd_d1;
                    4'd6: rx_data[5] <= uart_rxd_d1;
                    4'd7: rx_data[6] <= uart_rxd_d1;
                    4'd8: rx_data[7] <= uart_rxd_d1;
                endcase
            end
    end
    
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
         if(!sys_rst_n)
             begin
             	 uart_data <= 8'd0;
             	 uart_done <= 1'd0;
             end
         else if(rx_cnt == 4'd9)
             begin                       
             	 uart_data <= rx_data;    
         	     uart_done <= 1'd1;    
             end   
          else
             begin                 
             	 uart_data <= 8'd0;  
             	 uart_done <= 1'd0;  
             end                               
    end
endmodule

以下:如何在程序中捕获信号的高低电平:(可实现异步时钟的同步处理以及信号的边沿检测)

d0 <= 1'b1;

d1 <= 1'b1;

d0 <= uart_rxd;

d1 <= d0;

也就是说,在初始状态时将d0、d1拉高,通过两次打拍将d0延时1个时钟周期,d1延时2个时钟周期,从而根据d0和d1的状态,设置wire型的标志位flag来捕获需要的高低电平。

以下,是是串口发送端的波形图,整体过程和接收端基本类似,只需要进行某些变量名称的修改即可,但需要注意,接收到的数据实际上是串口接收端的发送的数据(uart_data);uart_done信号也在发送端检测边沿电平过程中以uart_en的形式完成了两次打拍延迟。

复制代码
`timescale 1ns / 1ps
// Create Date: 2025/01/06 14:50:27
// Design Name: 
// Module Name: uart_send

module uart_send(
	input         sys_clk   ,    //50Mhz系统时钟  
	input         sys_rst_n ,                 
	output  reg   uart_txd  ,   //准备发送的数据      
	                                          
	input         uart_en  ,       
	input   [7:0] uart_din     //从发送模块接收到的数据    

    );
    
    parameter  sys_freq = 50_000_000;
    parameter  uart_bps = 115_200;
    parameter  bps_cnt  = sys_freq/uart_bps - 1;//从0开始计算
    
    reg          uart_en_d0;
    reg          uart_en_d1; 
    reg          tx_flag;
    reg  [15:0]  clk_cnt;
    reg  [3:0]   tx_cnt; 
    reg  [7:0]   tx_data;//中间变量存储提取到的每个位的数据来实现串口端的串并转换
   
    wire         en_flag;
    //由高电平向低电平的跳变(下降沿),相当于d1延时2个时钟周期;d0延时1个时钟周期
    //因此判断d1是否为高且d0是否为低即可。
    assign  en_flag = (~uart_en_d1) & uart_en_d0;
    
    //边沿信号检测,检测上升沿
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            begin
                uart_en_d0 <= 1'b1;
                uart_en_d1 <= 1'b1;
            end
        else 
             begin
                uart_en_d0 <= uart_en;
                uart_en_d1 <= uart_en_d0;
             end
    end
    
    //tx_data
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
           tx_data <= 8'd0;          
        else if(en_flag == 1'b1) 
           tx_data <= uart_din;
        else 
           tx_data <= tx_data;
    end
    
    //tx_flag
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
     	    tx_flag <= 1'b0;
     	else if(en_flag == 1'b1)
     	    tx_flag <= 1'b1;
        else if((tx_cnt == 4'd9) && (clk_cnt == bps_cnt/2 - 1))
            tx_flag <= 1'b0;
        else 
            //tx_flag <= tx_flag; //这两句话在这里作用相同否?
            tx_flag <= 1'b1;
    end
    
    //clk_cnt
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            clk_cnt <= 16'd0;
        else if(tx_flag == 1'b1)
//            begin 
//                if(clk_cnt == uart_bps)
//                    clk_cnt <= 16'd0; 
//                else 
//                    clk_cnt <= clk_cnt + 16'd1;
//            end
              begin                               
                  if(clk_cnt < uart_bps - 16'd1)         
                     clk_cnt <= clk_cnt + 16'd1;           
                  else                            
                      clk_cnt <= 16'd0; 
              end                                                                     
        else 
            clk_cnt <= 16'd0;  
    end
    
     //tx_cnt
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            tx_cnt <= 4'd0;
        else if(tx_flag == 1'b1)
            begin
                if(clk_cnt == uart_bps - 16'd1)
                    tx_cnt <= tx_cnt + 4'd1;
                else 
                    tx_cnt <= tx_cnt;
            end
        else
            rx_cnt <= 4'd0;

    end
    
     //uart_txd
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            uart_txd <= 1'b1;
        else if((tx_flag == 1'b1)&&(clk_cnt == 16'd0)) 
            begin
                case(tx_cnt)//数据并转串,首先判断起始位,赋值低电平;
                            //随后将tx_data一次赋值给输出端口uart_txd;
                            //最后注意一帧数据的停止位,赋值高电平。
                    4'd0: uart_txd <= 1'b0;
                    4'd1: uart_txd <= tx_data[0];
                    4'd2: uart_txd <= tx_data[1];
                    4'd3: uart_txd <= tx_data[2];
                    4'd4: uart_txd <= tx_data[3];
                    4'd5: uart_txd <= tx_data[4];
                    4'd6: uart_txd <= tx_data[5];
                    4'd7: uart_txd <= tx_data[6];
                    4'd8: uart_txd <= tx_data[7];
                    4'd9: uart_txd <= 1'b1;
                endcase
            end
    end

endmodule

以下是顶层文件,分别将串口发送端和接收端两部分程序例化。

复制代码
`timescale 1ns / 1ps
// Create Date: 2025/01/06 15:49:15
// Design Name: 
// Module Name: top_uart

module top_uart(
	input    sys_clk   ,    //50Mhz系统
	input    sys_rst_n ,             
	input    uart_rxd  ,   //接收到的数据
	output   uart_txd      //准备发送的数据 
	
    );
    wire  [7:0]  uart_data;
    wire         uart_done;
    
    //串口接收模块的例化:
    uart_recv  uart_recv_u(
	
	    .sys_clk    (sys_clk   )  ,    //50Mhz系统时钟
	    .sys_rst_n  (sys_rst_n )  ,
	    .uart_rxd   (uart_rxd  )  ,   //接收到的数据

	    .uart_data  (uart_data )  ,   //输出的并行数据
	    .uart_done  (uart_done )     //一帧信号接收完成
    );
   
//     wire         uart_en ;
//     wire  [7:0]  uart_din;
    //串口发送模块的例化;
    uart_send  uart_send_u(
	
	    .sys_clk   (sys_clk  )   ,    //50Mhz系统时钟  
	    .sys_rst_n (sys_rst_n)   ,                 
	    .uart_txd  (uart_txd )   ,   //准备发送的数据      
	                   
	    .uart_en   (uart_done )  ,       
	    .uart_din  (uart_data )      //从发送模块接收到的数据    
    );

endmodule

以下是对应程序的RTL视图:

另注意1:

打两拍的异步时钟的同步处理,打一拍是为了将异步时钟转为同步时钟,如上本例子是将波特率时钟(115200bps)转换为系统时钟50Mhz,打两拍则是为了消除亚稳态(0 1之间的状态)。

参考连接:【Chips】跨时钟域的亚稳态处理、为什么要打两拍不是打一拍、为什么打两拍能有效?_跨时钟域为什么打两拍-CSDN博客

另注意2:

为什么在程序中最后1位的停止位需要延时半个周期而不是一个完整的周期?也就是发送模块的如下程序:

复制代码
always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
     	    tx_flag <= 1'b0;
     	else if(en_flag == 1'b1)
     	    tx_flag <= 1'b1;
        else if((tx_cnt == 4'd9) && (clk_cnt == bps_cnt/2 - 1))//停止位只有半个波特率周期
            tx_flag <= 1'b0;
        else 
            //tx_flag <= tx_flag; //
            tx_flag <= 1'b1;
    end

首先延时半个周期是为了给下一帧数据接收时检测起始位留够时间,在数据回环过程中不会出现太大问题,但是如果单独调用串口的发送模块(或者接收模块)时就会出现问题,在单独调用发送模块时,因为停止位只有半个周期,所以发送1帧数据过程总会先于上位机正常接收而提前结束,造成错误。

所以选择将停止位周期设置为1个时钟周期,但是实际上位机的波特率和实际计算的波特率仍有微小误差,在将停止位设置为1个时钟周期时,上位机(串口助手)的波特率实际会和自己设置的波特率(如9600,115200等)存在或多或少的误差,因此需要将停止位周期设置为1/2--1之间,和上位机什么时候检测到1帧数据的停止位有关。

另附加上视频中给出得数据环回模块的程序:

复制代码
`timescale 1ns / 1ps
// Create Date: 2025/01/07 10:50:37
// Intrduction:  将串口接收到的数据进行缓存,消除上位机和串口模块之间的时间误差;
// Module Name: uart_loop

module uart_loop(
    input              sys_clk    ,    //50Mhz系统时
    input              sys_rst_n  , 
                 
    input              recv_done  ,   //接收完成信号  
    input        [7:0] recv_data  ,   //接收到的数据                                             
    input              tx_busy    ,
    
    output  reg        send_en    ,   
    output  reg  [7:0] send_data     //待发送数据
    );
    
    reg    recv_done_d0  ;
    reg    recv_done_d1  ;
    reg    tx_ready      ;
    
    wire   recv_done_flag;  //捕获上升沿
    
    assign  recv_done_flag =  (~recv_done_d0) & recv_done_d1;
    //打两拍实现边沿检测
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
        if(!sys_rst_n)
            begin
                recv_done_d0 <= 1'b0;
                recv_done_d1 <= 1'b0;
            end
         else 
             begin                    
                 recv_done_d0 <= recv_done;
                 recv_done_d1 <= recv_done_d0;
             end                                                    
    end
    
    //判断接收完成信号,并在串口发送模块空闲时发送是使能信号
    always@(posedge sys_clk or negedge sys_rst_n)
    begin
         if(!sys_rst_n)
             begin
                tx_ready  <= 1'b0;
                send_en   <= 1'b0;  
                send_data <= 8'd0;
             end
          else
              begin
                  if(recv_done_flag)   
                      begin
                          tx_ready  <= 1'b1;     
                          send_en <= 1'b0;       
                          send_data <= recv_data;                      
                      end     
                  else if(tx_ready&&(~tx_busy))
                      begin
                          tx_ready <= 1'b0;
                          send_en  <= 1'b1;
                      end
              end
    end   
       
endmodule

再者,vivado中串口默认波特率115200bps,在调试过程需要注意,如哦需要修改可参考下链接:

https://zhuanlan.zhihu.com/p/633150036

相关推荐
LK_0731 分钟前
【Open3D】Ch.3:顶点法向量估计 | Python
开发语言·笔记·python
饮浊酒41 分钟前
Python学习-----小游戏之人生重开模拟器(普通版)
python·学习·游戏程序
li星野41 分钟前
打工人日报#20251011
笔记·程序人生·fpga开发·学习方法
摇滚侠44 分钟前
Spring Boot 3零基础教程,yml配置文件,笔记13
spring boot·redis·笔记
QT 小鲜肉1 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装 anaconda 及其相关终端命令行
linux·笔记·深度学习·学习·ubuntu·学习方法
QT 小鲜肉1 小时前
【个人成长笔记】在Ubuntu中的Linux系统安装实验室WIFI驱动安装(Driver for Linux RTL8188GU)
linux·笔记·学习·ubuntu·学习方法
急急黄豆1 小时前
MADDPG学习笔记
笔记·学习
BullSmall1 小时前
《道德经》第十七章
学习
Chloeis Syntax2 小时前
栈和队列笔记2025-10-12
java·数据结构·笔记·
知识分享小能手2 小时前
微信小程序入门学习教程,从入门到精通,项目实战:美妆商城小程序 —— 知识点详解与案例代码 (18)
前端·学习·react.js·微信小程序·小程序·vue·前端技术