首页 > 其他分享 >FPGA入门笔记009——UART串口发送模块设计

FPGA入门笔记009——UART串口发送模块设计

时间:2024-03-26 09:22:41浏览次数:29  
标签:Tx FPGA Clk UART key 串口 Rst 波特率

# FPGA入门笔记009——UART串口发送模块设计

1、UART通信原理

​ 如图1为UART通信连接图,其中tx为输入,rx为输出。通过tx连接rx进行数据间的发送和接收。

image-20240319112855748

图1——UART通信连接图

​ UART 通信在使用前需要做多项设置,最常见的设置包括:数据位数、波特率大小、奇偶校验类型和停止位数:

​ (1)数据位(Data bits):该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。可选择为:5、6、7 或者 8(默认)。

​ (2)波特率(Baud)大小:是指从一设备发到另一设备的波特率,即:每秒钟可以通信的数据比特个数。典型的波特率有 300bps/Hz, 1200bps/Hz, 2400bps/Hz, 9600bps/Hz, 19200bps/Hz, 115200bps/Hz 等。一般通信两端设备都要设为相同的波特率,但有些设备也可设置为自动检测波特率。

​ (3)奇偶校验类型(Parity Type):是用来验证数据的正确性。奇偶校验一般不使用,如果使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。

​ (4)停止位(Stop bits):在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。

​ 在 RS-232 标准中,最常用的配置是 8N1(即八个数据位、一个停止位),其发送一个字节时序图如图 2 所示。

​ 按照一个完整的字节包括:一位起始位、8 位数据位、一位停止位,总共十位数据来算, 要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束,如图 2 所示,其中BPS_CLK为波特率时钟信号:

image-20240319112559538

图2——UART发送一个字节时序图

2、串口发送模块整体设计

​ 基于上述原理,本章要实现的串口发送模块整体框图,如图 3 所示,其接口列表如表 1 所示。

image-20240319114701645

图3——串口发送模块整体框图

image-20240320150511652

表1——串口发送模块接口列表

​ 根据功能需求,串口发送模块可进一步细化为如图 4 所示详细结构图,其中每一子模块的作用如表 2 所示。其中绿色的框代表单一结构的寄存器,来实现数据的稳定输入以及输出。

image-20240319115342076

图4——串口发送模块结构图

image-20240319115447397

表2——串口发送子模块块功能描述

3、接口设置

module uart_byte_tx(
    Clk,
    Rst_n,
    Send_En,
    Data_Byte,
    Baud_Set,

    Rs232_Tx,
    Tx_Done,
    UART_state
);

    input Clk;
    input Rst_n;
    input Send_En;
    input [2:0]Baud_Set;
    input [7:0]Data_Byte;

    output reg Rs232_Tx;
    output reg Tx_Done;
    output reg UART_state;

endmodule

4、波特率时钟生成模块设计

​ 从原理部分可知,波特率是 UART 通信中需要设置的参数之一。在波特率时钟生成模 块中,计数器需要的计数值与波特率之间的关系如表 3 所示,其中系统时钟周期为 clk_period,这里为 20ns。如果接入到该模块的时钟频率为其他值,需要根据具体的 频率值修改该参数。

image-20240320104431683

表3——系统时钟50MHz波特率计算

​ 本模块的设计是为了保证模块的复用性。当需要不同的波特率时,只需设置不同的波特 率时钟计数器的计数值。使用查找表即可实现,下面的设计代码中(DR_LUT)只包含了针对 5 个波特率 的设置,如需要其他波特率可根据实际使用情况具体修改。

