FPGA实现串口回环

news2024/11/27 8:27:26

文章目录

  • 前言
  • 一、串行通信
    • 1、分类
      • 1、同步串行通信
      • 2、异步串行通信
    • 2、UART串口通信
      • 1、UART通信原理
      • 2、串口通信时序图
  • 二、系统设计
    • 1、系统框图
    • 2.RTL视图
  • 三、源码
    • 1、串口发送模块
    • 2、接收模块
    • 3、串口回环模块
    • 4、顶层模块
  • 四、测试效果
  • 五、总结
  • 六、参考资料


前言

环境:
1、Quartus18.0
2、vscode
3、板子型号:原子哥开拓者2(EP4CE10F17C8)
要求:
上位机通过串口调试助手发送数据给 FPGA,FPGA 通过 USB 串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。(这里我们以波特率115200、数据位为8位、无校验位、停止位为1位作为案例)。


一、串行通信

1、分类

1、同步串行通信

同步串行通信需要通信双方在同一时钟的控制下进行同步传输数据。

2、异步串行通信

异步串行通信指通信双方使用各自的时钟控制数据的发送和接收。

2、UART串口通信

1、UART通信原理

UART串口通信是一种采用异步串行通信方式的通用异步收发传输器,在收发数据过程中通过串并转换实现数据的发送、接收。UART串行通信一般需要两根信号线实现,一根用于串口发送,一根用于串口接收。下面是其通信的数据格式:
在这里插入图片描述

从通信格式看,我们可知收发的一帧数据涵盖了4部分:起始位、数据位、校验位、以及停止位。校验位一般是奇检验、偶检验,就是发送方应使数据位中 1 的个数与校验位中 1 的个数之和为奇数或偶数。数据位有5、6、7、8位,一般选择8位。串口通信的速率用波特率进行表示,表示每秒传输二进制的位数,常用9600、115200等。

  • 注意:
    UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。

2、串口通信时序图

在这里插入图片描述

二、系统设计

1、系统框图

在这里插入图片描述

2.RTL视图

在这里插入图片描述

uart_recv 为串口接收模块:从串口接收端口 uart_rxd 来接收上位机发送的串行数据,并在一帧数据接收结束后给出通知信号 uart_done。
uart_send 为串口发送模块:以 uart_en 为发送使能信号。uart_en 的上升沿将启动一次串口发送过程,
将 uart_din 接口上的数据通过串口发送端口 uart_txd 发送出去。
uart_loop 模块负责完成串口数据的环回功能:它在 uart_recv 模块接收完成后,将接收到的串口数据发送到 uart_send 模块,并通过 send_en 接口给出一个上升沿,以启动发送过程。

三、源码

1、串口发送模块

module uart_send(
    input sys_clk               ,
    input sys_rst_n             ,
    input uart_en               ,//发送使能
    input [7:0] uart_din        ,//待发送的数据

    output uart_tx_busy         ,//发送忙
    output en_flag              ,
    output reg tx_flag          ,//发送过程标志
    output reg [7:0] tx_data    ,//寄存发送数据
    output reg [3:0] tx_cnt     ,//发送数据计数器
    output reg uart_txd         //发送端口
);

parameter CLK_FREQ = 50_000_000;
parameter UART_BPS =9600;
localparam BPS_CNT = CLK_FREQ/UART_BPS;

reg uart_en_d0;//打拍信号
reg uart_en_d1;
reg [15:0] clk_cnt;

assign uart_tx_busy = tx_flag;

//捕获上升沿
assign en_flag = (~uart_en_d1) & uart_en_d0;

//打拍
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_en_d0 <= 1'b0;
        uart_en_d1 <= 1'b0;
    end
    else begin
        uart_en_d0 <= uart_en;
        uart_en_d1 <= uart_en_d0;
    end

end

//当上升沿来时,寄存待发送的数据,进入发送过程
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        tx_flag <= 1'b0;
        tx_data <= 8'd0;
    end
    else if(en_flag) begin//上升沿
        tx_flag <= 1'b1;
        tx_data <= uart_din;
    end
    else if((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT - (BPS_CNT/16)))begin
        tx_flag <= 1'b0;
        tx_data <= 8'd0;
    end
    else begin
        tx_flag <= tx_flag;
        tx_data <= tx_data;
    end
