野火FPGA跟练(四)——串口RS232、亚稳态

目录


简介

  • UART:Universal Asynchronous Receiver/Transmitter,异步串行通信接口。发送数据时并行转串行,接收数据时串行转并行。
  • RS232:UART包括多种接口标准规范和总线标准规范,RS232为其一,还有RS499、RS423、RS422、RS485等。

接口与引脚

重点关注RXD、TXD即可。

通信协议

  • 帧结构:包含8bit有效数据和起始位、停止位。空闲状态是高电平,起始位低电平,停止位高电平。低位数据先发,后发高位。

    假设我要传输字符"1",对应的ASCII码值是0x31,即"0011 0001" ,低位先发,那么我的这帧数据波形为 0 1000 1100 1

  • 波特率:假设波特率为9600Bps,传输一个码元(一个码元在这里代表一个二进制数,就是1bit数据)需要1/9600秒。假设系统时钟50MHz(周期为20ns),那么传输1bit数据所需的时间为 (1/9600)/(20*e-9) = 5208.33 ≈ 5208 个时钟周期。

亚稳态

  • 产生原因:

    触发器采集输入信号时需要一定的建立时间和保持时间,如果在这一期间输入信号发生变化,那么触发器将无法稳定输出,导致输出信号在高低电平间快速振荡,即进入亚稳态。

  • 不良影响:

    如果处于亚稳态的信号直接接入组合逻辑电路,会导致亚稳态在整个系统中传递,从而影响整个电路的运行,导致不稳定。

  • 解决方案:

    单比特信号可以采用"打两拍",多比特信号可以采用异步FIFO、格雷码、握手。

RS232接收模块

模块框图

输入输出描述:

  1. 时钟:50MHz,每10ns翻转一次
  2. 复位:高电平异步复位
  3. RX:接收的串口数据
  4. DATA:处理后的数据,并行输出
  5. FLAG:置高时代表已处理好一帧数据,只维持一个时钟周期

时序波形

示意时序图,以9600波特率为例。

时序图分析:

  • RX_REG
    • RX_REG1:是将 RX 同步到时钟信号上。
    • RX_REG2、3:打两拍,避免亚稳态。
  • START_FLAG 标志数据接收的开始、WORK_EN 标志数据接收的状态
    • START_FLAG:这里使用的是组合方式赋值,通过 RX_REG2、3 检测 RX_REG 的下降沿。由于 RX_REG 的数据位中也有可能存在下降沿,所以 START_FLAG 置高的前提条件还应有 WORK_EN 为 0 。
    • WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
  • BAUD_CNT、BIT_FLAG
    • BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
    • BIT_FLAG:在每比特数据传输时的中间时刻拉高
  • RX_DATA
    由于是低位数据先发,所以 RX_REG 的数据存在 RX_DATA 的高位,每次更新时 RX_DATA 右移,实现数据的串行转并行。RX_DATA 的值在 BAUD_CNT 为 1-8 并且 BIT_FLAG 为高时采集 RX_REG。

RTL 代码

