文章摘要
本文主要描述了串口接收模块的实现;

设计思路:
1.通过检测数据线上的下降沿来确定起始位;
2.检测每一位的中间位置的电平值,来确定该位的数据;

知识要点: 边沿检测,有限状态机;


硬件平台: EP4CE6F17C8
开发环境: Quartus II 13.1


/*
 * 功能描述:串口接收模块
 */
module uart_recv(
    input clk,              // 模块时钟
    input rst_n,            // 模块复位,低电平有效
    input rxd,              // 串口接收引脚    
    output reg[7:0] rdata,  // 接收到的数据
    output reg rxdone       // 接收完成标志,高电平有效  
);

parameter BAUD_CNT = 5208;      // 9600的波特率
parameter BAUD_CNT_HALF = 2604; // 中间值

parameter S_IDLE    = 4'd0;
parameter S_START   = 4'd1;
parameter S_BIT0    = 4'd2;
parameter S_BIT1    = 4'd3;
parameter S_BIT2    = 4'd4;
parameter S_BIT3    = 4'd5;
parameter S_BIT4    = 4'd6;
parameter S_BIT5    = 4'd7;
parameter S_BIT6    = 4'd8;
parameter S_BIT7    = 4'd9;
parameter S_STOP    = 4'd10;

reg [3:0] status;   // 状态机当前状态

reg  rxd_last;      // 接收引脚前一次电平
reg  rxd_curr;      // 接收引脚当前电平
wire rxd_start;     // 接收起始位标志(高电平有效)

reg [31:0] timer;   // 波特率计时器

//--------------------------------------------------
// 接收起始位处理
always @(posedge clk)
begin
    rxd_last <= rxd_curr;
    rxd_curr <= rxd;
end

// 前一次为高,当前值为低,视做下降沿
assign rxd_start = rxd_last & ~rxd_curr;

//--------------------------------------------------

always @(posedge clk or negedge rst_n)
begin
    if(~rst_n)
    begin
        status = S_IDLE;
    end    
    else
    begin    

        timer = timer + 1'b1;        // 波特率计算
        
        case(status)
        S_IDLE:
        begin
            rxdone <= 1'b0;         
            if(rxd_start)            // 空闲状态检测到下降沿   
            begin
                timer  <= 0;
                status <= S_START;   // 接收起始位    
            end
        end
        
        S_START:
        begin
            // 中间时刻仍为低电平,视做有效起始位
            if(timer == BAUD_CNT_HALF)          
            begin
                if(~rxd)
                begin
                    timer  <= 0;
                    status <= S_BIT0;               
                end
                else
                    status <= S_IDLE;   // 起始位无效,重新接收                   
            end     
        end
        
        S_BIT0:
        begin
            // 因为上一次是在中间位置重置timer,所以这里也是中间位置
            if(timer == BAUD_CNT)       
            begin
                timer  <= 0;        // 重置定时器
               rdata[0]<= rxd;      // 当前值即为有效数据
                status <= S_BIT1;   // 准备接收下一bit            
            end
            else
                status <= S_BIT0;   // 这一句纯粹是编程习惯
        end
        
        S_BIT1:
        begin
            if(timer == BAUD_CNT) 
            begin
                timer  <= 0;
                rdata[1] <= rxd;    
                status   <= S_BIT2;             
            end
            else
                status <= S_BIT1;
        end
        
        S_BIT2:
        begin
            if(timer == BAUD_CNT)   
            begin
                timer  <= 0;
               rdata[2] <= rxd;
                status   <= S_BIT3;              
            end
            else
                status <= S_BIT2;
        end

        S_BIT3:
        begin
            if(timer == BAUD_CNT)
            begin
                timer  <= 0;
               rdata[3] <= rxd;
                status   <= S_BIT4;              
            end
            else
                status <= S_BIT3;
        end
        
        S_BIT4:
        begin
            if(timer == BAUD_CNT) 
            begin
                timer  <= 0;
                rdata[4] <= rxd;
                status   <= S_BIT5;              
            end
            else
                status <= S_BIT4;
        end
        
        S_BIT5:
        begin
            if(timer == BAUD_CNT)
            begin
                timer  <= 0;
                rdata[5] <= rxd;
                status   <= S_BIT6;              
            end
            else
                status <= S_BIT5;
        end
        
        S_BIT6:
        begin
            if(timer == BAUD_CNT)    
            begin
                timer  <= 0;
                rdata[6] <= rxd;
                status   <= S_BIT7;              
            end
            else
                status <= S_BIT6;
        end
        
        S_BIT7:
        begin
            if(timer == BAUD_CNT)  
            begin
                timer  <= 0;
                rdata[7] <= rxd;
                status   <= S_STOP;     // 处理停止位             
            end
            else
                status <= S_BIT7;
        end
        
        S_STOP:
            // 因为上一次是在中间置0的,所以这里也是中点
            // 但是不等到接收完再跳转了,因为如果连续发送的话,可能会错过起始位
            if(timer == BAUD_CNT)   
            begin
                if(rxd)
                begin                   
                    rxdone <= 1'b1;     // 设置接收完成标置
                end               
                
                status <= S_IDLE;       // 无论如何,均进入空闲状态
                timer  <= 0;                    
            end 
        endcase
    end
end

endmodule