您现在的位置是:首页 >学无止境 >FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)网站首页学无止境

FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)

技术小董 2024-06-17 10:14:52
简介FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)


博主的念叨

最近趁热打铁做了一个关于STM32与FPGA通信并且控制高速DA模块产生不同频率信号的正弦波、方波、三角波和锯齿波的项目,从中收获到了很多东西,也踩了一些雷和坑,将分为几篇文章将整个过程分享出来。

这一次准备分享的是对串口数据的解析和赋值。解析的数据由STM32发出,通过串口连接至FPGA开发板的串口上,通过串口接收模块接收到数据后在回环模块中将数据进行处理,最终将处理的数据送到FPGA的串口发送模块中并将数据反馈回STM32中。

本文参考正点原子EP4CE系列开发板的源码,做了部分修改


一、任务介绍

1、本文目标

实现STM32和FPGA的串口通信,并将STM32传输过来的频率信息和波形信息解析存入定义的reg变量中。后续其他外设需要调用频率信息或者波形信息只需要实例化此串口模块即可。

2、设计思路

根据设计需求,需要对串口数据进行接收解析,同时需要把解析的数据传送给内存单元以及串口发送单元。其中发送单元的作用为方便调试观察数据是否被成功解析,解析的数据传输给内存单元单独设置一个v文件进行赋值。底层文件设置好以后设置顶层文件,实例化底层v文件,其中输入为时钟线、复位信号以及串口输入信号,输出为解析出的数据及串口输出信号。

3、设计注意事项

1、串口通信属于异步通信方式,因此出现信号是不确定的,为了防止亚稳态的产生,需要对信号打拍子避免亚稳态的产生。

2、一般采集数据在计数到数据位中间时采集较为稳定

3、串口波特率要对的上,否则无法进行正常通信

4、请事先了解一下串口通信协议

5、本文串口通信协议是 EA 频率高位 频率中位 频率低位 波形 AE,其中EA代表起始标志,AE代表停止标志。频率高位代表频率/65536,中位代表%65536/256,低位代表%65526%256

二、设计代码

1.串口接收代码

//****************************************Copyright (c)***********************************//                             
//----------------------------------------------------------------------------------------
// File name:           uart_recv
// Last modified Date:  2023/4/27 15:02:00
// Last Version:        V1.1
// Descriptions:        UART串口接收模块
//----------------------------------------------------------------------------------------
// Created by:          技术小董
// Created date:        2023/4/27 15:02:00
// Version:             V1.0
// Descriptions:        The original version
//----------------------------------------------------------------------------------------
//****************************************************************************************//
module uart_recv(
    input			     sys_clk,                  //系统时钟
    input              sys_rst_n,                //系统复位,低电平有效
    
    input              uart_rxd,                 //UART接收端口
    output  reg        uart_done,                //接收一帧数据完成标志
    output  reg        rx_flag,                  //接收过程标志信号
	output  reg        start_status,             //开始接收标志
	output  reg        stop_status,              //停止接收标志   
    output  reg [ 3:0] rx_cnt,                   //接收数据计数器
    output  reg [ 7:0] rxdata,
    output  reg [ 7:0] uart_data                 //接收的数据
    );   
//parameter define
parameter  CLK_FREQ = 50000000;                  //系统时钟频率
parameter  UART_BPS = 9600;                      //串口波特率
localparam  BPS_CNT  = CLK_FREQ/UART_BPS;        //为得到指定波特率,需要对系统时钟计数BPS_CNT次                                                                                 
//reg define
reg        uart_rxd_d0;
reg        uart_rxd_d1;
reg [15:0] clk_cnt;                              //系统时钟计数器
//wire define
wire       start_flag;
//*****************************************************
//**                    main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign  start_flag = uart_rxd_d1 & (~uart_rxd_d0);    
//对UART接收端口的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if (!sys_rst_n) begin 
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;          
    end
    else begin
        uart_rxd_d0  <= uart_rxd;                   
        uart_rxd_d1  <= uart_rxd_d0;
    end   
end

//当脉冲信号start_flag到达时,进入接收过程           
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                                  
        rx_flag <= 1'b0;
    else begin
        if(start_flag)                          //检测到起始位
            rx_flag <= 1'b1;                    //进入接收过程,标志位rx_flag拉高
        //计数到停止位中间时,停止接收过程
        else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))begin
            rx_flag <= 1'b0;                    //接收过程结束,标志位rx_flag拉低
		  end	
        else
            rx_flag <= rx_flag;
    end
