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

目录

  • 简介
  • 接口与引脚
  • 通信协议
  • 亚稳态
  • RS232接收模块
    • 模块框图
    • 时序波形
    • RTL 代码
    • 易错点
    • Testbench 代码
    • 仿真
  • RS232发送模块
    • 模块框图
    • 时序波形
    • RTL 代码
    • Testbench 代码
    • 仿真


简介

  • 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 代码

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 代码

`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 代码

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 代码

`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

仿真

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/693814.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Sentinel1.8.6更改配置同步到nacos(项目是Gateway)

本次修改的源码在&#xff1a;https://gitee.com/stonic-open-source/sentinel-parent 一 下载源码 地址&#xff1a;https://github.com/alibaba/Sentinel/releases/tag/1.8.6 二 导入idea&#xff0c;等待maven下载好各种依赖 三 打开sentile-dashboard这个模块&#xf…

HTML开发 Vue2.x + Element-UI 动态生成表单项并添加表单校验

基于vue2.x 和element-ui 动态生成表单项并添加表单校验&#xff1b; 1、需求问题 如下图&#xff0c;项目有个需求&#xff0c;点击添加按钮&#xff0c;新增一行设备信息&#xff0c;且每项信息必填&#xff1b; 2、代码 看到这个需求&#xff0c;首先想到要使用v-for的形…

大众汽车裁员加速,38万元遣散费起步

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 几周前&#xff0c;大众汽车宣布了一项新的裁员计划。 一、裁员行动与额外福利并行 大众汽车近期在裁员行动上取得了显著进展&#xff0c;其遣散…

基于I2C协议的OLED显示(利用U82G库)

目录 一、I2C协议的基本原理和时序协议I2C通信协议的原理I2C时序基本单元I2C时序 二、建立工程RCC配置TIM1配置时钟树配置工程配置 三、U8g2移植精简u8g2_d_setup.c精简u8g2_d_memory.c编写移植函数stm32_u8g2.hstm32_u8g2.c 四、实验1.U82G的demo例程2.显示网名昵称中文取模步…

Pytorch 从零实现 Transformer

前言 之前虽然了解过 Transformer 架构&#xff0c;但是没有自己实现过。 最近阅读 transformers 库中 Llama 模型结构&#xff0c;于是想试着亲手实现一个简单的 Transformer。 在实现过程中加深了理解&#xff0c;同时发现之前阅读 Llama 中一些错误的地方&#xff0c;因此…

蓝桥杯--跑步计划

问题描述 小蓝计划在某天的日期中出现 11 时跑 55 千米&#xff0c;否则只跑 11 千米。注意日期中出现 11 不仅指年月日也指星期。 请问按照小蓝的计划&#xff0c;20232023 年小蓝总共会跑步锻炼多少千米?例如&#xff0c;55 月 11 日、11 月 1313 日、1111 月 55 日、44 月…

Linux:基本指令

文章目录 ls指令pwd指令cd指令touch指令mkdir指令rmdir指令 && rm指令cp指令man指令echo指令输出重定向追加重定向 cat指令输入重定向 mv指令which指令alias指令more && less指令head && tail指令事件相关的指令date显示时间戳 cal指令find指令grep指令…

c++之旅第十弹——IO流

大家好啊&#xff0c;这里是c之旅第十弹&#xff0c;跟随我的步伐来开始这一篇的学习吧&#xff01; 如果有知识性错误&#xff0c;欢迎各位指正&#xff01;&#xff01;一起加油&#xff01;&#xff01; 创作不易&#xff0c;希望大家多多支持哦&#xff01; 一.流的概念&…

星火秘境游戏开发链游app定制开发源码部署

星火秘境是一款神秘而充满冒险的游戏&#xff0c;开发这样一款游戏需要综合考虑多个方面&#xff0c;包括游戏设计、美术设计、程序开发、音效制作等。下面我将简要介绍一下游戏开发和链游app搭建的一般流程&#xff1a; 游戏设计&#xff1a; 确定游戏类型&#xff1a;星火秘…

雷电模拟器中控实现,直通源码

