基于FPGA的串口指令帧接收与解析的verilog代码

基于FPGA的串口指令帧接收与解析的verilog代码
网上的verilog串口指令帧接收与解析源码很多,但大多数都说不到点子上,对初学者来说很不友好,今天分享一个自己调通的小工程。
串口指令帧格式如下:
在这里插入图片描述
串口接收模块直接用的正点原子的源码,个人感觉正点原子的代码虽然写得冗杂,但严谨性还行,数据在波特率计数周期的中间点采集,源码如下:

module uart_recv(
    input			  sys_clk,                  //系统时钟
    input             sys_rst_n,                //系统复位,低电平有效   
    (*mark_debug ="true"*) input             uart_rxd,                 //UART接收端口
    (*mark_debug ="true"*) output  reg       uart_done,                //接收一帧数据完成标志信号
    (*mark_debug ="true"*) output  reg [7:0] uart_data                 //接收的数据
    );
    
//parameter define
parameter  CLK_FREQ = 100_000_000;                 //系统时钟频率
parameter  UART_BPS = 230400;                     //串口波特率
localparam BPS_CNT  = CLK_FREQ/UART_BPS;        //为得到指定波特率,
                                                //需要对系统时钟计数BPS_CNT次
//reg define
reg        uart_rxd_d0;
reg        uart_rxd_d1;
reg [15:0] clk_cnt;                             //系统时钟计数器
reg [ 3:0] rx_cnt;                              //接收数据计数器
reg        rx_flag;                             //接收过程标志信号
reg [ 7:0] rxdata;                              //接收数据寄存器

//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))
            rx_flag <= 1'b0;                    //计数到停止位中间时,停止接收过程
        else
            rx_flag <= rx_flag;
    end
end

