通用异步收发传输器( Universal Asynchronous Receiver/Transmitter) ,通常称作UART。 UART 是一种通用的数据通信协议,也是异步串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。 它包括了 RS232、 RS499、 RS423、 RS422 和 RS485 等接口标准规范和总线标准规范。

RS232 通信协议简介

1、 RS232 是 UART 的一种,没有时钟线,只有两根数据线,分别是 rx 和 tx,这两根线都是 1bit 位宽的。其中 rx 是接收数据的线,tx 是发送数据的线。

RS232 帧结构

2、波特率:在信息传输通道中,携带数据信息的信号单元叫码元(因为串口是 1bit 进行传输的,所以其码元就是代表一个二进制数),每秒钟通过信号传输的码元数称为码元的传输速率,简称波特率,常用符号“Baud”表示,其单位为“波特每秒(Bps)”。串口常见的波特率有 4800、 9600、 115200 等。
3、比特率:每秒钟通信信道传输的信息量称为位传输速率,简称比特率,其单位为“每秒比特数(bps)”。比特率可由波特率计算得出,公式为:比特率=波特率 单个调制状态对应的二进制位数。如果使用的是 9600 的波特率,其串口的比特率为: 9600Bps 1bit= 9600bps。

整体模块

设计并实现基于串口 RS232 的数据收、发模块,使用收、发模块,完成串口数据回环实验。

工程整体框图

模块名称功能描述
uart_rx串口数据接收模块
uart_tx串口数据发送模块
rs232顶层模块

消除亚稳态

FPGA接收RX信号可以通过一个或多个D触发器级联,这些触发器被快速时钟(sys_clk)驱动。第一个D触发器捕获到的信号可能会处于亚稳态,但通过级联的第二个或第三个D触发器,亚稳态的影响可以被最小化。这样,信号在离开最后一个触发器时应该是稳定的。

串口接收模块

该模块的功能是接收通过 PC 机上的串口调试助手发送的固定波特率的数据。

串口接收模块框图

信号位宽类型功能描述
sys_clk1BitInput工作时钟,频率50MHz
sys_rst_n1BitInput复位信号,低电平有效
rx1BitInput串口接收信号
po_data8BitOutput串口接收后转成的8bit数据
po_data_flag1BitOutput串口接收后转成的8bit数据有效标志信号

串口接收模块波形图

串口数据发送模块

该模块的功能是将 FPGA 中的数据以固定的波特率发送到 PC 机的串口调试助手并打印出来,串口发送模块按照串口的协议组装成帧,然后按照顺序一个比特一个比特将数据发送至 PC 机,而 FPGA 内部的数据往往都是并行的,需将其转化为串行数据发送。

串口发送模块框图

信号位宽类型功能描述
sys_clk1BitInput工作时钟,频率50MHz
pi_data8BitInput发要送的8bit并行数据
pi_data_flag1BitInput要发送的8bit并行数据有效标志信号
tx1BitOutput串口发送信号

串口发送模块波形图

程序代码

rs232.v

module  rs232
(
    input            sys_clk  ,
    input            sys_rst_n,
    input            rx       ,

    output            tx


);

wire            [7:0]        rx_data;
wire                        tx_flag;


uart_rx
#(
    .UART_BPS    (9600),
    .CLK_FREQ    (50_000_000)
)
uart_rx_inst
(
    .sys_clk        (sys_clk),
    .sys_rst_n        (sys_rst_n),
    .rx               (rx ),

    .po_data        (rx_data ),
    .po_flag        (tx_flag  )
);


uart_tx  
#(
    .UART_BPS       (9600),
    .CLK_FREQ       (50_000_000)
)
uart_tx_inst
(
    .sys_clk        (sys_clk),
    .sys_rst_n        (sys_rst_n),
    .pi_data        (rx_data),
    .pi_flag        (tx_flag),

    .tx             (tx )
);

endmodule

tb_rs232.v

