FPGA串口接收解帧、并逐帧发送有效数据-2

FPGA串口接收解帧、并逐帧发送有效数据

工程实现的功能:FPGA串口接收到串口调试助手发来的数据,将其数据解帧。判断到正确的帧头和帧尾之后,将有效数据存入rx_data中;另一方面发送端将有效数据逐帧发送出去。

参考:正点原子官方FPGA串口通信实验

模块构成:

在原子哥的基础上改的代码。添加了接收状态机模块:rx_state_machine修改了串口发送模块:uart_send。其余部分代码基本不变(只加了例化,修改数据位宽)


本节接着上一章的内容来写,来写发送端的部分。实现功能:将数据打包发送,发送帧头+接收端解帧后的有效数据+帧尾。

假设发送端打包时,帧头是FF,帧尾是EE.

代码思路分析

首先,给出正点原子的代码

verilog 复制代码
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;                           //系统时钟计数器

//wire define
wire       en_flag;

//*****************************************************
//**                    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_en 为发送使能信号。uart_en 的上升沿将启动一次串口发送过程,将 uart_din 接口上的数据通过串口发送端口 uart_txd 发送出去。

tx_cnt是发送数据计数器,从0-9分别发送起始位、数据位、停止位。

我们要实现的功能是发送帧头+接收端解帧后的有效数据+帧尾。

所以只需要对这个代码进行简单的修改,即可满足功能。

首先,我加了一个计数器

verilog 复制代码
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n)        
		sent_cnt <= 4'd0;
	else if(tx_flag) begin		//处于发送过程时,开始计数。每个数都对应需要发送的数据
		if(sent_cnt < 4'd6)
			sent_cnt <= sent_cnt + 1'b1;
		else 
			sent_cnt <= 4'd0;
	end
end

reg [3:0] sent_cnt; //发送计数器,用来计数发送的数据是FF、data、EE

在原子哥的代码中,一个case语句,发送的是完整的一个字节(8bit)数据。但是现在,我想要一次发送帧头(1个字节)+数据(4个字节)+帧尾(1个字节)。所以,我用了两个case语句嵌套使用。

我外层的case语句,case (sent_cnt),判断发送的是第几个字节(如:第一个字节,发送帧头;第二个字节,发送数据高8位)

内层的case语句,则与原子哥相同,发送完整的一个字节(8bit)数据。

verilog 复制代码
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin        
    if (!sys_rst_n)  
        uart_txd <= 1'b1;        
    else if (tx_flag)
		case (sent_cnt)
			4'd0: uart_txd <= uart_txd;
			4'd1: begin
				case(tx_cnt)
					4'd0: uart_txd <= 1'b0;         //起始位 
					4'd1: uart_txd <= 1'b1;   //数据位最低位
					4'd2: uart_txd <= 1'b1;
					4'd3: uart_txd <= 1'b1;
					4'd4: uart_txd <= 1'b1;		//发送 帧头 FF
					4'd5: uart_txd <= 1'b1;
					4'd6: uart_txd <= 1'b1;
					4'd7: uart_txd <= 1'b1;
					4'd8: uart_txd <= 1'b1;   //数据位最高位
					4'd9: uart_txd <= 1'b1;         //停止位
					default: uart_txd <= uart_txd;
					endcase
			end
			4'd2: begin
				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
			end
			4'd3: begin
				case(tx_cnt)
				    4'd0: uart_txd <= 1'b0;         //起始位 
				    4'd1: uart_txd <= tx_data[8];   //数据位最低位
				    4'd2: uart_txd <= tx_data[9];
				    4'd3: uart_txd <= tx_data[10];
				    4'd4: uart_txd <= tx_data[11];
				    4'd5: uart_txd <= tx_data[12];
				    4'd6: uart_txd <= tx_data[13];
				    4'd7: uart_txd <= tx_data[14];
				    4'd8: uart_txd <= tx_data[15];   //数据位最高位
				    4'd9: uart_txd <= 1'b1;         //停止位
				    default: ;
				endcase
			end			
			4'd4: begin
				case(tx_cnt)
				    4'd0: uart_txd <= 1'b0;         //起始位 
				    4'd1: uart_txd <= tx_data[16];   //数据位最低位
				    4'd2: uart_txd <= tx_data[17];
				    4'd3: uart_txd <= tx_data[18];
				    4'd4: uart_txd <= tx_data[19];
				    4'd5: uart_txd <= tx_data[20];
				    4'd6: uart_txd <= tx_data[21];
				    4'd7: uart_txd <= tx_data[22];
				    4'd8: uart_txd <= tx_data[23];   //数据位最高位
				    4'd9: uart_txd <= 1'b1;         //停止位
				    default: ;
				endcase
			end		
			4'd5: begin
				case(tx_cnt)
				    4'd0: uart_txd <= 1'b0;         //起始位 
				    4'd1: uart_txd <= tx_data[24];   //数据位最低位
				    4'd2: uart_txd <= tx_data[25];
				    4'd3: uart_txd <= tx_data[26];
				    4'd4: uart_txd <= tx_data[27];
				    4'd5: uart_txd <= tx_data[28];
				    4'd6: uart_txd <= tx_data[29];
				    4'd7: uart_txd <= tx_data[30];
				    4'd8: uart_txd <= tx_data[31];   //数据位最高位
				    4'd9: uart_txd <= 1'b1;         //停止位
				    default: ;
				endcase
			end			
			4'd6: begin
				case(tx_cnt)
					4'd0: uart_txd <= 1'b0;         //起始位 
					4'd1: uart_txd <= 1'b0;   //数据位最低位
					4'd2: uart_txd <= 1'b1;
					4'd3: uart_txd <= 1'b1;
					4'd4: uart_txd <= 1'b1;		//发送 帧尾 EE
					4'd5: uart_txd <= 1'b0;
					4'd6: uart_txd <= 1'b1;
					4'd7: uart_txd <= 1'b1;
					4'd8: uart_txd <= 1'b1;   //数据位最高位
					4'd9: uart_txd <= 1'b1;         //停止位
					default: uart_txd <= uart_txd;
					endcase
			end		
		endcase
    else 
        uart_txd <= 1'b1;                   //空闲时发送端口为高电平
end

代码解释:发送过程标志信号tx_flag拉高时,进入发送状态。

首先case (sent_cnt)判断发送的是第几个字节

然后case(tx_cnt),发送完整的一个字节。

结果展示

verilog 复制代码
ila_1 ila_tx (
	.clk(sys_clk), // input wire clk

	.probe0(tx_flag), // input wire [0:0]  probe0  
	.probe1(din), // input wire [31:0]  probe1 
	.probe2(uart_txd), // input wire [0:0]  probe2 
	.probe3(tx_data), // input wire [31:0]  probe3 
	.probe4(tx_data[7:0]), // input wire [7:0]  probe4 
	.probe5(tx_data[15:8]), // input wire [7:0]  probe5 
	.probe6(tx_data[23:16]), // input wire [7:0]  probe6 
	.probe7(tx_data[31:24]) // input wire [7:0]  probe7
);

如上投入所示,发送帧头+接收端解帧后的有效数据+帧尾

  • tx_data_1是发送的有效数据

  • din 是接收端的有效数据。可以看出,这两个相同

  • uart_txd是FPGA串口发送的信号,由0和1组成,发送的是整个完整的数据(帧头+有效数据+帧尾)


各个模块的输入输出

发送端相对接收端简单、易理解

接下来我将所有模块的输入输出放下面,方便大家理解整体的框架

uart_loopback_top

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

    input           uart_rxd,           //UART接收端口
    output          uart_txd            //UART发送端口
    );