end

//进入接收过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                             
        clk_cnt <= 16'd0;                                  
    else if ( rx_flag ) begin                   //处于接收过程
        if (clk_cnt < BPS_CNT - 1)
            clk_cnt <= clk_cnt + 1'b1;
        else
            clk_cnt <= 16'd0;               	//对系统时钟计数达一个波特率周期后清零
    end
    else                              				
        clk_cnt <= 16'd0;						//接收过程结束,计数器清零
end

//进入接收过程后,启动接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                             
        rx_cnt  <= 4'd0;
    else if ( rx_flag ) begin                   //处于接收过程
        if (clk_cnt == BPS_CNT - 1)				//对系统时钟计数达一个波特率周期
            rx_cnt <= rx_cnt + 1'b1;			//此时接收数据计数器加1
        else
            rx_cnt <= rx_cnt;       
    end
	 else
        rx_cnt  <= 4'd0;						//接收过程结束,计数器清零
end

//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin 
    if ( !sys_rst_n)  
        rxdata <= 8'd0;                                     
    else if(rx_flag)                            //系统处于接收过程
        if (clk_cnt == BPS_CNT/2) begin         //判断系统时钟计数器计数到数据位中间
            case ( rx_cnt )
             4'd1 : rxdata[0] <= uart_rxd_d1;   //寄存数据位最低位
             4'd2 : rxdata[1] <= uart_rxd_d1;
             4'd3 : rxdata[2] <= uart_rxd_d1;
             4'd4 : rxdata[3] <= uart_rxd_d1;
             4'd5 : rxdata[4] <= uart_rxd_d1;
             4'd6 : rxdata[5] <= uart_rxd_d1;
             4'd7 : rxdata[6] <= uart_rxd_d1;
             4'd8 : rxdata[7] <= uart_rxd_d1;   //寄存数据位最高位
             default:;                                    
            endcase
        end
        else 
            rxdata <= rxdata;
    else
        rxdata <= 8'd0;
end

//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n) begin
        uart_data <= 8'd0;                               
        uart_done <= 1'b0;
		  start_status <= 1'b0;
		  stop_status <= 1'b0;
    end
    else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时
        case(rxdata)
		      8'hea : begin 
				    uart_data <= 8'haa;             //回复收到
					 start_status <= 1'b1;           //开始处理数据
		          end			 
				8'hae : begin
				    uart_data <= 8'hbb;             //回复结束
					 stop_status <= 1'b1;            //结束处理数据
					 end
				default:uart_data <= rxdata;
		  endcase	
        uart_done <= 1'b1;                      //并将接收完成标志位拉高
    end
    else begin
        uart_data <= 8'd0;                                   
        uart_done <= 1'b0; 
		  start_status <= 1'b0;
		  stop_status <= 1'b0;
    end    
end
endmodule	

输入总共定义三个变量,其中uart_rxd代表串口输入接入。uart_done的意思是接收完了一帧数据,会拉高一个时钟周期,需要注意的是在本设计之中接收完了一个数据后就需要对这个数据进行处理同时传给发送端口进行数据的发送。rx_flag信号代表串口处于接收状态,start_status代表我们定义的协议数据传输开始,stop_status代表协议数据传输结尾。rx_cnt为4位的数据,分别代表串口的10个过程,开始信号,八个数据位,停止信号。rxdata为此次接收到的数据,uart_data代表要输出给其它需要实例化此v文件的串口当前数据。

定义了两个全局变量和一个本地变量。方便实例化时更改波特率。定义的uart_rxd_d0和uart_rxd_d1两个变量是为了抑制突然输入进来的串口数据下降沿,通过打拍子的方式让输入信号延时两拍,避免亚稳态产生,同时也为了后面检测下降沿做准备。clk_cnt为时钟计数器,贯穿整个计数周期中。定义的wire变量start_flag,用来检测串口输入的下降沿信号。需要注意的是,当串口开始发送数据时会拉低串口信号,这样当接收机检测到串口输入引脚电平被拉低后,也即产生了下降沿以后就代表串口已经准备要开始发送数据了。

检测下降沿的通用代码:

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'b0;
        uart_rxd_d1 <= 1'b0;          
    end
    else begin
        uart_rxd_d0  <= uart_rxd;                   
        uart_rxd_d1  <= uart_rxd_d0;
    end   