end

//开启时钟计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        clk_cnt <= 16'd0;
    end
    else if(tx_flag) begin//发送过程
        if(clk_cnt < BPS_CNT - 1)
            clk_cnt <= clk_cnt + 1'b1;
        else
            clk_cnt <= 16'd0;
    end
    else
        clk_cnt <= 16'd0;
end
//开启发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        tx_cnt <= 4'd0;
    end
    else if(tx_flag) begin
        if(clk_cnt == BPS_CNT - 1)
            tx_cnt <= tx_cnt + 1'b1;
        else
            tx_cnt <= tx_cnt;
    end
    else
        tx_cnt <= 4'd0;
end
//并转串
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_txd <= 1'b1;
    end
    else if(tx_flag)
        case(tx_cnt)
            4'd0: uart_txd <= 1'b0;//起始位
            4'd1: uart_txd <= tx_data[0];//低位
            4'd2: uart_txd <= tx_data[1];
            4'd3: uart_txd <= tx_data[2];
            4'd4: uart_txd <= tx_data[3];
            4'd5: uart_txd <= tx_data[4];
            4'd6: uart_txd <= tx_data[5];
            4'd7: uart_txd <= tx_data[6];
            4'd8: uart_txd <= tx_data[7];
            4'd9: uart_txd <= 1'b1;
            default: ;
        endcase
    else
        uart_txd <= 1'b1;
end
endmodule

2、接收模块

module uart_recv(
    input sys_clk               ,
    input sys_rst_n             ,
    input uart_rxd              ,//接收端口

    output reg uart_done        ,//接收一帧数据结束标志
    output reg rx_flag          ,//接收过程标志信号
    output reg [3:0] rx_cnt     ,//接收数据计数器
    output reg [7:0] rxdata     ,
    output reg [7:0] uart_data  //接收的数据
);

parameter CLK_FREQ = 50_000_000;
parameter UART_BPS = 9600;//波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS;//为得到指定波特率需要计数的值

reg uart_rxd_d0;//用于打拍判定下降沿决定开始信号
reg uart_rxd_d1;

reg [15:0] clk_cnt;
wire start_flag;

assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);

//打拍
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n) begin
        uart_rxd_d0 <= 1'b0;
        uart_rxd_d1 <= 1'b0;
    end
    else begin
        uart_rxd_d0 <= uart_rxd;
        uart_rxd_d1 <= uart_rxd_d0;
    end
end

//下降沿到来时,开始接收
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rx_flag <= 1'b0;
    else begin
        if(start_flag)//下降沿到来
            rx_flag <= 1'b1;
        else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))//计数到第10位,且在第十位中间时接收结束
            rx_flag <= 1'b0;
        else
            rx_flag <= rx_flag;
    end
end

//进入接收过程后开启时钟计数器,为了满足特定波特率
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        clk_cnt <= 16'd0;
    else if(rx_flag) begin
        if(clk_cnt < BPS_CNT - 1)
            clk_cnt <= clk_cnt + 1'b1;
        else
            clk_cnt <= 16'd0;
    end
    else
        clk_cnt <= 16'd0;
end

//进入接收过程,开启接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rx_cnt <= 4'd0;
    else if(rx_flag)begin
        if(clk_cnt == BPS_CNT - 1)
            rx_cnt <= rx_cnt + 1'b1;
        else
            rx_cnt <= rx_cnt;
    end
    else
        rx_cnt <= 4'd0;
end

//根据接收数据计数器寄存uart接收端的数据
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rxdata <= 8'd0;
    else if(rx_flag)
        if(clk_cnt == BPS_CNT/2) begin//在中间取值较为稳妥
            case(rx_cnt)
            4'd1: rxdata[0] <= uart_rxd_d1;//低位
            4'd2: rxdata[1] <= uart_rxd_d1;
            4'd3: rxdata[2] <= uart_rxd_d1;
            4'd4: rxdata[3] <= uart_rxd_d1;
            4'd5: rxdata[4] <= uart_rxd_d1;
            4'd6: rxdata[5] <= uart_rxd_d1;
            4'd7: rxdata[6] <= uart_rxd_d1;
            4'd8: rxdata[7] <= uart_rxd_d1;
            default:;
            endcase
        end
        else
            rxdata <= rxdata;
    else
        rxdata <= 8'd0;
