基于FPGA的UART串口收发实例

本文基于FPGA实现了UART串口发送及接收模块。
开发环境:Win7
开发软件:Quartus17.1、Modelsim SE-64 10.2c、串口调试助手、Gvim编辑器
开发硬件:小梅哥AC6102_V2开发板

1.UART协议

关于UART的通信原理,上一篇博客已经完整介绍了,详情请看UART介绍

2.实现原理

2.1模块解析

UART串口收发包括串口接收模块、串口发送模块、DPRAM、DPRAM控制读写模块。
1.串口接收模块负责接收从串口调试软件发送过来的数据,串行数据转换成八位并行数据,同时将数据写入DPRAM中
2.串口发送模块负责将存储在DPRAM中的数据读取,并将八位数据转换成串行数据发送到串口
3.DPRAM控制读写模块负责DPRAM的读写控制
4.DPRAM存储由串口发送过来的数据
系统的框架图如下图所示。
基于FPGA的UART串口收发实例_第1张图片

2.2模块实现

1.串口发送模块
串口发送模块在上一篇博客已经写的很详细了,详情请看串口发送模块
2.串口接收模块
串口接收时序图如下图所示。

rs232_rx是串口发送过来的串行数据,由于数据在传输过程中可能由于电磁干扰等因素,导致数据不稳定,所以在这里,我们在一个数据周期内进行16次采样,再取平均值四舍五入,即16次采样,如果“1”的个数大于等于8,值取“1”,否则取“0”。
bps_clk就是我们要自己设计的采样时钟。
在进行逻辑设计时,设置一个接收标志flag_receive,检测到rs232_rx的下降沿时,将flag_recevice拉高,同时产生bps_clk对rs232_rx进行采样,当接收完10个数据(起始位+8位数据+停止位)后,将flag_receive拉低。
代码如下:

module  uart_rx(
        input                   clk                             ,//50M
        input                   rst_n                           ,
        input                   rs232_rx                        ,
        input         [ 3: 0]   baud_set                        ,
        output  reg             rx_done                         ,
        output  reg   [ 7: 0]   data_byte                       ,
        output  wire            uart_state 
);

//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
localparam                      BAUD_RATE_9600  =   324         ;//325
localparam                      BAUD_RATE_19200 =   162         ;//163
localparam                      BAUD_RATE_38400 =   81          ;
localparam                      BAUD_RATE_115200=   27          ;

localparam                      NUM_SAMPLE      =   16          ;
localparam                      DATA_BITS       =   10          ;

reg     [12: 0]                 BAUD_RATE                       ;

reg                             bps_clk                         ;
reg                             flag_receive                    ;
wire                            receive_en                      ;
reg                             rs232_rx_r1                     ;
reg                             rs232_rx_r2                     ;
wire                            neg_rs232_rx                    ;

reg     [ 4: 0]                 rx_16bit_sum                    ;
reg     [ 7: 0]                 data_byte_temp                  ;


reg     [ 9: 0]                 cnt0                            ;
wire                            add_cnt0                        ;
wire                            end_cnt0                        ;
reg     [ 3: 0]                 cnt1                            ;
wire                            add_cnt1                        ;
wire                            end_cnt1                        ;
reg     [ 3: 0]                 cnt2                            ;
wire                            add_cnt2                        ;
wire                            end_cnt2                        ;


//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
//BAUD_RATE
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        BAUD_RATE   <= BAUD_RATE_9600; 
    end
    else begin
        case(baud_set)
            4'd0:   BAUD_RATE   <= BAUD_RATE_9600;
            4'd1:   BAUD_RATE   <= BAUD_RATE_19200;
            4'd2:   BAUD_RATE   <= BAUD_RATE_38400;
            4'd3:   BAUD_RATE   <= BAUD_RATE_115200;
            default:BAUD_RATE   <= BAUD_RATE_9600;
        endcase
    end
end

//rs232_rx_r1,rs232_rx_r2
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rs232_rx_r1 <=  1'b1;
        rs232_rx_r2 <=  1'b1;
    end
    else begin
        rs232_rx_r1 <=  rs232_rx;
        rs232_rx_r2 <=  rs232_rx_r1;
    end
end

//neg_rs232_rx  检测下降沿
assign  neg_rs232_rx    =   ~flag_receive & ~rs232_rx_r1 & rs232_rx_r2;
assign  receive_en      =   neg_rs232_rx;

//flag_receive
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        flag_receive   <=  1'b0;
    end
    else if(receive_en)begin
        flag_receive   <=  1'b1;
    end
    else if(end_cnt2)begin//rx_done
        flag_receive   <=  1'b0;
    end
end

//cnt0
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
    else begin
        cnt0 <= 0;
    end
end

assign  add_cnt0        =       flag_receive;
assign  end_cnt0        =       add_cnt0 && cnt0 == BAUD_RATE-1;

//cnt1
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign  add_cnt1        =       end_cnt0;
assign  end_cnt1        =       add_cnt1 && cnt1 == NUM_SAMPLE-1;

