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

这篇具有很好参考价值的文章主要介绍了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核生成特定频率的波形。文章来源地址https://www.toymoban.com/news/detail-459712.html

到了这里,关于FPGA之手把手教你写串口协议解析(STM32与FPGA数据互传)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • QT初体验:手把手带你写一个自己的串口助手

    本文记录一下用QT Creator 写一个基本功能齐全的串口助手的过程,整个工程只有几百行代码,跟着做下来对新手来说可以更快了解整个QT项目的开发过程和一些常用控件的使用方法。对新手学习QT能增强信心,话不多说,正文开始 先看成品: (1) 创建QMainWindow工程。这一步就不

    2024年02月05日
    浏览(62)
  • 正则表达式详解(零基础教学,手把手教你写正则)

    本篇文章将从零讲解什么是正则表达式,以及正则表达式的规则、在python中的应用,用通俗易懂的描述方式进行零基础级别的讲解,尽量做到全网最全讲解,力求最高质量文章,欢迎关注!点击目录可直接进行相关位置跳转。 目录: 什么是正则? 为什么需要正则? 元字符

    2023年04月08日
    浏览(43)
  • [Kotlin]手把手教你写一个安卓APP(第一章注册登录)

    开发软件:Android Studio 1.创建项目默认选择Empty Activity                                                                      点击Next  2.生成项目设置包名选择开发语言(这里我用的是kotlin)  在生成项目后我们要做的就是添加需要的配置打开我们的app目录下的 buil

    2023年04月23日
    浏览(81)
  • 【Java】手把手教你写学生信息管理系统(窗口化+MYSQL)

                (本项目使用到了数据库的可视化软件DataGrip,需要同学们自行下载并配置环境) 首先我们需要在DataGrip中建立一个student的框架                                                         然后建立一个studenttable表                   

    2024年02月04日
    浏览(40)
  • 【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 前几天瑶瑶子学习了Go语言的基础语法知识,那么今天我们就写个

    2024年02月06日
    浏览(54)
  • 手把手教你写代码——基于控制台的通讯录管理系统(单表)

    本栏目专为入门java学习者设计的一些简单的入门项目 本项目为简单的基于控制台的通讯录管理系统,所需要的环境仅仅为jdk以及mysql(版本不限)!只有一个简单的eclipse软件以及我们的mysql可视化工具(视频使用navicat) 本项目数据库表仅有一个,单表操作,方便学习! 本项

    2024年02月15日
    浏览(45)
  • 数据结构:线性表————顺序表的实现、项目和OJ题目(手把手教你写代码)

    🌈 个人主页: 小新_- 🎈个人座右铭:“成功者不是从不失败的人,而是从不放弃的人!”🎈 🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝 🏆所属专栏:  话说那些与C++的爱恨情仇   欢迎订阅,持续更新中~~~                                           ✨让小新带着你

    2024年04月16日
    浏览(109)
  • 基于STM32F103RCT6之手把手教你写智能家居项目(2)

            上一节我们简述了智能家居项目,实现了点灯的相关代码编写,还有WIFI模块的固件烧录。 连接什么平台:         我们想要远程控制家具的开关和获取家中的状态,少不了一个可以传输数据的云平台。我认为易监控是一个简单好用的云平台。 怎么连接平台:

    2024年02月20日
    浏览(162)
  • 手把手教你移植 tinyriscv 到FPGA上

    我是 雪天鱼 ,一名FPGA爱好者,研究方向是FPGA架构探索和数字IC设计。 关注公众号【集成电路设计教程】,获取更多学习资料,并拉你进“ IC设计交流群 ”。 QQ IC设计交流群 群号: 866169462 。 所用开发板:正点原子达芬奇FPGA开发板 芯片型号:Xilinx Artix-7 35T tinyriscv 官方库链

    2023年04月17日
    浏览(46)
  • 手把手教你FreeRTOS源码解析(一)——内存管理

    FreeRTOS中一共有5种内存分配的方法,分别在文件heap_1.c,heap_2.c, heap_3.c,heap_4.c,heap_5.c种。 虽然标准C库中的 malloc()和 free()也可以实现动态内存管理,但是它有以下缺陷: 1、在小型嵌入式系统种效率不高。 2、线程不安全。 3、具有不确定性,每次执行的时间不同。 4、会导致内

    2024年02月07日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包