end

//数据接收完毕给出结束标志并寄存输出接收的数据
always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_data <= 8'd0;
        uart_done <= 1'b0;
    end
    else if(rx_cnt == 4'd9)begin
        uart_data <= rxdata;
        uart_done <= 1'b1;
    end
    else begin
        uart_data <= 8'd0;
        uart_done <= 1'b0;
    end
end
endmodule

3、串口回环模块

module uart_loop(
    input               sys_clk               ,
    input               sys_rst_n             ,
    input               recv_done             ,//接收一帧数据结束标志
    input [7:0]         recv_data             ,//接收的数据
    input               tx_busy               ,//发送忙

    output reg          send_en               ,//发送使能
    output reg [7:0]    send_data              //待发送数据
);

//打拍信号
reg recv_done_d0;
reg recv_done_d1;

reg tx_ready;
wire recv_done_flag;

//捕获上升沿,一帧结束
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;

//打拍
always @(posedge sys_clk or negedge sys_rst_n) begin
    if(!sys_rst_n) begin
        recv_done_d0 <= 1'b0;
        recv_done_d1 <= 1'b0;
    end
    else begin
        recv_done_d0 <= recv_done;
        recv_done_d1 <= recv_done_d0;
    end
end

//一帧接收结束后进入准备阶段,寄存接收的数据用于发送
//当准备阶段且发送空闲时,使能发送信号

always @(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n) begin
        tx_ready <= 1'b0;
        send_en <= 1'b0;
        send_data <= 8'd0;
    end
    else begin
        if(recv_done_flag)begin
            tx_ready <= 1'b1;
            send_en <= 1'b0;
            send_data <= recv_data;
        end
        else if(tx_ready && (~tx_busy))begin
            tx_ready <= 1'b0;
            send_en <= 1'b1;
        end
    end
end
endmodule

4、顶层模块

module uart_loopback_top(
    input sys_clk   ,
    input sys_rst_n ,
    input uart_rxd  ,

    output uart_txd
);

parameter CLK_FREQ = 50_000_000;
parameter UART_BPS = 115200;

wire        uart_recv_done  ;//接收完成标志
wire [7:0]  uart_recv_data  ;//接收数据
wire        uart_send_en    ;//发送使能
wire [7:0]  uart_send_data  ;//发送数据
wire        uart_tx_busy    ;//发送忙状态标志

//串口接收模块
uart_recv #(.CLK_FREQ(CLK_FREQ),.UART_BPS(UART_BPS)) uart_recv_inst(
    /*input           */    .sys_clk    (sys_clk  )           ,
    /*input           */    .sys_rst_n  (sys_rst_n)           ,
    /*input           */    .uart_rxd   (uart_rxd )           ,//接收端口

    /*output reg      */    .uart_done  (uart_recv_done)      ,//接收一帧数据结束标志
    /*output reg [7:0]*/    .uart_data  (uart_recv_data)//接收的数据
);

//串口发送模块
uart_send #(.CLK_FREQ(CLK_FREQ),.UART_BPS(UART_BPS)) uart_send_inst(
    /*input           */    .sys_clk        (sys_clk  )       ,
    /*input           */    .sys_rst_n      (sys_rst_n)       ,
    /*input           */    .uart_en        (uart_send_en  )       ,//发送使能
    /*input [7:0]     */    .uart_din       (uart_send_data)         ,//待发送的数据

    /*output          */    .uart_tx_busy   (uart_tx_busy)      ,//发送忙
    /*output reg      */    .uart_txd       (uart_txd)  //发送端口
);

//串口回环模块
uart_loop uart_loop_inst(
    /*input           */    .sys_clk               (sys_clk  ),
    /*input           */    .sys_rst_n             (sys_rst_n),
    /*input           */    .recv_done             (uart_recv_done),//接收一帧数据结束标志
    /*input [7:0]     */    .recv_data             (uart_recv_data),//接收的数据
    /*input           */    .tx_busy               (uart_tx_busy),//发送忙

    /*output reg      */    .send_en               (uart_send_en  ),//发送使能
    /*output reg [7:0]*/    .send_data             (uart_send_data) //待发送数据
);
endmodule

