• FPGA之旅设计99例之第三例-----UART串口通信


    一. 简介

    这是FPGA之旅的第三个设计实例了,通过串口协议,就可以和电脑上的串口调试助手进行通信啦。串口一般用来输出程序中的一些信息,用来调试,也可以用来进行信息交互。在stm32中是可以通过串口下载程序的,是不是很nice。本例将实现一个基本的串口模块。

    感兴趣的话,欢迎关注微信公众号 FPGA之旅

    二. 串口电路

    串口电路一般使用的是CH340芯片,将串口协议中的RX和TX数据线,转换成电脑上可以识别的COM口,让电脑知道这个外部设备是串口设备。当然不弄硬件哈,只简单了解一下即可,还是重点学习下面的串口协议。
    在这里插入图片描述

    三. 串口协议

    串口协议的全程为串行异步通信协议,UART

    异步,就说明通信双方需要提前约定好数据传输的速率,即波特率,每秒传输bit的数量。串行,就说明数据是按bit一个一个发送出去的(其他的有可能是2bit,3bit,或者更多)。

    串口协议只需要两个数据线,一个用来发送,一个用来接收。具体协议规定如下

    1. 空闲位 :数据线都为高电平。
    2. 起始位:数据线拉低1bit,也就是拉低1bit所占用的时间。
    3. 数据位: 一比特一比特的将数据发送出去,先发送低位,后发送高位,数据位宽可以为5,6,7,8。一般为8bit。
    4. 校验位: 可以设置为奇校验,偶校验和无校验位。一般设置为无校验位,即去掉。
    5. 停止位: 数据线拉高0.5、1、1.5bit。一般设置为1bit。

    在这里插入图片描述

    以上就是串口协议的全部。

    四. 实现思路

    按照协议的规定,可以非常轻松得到状态机一共用那几个状态

    1. 空闲态
    2. 起始态
    3. 数据态
    4. 停止态

    一共四个状态,每个状态所需要做的事情,在协议中已经明确指出来了。

    还有一个问题就波特率怎么计算。FPGA的时钟一般为50Mhz,也就是一个时钟周期为20ns。可以先把波特率换成发送一个bit需要多少ns。最后再除以20,得到发送一个bit需要多少个时钟周期。例如波特率为115200,则时钟周期数计算方法如下

    K = ((1 / 115200)* 1000 * 1000 * 1000) /20

    这样就可以开始安心写代码啦!!代码一共分为两个部分,分别为TX和RX两个。

    五. TX发送模块编写

    1. 先来定义端口信号.

      请求信号来了之后,就开始发送idat数据

    module  UART_TX(
        input       sys_clk,       /*系统时钟 50M*/
        input       rst_n,         /*系统复位 低电平有效*/
        
        input       uart_tx_req,   /*串口发送请求*/
        output      uart_tx_done,  /*串口发送完成*/
    
        input[7:0]  idat,          /*发送数据*/
        output      uarttx         /*uart tx数据线*/
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 再来定义波特率以及各个状态

      时钟周期这里有一点点小改动,避免出现小数,而产生数值错误。

    parameter   UARTBaud   = 'd115200;     /*波特率*/
    localparam  UARTCLKPer =  (('d1000_000_000 / UARTBaud) /20) -1;   /*每Bit所占的时钟周期*/
    localparam  UART_Idle       =   4'b0001;    /*空闲态*/
    localparam  UART_Start      =   4'b0010;    /*起始态*/
    localparam  UART_Data       =   4'b0100;    /*数据态*/
    localparam  UART_Stop       =   4'b1000;    /*停止态*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    六. RX接收模块编写

    1. 定义端口信号。
    module UART_RX(
        input          sys_clk,        /*系统时钟 50M*/
        input          rst_n,          /*系统复位*/
        output         uart_rx_done,   /*串口接收完成*/
        output[7:0]    odat,           /*接收数据*/
        input          uartrx         /*uart rx数据线*/
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 边沿检测。在接收数据的时候,需要判断数据的边沿,用来确定起始信号。通过缓存前两个时钟周期获取到的数据信号。然后通过取反再与的操作,是否发生了跳变,也就是上升沿和下降沿。
    /*缓存rx数据*/
    reg uartrxd0,uartrxd1,uartrxd2;
    /*检测rx 上下边沿*/
    wire  uartrxPosedge , uartrxNegedge;
    
    assign uartrxPosedge = (uartrxd1) & ( ~uartrxd2);
    assign uartrxNegedge = (~uartrxd1) & ( uartrxd2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由于两个模块的总代码量一共有300行左右,就不全部粘贴出来了,需要的自行在微信公众号中获取,私聊即可。

    七. testbeach编写

    代码编写好了,怎么能不去仿真,验一验呢!万一有bug呢?

    `timescale 1ns/1ps
    /*仿真文件编写*/
    module testbeach();
        reg clk;
        reg rst_n;
        wire       uart_rx_done;
        wire[7:0]  uart_rx_data;
        wire       uart;
        reg[7:0]    data;
        reg         uart_tx_req;
        wire        uart_tx_done;
        always #50 clk <= ~clk;
        initial begin
            clk = 1'b0;
            rst_n = 1'b1;
            data = 'd23;
            uart_tx_req = 1'b0;
            #100     /*手动复位一下,不然仿真会有高阻态*/
            rst_n = 1'b0;
            #100
            rst_n = 1'b1;
            #100 
            uart_tx_req = 1'b1;
        end
        /*数据发送完成后,左一位,进行发送*/
        always@(posedge clk)
        begin
            if(uart_rx_done == 1'b1)
                data <= data << 1;
        end
    UART_RX UART_RX_HP(
        .sys_clk        (clk),        /*系统时钟 50M*/
        .rst_n          (rst_n),          /*系统复位*/
        .uart_rx_done   (uart_rx_done),   /*串口接收完成*/
        .odat           (uart_rx_data),             /*接收数据*/
        .uartrx         (uart)         /*uart rx数据线*/
    );
    UART_TX UART_TX_HP(
        .sys_clk        (clk),        /*系统时钟 50M*/
        .rst_n          (rst_n),          /*系统复位*/
        .uart_tx_req    (uart_tx_req),   /*串口发送请求*/
        .uart_tx_done   (uart_tx_done),  /*串口发送完成*/
        .idat           (data),          /*发送数据*/
        .uarttx         (uart)       /*uart tx数据线*/
    );
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    通过仿真可以看到,代码正确的运行了,下面就是上板测试了,如果有条件的话。

    在这里插入图片描述

    不会以为串口就这样完了吧,怎么可能,这才仅仅实现了最基本的功能。更多高级东西还在后头呢!

  • 相关阅读:
    Matplotlib:Python数据可视化的全面指南
    【整顿C盘】pycharm、chrome等软件,缓存移动
    前端论坛项目(九)------如何实现UserProfileInfo里面的关注按钮
    画一个时钟(html+css+js)
    【编程英语】Python常用英语单词
    stm32的ADC通道错乱原因分析
    【SQL语法基础】如何使用Python操作MySQL?
    Vue 最简单路由 页面路由 配置路由
    ThreedLocal在单线程中的应用【获取在拦截器中登录的用户信息】
    娄底污水处理厂实验室建设管理
  • 原文地址:https://blog.csdn.net/weixin_44678052/article/details/126332721