rs232串口功能拓展

        基于上一章串口回环的内容,进行一个串口的拓展学习。本章节使用串口回环进行一个对接收模块接收的数据进行一个编码以及解码的过程,通过解码通过发送模块将接收到的指令发送出去。

1.设计需求

        通过串口助手发送某个关键字,比如:通过串口助手发送“hello”指令给FPGA,FPGA在检测接收到“hello”后,通过发送端口给上位机发送一行指令如“hello verilog!”

        根据设计需求可以绘制我们的整体串口回环模块:

         根据绘制的模块框图可以知道,本次设计只需要在回环中添加一个编码模块以及一个解码模块。

2.编码模块分析

        

         编码模块的主要目的是将FPGA接收到的数据进行一个编码处理,判断接收的数据是否为连续的“hello”字符。一般检测数据常用的两种方式,一种是状态机,另一种为线性序列机,本次使用状态机。

         上图为状态机,有点丑,但是大概就是这个意思。首先为IDLE状态,如果检测到的字符为h,则跳转到h,否则则继续再IDLE状态进行检测;在h状态检测是否为e,如果为e跳转到e的状态,否则跳转回IDLE状态;同理一直检测跳转到O。

        根据编码模块以及状态机可以绘制波形图:

         编码模块波形很简单,只是做一个打拍然后取一个上升沿作为接收完成标志位进行状态检测,如果检测到字符为“hello”,则发送使能拉高。

        代码如下:

//接收数据编码模块
module uart_coding(
    input        sys_clk       ,    // 系统时钟
    input        sys_rst_n     ,    // 系统复位,低电平有效
    input        recv_done     ,    // 接收一帧数据完成标志
    input  [7:0] recv_data     ,    // 接收的数据
    input        send_all_done ,    // 数据全部发送完成标志
    output reg   send_start         // 开始发送数据标志
    );

// parameter define
parameter IDLE     =  6'b000_001;  //空闲状态
parameter STATE_H  =  6'b000_010;  //h
parameter STATE_E  =  6'b000_100;  //e
parameter STATE_L  =  6'b001_000;  //l
parameter STATE_L1 =  6'b010_000;  //l
parameter STATE_O  =  6'b100_000;  //o

parameter CHAR_H   =  8'd104,
          CHAR_E   =  8'd101,  
          CHAR_L   =  8'd108,
          CHAR_O   =  8'd111;
   
//reg dfine
reg       recv_done_d0  ;
reg       recv_done_d1  ;
reg       recv_done_d2  ;
reg [5:0] current_state ;   //状态机的现态
reg [5:0] next_state    ;   //状态机的次态

//wire define
wire recv_done_flag;

//*****************************************************
//**                    main code
//*****************************************************

//捕获 recv_done 上升沿,得到一个时钟周期的脉冲信号
assign recv_done_flag = (~recv_done_d2) & 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;
        recv_done_d2 <= 1'B0;
    end
    else begin
        recv_done_d0 <= recv_done   ;
        recv_done_d1 <= recv_done_d0;
        recv_done_d2 <= recv_done_d1;
    end
end

//同步时序模块
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n) 
        current_state <= IDLE;
    else
        current_state <= next_state;
end