//波特率时钟生成模块
    reg [15:0]bps_DR;   //分频计数最大值
    reg [15:0]div_cnt;  //分频计数器
    reg bps_clk;        //波特率时钟
    reg [3:0]bps_cnt;   //波特率时钟计数器

    //查找表(DR_LUT)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_DR <= 16'd5207;
    else begin
        case(Baud_Set)
            0:bps_DR <= 16'd5207;
            1:bps_DR <= 16'd2603;
            2:bps_DR <= 16'd1302;
            3:bps_DR <= 16'd867;
            4:bps_DR <= 16'd433;
            default:bps_DR <= 16'd5207;
        endcase
    end

    //分频计数器(Div_Cnt)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        div_cnt <= 16'd0;
	else if(UART_state)begin
        if(div_cnt == bps_DR)
            div_cnt <= 16'd0;
        else
            div_cnt <= div_cnt + 1'b1;
    end
    else
        div_cnt <= 16'd0;

    //产生bps_clk脉冲信号(Div_Cnt)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_clk <= 1'b0;
    else if(div_cnt == 16'd1)   //当计数器开始工作时,bps_clk就产生一个高脉冲,若计数器计数到bps_DR,则脉冲信号会延后一位。
        bps_clk <= 1'b1;
    else
        bps_clk <= 1'b0;

    //波特率时钟计数器(bps_cnt)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        bps_cnt <= 4'd0;
    else if(Tx_Done)
        bps_cnt <= 4'd0;
    else if(bps_clk)
        bps_cnt <= bps_cnt + 1'b1;
    else
        bps_cnt <= bps_cnt;

    //发送结束信号生成(Tx_Done)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Tx_Done <= 1'b0;
    else if(bps_cnt == 4'd11)
        Tx_Done <= 1'b1;
    else 
        Tx_Done <= 1'b0;

5、数据输出模块设计

//数据输出模块
    //为了保证输出稳定,设置一个内部寄存器r_data_byte = Data_Byte。这样当Send_En = 1的时候,无论data_byte怎么变化,r_data_byte的值都不变。
    reg [7:0]r_data_byte;

    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        r_data_byte <= 8'd0;
    else if(Send_En)
        r_data_byte <= Data_Byte;

    //10选1多路器
    localparam START_BIT = 1'b0;
    localparam STOP_BIT = 1'b1;

    always@(posedge Clk or negedge Rst_n)   //用'*'号可能会出现毛刺,所有用时序逻辑来编写组合逻辑电路,较稳定
    if(!Rst_n)
        Rs232_Tx <= 1'b1;   //Rs232_Tx默认为1
    else begin
        case(bps_cnt)
            0:Rs232_Tx <= 1'b1;
            1:Rs232_Tx <= START_BIT;
            2:Rs232_Tx <= r_data_byte[0];
            3:Rs232_Tx <= r_data_byte[1];
            4:Rs232_Tx <= r_data_byte[2];
            5:Rs232_Tx <= r_data_byte[3];
            6:Rs232_Tx <= r_data_byte[4];
            7:Rs232_Tx <= r_data_byte[5];
            8:Rs232_Tx <= r_data_byte[6];
            9:Rs232_Tx <= r_data_byte[7];
            10:Rs232_Tx <= STOP_BIT;
            default:Rs232_Tx <= 1'b1;
        endcase
    end

6、发送状态模块

//发送状态模块(UART_state)
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        UART_state <= 1'b0;
    else if(Send_En)
        UART_state <= 1'b1;
    else if(Tx_Done)
        UART_state <= 1'b0;
    else
        UART_state <= uart_state;

7、仿真

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_tx_tb;

    reg Clk;
    reg Rst_n;
    reg Send_En;
    reg [2:0]Baud_Set;
    reg [7:0]Data_Byte;

    wire Rs232_Tx;
    wire Tx_Done;
    wire UART_state;

    uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Send_En(Send_En),
        .Data_Byte(Data_Byte),
        .Baud_Set(Baud_Set),

        .Rs232_Tx(Rs232_Tx),
        .Tx_Done(Tx_Done),
        .UART_state(UART_state)
    );

    initial Clk = 1'b1;
    always#(`clk_period/2) Clk = ~Clk;

    initial begin
        Rst_n = 1'b0;
        Data_Byte = 8'b0;
        Send_En = 1'b0;
        Baud_Set = 3'd4;

        #(`clk_period*20 + 1)   //让复位信号不与时钟边沿对其,更好观察时序关系
        Rst_n = 1'b1;

        #(`clk_period*50)
        Data_Byte = 8'haa;
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)  //当Tx_Done信号出现上升沿时,进行接下来的语句

        #(`clk_period*5000)
        Data_Byte = 8'h55;
        Send_En = 1'b1;
        #`clk_period
        Send_En = 1'd0;

        @(posedge Tx_Done)
        #(`clk_period*5000)
        $stop;
    end

endmodule

8、板级调试

​ 为了实现导读中所设定的目标,将以前编写好的按键消抖模块添加到工程当中,并再次 使用 ISSP,其参数配置如图 5 所示,并加入到工程中。

image-20240321111114234

图5——ISSP参数配置

​ 然后,新建一个顶层文件,并将按键消抖串口发送以及 ISSP 例化,并将按键状态与串口发送使能端连接即可。设计如下所示,并将串口发送状态(UART_state)连接到 LED 上,以更好的观察数据发送状态。