`timescale  1ns/1ns
module  tb_rs232();

reg            sys_clk;
reg            sys_rst_n;
reg            rx;

wire        tx;

//初始化系统时钟、全局复位和输入信号
initial
    begin
        sys_clk  = 1'b1;
        sys_rst_n <= 1'b0;
        rx          <= 1'b1;
        #20
        sys_rst_n <= 1'b1;

    end
always #10 sys_clk = ~sys_clk;

//调用任务rx_byte
initial
    begin
        #200
        rx_byte();
    end

//创建任务rx_byte,本次任务调用rx_bit任务,发送8次数据,分别为0~7
task    rx_byte();  //因为不需要外部传递参数,所以括号中没有输入

    integer    j;
    for(j = 0; j<8; j= j+1) //调用8次rx_bit任务,每次发送的值从0变化7
        rx_bit(j);
endtask

//创建任务rx_bit,每次发送的数据有10位,data的值分别为0到7由j的值传递进来
task    rx_bit
(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
    input   [7:0]    data
);

    integer    i;
for(i = 0; i<10; i= i+1)
begin
    case(i)
        0:    rx <= 1'b0;
        1:     rx <= data[0];
        2:     rx <= data[1];
        3:     rx <= data[2];
        4:     rx <= data[3];
        5:     rx <= data[4];
        6:     rx <= data[5];
        7:     rx <= data[6];
        8:     rx <= data[7];
        9:     rx <= 1'b1;
    endcase
    #(5208*20);//每发送1位数据延时5208个时钟周期
end
endtask

rs232    rs232_inst
(
    .sys_clk  (sys_clk ),
    .sys_rst_n(sys_rst_n),
    .rx       (rx),

    .tx       (tx)

);


endmodule

uart_rx.v

module  uart_rx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input    wire            sys_clk        ,
    input   wire            sys_rst_n    ,
    input    wire            rx           ,

    output  reg    [7:0]       po_data     ,
    output    reg                po_flag 
);

localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ; 

reg            rx_reg1        ;
reg            rx_reg2        ;
reg            rx_reg3        ;
reg            start_flag    ;
reg            work_en        ;
reg    [15:0]    baud_cnt    ;
reg            bit_flag    ;
reg [3:0]   bit_cnt        ;
reg    [7:0]   rx_data        ;
reg         rx_flag        ;

//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg1 <= 1'b1;
    else
        rx_reg1 <= rx;

//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg2 <= 1'b1;
    else
        rx_reg2 <= rx_reg1;

//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_reg3 <= 1'b1;
    else
        rx_reg3 <= rx_reg2;

//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        start_flag <= 1'b0;
    else    if((rx_reg3 == 1'b1) && (rx_reg2 == 1'b0) && (work_en == 1'b0))
        start_flag <= 1'b1;
    else
        start_flag <= 1'b0;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(start_flag == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        work_en <= 1'b0;
    else
        work_en <= work_en;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 16'b0;
    else    if((baud_cnt == BAUD_CNT_MAX -1) || (work_en == 1'b0))
        baud_cnt <= 16'd0;
    else
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if(baud_cnt == BAUD_CNT_MAX/2 - 1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 4'b0;
    else    if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
        bit_cnt <= 4'b0;
    else    if(bit_flag == 1'b1)
        bit_cnt <= bit_cnt + 1'b1;

//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_data <= 8'b0;
    else    if((bit_cnt >= 4'd1) && (bit_cnt <= 4'd8) && (bit_flag == 1'b1))
        rx_data <= {rx_reg3,rx_data[7:1]};

//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        rx_flag <= 1'b0;
    else    if((bit_cnt == 4'd8) && bit_flag == 1'b1)
        rx_flag <= 1'b1;
    else
        rx_flag <= 1'b0;

//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_data <= 8'b0;
    else    if(rx_flag == 1'b1)
        po_data <= rx_data;

//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        po_flag <= 1'b0;
    else
        po_flag <= rx_flag;


endmodule

tb_uart_rx.v

`timescale 1ns/1ns
module  tb_uart_rx();

reg            sys_clk        ;
reg            sys_rst_n    ;
reg            rx            ;

wire    [7:0]   po_data;
wire            po_flag;

//初始化系统时钟、全局复位和输入信号
initial
    begin
        sys_clk = 1'b1;
        sys_rst_n <= 1'b0;
        rx           <= 1'b1;
        #20
        sys_rst_n <= 1'b1;
    end