//转态转换条件
always@(*)begin
    if(!sys_rst_n)
        next_state   <= 1'b0;
    else begin
        case(current_state)
            IDLE:
                if(recv_done_flag)                  // 检测串口接收到数据
                    if(recv_data == CHAR_H)         // 检测字符"h"条件判断
                        next_state <= STATE_H;      // 前往状态"STATE_H"
                    else
                        next_state <= IDLE;         // 没有检测到“h”,返回初始态
                else                                
                    next_state <= IDLE;             // 未检测到串口接收到数据                       
            STATE_H:                                
                if(recv_done_flag)                  // 检测串口接收到数据
                    if(recv_data == CHAR_E)         // 检测字符"e"条件判断
                        next_state <= STATE_E;      // 前往状态"STATE_E"
                    else if(recv_data == CHAR_H)    // 检测到“h”
                        next_state <= STATE_H;      // 前往状态"STATE_H"
                    else                            
                        next_state <= IDLE;         // 检测到数据但不是"e",返回初始态
                else                                
                    next_state <= STATE_H;          // 未检测到串口接收到数据 
            STATE_E:                                
                if(recv_done_flag)                  // 检测串口接收到数据
                    if(recv_data == CHAR_L)         // 检测字符"l"
                        next_state <= STATE_L;      // 前往状态"STATE_L"
                    else if(recv_data == CHAR_H)    // 检测到"h"
                        next_state <= STATE_H;      // 前往状态"STATE_H"
                    else
                        next_state <= IDLE;         // 检测到数据但不是"l",返回初始态
                else                                
                    next_state <= STATE_E;          //未检测到串口接收到数据
            STATE_L:                                
                if(recv_done_flag)                  // 检测串口接收到数据
                    if(recv_data == CHAR_L)         // 检测字符"l"
                        next_state <= STATE_L1;     // 前往状态"STATE_L1"
                    else if(recv_data == CHAR_H)    // 检测到"h"
                        next_state <= STATE_H;      // 前往状态"STATE_H"
                    else                            
                        next_state <= IDLE;         // 检测到数据但不是"l",返回初始态
                else                                
                     next_state <= STATE_L;         // 未检测到串口接收到数据
            STATE_L1:                               
                if(recv_done_flag)                  // 检测串口接收到数据
                    if(recv_data ==CHAR_O)          // 检测字符“o”
                        next_state <= STATE_O;      // 前往状态"STATE_O"
                    else if(recv_data == CHAR_H)    // 检测到"h"
                        next_state <= STATE_H;      // 前往状态"STATE_H"
                    else                            
                        next_state <= IDLE;         // 检测到数据但不是"o",返回初始态
                else                                
                     next_state <= STATE_L1;         // 未检测到串口接收到数据
            STATE_O:                                
                if(send_all_done)                   // 一帧数据全部发送完成
                    next_state   <= IDLE;           // 返回初始态
                else
                    next_state <= STATE_O;         
            default:
                next_state <= IDLE;
        endcase   
    end
end

//赋值模块
always  @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        send_start <= 1'b0;    
    else if(current_state==STATE_O) //成功检测到“hello”
        send_start <= 1'b1;     
        //检测过程结束,标志位 send_start 拉高
    else 
        send_start <= 1'b0;
end


endmodule

        仿真代码参考串口回环部分,这边直接看一下 仿真波形:

         首先是打三拍进行取一个上升沿作为发送完成标志,等于h则跳转到下一个状态,最好检测完整个字符跳转到100_000状态,发送使能拉高。

 3.解码模块分析

         同样先绘制解码模块框图:

        解码模块需要两个输出信号,一个发送使能,一个发送的数据。输入的send_start信号同样需要进行一个打拍操作,单纯为了防止亚稳态,uart_tx_busy为发送模块中发送中的一个标志信号。发送数据端口总共需要发送“hello verilog!”,总共14个字符,所以还需定义一个data_cnt模块。

        下面是波形图:

         同样根据波形图可以绘制代码:

// 发送数据准备模块
module uart_decoding(
    input            sys_clk       ,    // 系统时钟
    input            sys_rst_n     ,    // 系统复位,低电平有效
    input            send_start    ,    // 开始发送数据标志
    input            uart_tx_busy  ,    // UART发送忙状态标志
    output reg       send_all_done ,    // 数据全部发送完成标志
    output reg       send_en       ,    // 串口发送使能信号
    output reg [7:0] send_data          // 发送的寄存数据
    );

parameter CHAR_H   =  8'd104,
          CHAR_E   =  8'd101,  
          CHAR_L   =  8'd108,
          CHAR_O   =  8'd111,
          CHAR_V   =  8'd118,
          CHAR_R   =  8'd114,
          CHAR_I   =  8'd105,
          CHAR_G   =  8'd103,
          CHAR_32  =  8'd32,
          CHAR_33  =  8'd33;
//reg define     
reg       send_done_d0       ;
reg       send_done_d1       ;
reg       send_done_d2       ;
reg       send_start_d0      ;
reg       send_start_d1      ;
reg       send_start_d2      ;
reg [4:0] data_cnt           ;     //发送数据寄存器