uart_recv

verilog 复制代码
module uart_recv(
    input			  sys_clk,                  //系统时钟
    input             sys_rst_n,                //系统复位,低电平有效
    
    input             uart_rxd,                 //UART接收端口
    output  reg       uart_done,                //接收一帧数据完成标志
    output  reg       rx_flag,                  //接收过程标志信号
    output  reg [ 3:0] rx_cnt,                  //接收数据计数器
    output  reg [ 7:0] rxdata,
    output  reg [7:0] uart_data,                 //接收的数据
	output  reg [31:0] rx_data
    );

rx_state_machine

verilog 复制代码
module rx_state_machine(
	input			sys_clk		,
	input			sys_rst_n	,
	
	input			uart_done	,		//接收一帧数据完成标志
	input	[7:0] 	uart_data	,		//串口接收到上位机的所有数据
	input			rx_flag		,

	output	reg		rx_vaild	,		//接收解帧有效数据
	output	reg		rx_error	,		//接收解帧错误信号
	output	reg [31:0]	rx_data				//接收有效数据
	
);

uart_send

verilog 复制代码
module uart_send(
    input	      sys_clk,                  //系统时钟
    input         sys_rst_n,                //系统复位,低电平有效
    
    input         uart_en,                  //发送使能信号
    input  [7:0]  uart_din,                 //待发送数据
    input  [31:0] din,                 
    output        uart_tx_busy,             //发送忙状态标志 
    output        en_flag     ,
    output  reg   tx_flag,                  //发送过程标志信号
    output  reg [ 31:0] tx_data,             //寄存发送数据
    output  reg [ 3:0] tx_cnt,              //发送数据计数器
    output  reg   uart_txd                  //UART发送端口
    );

uart_loop

verilog 复制代码
module uart_loop(
    input	         sys_clk,                   //系统时钟
    input            sys_rst_n,                 //系统复位,低电平有效
     
    input            recv_done,                 //接收一帧数据完成标志
    input      [31:0] recv_data,                 //接收的数据
     
    input            tx_busy,                   //发送忙状态标志      
    output reg       send_en,                   //发送使能信号
    output reg [31:0] send_data                  //待发送数据
    );

有几个可能出错的地方:我的发送端的数据高低位可能颠倒了。如果大家觉得确实发送数据高低位错了,请自行改正。

有好些信号我没有用到,但是在代码中我没有删除。

另外,数据环回的功能好像没有做好,我觉得还是数据位宽的原因。

相关推荐
fei_sun16 小时前
【Verilog】第一章作业
fpga开发·verilog
深圳市雷龙发展有限公司longsto17 小时前
基于FPGA(现场可编程门阵列)的SD NAND图片显示系统是一个复杂的项目,它涉及硬件设计、FPGA编程、SD卡接口、NAND闪存控制以及图像显示等多个方面
fpga开发
9527华安21 小时前
FPGA实现PCIE3.0视频采集转10G万兆UDP网络输出,基于XDMA+GTH架构,提供工程源码和技术支持
网络·fpga开发·udp·音视频·xdma·pcie3.0·万兆网
able陈1 天前
为什么verilog中递归函数需要定义为automatic?
fpga开发
fei_sun1 天前
【Verilog】第二章作业
fpga开发·verilog
VVVVWeiYee1 天前
Mesh路由组网
运维·网络·智能路由器·信息与通信
碎碎思1 天前
如何使用 Vivado 从源码构建 Infinite-ISP FPGA 项目
fpga开发·接口隔离原则
江山如画,佳人北望1 天前
fpga-状态机的设计及应用
fpga开发
晓晓暮雨潇潇1 天前
Xilinx IP核(3)XADC IP核
fpga开发·vivado·xadc·ip核
CWNULT1 天前
AMD(Xilinx) FPGA配置Flash大小选择
fpga开发