c 复制代码
module RS232_RX
#(
    parameter UART_BPS = 'd9600,
    parameter CLK_FREQ = 'd50_000_000
)
(
    input   wire         clk,
    input   wire         rst,
    input   wire         rx,
    output  reg   [7:0]  data,
    output  reg          flag
    );
    
    parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
    
    reg rx_reg1;
    reg rx_reg2;
    reg rx_reg3;
    wire start_flag;
    reg work_en;
    reg [16:0] baud_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    reg [7:0] rx_data;
    reg data_flag;
    
    // 赋值rx_reg1
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg1 <= 1'b1;
        else
            rx_reg1 <= rx;
    end
    // 赋值rx_reg2
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg2 <= 1'b1;
        else
            rx_reg2 <= rx_reg1;
    end
    // 赋值rx_reg3
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            rx_reg3 <= 1'b1;
        else
            rx_reg3 <= rx_reg2;
    end
    // 赋值start_flag
    assign start_flag = ((rx_reg2 == 1'b0) && (rx_reg3 == 1'b1));
    // 赋值work_en
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            work_en <= 1'b0;
        else if(start_flag == 1'b1)
            work_en <= 1'b1;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            work_en <= 1'b0;
        else
            work_en <= work_en;
    end
    // 赋值baud_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            baud_cnt <= 17'b0;
        else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
            baud_cnt <= 17'b0;
        else
            baud_cnt <= baud_cnt + 1'b1;
    end 
    // 赋值bit_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_flag <= 1'b0;
        else if(baud_cnt == BAUD_CNT_MAX / 2 - 1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;
    end  
    // 赋值bit_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_cnt <= 4'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            bit_cnt <= 4'b0;
        else if(bit_flag == 1'b1)
            bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end
    // 赋值rx_data
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            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]};
        else
            rx_data <= rx_data;
    end
    // 赋值data_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            data_flag <= 1'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd8))
            data_flag <= 1'b1;
        else
            data_flag <= 1'b0;
    end
    // 赋值data
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            data <= 8'd0;
        else if(data_flag == 1'b1)
            data <= rx_data;
        else
            data <= data;
    end
    // 赋值flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            flag <= 1'b0;
        else
            flag <= data_flag;
    end
endmodule

易错点

如果数据是多位的,赋值时需要注意其位宽大小。如果一个多位宽的数据赋值为"1'b0",这将只赋值给这个数据的最低位,其他位将保留原值,与本意相悖。

Testbench 代码

c 复制代码
`timescale 1ns / 1ps
module tb_RS232_RX();

    reg     clk;
    reg     rst;
    reg     rx;
    wire [7:0]  data;
    wire    flag;
    
    // 初始化clk和rst
    initial begin
        clk <= 1'b0;
        rst <= 1'b1;
        #60;
        rst <= 1'b0;
    end 
    always #10 clk = ~clk;
    
    // 初始化rx
    initial begin
        rx <= 1'b1;
        #100;
        rx_bit(8'b1001_0001);
        rx_bit(8'b0101_0010);
        rx_bit(8'b1110_0101);
        rx_bit(8'b0110_1000);
        rx_bit(8'b0110_1011);
        rx_bit(8'b0011_0110);
        rx_bit(8'b0001_1110);
    end
    
    // rx_bit函数,生成串行数据
    task rx_bit(
        input [7:0] rx_data
    );
        integer i;
        for(i = 0; i < 10; i = i + 1) begin
            case(i)
                0: rx <= 1'b0;
                1: rx <= rx_data[0];
                2: rx <= rx_data[1];
                3: rx <= rx_data[2];
                4: rx <= rx_data[3];
                5: rx <= rx_data[4];
                6: rx <= rx_data[5];
                7: rx <= rx_data[6];
                8: rx <= rx_data[7];
                9: rx <= 1'b1;
            endcase
            #(5208*20);   
        end         
    endtask
    
    // 实例化模块
    RS232_RX #(
        .UART_BPS(9600),
        .CLK_FREQ(50_000_000)
    )tb_RS232_RX
    (
        .clk      (clk  ),
        .rst      (rst  ),
        .rx       (rx   ),
        .data     (data ),
        .flag     (flag )
    );
endmodule

仿真

RS232发送模块

模块框图

输入输出描述:

  1. 时钟:50MHz,每10ns翻转一次
  2. 复位:高电平异步复位
  3. DATA:待发送的并行数据
  4. FLAG:数据标志位
  5. TX:输出处理后的串行数据

时序波形

示意时序图,以9600波特率为例。

时序图分析:

  • WORK_EN:WORK_EN 通过判断 START_FLAG 的状态而置高,通过判断 BIT_CNT、BIT_FLAG 的状态而置低,其余时刻保持不变。
  • BAUD_CNT、BIT_FLAG
    • BAUD_CNT:BAUD_CNT 只在 WORK_EN 为高时计数
    • BIT_FLAG:在 BAUD_CNT 计数为1时刻拉高

RTL 代码

c 复制代码
module RS232_TX
#(
    parameter UART_BPS = 'd9600,
    parameter CLK_FREQ = 'd50_000_000
)
(
    input wire clk,
    input wire rst,
    input wire [7:0] data,
    input wire flag,
    output reg tx
    );
    
    parameter BAUD_CNT_MAX = CLK_FREQ / UART_BPS;
    
    reg work_en;
    reg [16:0] baud_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    
    // 赋值work_en
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            work_en <= 1'b0;
        else if(flag == 1'b1)
            work_en <= 1'b1;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
            work_en <= 1'b0;
        else
            work_en <= work_en;
    end
    // 赋值baud_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            baud_cnt <= 17'b0;
        else if((baud_cnt == BAUD_CNT_MAX - 1'b1) || (work_en == 1'b0))
            baud_cnt <= 17'b0;
        else
            baud_cnt <= baud_cnt + 1'b1;
    end
    // 赋值bit_flag
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_flag <= 1'b0;
        else if(baud_cnt == 17'b1)
            bit_flag <= 1'b1;
        else
            bit_flag <= 1'b0;
    end
    // 赋值bit_cnt
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            bit_cnt <= 4'b0;
        else if((bit_flag == 1'b1) && (bit_cnt == 4'd10))
            bit_cnt <= 4'b0;
        else if(bit_flag == 1'b1)
            bit_cnt <= bit_cnt + 1'b1;
        else
            bit_cnt <= bit_cnt;
    end
    // 赋值tx
    always@(posedge clk or posedge rst) begin
        if(rst == 1'b1)
            tx <= 1'b1;
        else begin
            case(bit_cnt)
                4'd1: tx <= 1'b0;
                4'd2: tx <= data[0];
                4'd3: tx <= data[1];
                4'd4: tx <= data[2];
                4'd5: tx <= data[3];
                4'd6: tx <= data[4];
                4'd7: tx <= data[5];
                4'd8: tx <= data[6];
                4'd9: tx <= data[7];
                4'd10: tx <= 1'b1;
                default: tx <= 1'b1;
            endcase
        end
    end
endmodule

Testbench 代码

c 复制代码
`timescale 1ns / 1ps
module tb_RS232_TX();
    
    reg clk;     
    reg rst;    
    reg [7:0] data;
    reg flag;
    wire tx;       

    // 初始化clk和rst
    initial begin
        clk <= 1'b0;
        rst <= 1'b1;
        #60;
        rst <= 1'b0;
    end 
    always #10 clk = ~clk;
    
    // 初始化data和flag
    initial begin
        data <= 8'd0;
        flag <= 1'd0;
        #200;
        // 数据1
        data <= 8'b0010_1011;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
        // 数据2
        data <= 8'b0111_0101;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
        // 数据3
        data <= 8'b1010_0001;
        flag <= 1'd1;
        #20;
        flag <= 1'd0;
        #(5208*12*20);
    end
    
    // 实例化模块
    RS232_TX #(
        .UART_BPS(9600),
        .CLK_FREQ(50_000_000)
    )tb_RS232_TX
    (
        .clk    (clk),       
        .rst    (rst),       
        .data   (data),
        .flag   (flag),      
        .tx     (tx)      
    );
endmodule

仿真

相关推荐
IM_DALLA5 小时前
【Verilog学习日常】—牛客网刷题—Verilog进阶挑战—VL25
学习·fpga开发·verilog学习
辣个蓝人QEX6 小时前
【FPGA开发】Modelsim如何给信号分组
fpga开发·modelsim·zynq
li星野9 小时前
ZYNQ:点亮LED灯
fpga开发·zynq·7010
9527华安9 小时前
FPGA实现PCIE视频采集转HDMI输出,基于XDMA中断架构,提供3套工程源码和技术支持
fpga开发·音视频·pcie·xdma·ov5640·hdmi
乌恩大侠10 小时前
【Xcode Command Line Tools】安装指南
macos·fpga开发·c
apple_ttt11 小时前
从零开始讲PCIe(9)——PCIe总线体系结构
fpga开发·fpga·pcie
Little Tian14 小时前
信号用wire类型还是reg类型定义
fpga开发
apple_ttt1 天前
从零开始讲PCIe(6)——PCI-X概述
fpga开发·fpga·pcie
水饺编程1 天前
【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,1-2
linux·嵌入式硬件·fpga开发
apple_ttt1 天前
从零开始讲PCIe(5)——66MHZ的PCI总线与其限制
fpga开发·fpga·pcie