module uart_byte_tx_top(
    Clk,
    Rst_n,
    Rs232_Tx,
    key_in0,
    led
);
    input Clk;
    input Rst_n;
    input key_in0;

    output Rs232_Tx;
    output led;

    wire Send_En;
    wire [7:0]Dta_Byte;
    wire key_flag0;
    wire key_state0;

    assign Send_En = key_flag0 & !key_state0;   //对Send_En进行按键消抖

//uart串口发送模块
    uart_byte_tx uart_byte_tx(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Send_En(Send_En),
        .Data_Byte(Data_Byte),
        .Baud_Set(3'd4),    //波特率为115200

        .Rs232_Tx(Rs232_Tx),
        .Tx_Done(),
        .UART_state(led)
    );

//按键消抖模块
    key_filter key_filter(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .key_in(key_in0),
        .key_flag(key_flag0),
        .key_state(key_state0)
    );

//ISSP激励源
    issp issp(
        .probe(),
        .source(Data_Byte)
    );

endmodule

​ 编译无误后,点击 RTL_viewer 可以看到如图 6 的各模块连接图。

image-20240321111643330

图6——RTL_viewer

​ 到此,程序和模块设计部分的内容完成。

9、串口发送多个字节的数据方案

​ 目标:通过串口发送“HELLO”字符串。

​ 在uart_tx_top文件中,去掉ISSP模块和该行代码:assign Send_En = key_flag0 & !key_state0; ,并编写如下代码:

//字符串"HELLO\0"发送模块
    reg [2:0] cnt;

    localparam
        byte1 = "H",
        byte2 = "E",
        byte3 = "L",
        byte4 = "L",
        byte5 = "O",
        byte6 = "\0";

    //计数器计数
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        cnt <= 3'd0;
    else if(Tx_Done)    //D触发器的特征:当检测到Tx_Done时,cnt不会立即更新,cnt会在下一个时钟周期上升沿到来时才跟新(延迟一拍)。
        cnt <= cnt + 1'b1;
    else if(key_flag0 & !key_state0)    //key_flag0 & !key_state0表示按键按下并状态稳定,没有滞后
        cnt <= 3'd0;

    //通过发送结果控制发送
    always@(posedge Clk or negedge Rst_n)
    if(!Rst_n)
        Send_En <= 1'b0;
    else if(key_flag0 & !key_state0)    //第一次按下按键,启动一次发送,没有滞后
        Send_En <= 1'b1;
	else if(Tx_Done &(cnt < 3'd5))    //后面每次发送完成,Tx_Done产生一次高脉冲,启动下一次发送。最后一个字节发送完成的Tx_Done不能触发新一次的发送
        Send_En <= 1'b1;    //Send_En滞后一拍变成0,当第6个字节发送完成,产生Tx_Done脉冲时,此时cnt还没自加1,还是5
    else
        Send_En <= 1'b0;

    //要发送的数据
    always@(*)  //组合逻辑
        case(cnt)
            3'd0:Data_Byte <= byte1;
            3'd1:Data_Byte <= byte2;
            3'd2:Data_Byte <= byte3;
            3'd3:Data_Byte <= byte4;
            3'd4:Data_Byte <= byte5;
            3'd5:Data_Byte <= byte6;
        default:Data_Byte = 0;
        endcase

​ 仿真代码如下所示:

`timescale 1ns/1ns
`define clk_period 20

module uart_byte_tx_top_tb;

    reg Clk;
    reg Rst_n;
    
    wire key;
    wire led;
    wire Rs232_Tx;

    reg press;

    uart_byte_tx_top uart_byte_tx_top(
        .Clk(Clk),
        .Rst_n(Rst_n),
        .Rs232_Tx(Rs232_Tx),
        .key_in0(key),
        .led(led)
    );

    key_model key_model(
        .press(press),
        .key(key)
    );

    initial Clk = 1'b1;
    always#(`clk_period/2) Clk = ~Clk;

    initial begin
        Rst_n = 1'b0;
        press = 1'b0;
        #(`clk_period*20 + 1);
        Rst_n = 1'b1;
        #(`clk_period*20 + 1);
        press = 1'b1;
        #(`clk_period*20 + 1);
        press = 1'b0;

        wait(uart_byte_tx_top.Tx_Done &(uart_byte_tx_top.cnt == 3'd5));	//wait函数调用、层次的引用
        #(`clk_period*200 + 1);
        $stop;
    end

endmodule

​ 在如上所示的代码中,所用到的知识点为:

​ 1、加入一个仿真模块:key_model;

​ 2、wait函数调用

​ 3、层次的引用

标签:Tx,FPGA,Clk,UART,key,串口,Rst,波特率
From: https://www.cnblogs.com/little55/p/18095872

相关文章

  • 基于EP4CE6F17C8的FPGA双数码管六十进制秒计数实例
    一、电路模块本例的电路模块与“基于EP4CE6F17C8的FPGA数码管动态显示实例”中的完全一样,此处就不再给出了。二、实验代码本例实现2个数码管循环显示00~59,显示间隔为1秒,代码使用Verilog编写,采用例化的形式,共有三个文件。先编写数码管实现显示字形解码的程序,模块名称为seg_deco......
  • 基于LabVIEW上位机与Arduino单片机串口通信的DS18B20环境温度采集
    基于LabVIEW上位机与Arduino单片机串口通信的DS18B20环境温度采集Arduino代码#include<OneWire.h>#include<DallasTemperature.h>#defineONE_WIRE_BUS2//DS18B20接至Arduino数字口2OneWireoneWire(ONE_WIRE_BUS);DallasTemperaturesensors(&oneWire);byteco......
  • 01-【HAL库】STM32实现串口打印
    一、什么是串口串口通讯(SerialCommunication)是一种设备间非常常用的串行通讯方式,因为它简单便捷,因此大部分电子设备都支持该通讯方式,电子工程师在调试设备时也经常使用该通讯方式输出调试信息。在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片......
  • FPGA图像处理——前置基础篇
    FPGA图像处理——前置基础篇本系列博客旨在结合FPGA来进行图像处理,致力于各种图像处理算法的实现,同时参考了博客原创作者——咸鱼FPGA的资料。以下是前置基础三节,我会以一个小白的角度去思考,解析学习过程中遇到的各种问题。1.Modelsim联合Matlab的图像仿真平台这是一个用于验......
  • fpga_fpga
    1电路 与c语言不同,verilog是并行执行语言,在看到一段简单的程序模块时,应该结合fpga内部逻辑模型,想到是什么样的功能电路。2时序 fpga的设计主要是以时序电路为主,它的所有动作都是在时钟一拍一拍的节奏下转变触发,即时钟是时序电路的控制者。 可以理解为时钟为心脏,功......
  • 基于肤色模型和中值滤波的手部检测算法FPGA实现,包括tb测试文件和MATLAB辅助验证
    1.算法运行效果图预览RTL图:   仿真图:   导入到matlab显示效果如下:   2.算法运行软件版本matlab2022a vivado2019.2 3.算法理论概述      在计算机视觉领域,基于肤色模型和中值滤波的手部检测方法是一种常见的初步定位策略。该方法主要分为......
  • 基于FPGA温度采集的方案
    1.使用温度传感器与FPGA连接:FPGA可以通过接口与外部温度传感器进行通信,实时读取温度数据并进行处理。其中一种常用的温度传感器是LM75系列传感器,如LM75A、LM75B等。这些传感器具有高精度、温度测量范围广、低功耗等特点。                 ......
  • FPGA的串口接收部分的知识点
    在串口接收图像,存到RAM,然后读取RAM数据显示在TFT上的实验中发现发送图片的时候,发现花屏,发现是串口这边的问题。估计当时的串口接收代码没写好,这边重新看一下。moduleuart_byte_rx(Clk,Reset_n,uart_rx,Rx_Done,Rx_Data);inputClk;input......
  • FPGA使用两个HC595驱动8位数码管
    FPGA使用两个HC595驱动8位数码管本文章给出使用FPGA3根线来驱动8位数码管的示例代码,输入为disp_data,共7*8=56位,输出输入如图所示。硬件方面参数该程序只能控制数码管的7位,如有小数点位则控制不了,如有需要请自行修改。最低7位是最右边的那个数码管(这个需要根据你自己的板子......
  • 基于EP4CE6F17C8的FPGA单数码管秒计数实例
    一、电路模块本例的电路模块与“基于EP4CE6F17C8的FPGA数码管动态显示实例”中的完全一样,此处就不再给出了。二、实验代码本例实现1个数码管循环显示字符1~F,显示间隔为1秒,代码使用Verilog编写,采用例化的形式,共有三个文件。先编写数码管实现显示字形解码的程序,模块名称为seg_de......