//进入接收过程后,启动系统时钟计数器与接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin         
    if (!sys_rst_n) begin                             
        clk_cnt <= 16'd0;                                  
        rx_cnt  <= 4'd0;
    end                                                      
    else if ( rx_flag ) begin                   //处于接收过程
            if (clk_cnt < BPS_CNT - 1) begin
                clk_cnt <= clk_cnt + 1'b1;
                rx_cnt  <= rx_cnt;
            end
            else begin
                clk_cnt <= 16'd0;               //对系统时钟计数达一个波特率周期后清零
                rx_cnt  <= rx_cnt + 1'b1;       //此时接收数据计数器加1
            end
        end
        else begin                              //接收过程结束,计数器清零
            clk_cnt <= 16'd0;
            rx_cnt  <= 4'd0;
        end
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;
    end
    else if(rx_cnt == 4'd9) begin               //接收数据计数器计数到停止位时           
        uart_data <= rxdata;                    //寄存输出接收到的数据
        uart_done <= 1'b1;                      //并将接收完成标志位拉高
    end
    else begin
        //uart_data <= 8'd0;     
        uart_data <= uart_data;                               
        uart_done <= 1'b0; 
    end    
end

endmodule	

每当接收到一个字节的数据,uart_done就会拉高一个时钟周期,同时输出一个字节的数据;
串口数据帧解析可分为6个状态:
接收到AA–>状态1;
接收到BB–>状态2;
接收完4个字节的数据–>状态3;
接收到和校验–>状态4;
接收到CC–>状态5;
接收到DD–>状态6;
设置一个计数器rx_done_cnt,初始值0,当uart_done 拉高时计数一次,由此可知:
当收到AA时,rx_done_cnt=1;
当收到BB时,rx_done_cnt=2;
当收到DATA1时,rx_done_cnt=3;
当收到DATA2时,rx_done_cnt=4;
当收到DATA3时,rx_done_cnt=5;
当收到DATA4时,rx_done_cnt=6;
当收到和校验时,rx_done_cnt=7;
当收到CC时,rx_done_cnt=8;
当收到DD时,rx_done_cnt=9;
当收到rx_done_cnt=9时,rx_done_cnt清零,准备下一帧数据接收;

那么,状态机status,初始值status=0,的逻辑也就清晰了,如下:
当status=0&&rx_done_cnt=1&&uart_data=AA时,status=1;–>收到了AA;
当status=1&&rx_done_cnt=2&&uart_data=BB时,status=2;–>收到了BB;
当status=2&&uart_data=AA时,status=3;–>收到了4个字节的数据;
当status=3&&data_sum_ok=1时,status=4;–>和校验正确;
当status=4&&rx_done_cnt=8&&uart_data=CC时,status=5;–>收到了BB;
当status=5&&rx_done_cnt=9&&uart_data=DD时,status=6;–>收到了BB;
如此设计,每个状态的值和跳转条件都与上一个状态密切相关,这样就可以保证,只要一帧数据错一个字节甚至是一个bit,状态机就会失效,接收就会失败,最大限度的保证了接收的正确性;我的该部分源码如下:

/status/
always @(posedge clk_100m) begin
	if(!rst_n) status<=8'd0;
	else if(status==8'd0&&rx_done_cnt==8'd1 && r_rx_data==8'haa) status<=8'd1;
	else if(status==8'd1&&rx_done_cnt==8'd2 && r_rx_data==8'hbb) status<=8'd2;	//进入接收有效数据阶段
	else if(status==8'd2&&rx_done_cnt==8'd7) status<=8'd3;	//进入和校验阶段
	else if(status==8'd3&&data_sum_ok==1'd1) status<=8'd4;	//校验和正确
	else if(status==8'd4&&rx_done_cnt==8'd8 && r_rx_data==8'hcc) status<=8'd5;
	else if(status==8'd5&&rx_done_cnt==8'd9 && r_rx_data==8'hdd) status<=8'd6;
	else if(status==8'd6) status<=8'd0;
end
/status/

当状态机status=6时,输出有效数据和有效脉冲,设计如下:

/valid_data_out/
always @(posedge clk_100m) begin
	if(!rst_n) o_x_data<='d0;
	else if(status==8'd6) o_x_data<={r_data1,r_data2,r_data3,r_data4};
end
/valid_data_out/

/valid_data_out_req/
always @(posedge clk_100m) begin
	if(!rst_n) o_req<='d0;
	else if(status==8'd6) o_req<=1'd1;
	else o_req<='d0;
end
/valid_data_out_req/

最后:整个源代码并未给出,目的是给兄弟们一个练手的机会,因为这个很简单,加之我的讲解,应该问题不大,如果你在自身尝试的基础上,实在搞不出来,可以下载源码:
源码下载链接:https://download.csdn.net/download/qq_41667729/87767016?spm=1001.2014.3001.5503

  • 17
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
基于FPGA串口指令接收解析Verilog代码,可以通过以下步骤实现: 1. 首先,我们需要定义一些参数,如波特率、数据位、停止位等。可以使用参数定义方式,在代码中进行设置。 2. 接着,需要定义连续的寄存器,用于存储接收到的数据。可以使用一个FIFO(First-In-First-Out)缓冲区来存储接收到的数据。在Verilog中,可以使用$fifo模块来实现该缓冲区。 3. 编写串口接收模块。首先,需要使用一个接收时钟,通过对时钟进行分频来实现波特率的定义。然后,在每个时钟周期中,通过接收状态机来控制接收操作。接收状态机可以使用case语句实现,根据当前状态进行相应的操作。在接收模块中,需要包括以下功能: - 等待起始位的检测:当串口接收到起始位(逻辑低电平)时,进入下一个状态。 - 接收数据位:在每个时钟周期中,从串口接收数据线上读取数据,并保存在寄存器中。 - 接收停止位:在读取完所有数据位后,再读取停止位(逻辑高电平),并将数据存储到FIFO缓冲区中。 - 等待下一个起始位:在完成一接收后,等待下一个起始位。 4. 编写串口解析模块。这个模块负责从FIFO缓冲区中读取接收到的数据,并进行解析处理。可以根据接收到的数据结构,使用条件语句对不同的指令进行处理。 在Verilog代码中,可以使用参数、寄存器、状态机、FIFO缓冲区等模块,来实现基于FPGA串口指令接收解析的功能。以上是一个简单的代码框架,具体的实现细节需要根据具体的需求进行调整和完善。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

9527华安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值