目录 前言 开发 需求 初始环境 UI搭建 功能实现 前言 本篇为易语言雷电模拟器中控项目实现操作&#xff0c;一般用于&#xff1a;脚本开发多线程模拟操作等起始模板框架&#xff0c;使用易语言原因为其前后端一体化&#xff0c;对于脚本开发而言更为方便。 开发 需求 以…

每天壁纸不重样~下载必应每日图片

下载必应每日图片 必应不知道你用过没有你下载过必应的图片没有你又没搜索过桌面图片你是不是安装过桌面图片软件你是不是为找一个好看的图片下载过很多桌面软件 必应每日图片 必应每天都会有一张不同的风景图片&#xff0c;画质清晰&#xff0c;而且不收费可以下载使用 但…

MySQL之多表查询—列子查询

一、引言 标量子查询上篇博客已学习。接下来这篇博客学习子查询的第二种形式——列子查询 列子查询 子查询返回的结果是一列&#xff08;当然也可以是多行)&#xff0c;这种子查询称为列子查询。 列子查询可以使用的操作符 IN、NOT IN 、ANY&#xff08;any&#xff09;、SOME…

计算机组成结构—IO系统概述

目录 一、I/O 系统的发展 1. 早期阶段 2. 接口模块和 DMA 阶段 3. 通道结构阶段 4. 处理机阶段 二、I/O 系统的组成 1. I/O 软件 2. I/O 硬件 三、I/O 设备 1. I/O 设备分类 2. I/O 设备的组成 在计算机中&#xff0c;除 CPU 和主存两大模块之外&#xff0c;第三个重…

C#中使用Mysql批量新增数据 MySqlBulkCopy

在C#中使用MySqlBulkCopy类来批量复制数据到MySQL数据库&#xff0c;首先需要确保你的项目中已经引用了MySQL Connector。以下是使用MySqlBulkCopy的基本步骤&#xff1a; 1.安装MySQL Connector。 可以通过NuGet安装MySQL Connector&#xff1a; 2.在代码中引用必要的命名空间…

物资材料管理系统建设方案(Word)—实际项目方案

二、 项目概述 2.1 项目背景 2.2 现状分析 2.2.1 业务现状 2.2.2 系统现状 三、 总体需求 3.1 系统范围 3.2 系统功能 3.3 用户分析 3.4 假设与依赖关系 四、 功能需求 4.4.11.7 非功能性需求 五、 非功能性需求 5.1 用户界面需求 5.2 软硬件环境需求 5.3 产品质量需求 5.4 接口…

[线程与网络] Java虚拟机常考面试题(线程与网络完结)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;线程与…

使用 GPT-4 创作高考作文 2024年

使用 GPT-4 创作高考作文 2024年 使用 GPT-4 创作高考作文&#xff1a;技术博客指南 &#x1f914;✨摘要引言正文内容&#xff08;详细介绍&#xff09; &#x1f4da;&#x1f4a1;什么是 GPT-4&#xff1f;高考作文题目分析 ✍️&#x1f9d0;新课标I卷 人类智慧的进步&…

atomic特质的局限性

为什么在实际的 Objective-C 开发中, 几乎所有的属性都声明为 nonatomic ? 声明为 atomic 的属性我是真的没见过 在实际的 Objective-C 开发中&#xff0c;大多数属性通常声明为 nonatomic&#xff0c;主要原因包括性能考虑和常见的设计模式。具体原因如下&#xff1a; 性能问…

openai 前员工释放出关于AGI的前世今生和未来发展趋势的详细报告

目录 1.引言2.AGI的临近3.投资与工业动员4.国家安全与AI竞赛5.技术挑战与机遇6.项目与政策7.结语8.原文PDF链接PS.扩展阅读ps1.六自由度机器人相关文章资源ps2.四轴机器相关文章资源ps3.移动小车相关文章资源 1.引言 2024年&#xff0c;我们站在了一个全新的科技前沿。在这篇文…

【wiki知识库】06.文档管理页面的添加--前端Vue部分

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;今日目标 二、&#x1f43b;前端Vue模块的改造 BUG修改 1.wangeditor无法展示问题 2.弹窗无法正常关闭问题 2.1 添加admin-doc.vue 2.1.1 点击admin-ebook中的路由跳转到admin-doc 2.2.2 进入…