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

相关推荐
nuise_5 分钟前
李宏毅机器学习课程笔记03 | 类神经网络优化技巧
笔记·神经网络·机器学习
fnd_LN35 分钟前
HTML学习笔记记录---速预CSS(1) 选择器类型
css·笔记·学习·html
异步时刻1 小时前
Avalonia 入门笔记(零):概述
笔记·.net
viperrrrrrrrrr71 小时前
大数据学习(33)-spark-transformation算子
大数据·hive·学习·spark·mapreduce
黄交大彭于晏1 小时前
本地视频进度加入笔记+根据进度快速锁定视频位置
笔记·音视频
Xudde.1 小时前
HTML中meta的用法
java·前端·javascript·笔记·html
LiuIleCPP_Golang1 小时前
【2025 Rust学习 --- 15 迭代器的消耗】
学习·rust
陌然。。2 小时前
【39. 组合总和 中等】
数据结构·c++·笔记·算法·leetcode
私人珍藏库2 小时前
《废品机械师抢先版》V0.7.3.b776官方中文学习版
学习
morning_judger2 小时前
【Python学习系列】数据类型(二)
开发语言·python·学习