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

news2024/11/26 7:52:23

目录

  • 简介
  • 接口与引脚
  • 通信协议
  • 亚稳态
  • 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.coloradmin.cn/o/1805696.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

sick0s1.1 靶机实战

sick0s1.1 信息收集 nmap存活及端口&#xff1a; nmap服务扫描&#xff1a; web 80和8080都没有开放&#xff0c;&#xff0c;无法访问&#xff0c;gobuster等工具也跑不了&#xff0c;访问一下3128试试 根据端口服务扫描也能得知这是个http的代理服务器&#xff0c;&#x…

机器学习常见知识点 2:决策树

文章目录 决策树算法1、决策树树状图2、选择最优决策条件3、决策树算法过程→白话决策树原理决策树构建的基本步骤常见的决策树算法决策树的优缺点 【五分钟机器学习】可视化的决策过程&#xff1a;决策树 Decision Tree 关键词记忆&#xff1a; 纯度、选择最优特征分裂、熵、基…

SLAM小题目

1、最小二乘题目&#xff1a; 假设有三个WIFI热点&#xff0c;位置分别在(x1,y1), (x2,y2), (x3,y3), 移动端测量到每一个热点的距离L1,L2和L3&#xff0c;要求解移动端的位置. #include <iostream> #include <vector> #include <cmath> class Point { pub…

数据结构笔记 4 树和二叉树

二叉树和完全二叉树的区别&#xff1f; 二叉树和完全二叉树的主要区别在于它们的结构特性和节点排列方式&#xff1a; 1. **二叉树**&#xff1a; - 是一种数据结构&#xff0c;其中每个节点最多有两个子节点&#xff0c;通常称为左子节点和右子节点。 - 节点的子节点数量…

海思SD3403,SS928/926,hi3519dv500,hi3516dv500移植yolov7,yolov8(21)Yolov9s测试

四天前yolov9的作者终于开源了yolov9s和yolov9t模型。这个作者之前一直没开源t,s,只有c开始的,而且onnx转换后数据大小特别大,当时直接就放弃测试了。 另外之前代码有很明显的抄v5的痕迹。所以印象很不好。 现在总算是开源t,s模型,而且这里评估的结果上来看是好于yolov8的…

两款好用的IOS、Android图片处理应用

GIF 小助手 GIF工具包是一个简单实用的GIF动画编辑器&#xff0c;目前仅支持IOS平台。 使用该软件&#xff0c;可以将多个图像、视频和现场照片创建为gif。 主要功能&#xff1a; 多种输入源&#xff1a;用户可以将多个图片、视频或Livephoto转换成GIF动图。 编辑功能&#…

RDK X3(aarch64) 测试激光雷达思岚A1

0. 环境 - 亚博智能的ROSMASTER-X3 - RDK X3 1.0 0.1 资料 文档资料 https://www.slamtec.com/cn/Support#rplidar-a-series SDK https://github.com/slamtec/rplidar_sdk ROS https://github.com/slamtec/rplidar_ros https://github.com/Slamtec/sllidar_ros2 1. robostu…

windows上安装MongoDB,springboot整合MongoDB

上一篇文章已经通过在Ubuntu上安装MongoDB详细介绍了MongoDB的各种命令用法。 Ubuntu上安装、使用MongoDB详细教程https://blog.csdn.net/heyl163_/article/details/133781878 这篇文章介绍一下在windows上安装MongoDB&#xff0c;并通过在springboot项目中使用MongoDB记录用户…

Java:112-SpringMVC的底层原理(下篇)

这里继续续写上一章博客&#xff08;111章博客&#xff09;&#xff1a; Spring MVC 源码深度剖析&#xff1a; 既然我们自行写出了一个&#xff0c;那么我们可以选择看看mvc源码&#xff1a; 前端控制器 DispatcherServlet 继承结构&#xff1a; 前面我们知道mvc是操作同…

实验六、IPv4 地址的子网划分,第 2 部分《计算机网络》

你有没有发现&#xff0c;困的时候真的清醒不了。 目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 完成本练习之后&#xff0c;您应该能够确定给定 IP 地址和子网掩码的子网信息。 知道 IP 地址、网络掩码和子网掩码后&#xff0c;您应该能够确定有关该 IP 地…