end

此代码的意义在于当出现下降沿以后,start_flag会被拉高一个时钟周期。当串口一直处于高电平时,打了一拍的信号和打了两拍的信号都为高电平,所以srat_flag=1&0也就是0。当信号产生下降沿以后,d0信号为低电平,此时d1信号还处于上一个周期的电平高电平,所以start_flag=1&1=1,故此刻start_flag被拉高。过了一个时钟周期后,d1和d0信号都为低电平了,因此start_flag为低电平,相当于当信号下降沿来临后,延时一个周期将产生start_flag信号的上升沿。

在第二个always里面,如果判断start_flag为高电平,即检测到串口线下降沿产生,则将rx_flag置1,表示此刻串口的接收端正在工作中,不允许其他数据进入。else if里面的判断语句含义是当rx_cnt计数到9且单次计数到波特率计数的一半时,代表串口接收过程结束,rx_flag拉低代表串口处于空闲状态,可进行下一次数据的接收。在均不满足条件的情况下,rx_flag变量不变。

第三个always里面是对波特率进行计数,前文提到了FPGA主频为50MHz,BPS_CNT=主频除以波特率,故当我们用主频从0计数到BPS_CNT-1就代表一个波特率周期。

第四个always是对波特率周期进行计数,当clk_cnt到达BPS_CNT-1时,对应的rx_cnt,也就是串口对应的位标识+1,当rx_flag==0也就是忙碌状态结束后,此位标识将会自动归零。

第五个always是对数据进行寄存,在else if语句中,如果计数到波特率的中间时期,将根据rx_cnt对数据进行赋值,当rx_cnt为0的时候,代表处于起始位或者没有开始串口传输,因此不对任何数据进行处理。当rx_cnt非零后,根据不同的rx_cnt的值,将当前接收到的二进制数据赋值给rxdata对应的位,这样当rx_flag没有归零之前,rxdata里面是寄存了串口发送的数据。

第六个always是对数据进行处理。当rx_cnt等于9的时候,也就是进入到最后停止位截至之前判断的这一段时间,对rxdata数据进行判断。此刻如果rxdata数据为0xea,则代表是我们协议的开头八位,故此刻将uart_data,也就是转发给TX的数据赋值为aa,代表接收到了协议开头,同时拉高start_status数据。如果rxdata数据为0xae,就代表接收到了协议末尾,同时拉高stop_status数据。若不属于协议开头也不属于协议结尾,则将收到的串口数据直接赋值给TX发送。

那么根据这个原理,我们可以任意定义协议开头或者协议结尾,相对应拉高开始信号和结束信号,在下面的v文件中将会讲到开始信号和结束信号的作用,此处我们只需要记得对这个数据进行判断。

2.串口发送代码

module uart_send(
    input	      sys_clk,                  //系统时钟
    input         sys_rst_n,                //系统复位,低电平有效
    
    input         uart_en,                  //发送使能信号
    input  [7:0]  uart_din,                 //待发送数据
    output        uart_tx_busy,             //发送忙状态标志 
    output        en_flag     ,
    output  reg   tx_flag,                  //发送过程标志信号
    output  reg [ 7:0] tx_data,             //寄存发送数据
    output  reg [ 3:0] tx_cnt,              //发送数据计数器
    output  reg   uart_txd                  //UART发送端口
    );
    
//parameter define
parameter  CLK_FREQ = 50000000;            //系统时钟频率
parameter  UART_BPS = 9600;                //串口波特率
//其余本地参量
localparam  BPS_CNT  = CLK_FREQ/UART_BPS;   //为得到指定波特率,对系统时钟计数BPS_CNT次
//reg define
reg        uart_en_d0; 
reg        uart_en_d1;  
reg [15:0] clk_cnt;                           //系统时钟计数器
//*****************************************************
//**                    main code
//*****************************************************
//在串口发送过程中给出忙状态标志
assign uart_tx_busy = tx_flag;
//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;
//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        uart_en_d0 <= 1'b0;                                  
        uart_en_d1 <= 1'b0;
    end                                                      
    else begin                                               
        uart_en_d0 <= uart_en;                               
        uart_en_d1 <= uart_en_d0;                            
    end
