UART设计

news2024/11/18 18:20:41

一、UART通信简介

通用异步收发器,

特点:串行、异步、全双工通信

优点:通信线路简单,传输距离远

缺点:传输速度慢

数据传输速率:波特率(单位:baud,波特)

常见的波特率为:1200、2400、4800、19200、38400、57600、115200

最常用的:9600、115200                

数据通信格式:1个数据位+n个数据位+1个校验位+1个结束位

其中n个数据位:通常为8位,即1个字节

空闲位:当总线处于空闲状态时信号线的状态为1,表示当前线路没有进行数据传输。

起始位:每开始一次通信时发送方先发出一个逻辑0的信号,表示传输字符的开始。(因为总线空闲时为高电平,所以开始一次通信时先发送一个明显区别于空闲状态的信号,即低电平)

数据位:起始位之后就是我们所要传输的数据,数据位可以是5,6,7,8,9位等,构成一个字符。先发送最低位,最后发送最高位

奇偶校验位

数据位加上这一位后,使得1的位数应为偶数(偶校验)或奇数(奇校验)。

串口校验的几种方式:

  1. 无校验
  2. 奇校验:如果数据位中“1”的数目是偶数,则校验位为1,如果“1”的数目是奇数,则校验位为0
  3. 偶校验:如果数据位中1的数目是偶数,则校验位为0,如果是奇数,校验位为1
  4. Mark parity:校验位始终为1(不常用)
  5. Parity:校验位始终为0(不常用)

停止位:

它是一个字符数据的结束标志。可以是1位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备之间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟的机会。停止位个数越多,数据传输越稳定,但是数据传输速度也越慢

传输时间:计数=时钟频率除以波特频率

二、Verilog实现:

1.UART_TX设计框图

信号

位宽

类型

功能描述

clk

1bit

input

工作时钟,频率50MHz

tx_data

8bit

input

发送数据

tx_flag

1bit

input

发送数据的有效标志信号

tx

1bit

output

串口发送信号

Verilog代码:

uart_tx:

module uart_tx
#(
    parameter  UART_BPS = 'd9600, //串口波特率
    parameter CLK_FREQ  = 'd50_000_000  //时钟频率

)