【学术小白成长之路】02三方演化博弈(基于复制动态方程)期望与复制动态方程

从本专栏开始&#xff0c;笔者正式研究演化博弈分析&#xff0c;其中涉及到双方演化博弈分析&#xff0c;三方演化博弈分析&#xff0c;复杂网络博弈分析等等。 先阅读了大量相关的博弈分析的文献&#xff0c;总结了现有的研究常用的研究流程&#xff0c;针对每个流程进行拆解。…

cmake使用make和Ninja构建对比

前提 make和Ninja是两个常见的构建工具&#xff0c;在网上查阅了一些资料&#xff0c;说是Ninja比make构建速度要快很多。但是具体不知道快多少&#xff0c;所以趁着这次编译clang的机会&#xff0c;分享下它们在时间方面差多少。 步骤 下载llvm 参考llvm官网&#xff0c;这…

Shell脚本学习_内置命令

目录 1.内置命令介绍&#xff1a; 2.Shell内置命令&#xff1a;alias设置别名 3.Shell内置命令&#xff1a;echo输出字符串 4.Shell内置命令&#xff1a;read读取控制台输入 5.Shell内置命令&#xff1a;exit退出 6.Shell内置命令&#xff1a;declare设置变量 1.内置命令…

详解SM3算法加密流程(SM3加密算法一)

1、SM3 算法简介 SM3是中国国家密码管理局发布的消息摘要算法&#xff0c;首次发布于2010年&#xff0c;并于2016年发布了正式的国家标准GB/T 32905-2016。类似于国际上广泛应用的SHA-256算法&#xff0c;但有其独特的设计和实现细节。 该算法应用于各种数据加密和验证场景&…

【NI国产替代】产线综测仪:锂电池保护板测试仪,支持快速定制

• 精度等级01% • 支持直流电压、电流、nA 级待机电流电阻等&#xff0c;常规测试 • 支持过压、欠压、过冲、过放、过温,短路等&#xff0c;保护测试 • 通讯总线电平可编程&#xff0c;兼容多种 • 支持 SWD 或IIC 固件烧录 • 测试速度快&#xff0c;支持最多 24 通道…

Windows关闭自动更新最有效的方法

按WR打开电脑命令框输入“regedit”进入注册表 依次点击以下几个 右击新建一个“DWORD(32位)值”&#xff0c;命名为”FlightSettingsMaxPauseDays“ 右边选择十进制&#xff0c;左边填写暂停更新的天数 打开windows更新&#xff0c;进入高级选项 选择暂停更新的天数&#xff…

数据库(27)——多表查询——自连接

语法 SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件...; 自连接可以是内连接查询也可以是外连接查询。 演示 我新增了字段friend便于演示 查询所有人的名字以及他们的friend的人的名字&#xff1a; select a.name,b.name from user a,user b where a.friendb.id; 其…

L48---1637. 两点之间不包含任何点的最宽垂直区域(排序)---Java版

1.题目描述 2.思路 &#xff08;1&#xff09;返回两点之间内部不包含任何点的 最宽垂直区域 的宽度。 我的理解是相邻两个点&#xff0c;按照等差数列那样&#xff0c;后一个数减去相邻的前一个数&#xff0c;才能保证两数之间不含其他数字。 &#xff08;2&#xff09;所以&…

kali2022安装教程(附安装包)

第一步&#xff1a;下载镜像文件 百度网盘下载https://pan.baidu.com/s/1efRQGFTbq6Kgw9axLOmWzg?pwdemxf 第二步&#xff1a;打开Vmware 第三步&#xff1a;进行各项配置 创建新的虚拟机&#xff0c;选择高级&#xff0c;然后下一步 直接默认下一步 选择稍后安装然后下…

CUDA 编程(1):使用Grid 和 Block分配线程

1 介绍 1.1 Grid 和 Block 概念 核函数以线程为单位进行计算的函数,cuda编程会涉及到大量的线程(thread),几千个到几万个thread同时并行计算,所有的thread其实都是在执行同一个核函数。 对于核函数(Kernel),一个核函数一般会分配1个Grid, 1个Grid又有很多个Block,1个Bloc…