end
//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程          
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                                  
        tx_flag <= 1'b0;
        tx_data <= 8'd0;
    end 
    else if (en_flag) begin                 //检测到发送使能上升沿                      
            tx_flag <= 1'b1;                //进入发送过程,标志位tx_flag拉高
            tx_data <= uart_din;            //寄存待发送的数据
        end
                                            //计数到停止位结束时,停止发送过程
        else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16))) begin                                       
            tx_flag <= 1'b0;                //发送过程结束,标志位tx_flag拉低
            tx_data <= 8'd0;
        end
        else begin
            tx_flag <= tx_flag;
            tx_data <= tx_data;
        end 
end
//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                             
        clk_cnt <= 16'd0;                                  
    else if (tx_flag) begin                 //处于发送过程
        if (clk_cnt < BPS_CNT - 1)
            clk_cnt <= clk_cnt + 1'b1;
        else
            clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
    end
    else                             
        clk_cnt <= 16'd0; 				    //发送过程结束
end
//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)                             
        tx_cnt <= 4'd0;
    else if (tx_flag) begin                 //处于发送过程
        if (clk_cnt == BPS_CNT - 1)			//对系统时钟计数达一个波特率周期
            tx_cnt <= tx_cnt + 1'b1;		//此时发送数据计数器加1
        else
            tx_cnt <= tx_cnt;       
    end
    else                              
        tx_cnt  <= 4'd0;				    //发送过程结束
end
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n)  
        uart_txd <= 1'b1;        
    else if (tx_flag)
        case(tx_cnt)
            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;         //停止位
            default: ;
        endcase
    else 
        uart_txd <= 1'b1;                   //空闲时发送端口为高电平
end
endmodule	          

输入有四个变量,分别是系统时钟,系统复位,发送使能信号以及发送数据。其中发送使能信号是触发信号,也需要考虑亚稳态的问题,故与串口接收模块类似,需要进行打拍去除亚稳态。输出变量有uart_tx_busy,为高电平时代表此时正处于串口发送状态。en_flag为捕获上升沿,类比于接收模块中的start_flag。tx_flag代表串口正在发送中,通过assign语句赋值给busy信号。tx_data为发送的数据,tx_cnt为计数器,uart_txd为串口发送线。

与接收模块类似的地方就不提了,需要注意的是捕获上升沿与下降沿的区别在于,前者是对d1信号进行取反再与d0相与,后者是对d0信号取反再与d1相与,不同的与方法将会检测到不同的边沿。

第二个always里面通过判断发送使能是否出现高电平,执行相对应的功能。如果检测到出现高电平,则将tx_flag赋值为高电平,同时通过assign语句赋值给uart_tx_busy,提示当前串口正在发送数据。通过tx_data寄存获得的串口发送数据,同样也有对停止位的判断,不过略有区别在于串口发送的停止位时间会略长一点,主要是为了保持发送的连续性。

第三个always里面是对波特率的计数。

第四个always是对位标志的计数。

第五个always是通过位标志的不同值将数据赋值给串口发送接口uart_txd,与串口接收略有区别的在于在位标志为0时赋值低电平给输出接口,原因在于从机接收主机发送的串口信息的判断依据是数据被拉低,故此处进行了拉低操作。

3.串口解析代码

//****************************************Copyright (c)***********************************//                             
//----------------------------------------------------------------------------------------
// File name:           uart_loop
// Last modified Date:  2023/4/27 15:02:00
// Last Version:        V1.1
// Descriptions:        UART串口数据处理模块
//----------------------------------------------------------------------------------------
// Created by:          技术小董
// Created date:        2023/4/27 15:02:00
// Version:             V1.0
// Descriptions:        The original version
//----------------------------------------------------------------------------------------
//****************************************************************************************//

module uart_loop(
    input	         sys_clk,                   //系统时钟
    input            sys_rst_n,                 //系统复位,低电平有效
     
    input            recv_done,                 //接收一帧数据完成标志
    input      [7:0] recv_data,                 //接收的数据
	 
	input            start_status,              //定义启动状态
	input            stop_status,               //定义停止状态
     
    input            tx_busy,                   //发送忙状态标志      
    output reg       send_en,                   //发送使能信号
    output reg [7:0] send_data,                 //待发送数据 
	output reg [23:0]freq,                      //频率数据
	output reg [7:0] wave                       //波形数据
    );