(
    input wire clk, //系统时钟
    input wire rstn,    //全局复位
    input wire [7:0] tx_data,   //发送8bit数据
    input wire tx_flag, //发送数据有效标志信号
    output reg tx //串转并后的1bit数据
);

    localparam cnt_max = CLK_FREQ / UART_BPS;
    reg [12:0] bd_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    reg work_en;    //接收数据工作使能信号

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

    //bd_cnt:波特率计数器计数,从0计数到5207
    always @(posedge clk or negedge  rstn) begin
        if(rstn == 1'b0)
            bd_cnt <= 13'b0;
        else if((bd_cnt == cnt_max - 1) ||(work_en == 1'b0))
            bd_cnt <= 13'b0;
        else if(work_en == 1'b1)
            bd_cnt <= bd_cnt + 1'b1;   
    end

    //bit_flag : 当bd_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
    always @(posedge clk or negedge rstn) begin
        if(rstn == 1'b0)
            bit_flag <= 1'b0;
        else if(bd_cnt == 13'd1)
            bit_flag <= 1'd1;
        else 
            bit_flag <= 1'b0;
    end

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

    end

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


endmodule

testbench:

module tb_uart_tx();

    reg clk;
    reg rstn;
    reg [7:0] tx_data;
    reg tx_flag;
    wire tx;

    //初始化系统时钟,全局复位
    initial begin
        clk = 1'b1;
        rstn = 1'b0;
        #20;
        rstn <= 1'b1;
    end

  //模拟发送8次数据,分别为0~7
    initial begin
        tx_data <= 8'b0;
        tx_flag <= 1'b0;
        #200
     //发送数据0
        tx_data <= 8'd0;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
    // 每发送1bit数据需要5208个时钟周期,一帧数据为10bit
    //所以需要数据延时(5208*20*10)后再产生下一个数据
        #(5208*20*10);
     
     //发送数据1
        tx_data <= 8'd1;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

     //发送数据2
        tx_data <= 8'd2;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

        //发送数据3
        tx_data <= 8'd3;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

        //发送数据4
        tx_data <= 8'd4;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

        //发送数据5
        tx_data <= 8'd5;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

       //发送数据6
        tx_data <= 8'd6;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
        #(5208*20*10);

        //发送数据7
        tx_data <= 8'd7;
        tx_flag <= 1'b1;
        #20
        tx_flag <= 1'b0;
       
    end

    //clk:每10ns电平翻转一次,产生一个50MHz的时钟信号
    always #10 clk = ~clk;

    uart_tx uart_tx_inst(
        .clk(clk),
        .rstn(rstn),
        .tx_data(tx_data),
        .tx_flag(tx_flag),
        .tx(tx)
    );

endmodule

仿真截图:

接收模块

uart_rx的设计框图:

信号

位宽

类型

功能描述

clk

1bit

Input

工作时钟,频率50MHz

rstn

1bit

Input

复位信号,低电平有效

rx

1bit

Input

串口接收信号

rx_data

8bit

output

串口接收后转成的8bit数据

rx_flag

1bit

output

串口接收后转成的8bit数据有效标志

Verilog代码:

module uart_rx
#(
    parameter UART_BPS = 'd9600,
    parameter CLK_FREQ = 'd50_000_000
)

(
    input wire clk,
    input wire rstn,
    input wire rx,  //串口接收数据
    output reg[7:0] rx_data, //串转并后的8bit数据
    output reg rx_flag //串转并后的数据有效标志信号
);

    localparam cnt_max = CLK_FREQ / UART_BPS;

    reg rx_reg1;
    reg rx_reg2;
    reg rx_reg3;
    
    reg start_nedge;
    reg work_en;
    reg [12:0] bd_cnt;
    reg bit_flag;
    reg [3:0] bit_cnt;
    reg [7:0] data;
    reg flag;

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

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

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

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

    //bd_cnt:波特率计数器计数,从0计数到5207
    always @(posedge clk or negedge rstn) begin
        if(rstn == 1'b0)
            bd_cnt <= 13'b0;
        else if((bd_cnt == cnt_max - 1)||(work_en == 1'b0))
            bd_cnt <= 13'b0;
        else if(work_en == 1'b1)
            bd_cnt <= bd_cnt + 1'b1;
    end

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

    //bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
    //都接收完成后计数器清零
    always @(posedge clk or negedge rstn) begin
        if(rstn == 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;
    end

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

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

    //rx_data:输出完整的8位有效数据
    always @(posedge clk or negedge rstn) begin
        if(rstn == 1'b0)
            rx_data <= 8'b0;
        else if(rx_flag == 1'b1)
            rx_data <= data;
    end

    //rx_flag:输出数据有效标志(比flag延后一个时钟周期,为了和rx_data同步)
    always @(posedge clk or rstn) begin
        if(rstn == 1'b0)
            rx_flag <= 1'b0;
        else
            rx_flag <= flag;
    end

endmodule

tb_uart_rx:

module tb_uart_rx();

    reg clk;
    reg rstn;
    reg rx;
    wire [7:0] rx_data;
    wire rx_flag;

    //初始化系统时钟,全局复位和输入信号
    initial begin
        clk = 1'b1;
        rstn <= 1'b0;
        rx <= 1'b1;

        #20
        rstn <= 1'b1;
    end

    //模拟发送8次数据,分别为0~7
    initial begin
        #200
        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 clk = ~clk;

    //定义一个名为rx_bit的任务,每次发送的数据有10位
    //data的值分别为0~7由i的值传递进来
    //任务以task开头, 后面紧跟任务名,调用时使用

    task rx_bit(
        input [7:0] data
    );
    integer  i;
    //用for循环产生一帧数据,for括号中最后执行的内容只能写i=i+1;

    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 //任务以endtask结束

    uart_rx uart_rx_inst(
        .clk(clk),
        .rstn(rstn),
        .rx(rx),
        .rx_data(rx_data),
        .rx_flag(rx_flag)
    );

endmodule

仿真截图

参考资料:

5. 串口rs232 — [野火]FPGA Verilog开发实战指南——基于Altera EP4CE10 征途Pro开发板 文档

FPGA协议篇:最简单且通用verilog实现UART协议 - 知乎

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

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

相关文章

重装系统之后,电脑连网卡都没反应怎么办?

前言 有些电脑比较奇葩&#xff0c;安装完成之后会出现网卡连驱动都没有&#xff0c;这时候要安装电脑驱动可是真的烦躁。怎么下手呢&#xff1f; 如果确定电脑的网卡型号还好&#xff0c;直接找个电脑下载个对应的网卡驱动&#xff0c;用U盘复制过去就能安装。 但如果不知道…

golang 选择排序

学习笔记&#xff5e; // Author sunwenbo // 2024/4/6 21:49 package mainimport "fmt"/* 选择排序基本介绍选择式排序也属于内部排序法&#xff0c;是从预排序的数据中按指定的规则选出某一元素&#xff0c;经过和其他元素重整&#xff0c;再依原则交换位置后达到…

Java集合——Map、Set和List总结

文章目录 一、Collection二、Map、Set、List的不同三、List1、ArrayList2、LinkedList 四、Map1、HashMap2、LinkedHashMap3、TreeMap 五、Set 一、Collection Collection 的常用方法 public boolean add(E e)&#xff1a;把给定的对象添加到当前集合中 。public void clear(…

qt-C++笔记之QLabel加载图片

qt-C笔记之QLabel加载图片 —— 2024-04-06 夜 code review! 文章目录 qt-C笔记之QLabel加载图片0.文件结构1.方法一&#xff1a;把图片放在项目路径下&#xff0c;在 .pro 文件中使用 DISTFILES添加图片文件1.1.运行1.2.qt_test.pro1.3.main.cpp 2.方法二&#xff1a;不在 .pr…

hydra九头蛇

一、hydra简介 Hydra是一款非常强大的暴力破解工具&#xff0c;它是由著名的黑客组织THC开发的一款开源暴力破解工具。Hydra是一个验证性质的工具&#xff0c;主要目的是&#xff1a;展示安全研究人员从远程获取一个系统认证权限。 目前该工具支持以下协议的爆破&#xff1a; A…

快速理解JS中的原型和原型链

快速理解JS中的原型和原型链 在我们学习JS的过程中&#xff0c;我们总会接触到一些词&#xff1a;“原型”&#xff0c;“原型链”。那么今天我就来带大家来学习学习原型和原型链的知识吧&#xff01; 在开始之前&#xff0c;我们明确一下我们接下来想要学习的目标&#xff1a…

Redis从入门到精通(八)Redis实战(五)分布式锁误删与原子性问题、Redisson

↑↑↑请在文章开头处下载测试项目源代码↑↑↑ 文章目录 前言4.4 分布式锁4.4.4 分布式锁的误删问题4.4.4.1 问题说明4.4.4.2 解决方案4.4.4.3 代码实现 4.4.5 Redis分布式锁的原子性问题4.4.5.1 问题说明4.4.5.2 解决方案4.4.5.3 代码实现 4.4.6 分布式锁小结 4.5 分布式锁-R…

C++要点细细梳理(下)(内存分配、异常处理、template和文件读写)

4. 类动态内存分配 4.1 C语言动态内存分配&#xff1a;malloc和free 4.2 C动态内存分配&#xff1a;new和delete 思考&#xff1a;定义一个对象和定义一个普通变量有何区别? 普通变量:分配足够空间即可存放数据对象:除了需要空间&#xff0c;还要构造/析构 类比&#xff1a;…

windows下部署mongoDB

目录 1. 下载zip安装包并解压&#xff1a;Download MongoDB Community Server | MongoDB 2. 在解压后的文件夹中新建文件夹data及下级文件夹db和log 3. 新建一个mongod.cfg文件&#xff0c;并配置以下内容 4. 在cmd中启动mongodb&#xff0c;并进行验证 5. 部署到本地服务器…

物联网实战--驱动篇之(二)Modbus协议

目录 一、modbus简介 二、功能码01、02 三、modbus解析 四、功能码03、04 五、功能码05 六、功能码06 七、功能码16 一、modbus简介 我们在网上查阅modbus的资料发现很多很杂&#xff0c;modbus-RTU ASCII TCP等等&#xff0c;还有跟PLC结合的&#xff0c;地址还分1开…

WCH恒沁单片机-CH32V307学习记录2----FreeRTOS移植

RISC-V 单片机 FreeRTOS 移植 前面用了 5 篇博客详细介绍了 FreeRTOS 在 ARM Cortex-M3 MCU 上是如何运行的。 FreeRTOS从代码层面进行原理分析系列 现在我直接用之前的 RISC-V MCU 开发板子&#xff08;CH32V307VCT6&#xff09;再次对 FreeRTOS 进行移植&#xff0c;其实也…

《图解Vue3.0》- 调试

如何对vue3项目进行调试 调试是开发过程中必备的一项技能&#xff0c;掌握了这项技能&#xff0c;可以很好的定义bug所在。一般在开发vue3项目时&#xff0c;有三种方式。 代码中添加debugger;使用浏览器调试&#xff1a;sourcemap需启用vs code 调试&#xff1a;先开启node服…

Android APP加固利器:深入了解混淆算法与混淆配置

Android APP 加固是优化 APK 安全性的一种方法&#xff0c;常见的加固方式有混淆代码、加壳、数据加密、动态加载等。下面介绍一下 Android APP 加固的具体实现方式。 混淆代码 使用 ipaguard工具可以对代码进行混淆&#xff0c;使得反编译出来的代码很难阅读和理解&#xff…

VMwear桥接网络正确配置+静态IP设置

1.桥接网络配置 很多时候在VMware安装完虚拟机之后&#xff0c;会发现配置的桥接网络没有起作用&#xff0c;如果是Linux下输入ifconfig发现只有ipv6的地址而没有ipv4&#xff0c;说明没有桥接没有启用成功&#xff0c;需要按照以下方式来设置 在VMware的左上角打开编辑&#…

注解式 WebSocket - 构建 群聊、单聊 系统

目录 前言 注解式 WebSocket 构建聊天系统 群聊系统&#xff08;基本框架&#xff09; 群聊系统&#xff08;添加昵称&#xff09; 单聊系统 WebSocket 作用域下无法注入 Spring Bean 对象&#xff1f; 考虑离线消息 前言 很久之前&#xff0c;咱们聊过 WebSocket 编程式…

Nuxt 3 项目中配置 Tailwind CSS

官方文档&#xff1a;https://www.tailwindcss.cn/docs/guides/nuxtjs#standard 安装 Tailwind CSS 及其相关依赖 执行如下命令&#xff0c;在 Nuxt 项目中安装 Tailwind CSS 及其相关依赖 npm install -D tailwindcss postcss autoprefixerpnpm install -D tailwindcss post…

字符迁移.

3.字符迁移【算法赛】 - 蓝桥云课 (lanqiao.cn) 问题描述 小蓝最近获得了一个长度为N 的字符串S&#xff0c;他对它爱不释手。 小桥为了考验小蓝对字符串的处理能力&#xff0c;决定给他提出一个挑战&#xff0c;她会进行 Q次操作&#xff1a; 每次操作给定三个整数 l , r , k …

Vue3调试

如何对vue3项目进行调试 调试是开发过程中必备的一项技能&#xff0c;掌握了这项技能&#xff0c;可以很好的定义bug所在。一般在开发vue3项目时&#xff0c;有三种方式。 代码中添加debugger;使用浏览器调试&#xff1a;sourcemap需启用vs code 调试&#xff1a;先开启node服…

夯实智慧新能源数据底座,TiDB Serverless 在 Sandisolar+ 的应用实践

本文介绍了 SandiSolar通过 TiDB Serverless 构建智慧新能源数据底座的思路与实践。作为一家致力于为全球提供清洁电力解决方案的新能源企业&#xff0c;SandiSolar面临着处理大量实时数据的挑战。为了应对这一问题&#xff0c;SandiSolar选择了 TiDB Serverless 作为他们的数据…

PostgrerSQL基本使用与数据备份

前言 上篇了解了 PostgrerSQL 数据库的部署PostgreSQL关系型数据库介绍与部署-CSDN博客&#xff0c;本篇将继续就其基本操作、备份与还原内容做相关介绍。 目录 一、数据库的操作 1. 本机登录 2. 开启远程登录 2.1 开放远程端口 2.2 编辑配置文件 2.3 修改配置密码 2.…