野火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

仿真

相关推荐
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner2 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20202 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子3 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun4 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议