//cnt2
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt2 <= 0;
    end
    else if(add_cnt2)begin
        if(end_cnt2)
            cnt2 <= 0;
        else
            cnt2 <= cnt2 + 1;
    end
end

assign  add_cnt2     =       end_cnt1;       
assign  end_cnt2     =       add_cnt2 && cnt2 == DATA_BITS-1;   

//rx_done
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rx_done <=  1'b0;
    end
    else if(end_cnt2)begin
        rx_done <=  1'b1;
    end
    else begin
        rx_done <=  1'b0;
    end
end

//bps_clk
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        bps_clk <=  1'b0;
    end
    else if(cnt0 == ((BAUD_RATE>>1) - 1))begin
        bps_clk <=  1'b1;
    end
    else begin
        bps_clk <=  1'b0;
    end
end


//rx_16bit_sum
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rx_16bit_sum   <=  5'd0;
    end
    else if(bps_clk)begin
        rx_16bit_sum   <=  rx_16bit_sum + rs232_rx_r2;   
    end
    else if(end_cnt1)begin
        rx_16bit_sum   <=  5'd0;  
    end
    else if(!flag_receive)begin
        rx_16bit_sum   <=  5'd0;  
    end
end

//data_byte_temp
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_byte_temp  <=  {8{1'b1}};
    end
    else if(end_cnt1)begin
        data_byte_temp  <=  {rx_16bit_sum[3]|rx_16bit_sum[4], data_byte_temp[7:1]};
    end
    else if(!flag_receive)begin
        data_byte_temp  <=  {8{1'b1}};
    end
end

//data_byte
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data_byte   <=  {8{1'b0}};
    end
    else if(end_cnt2)begin//串口先发送低位
        data_byte   <=  data_byte_temp;
    end
end

//uart_state
assign  uart_state  =   flag_receive;   

endmodule

3.DPRAM控制读写模块
DPRAM控制读写模块主要是对DPRAM写使能、写地址、读使能、读地址的控制。
代码如下:

module  uart_dpram_ctrl(
        input                   clk                             ,
        input                   rst_n                           ,
        input                   key_send                        ,
        input                   rx_done                         ,
        input                   tx_done                         ,
        input                   uart_state_r                    ,
        input                   uart_state_t                    ,
        output  wire            led_flag                        ,
        output  wire            send_en                         ,
        output  wire            wr_en                           ,
        output  reg   [ 9: 0]   wr_addr                         ,
        output  reg             rd_en                           ,
        output  wire  [ 9: 0]   rd_addr                         
);

//======================================================================\
//************** Define Parameter and Internal Signals *****************
//======================================================================/
reg     [ 2: 0]                 send_en_r                       ;

reg                             send_flag                       ;

reg     [ 9: 0]                 cnt                             ;
wire                            add_cnt                         ;
wire                            end_cnt                         ;

//======================================================================\
//**************************** Main Code *******************************
//======================================================================/
assign  led_flag    =           ~(uart_state_r | uart_state_t)  ;


//wr_en
assign  wr_en       =           rx_done                         ;
//wr_addr
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        wr_addr <=  10'd0;
    end
    else if(wr_en)begin
        wr_addr <=  wr_addr + 1'd1;
    end
end


//cnt
always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt <= 0;
    end
    else if(add_cnt)begin
        if(end_cnt)
            cnt <= 0;
        else
            cnt <= cnt + 1;
    end
end

assign  add_cnt     =       rd_en;       
assign  end_cnt     =       add_cnt && cnt == 1024-1;   

 
//send_flag
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        send_flag   <=  1'b0;
    end
    else if(key_send)begin
        send_flag   <=  1'b1;
    end
    else if(end_cnt)begin
        send_flag   <=  1'b0;  
    end
end

//rd_en,读使能
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rd_en   <=  1'b0;   
    end
    else if(key_send || (tx_done && send_flag))begin
        rd_en   <=  1'b1;
    end
    else begin
        rd_en   <=  1'b0;
    end
end

//rd_addr,读地址
assign  rd_addr =   send_flag ? cnt : 0;



//send_en_r
always  @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        send_en_r   <=  3'b000;
    end
    else begin
        send_en_r   <=  {send_en_r[1:0], rd_en};
    end
end

//send_en
assign  send_en     =           send_en_r[2]                    ;



endmodule

3.实现效果

我们首先看一下,该工程实现的效果。
①烧写FPGA程序
②打开串口调试助手,首先进行串口的配置,选择正确的端口,设置波特率为115200,数据位为8位等等,打开串口,然后发送图中所示数据,FPGA将数据保存在双端口存储器中,等待发送。
基于FPGA的UART串口收发实例_第2张图片
③按下按键,FPGA将接收的数据发送到串口,串口调试助手显示接收到的数据,图中显示串口调试助手准确接收到了FPGA发送过来的数据。
基于FPGA的UART串口收发实例_第3张图片
工程下载链接:基于FPGA的UART串口收发实例工程下载

你可能感兴趣的:(基于FPGA的UART串口收发实例)