四、测试效果

在这里插入图片描述


五、总结

串口通信其实算是几种通信协议里较简单的一种了,看着通信时序图就已经对UART串行通信理解了许多,但是里面还是有许多细节需要注意,所以为了巩固在UART串口通信的知识,打算在最近通过FPGA实现UART串口通信实现一个测量温湿度并自动传回到上位机的学习。还有这里请允许我偷个懒,没有进行波形的仿真和SignalTap II在线抓波形,今天没什么心情。

六、参考资料

以上资料均来自正点原子的教学视频或开拓者2开发教程:原子官方

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

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

相关文章

【框架篇】Spring Boot核心介绍及项目创建(详细教程)

Spring Boot介绍及项目创建 一&#xff0c;Spring Boot 核心介绍 Spring Boot 是基于 Spring 开发的一种轻量级的全新框架&#xff0c;不仅继承了 Spring 框架原有的优秀特性&#xff0c;而且还通过简化配置来进一步简化了 Spring 应用的整个搭建和开发过程。通过Spring Boot&…

NO.453 最小操作次数使数组元素相等

题目 给你一个长度为 n 的整数数组&#xff0c;每次操作将会使 n - 1 个元素增加 1 。返回让数组所有元素相等的最小操作次数。 思路 本题要求&#xff0c;获取最小操作次数&#xff0c;即在满足所有元素均相等的情况下&#xff0c;操作次数最少。 由于本题无法确定最终元素…

火车头采集器伪原创插件【php源码】

火车头采集是一款基于Python语言开发的网络爬虫工具&#xff0c;用于快速高效地从互联网上采集数据并存储到本地或远程数据库。它简单易用且功能强大&#xff0c;在各行各业广泛应用。 火车头采集器AI伪原创PHP源码&#xff1a; <?php header("Content-type: text/h…

(四)springboot实战——springboot的事件与监听器原理

前言 本节内容是关于springboot的一些核心原理的总结&#xff0c;包括springboot的事件原理、生命周期流程、事件触发流程等核心内容的介绍&#xff0c;从而帮助我们更好的理解与使用springboot&#xff0c;这里只做概念性的内容总结&#xff0c;实战的部分请关注作者后续博客…

数学建模-分类模型 Fisher线性判别分析

论文中1. 判别分析系数 2. 分类结果 多分类问题 勾选内容和上面一样

06微分方程模型练习

用Matlab求解微分方程 y ′ − 2 y 2 x 2 2 x , y ( 0 ) 1 y-2y2x^{2}2x,y\left( 0\right) 1 y′−2y2x22x,y(0)1 y1 dsolve(Dy-2*y2*x.^22*x) y2 dsolve(Dy-2*y2*x.^22*x,y(0)1,x)用Matlab分别求解微分方程 y ′ − 2 y 2 x 2 2 x , y ( 0 ) 1 y-2y2x^{2}2x,y\left…

C语言动态内存管理(二)经典笔试题

第二篇内容为大家详细剖析关于动态内存管理的几个经典笔试题 目录 四、笔试题1.请问运行Test函数会有什么样的结果&#xff1f;结果&#xff1a;结果运行出错的原因&#xff1a;本题目注意点&#xff1a;改正该题目的错误&#xff1a;正确修改1&#xff1a;&#xff08;利用传值…

Electron入门学习_使用预加载脚本

学习网址&#xff1a; https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-preload 1.什么是预加载脚本 Electorn 的主进程是一个拥有完全操作系统访问权限的Node.js ,除了Electron 模组&#xff0c;之外&#xff0c;您也可以访问Node.js 内置模块和所有通过npm安装…

质效两全:媒体服务的创新“顶设”

做媒体服务&#xff0c;一定要有刻入骨髓的抽象思维。 视频化浪潮汹涌、生成式人工智能AIGC极速迭代、体验需求和应用场景愈发多样......面对“视频生产力”的变革&#xff0c;我们能否透过纷繁复杂的表象&#xff0c;洞察音视频行业的“真正需求”&#xff1f; 是否存在一套…