//*****************************************************
//**                    main code
//*****************************************************

//检测到一个下降沿,得到一个时钟周期信号
assign send_done = send_done_d2 & (~send_done_d1 ) ;

//检测到一个上升沿,得到一个时钟周期信号
assign send_start_done = (~send_start_d2) & send_start_d1 ;

//延迟3个时钟周期消除数据亚稳态
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        send_start_d0 <= 1'b0 ;
        send_start_d1 <= 1'b0 ;
        send_start_d2 <= 1'b0 ;
    end
    else begin
        send_start_d0 <= send_start    ;
        send_start_d1 <= send_start_d0 ;  
        send_start_d2 <= send_start_d1 ;         
    end
end

//延迟3个时钟周期消除数据亚稳态
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        send_done_d0 <= 1'b0 ;
        send_done_d1 <= 1'b0 ;
        send_done_d2 <= 1'b0 ;
    end
    else begin
        send_done_d0 <= uart_tx_busy  ;
        send_done_d1 <= send_done_d0  ;    
        send_done_d2 <= send_done_d1  ;
    end
end

// 发送数据计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        data_cnt <= 5'b0;  
    else if(send_done) begin   // 一个字节数据发送完成,send_done信号拉高
        if (data_cnt == 13)     // 计数到最后一个数据,计数器清零
            data_cnt <= 1'b0;
        else 
            data_cnt <= data_cnt +1;
    end
end

//数据全部发送完成的过程
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        send_all_done <= 1'b0;
    else if((data_cnt == 13) && send_done ) 
    //数据计数到最后一位并且发送完成
        send_all_done <= 1'b1;    // 数据全部发送完成,send_all_done拉高
    else
        send_all_done <= 1'b0;
end

//判断向上位机发送数据信号,并在串口发送模块空闲时给出发送使能信号
always@(posedge sys_clk or negedge sys_rst_n )begin
    if(!sys_rst_n)
        send_en <= 1'b0 ;
   else if(send_start_done || (send_done && data_cnt < 13))
   //接收数据完成开始发送或者寄存的一个字节数据发送完成
            send_en <= 1'b1;
    else
        send_en <= 1'b0;
end

//将对应的发送数据赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
	    send_data <= 8'b0;
    else if(send_start)begin   //检测到开始发送数据标志
        case(data_cnt)
            5'd0  : send_data <= CHAR_H;  // h
            5'd1  : send_data <= CHAR_E;  // e
            5'd2  : send_data <= CHAR_L;  // l
            5'd3  : send_data <= CHAR_L;  // l
            5'd4  : send_data <= CHAR_O;  // o
            5'd5  : send_data <= CHAR_32;  //  
            5'd6  : send_data <= CHAR_V;  // v
            5'd7  : send_data <= CHAR_E;  // e
            5'd8  : send_data <= CHAR_R;  // r
            5'd9  : send_data <= CHAR_I;  // i
            5'd10 : send_data <= CHAR_L;  // l
            5'd11 : send_data <= CHAR_O;  // o
            5'd12 : send_data <= CHAR_G;  // g
            5'd13 : send_data <= CHAR_33;  // !
            default:;
        endcase
    end
    else
        send_data <= send_data;
end
 
endmodule
    

        波形图如下:

         同样先对sen_start_done打三拍取一下他的上升沿,send_en信号只有在发送开始信号为1的适合,或者在data_cnt小于13的起始位拉高。

         而send_done信号则是对发送忙碌信号拉低之后进行打3拍之后取它们的一个下降沿,作为发送1位数据的完成信号进行拉高一个时钟周期。

         再send_en发送使能拉高时,进行一位数据的发送,当所有数据发送完成指挥,send_all_done信号拉高,表示解码模块的数据已经发送完成。

        最好将编码解码模块例化到之前的串口回环模块中,下载验证图片如下:

        如果有小伙伴需要工程可以私一下我,这个实验相对来说还是比较简单,应该是没有什么问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

伊藤诚诚诚诚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值