//模拟发送8次数据,分别为0~7
initial
    #200
    begin
        rx_bit(8'd0);  //任务的调用,任务名+括号中要传递进任务的参数
        rx_bit(8'd1);
        rx_bit(8'd2);
        rx_bit(8'd3);
        rx_bit(8'd4);
        rx_bit(8'd5);
        rx_bit(8'd6);
        rx_bit(8'd7);
    end

always   #10 sys_clk = ~sys_clk;

//定义一个名为rx_bit的任务,每次发送的数据有10位
//data的值分别为0~7由j的值传递进来
//任务以task开头,后面紧跟着的是任务名,调用时使用
task    rx_bit
(
    //传递到任务中的参数,调用任务的时候从外部传进来一个8位的值
    input   [7:0]    data
);

    integer    i;
for(i = 0; i<10; i= i+1)
begin
    case(i)
        0:    rx <= 1'b0;
        1:     rx <= data[0];
        2:     rx <= data[1];
        3:     rx <= data[2];
        4:     rx <= data[3];
        5:     rx <= data[4];
        6:     rx <= data[5];
        7:     rx <= data[6];
        8:     rx <= data[7];
        9:     rx <= 1'b1;
    endcase
    #(5208*20);
end
endtask

uart_rx
#(
    .UART_BPS    (9600),
    .CLK_FREQ    (50_000_000)
)
uart_rx_inst
(
    .sys_clk        (sys_clk),
    .sys_rst_n        (sys_rst_n),
    .rx               (rx ),

    .po_data        (po_data ),
    .po_flag        (po_flag  )
);

endmodule

uart_tx.v

module uart_tx
#(
    parameter   UART_BPS    =   'd9600,         //串口波特率
    parameter   CLK_FREQ    =   'd50_000_000    //时钟频率
)
(
    input    wire            sys_clk        ,
    input    wire              sys_rst_n    ,
    input    wire      [7:0]    pi_data        ,       //模块输入的8bit数据
    input    wire            pi_flag        ,       //并行数据有效标志信号

    output    reg                tx
);

localparam  BAUD_CNT_MAX    =   CLK_FREQ/UART_BPS   ; 


reg                 work_en;
reg            [15:0]  baud_cnt;
reg                  bit_flag;
reg            [3:0]   bit_cnt;

//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        work_en <= 1'b0;
    else    if(pi_flag == 1'b1)
        work_en <= 1'b1;
    else    if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
        work_en <= 1'b0;

//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        baud_cnt <= 16'd0;
    else    if((work_en == 1'b0) || (baud_cnt == BAUD_CNT_MAX))
        baud_cnt <= 16'd0;
    else    if(work_en == 1'b1)
        baud_cnt <= baud_cnt + 1'b1;

//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_flag <= 1'b0;
    else    if(baud_cnt == 16'd1)
        bit_flag <= 1'b1;
    else
        bit_flag <= 1'b0;

//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        bit_cnt <= 1'b0;
    else    if((bit_cnt == 4'd9) && (bit_flag == 1'b1))
        bit_cnt <= 1'b0;
    else    if((work_en == 1'b1) && (bit_flag == 1'b1))
        bit_cnt <= bit_cnt +1'b1;

//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
    if(sys_rst_n == 1'b0)
        tx <= 1'b0;//空闲状态时为高电平
    else    if(bit_flag == 1'b1)
        case (bit_cnt)
            0    :tx <= 1'b0;
            1    :tx <= pi_data[0];
            2    :tx <= pi_data[1];
            3    :tx <= pi_data[2];
            4    :tx <= pi_data[3];
            5    :tx <= pi_data[4];
            6    :tx <= pi_data[5];
            7    :tx <= pi_data[6];
            8    :tx <= pi_data[7];
            9    :tx <= 1'b1;
            default:tx <= 1'b1;
        endcase

endmodule

tb_uart_tx.v

`timescale 1ns/1ns
module tb_uart_tx();

reg                  sys_clk        ;
reg                  sys_rst_n    ;
reg        [7:0]     pi_data        ;
reg                pi_flag        ;

wire            tx;

initial
    begin
        sys_clk    = 1'b1;
        sys_rst_n <= 1'b0;
        #20
        sys_rst_n <= 1'b1;
    end

always #10 sys_clk = ~sys_clk;


initial
    begin
        pi_data <= 8'd0;
        pi_flag <= 1'b0;
        #200
        //数据0
        pi_data <= 8'd0;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据1
        pi_data <= 8'd1;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据2
        pi_data <= 8'd2;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据3
        pi_data <= 8'd3;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据4
        pi_data <= 8'd4;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据5
        pi_data <= 8'd5;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据6
        pi_data <= 8'd6;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;
        #(5208*10*20)
        //数据7
        pi_data <= 8'd7;
        pi_flag <= 1'b1;
        #20
        pi_flag <= 1'b0;

    end


uart_tx  
#(
    .UART_BPS       (9600),
    .CLK_FREQ       (50_000_000)
)
uart_tx_inst
(
    .sys_clk        (sys_clk),
    .sys_rst_n        (sys_rst_n),
    .pi_data        (pi_data),
    .pi_flag        (pi_flag),

    .tx             (tx )
);


endmodule

参考资料

[1] 野火《FPGA Verilog开发实战指南》:

[[野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档 (embedfire.com)](https://doc.embedfire.com/fpga/altera/ep4ce10_pro/zh/latest/index.html)

最后修改:2023 年 11 月 03 日
如果觉得我的文章对你有用,请随意赞赏