reg [7:0] message[0:3];                         //存储数据的地址
reg [3:0] message_count;
//reg define
reg recv_done_d0;
reg recv_done_d1;
reg recv_done_d2;
reg start_done_d0;
reg start_done_d1;
reg stop_done_d0;
reg stop_done_d1;
reg tx_ready;
//wire define
wire recv_done_flag;
wire start_done_flag;
wire stop_done_flag;
//*****************************************************
//**                    main code
//*****************************************************
//捕获recv_done上升沿,得到一个时钟周期的脉冲信号
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;
assign start_done_flag = (~start_done_d1) & start_done_d0;
assign stop_done_flag = (~stop_done_d1) & stop_done_d0;                                       
//对发送使能信号recv_done延迟两个时钟周期
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;
		  recv_done_d2 <= 1'b0;
    end                                                      
    else begin                                               
        recv_done_d0 <= recv_done;                               
        recv_done_d1 <= recv_done_d0; 
        recv_done_d2	<= recv_done_flag;	  
    end
end
//对发送使能信号recv_done延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        start_done_d0 <= 1'b0;                                  
        start_done_d1 <= 1'b0;
    end                                                      
    else begin                                               
        start_done_d0 <= start_status;                               
        start_done_d1 <= start_done_d0;                            
    end
end
//对停止处理信号stop_status延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin
        stop_done_d0 <= 1'b0;                                  
        stop_done_d1 <= 1'b0;
    end                                                      
    else begin                                               
        stop_done_d0 <= stop_status;                               
        stop_done_d1 <= stop_done_d0;                            
    end
end
//状态的切换
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n)begin
	     freq <= 24'd500_000;
		  wave <= 8'd0;
		  message_count <= 1'b0;
	 end
    else if(start_done_flag && message_count == 4'd0)
		  message_count <= 1'b1;	 
	 else if(stop_done_flag && message_count == 4'd5)begin
	     message_count <= 1'b0;
		  freq[23:16] <= message[0];
		  freq[15:8]  <= message[1];
		  freq[7:0]   <= message[2];
		  wave        <= message[3];       
	 end
	 else if(recv_done_flag && message_count)begin
	     message[message_count-1'b1] <= recv_data;
		  message_count <= message_count + 1'b1;
	 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_d2)begin            //检测串口接收到数据
            tx_ready  <= 1'b1;                  //准备启动发送过程
            send_en   <= 1'b0;
				case (message_count)
				  4'd2 : send_data <= freq[23:16];
				  4'd3 : send_data <= freq[15:8];
				  4'd4 : send_data <= freq[7:0];
				  default : send_data <= recv_data;             //寄存串口接收的数据
            endcase
        end
        else if(tx_ready && (~tx_busy)) begin   //检测串口发送模块空闲
            tx_ready <= 1'b0;                   //准备过程结束
            send_en  <= 1'b1;                   //拉高发送使能信号
        end
    end
end

endmodule 

串口数据处理模块总共有七个输入参量和四个输出参量。其中输入参量分别为系统时钟,系统复位。recv_done和recv_data是由串口接收模块传递过来的数据接收完毕标志和接收到的数据,start_status和stop_status是表明定义的协议开头和协议结束的标志。tx_busy是串口发哦那个模块传递过来的串口是否处于空闲状态的标志。输出参量中的send_en是传递给发送模块的发送使能标志,send_data是传递给发送模块的发送数据,freq是从协议中解析出的频率数据,wave是从协议中解析出的波形数据。

定义的reg变量里面message是定义的数组变量,分别存储频率的高位,中位,低位,以及波形数据,用作后面频率的赋值和波形的赋值。message_count是代表当前存储的是协议中第几-1位数据,不包括协议的开头和结尾。

下方定义的d0和d1变量其实都是为了打节拍避免亚稳态,tx_ready代表发送的中间变量。

第四个always语句是对频率信号和波形数据进行解析。当检测到start_done_flag数据为高电平且当前检测数据计数为0的时候,将message_count置1,代表数据传输即将开始。当检测到stop_done_flag数据为高电平且message_count为5时复位message_count且将采集到的数据赋值给freq和wave变量。在接下来的else if中每次对message_count数据进行判断,如果不为0的话通过赋值语句将当前接收到的数据赋值给message数组,当message_count==1时,将当前recv_data数据赋值给message[0],依次类推。

第五个always是当检测到串口接收到数据以后,先进入到启动发送过程,将send_en拉低,此时串口不发送数据。在这个阶段根据message_count的值将对应的频率数据发送给串口模块,这一段主要是为了验证我们采集到的频率数据是否正确。message_count==2,3,4正好对应freq的三个变量。wave数据由于只有一位,所以直接赋值即可。当要发送的数据被赋值好了以后,下一个时钟周期tx_reday为1时就会执行tx_ready=0,send_en=1的语句,即触发了串口发送。

4.顶层代码

module uart_loopback_top(
    input           sys_clk,            //外部50M时钟
    input           sys_rst_n,          //外部复位信号,低有效

    input           uart_rxd,           //UART接收端口
    output          uart_txd,           //UART发送端口
	output  [23:0]  freq,
	output  [7:0]   wave
    );

//parameter define
parameter  CLK_FREQ = 50000000;         //定义系统时钟频率
parameter  UART_BPS = 115200;           //定义串口波特率    
//wire define   
wire       uart_recv_done;              //UART接收完成
wire [7:0] uart_recv_data;              //UART接收数据
wire       uart_send_en;                //UART发送使能
wire [7:0] uart_send_data;              //UART发送数据
wire       uart_tx_busy;                //UART发送忙状态标志
wire       uart_start_status;
wire       uart_stop_status;
//*****************************************************
//**                    main code
//*****************************************************
//串口接收模块     
uart_recv #(                          
    .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
    .UART_BPS       (UART_BPS))         //设置串口接收波特率
u_uart_recv(                 
    .sys_clk        (sys_clk), 
    .sys_rst_n      (sys_rst_n),
	.start_status   (uart_start_status),              //定义启动状态
	.stop_status    (uart_stop_status),      //定义停止状态       
    .uart_rxd       (uart_rxd),
    .uart_done      (uart_recv_done),
    .uart_data      (uart_recv_data)
    );

//串口发送模块    
uart_send #(                          
    .CLK_FREQ       (CLK_FREQ),         //设置系统时钟频率
    .UART_BPS       (UART_BPS))         //设置串口发送波特率