【mysql数据库】MySQL7在Centos7的环境安装

说明&#xff1a; 安装与卸载中&#xff0c;用户全部切换成为root&#xff0c;⼀旦安装&#xff0c;普通用户就能使用。初期练习&#xff0c;mysql不进行用户管理&#xff0c;全部使⽤root进⾏&#xff0c;尽快适应mysql语句&#xff0c;后⾯学了用户管理&#xff0c;在考虑新…

matlab dot()函数求矩阵内积,三维 ,多维 详解

matlab dot()函数求矩阵内积&#xff0c;三维 &#xff0c;多维 详解 Cdot(A,b,X)&#xff0c;这个参数X 只能取1,或者2。1 表示按列&#xff0c;2表示按行&#xff0c;如果没有参数。默认按列。 1&#xff09;按列优先计算 Cdot(A,B)dot(A,B,1)[a1*b1a4*b4 ,a2*b2a5*b5 ,a…

html,css初学

安装VSCODE ,插件&#xff1a;live server &#xff0c;html support html 然后为了更好地理解&#xff0c;请逐步输入&#xff0c;并及时查看效果 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>D…

A--自动收小麦机--2023河南萌新联赛第(二)场:河南工业大学

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 示例1 输入 4 1 2 1 1 4 5 2 2 2 3 4 输出 10 说明 在第4格放出水流后&#xff0c;水流会流向第3格&#xff0c;由于第3格高度比第4格低&#xff0c;所以水流继续向左流向第2格&#xff0…

爬虫的编解码方式

get请求的quote方法 我们在对爬取一个网页的时候&#xff0c;我们复制了这个网页的地址&#xff0c;但我们发现在将他粘贴下来以后不会是汉字&#xff0c;而是一串字符&#xff0c;这时候&#xff0c;我们需要去对字符进行编码&#xff0c;以便于我们能够继续去爬取网页。 例…

markdown编写数学公式

在计算机这一块&#xff0c;我们肯定会接触到数学&#xff0c;数学中又包含很多公式&#xff0c;但是到现在&#xff0c;手写这些公式应该不陌生&#xff0c;但是如果让你电脑敲出来&#xff0c;你绝对很懵逼&#xff0c;这也造成了我们有时候写笔记时一些公式没办法在电脑上像…

ffmpeg离线安装ffmpeg-4.1.4-amd64-static.tar.xz

1.下载离线包 John Van Sickle - FFmpeg Static Builds 找历史版本&#xff1a;Index of /ffmpeg/old-releases 我选择是的4.1.4版本 2.解压 tar -xvJf ffmpeg-4.1.4-amd64-static.tar.xz 3.移动文件到opt目录下 4.添加全局链接 ln -s /opt/ffmpeg-4.1.4-amd64-static/ffm…

【C++基础(五)】类和对象(上)

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C初阶之路⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 类和对象-上 1. 前言2. 类的引入3. 类的定义4. 类的…

代码随想录-回溯(组合问题)|ACM模式

目录 前言&#xff1a; 77.组合 题目描述&#xff1a; 输入输出示例&#xff1a; 思路和想法&#xff1a; 216. 组合总和 III 题目描述&#xff1a; 输入输出示例&#xff1a; 思路和想法&#xff1a; 17. 电话号码的字母组合 题目描述&#xff1a; 输入输出描述&a…

MODBUS TCP转CCLINK IE协议网关profinet接口和以太网接口的区别

你是否曾经遇到过需要将不同的设备连接到一个统一的网络中&#xff1f;或者你是否曾经遇到过设备之间的通讯协议不兼容的问题&#xff1f;远创智控的YC-CCLKIE-TCP通讯网关就是为解决这些问题而设计的。 YC-CCLKIE-TCP通讯网关是一款自主研发的CCLINK IE FIELD BASIC从站功能…

OpenAI报错 time out:HTTPSConnectionPool(host=‘api.openai.com‘, port=443)

项目场景&#xff1a; 使用openai的api调用chatGPT报错&#xff0c;同样的代码在另一台机器没有问题 问题描述 使用官方示例 import openaiopenai.api_key sk-xxxx def chat_gpt(prompt):prompt promptmodel_engine "text-davinci-003"completion openai.Comp…