u_uart_send(                 
    .sys_clk        (sys_clk),
    .sys_rst_n      (sys_rst_n),
     
    .uart_en        (uart_send_en),
    .uart_din       (uart_send_data),
    .uart_tx_busy   (uart_tx_busy),
    .uart_txd       (uart_txd)
    );
    
//串口环回模块    
uart_loop u_uart_loop(
    .sys_clk            (sys_clk),             
    .sys_rst_n          (sys_rst_n),           
   
    .recv_done          (uart_recv_done),   //接收一帧数据完成标志信号
    .recv_data          (uart_recv_data),   //接收的数据
	.start_status       (uart_start_status),              //定义启动状态
	.stop_status        (uart_stop_status),      //定义停止状态   
    .tx_busy            (uart_tx_busy),     //发送忙状态标志      
    .send_en            (uart_send_en),     //发送使能信号
    .send_data          (uart_send_data),    //待发送数据
	.freq               (freq),      //频率数据
	.wave               (wave)       //波形数据
    );
endmodule

在顶层文件中,除了定义输入的时钟和复位信号,还定义了uart_rxd和uart_txd。分别为串口输入和串口输出。另外还输出了频率信号和波形信号。如果仅仅是只用到了串口模块的话,可以去除掉freq和wave输出,只需要在顶层v文件里面定义reg变量即可,但是如果需要将处理的数据送入其它的模块中,就需要将freq和wave进行输出。本次设计是为了获得频率数据和波形数据,所以需要将其进行输出,不需要的话去除即可。

定义了CLK_FREQ和UART_BPS,根据主频和所需要的波特率可以一键修改。定义了各种中间变量进行调用。此外,实例化的模块并不一定需要输出所有的参数,因此可根据自己的需求输出模块参数。

在串口接收实例中,需要输入sys_clk,sys_rst_n和uart_rxd,输出start_status和stop_status以及recv_done和recv_data。这几个变量将会同步实例化到uart_loop模块中去作为输入变量,输出变量为uart_tx_busy,uart_send_en,uart_send_data,freq和wave。其中uart_tx_busy,uart_send_en,uart_send_data将会被实例化成uart_send模块的输入。

自此,就完成了顶层模块对于各底层模块的调用。


总结

有什么不懂的可以在下方留言,只要学会了方法,对于串口数据的解析会变得很简单。这个仅仅是对数据进行处理,后续会发一篇DDS信号发生器的总代码,告诉大家如何调用PLL和ROM核生成特定频率的波形。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。