FPGA学习笔记之串口收发回环实验

FPGA学习笔记之串口收发回环实验

这只是笔者的学习笔记,笔者也是为了学习,顺便分享给大家,里面有很多不足的地方,望大家指出来,笔者好修稿!!!谢谢

“串口”—就是“串行接口”的简称,就是采用串行通信方式的接口。

一,uart串口的简介

首先,我们要明白个概念,通信的方式分为串行通信和并行通信;这里我们要说的是串行通信,串行通信分为两种:同步串行通信方式和异步串行通信方式。同步串行通信是指双方必须在同一时钟的作用下来进行数据的传输,因此便要多加一根线;而异步串行通信则是需要双方不需要加时钟线,双方约定个时钟来传输数据。

uart是一种采用异步串行传输方式的通用异步收发传输器(universal asynchronous receiver-transmitter);他的原理也就是并串转换,在发送时,将数据从并行转换成串行传输出去;在接收时,将串行数据转换成并行数据。因此uart串口只需要两根信号线,一根用于发送数据,一根用于接收数据。

uart的协议层

UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。在这里插入图片描述
其中各位的意义如下:

起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:传输的二进制的数据,一般为8位。
奇偶校验位:这个位的作用时检查数据传输有没有误。
停止位:它是一个字符数据的结束标志。

而本次实验数据位采用的是8位数据,并且不用奇偶效验位,所以一共为10位。
还有一个非常重要的概念——波特率bps,波特率反应的是数据传输的速率,也就是每秒传输二进制的位数,单位是bps(位/秒);一般有9600,115200等等越大传输的越快。

uart的物理层

uart采用的接口标准有许多,比如RS232,RS422,RS485等。RS232是最常用的接口标准,RS232 接口标准出现较早, 可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过 15m) , RS232 是串行通信最常用的接口标准,本章主要介绍针对 RS-232 标准的 UART 串口通信。
RS-232 标准的串口最常见的接口类型为 DB9, 样式如图所示,工业控制领域中用到的工控机一般都配备多个串口, 很多老式台式机也都配有串口。但是笔记本电脑以及较新一点的台式机都没有串口,它们一般通过 USB 转串口线(图 14.1.3)来实现与外部设备的串口通信。
在这里插入图片描述
DB9 接口定义以及各引脚功能说明如图 所示,我们一般只用到其中的2 (RXD)、 3(TXD) 、5(GND) 引脚,其他引脚在普通串口模式下一般不使用.在这里插入图片描述

二,试验任务

本次实验的任务是设计一个uart串口环回收发系统,只做仿真,暂不做板上验证。

三,程序设计

设计的思路如下框图:
在这里插入图片描述

uart接收模块

时序图:
在这里插入图片描述
sys_clk系统时钟频率;
uart_rxd串口接收的数据输入;
start_flag检测开始位的信号标志,检测开始位的下降沿就拉高;
rx_flag数据的有效段的标志;
clk_cnt系统时钟计数器;
rx_cnt波特率周期的计数器;
uart_done传输完成的标志,拉高;
uart_data串转并的数据,接收一帧的数据;

不多说,直接上代码!!!

//uart接收模块
module uart_receive(
    input               sys_clk,//系统时钟
    input               sys_rst_n,//系统复位,低电平有效
    input               uart_rxd,//串口接收的二进制数据
    
    output reg [7:0]    uart_data,//串口接收模块并转串输出的8位数据
    output reg          uart_done//接收完的标志,拉高
    );
    
parameter   CLK_FRED = 50_000_000;//时钟周期频率
parameter   UART_BPS = 115200;//波特率
parameter   BPS_CNT  = CLK_FRED/UART_BPS;//波特率周期
    
reg         uart_rxd_reg0;//定义两个寄存器
reg         uart_rxd_reg1;
wire        start_flag;//开始的标志,检测下降沿的标志
reg         rx_flag;//数据有效段的标志,拉高为有效
reg [3:0]   rx_cnt;//波特率周期的计数器
reg [15:0]  clk_cnt;//系统时钟的计数器
reg [7:0]   rxdata;//输出数据的寄存器

//这就是一个边缘检测的功能,检测数据的低电平,开始位,数据传输开始就拉高start_flag
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_rxd_reg0 <= 1'b1;
        uart_rxd_reg1 <= 1'b1;
    end
    else begin
        uart_rxd_reg0 <= uart_rxd;
        uart_rxd_reg1 <= uart_rxd_reg0;     
    end
end
assign start_flag = (uart_rxd_reg1)&(~uart_rxd_reg0);

//根据start_flag传输开始的标志,设置传输的有效段的范围,即从开始位到结束位都拉高rx_flag,至于为甚后面是波特率周期的一办,是为了给一帧数据留一个缓冲的时间;
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rx_flag <= 0;
    else begin
        if(start_flag)
            rx_flag <= 1'b1;
        else if(rx_cnt == 4'd9 && clk_cnt == BPS_CNT/2)
            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 <= 0;
    else begin
        if(rx_flag)begin
            if(clk_cnt < BPS_CNT - 1)
                clk_cnt <= clk_cnt + 1'b1;
            else
                clk_cnt <= 0;    
        end
        else
            clk_cnt <= 0;    
    end 
end
//波特率周期计数器,每当系统时钟计数器计数到一个波特率周期时,波特率周期计数器就加一
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rx_cnt <= 0;
    else begin
        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 <= 0;
    end 
end
//串行数据转换为并行数据,至于为什么是计数到波特率周期的一半,是为了采样中间的值,这样采样的正确性高
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        rxdata <= 0;
    else begin
        if(rx_flag)begin
            if(clk_cnt == BPS_CNT/2)begin
                case(rx_cnt)
                    4'd1:   rxdata[0] <= uart_rxd_reg1;      
                    4'd2:   rxdata[1] <= uart_rxd_reg1; 
                    4'd3:   rxdata[2] <= uart_rxd_reg1; 
                    4'd4:   rxdata[3] <= uart_rxd_reg1; 
                    4'd5:   rxdata[4] <= uart_rxd_reg1; 
                    4'd6:   rxdata[5] <= uart_rxd_reg1; 
                    4'd7:   rxdata[6] <= uart_rxd_reg1; 
                    4'd8:   rxdata[7] <= uart_rxd_reg1; 
                    default:;
                endcase       
            end
            else
                rxdata <= rxdata;    
        end
        else
            rxdata <= 0;
    end
end
//最后将寄存的输出数据传给输出,并拉高输出完的标志位uart_done
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_data <= 0;
        uart_done <= 0;
    end
    else begin
        if(rx_cnt == 4'd9)begin
            uart_data <= rxdata;
            uart_done <= 1'b1; 
        end
        else begin
            uart_data <= 0;
            uart_done <= 0;
        end
    end
end
endmodule

uart发送模块

时序图:
在这里插入图片描述
这里各信号的含义以及代码的具体含义我就不介绍了,详情参考上面的接收模块!!!
uart_tx_busy这个信号是表示发送模块正在发送数据的繁忙信号!!!
上代码!!!

//uart发送模块
module uart_send(
    input               sys_clk,
    input               sys_rst_n,
    input      [7:0]    uart_din,
    input               uart_en,
    
    output              uart_tx_busy,
    output reg          uart_txd    
    );
    
parameter   CLK_FRED = 50_000_000;
parameter   UART_BPS = 115200;
parameter   BPS_CNT  = CLK_FRED/UART_BPS;

reg                 uart_en_reg0;
reg                 uart_en_reg1;
wire                en_flag;
reg                 tx_flag;
reg  [7:0]          txdata;
reg  [3:0]          tx_cnt;
reg  [15:0]         clk_cnt;

assign uart_tx_busy = tx_flag;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        uart_en_reg0 <= 0;
        uart_en_reg1 <= 0;
    end
    else begin
        uart_en_reg0 <= uart_en;
        uart_en_reg1 <= uart_en_reg0;
    end
end

assign  en_flag = (~uart_en_reg1 & uart_en_reg0);

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        txdata <= 0;
        tx_flag <= 0;    
    end
    else begin
        if(en_flag)begin
            tx_flag <= 1'b1;
            txdata <= uart_din;    
        end
        else if(tx_cnt == 4'd9 && clk_cnt == BPS_CNT - 1'b1)begin
            txdata <= 0;
            tx_flag <= 0;    
        end
        else begin
            tx_flag <= tx_flag;
            txdata <= txdata;
        end
    end 
end

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        clk_cnt <= 0;
    else begin
        if(tx_flag)begin
            if(clk_cnt == BPS_CNT-1'b1)
                clk_cnt <= 0;
            else
                clk_cnt <= clk_cnt + 1'b1;    
        end
        else
            clk_cnt <= 0;
    end     
end

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        tx_cnt <= 0;
    else begin
        if(tx_flag)begin
            if(clk_cnt == BPS_CNT-1'b1)
                tx_cnt <= tx_cnt + 1'b1;
            else
                tx_cnt <= tx_cnt;    
        end
        else
            tx_cnt <= 0;
    end 
end

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        uart_txd <= 1'b1;
    else begin
        case(tx_cnt)
            4'd0 : uart_txd <= 1'b0;
            4'd1 : uart_txd <= txdata[0];
            4'd2 : uart_txd <= txdata[1];
            4'd3 : uart_txd <= txdata[2];
            4'd4 : uart_txd <= txdata[3];
            4'd5 : uart_txd <= txdata[4];
            4'd6 : uart_txd <= txdata[5];
            4'd7 : uart_txd <= txdata[6];
            4'd8 : uart_txd <= txdata[7];
            4'd9 : uart_txd <= 1'b1;
            default:;
        endcase
    end
end             
endmodule
环回模块

环回模块的作用是将接收数据缓一下再发送到发送模块,防止直接使用接收模块和发送模块产生的停止位的时间不够而引起数据传输有问题!!!

module uart_loop(
    input               sys_clk,//系统时钟
    input               sys_rst_n,//系统复位,低电平有效
    
    input       [7:0]   recv_data,//接收的数据
    input               recv_done,//接收数据完的标志
    input               tx_busy,//数据发送模块繁忙
    
    output reg  [7:0]   send_data,//发送数据
    output reg          send_en  // 发送使能   
    );
    
reg         recv_done_reg0;
reg         recv_done_reg1;
wire        recv_done_flag;//检测接收标志上升沿的标志
reg         tx_ready;//发送准备信号

//这就是个检测接收数据使能上升沿的边缘检测模块
assign recv_done_flag = (~recv_done_reg1 & recv_done_reg0);
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        recv_done_reg0 <= 0;
        recv_done_reg1 <= 0; 
    end
    else begin
        recv_done_reg0 <= recv_done;
        recv_done_reg1 <= recv_done_reg0;
    end
end
//只有当数据接收完,才会准备把输入的数据输出给输出口,并且只有当发送准备标志开始时并且发送模块传来不繁忙时才会传输
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        tx_ready <= 0;
        send_data <= 0;
        send_en <= 0;       
    end
    else begin
        if(recv_done_flag)begin
            tx_ready <= 1'b1;
            send_data <= recv_data;
            send_en <= 0;    
        end
        else if((tx_ready)&(~tx_busy))begin
            tx_ready <= 0;
            send_en <= 1'b1;
        end
    end
end                  
endmodule
顶层模块

顶层模块就只是例化各个子模块而已

module top_uart_loopback(
    input               sys_clk,
    input               sys_rst_n,
    
    input               uart_rxd,
    output              uart_txd    
    );
    
parameter   CLK_FRED = 50_000_000;
parameter   UART_BPS = 115200;

wire   [7:0]    uart_recv_data;
wire            uart_recv_done;
wire   [7:0]    uart_send_data;
wire            uart_send_en;
wire            uart_tx_busy;
uart_receive #(
    .CLK_FRED      (CLK_FRED),
    .UART_BPS      (UART_BPS)
)
u_uart_receive(
    .sys_clk       (sys_clk),
    .sys_rst_n     (sys_rst_n),
    .uart_rxd      (uart_rxd),

    .uart_data     (uart_recv_data),
    .uart_done     (uart_recv_done)
    );
//    
uart_send #(
    .CLK_FRED      (CLK_FRED),
    .UART_BPS      (UART_BPS)
)    
u_uart_send(
    .sys_clk       (sys_clk),
    .sys_rst_n     (sys_rst_n),
    .uart_din      (uart_send_data),
    .uart_en       (uart_send_en),

    .uart_tx_busy  (uart_tx_busy),
    .uart_txd      (uart_txd)
    );
//    
uart_loop u_uart_loop(
    .sys_clk       (sys_clk),
    .sys_rst_n     (sys_rst_n),
   
    .recv_data     (uart_recv_data),
    .recv_done     (uart_recv_done),
    .tx_busy       (uart_tx_busy),

    .send_data     (uart_send_data),
    .send_en       (uart_send_en)
    );    
endmodule

功能仿真

`timescale 1ns / 1ps
//
module tb_uart_loopback();
    reg        sys_clk; 
    reg        sys_rst_n;     
    reg        uart_rxd;
    wire        uart_txd; 

top_uart_loopback u_top_uart_loopback(
    .sys_clk          (sys_clk),
    .sys_rst_n        (sys_rst_n),

    .uart_rxd         (uart_rxd),
    .uart_txd         (uart_txd)
    );

initial begin
    sys_rst_n = 0;
    #200
    sys_rst_n = 1;
end
initial begin
    sys_clk = 0;
    forever #10 sys_clk = ~sys_clk;
end  
 
parameter   CLK_FRED = 50_000_000;
parameter   UART_BPS = 115200;
parameter   BPS_CNT  = CLK_FRED/UART_BPS;
 
reg [3:0] i;
reg [15:0] cnt0;
reg [3:0]  cnt1;

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cnt0 <= 0;
    else begin
        if(cnt0 == BPS_CNT - 1)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1'b1;    
    end     
end    

always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)
        cnt1 <= 0;
    else begin
        if(cnt0 == BPS_CNT - 1)
            cnt1 <= cnt1 + 1'b1;
        else 
            cnt1 <= cnt1;    
    end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
    if(!sys_rst_n)begin
        i <= 0;
        uart_rxd <= 1'b1;
    end
    else begin
        case(i)
            0: i <= i + 1;  
            1: begin
                if(cnt1 == 4'd1)begin 
                    uart_rxd <= 1'b0;
                    i <= i + 1;
                end
               end
            2: begin
                if(cnt1 == 4'd2)begin 
                    uart_rxd <= 1'b1;
                    i <= i + 1;
                end
               end
            3: begin 
                if(cnt1 == 4'd3)begin 
                    uart_rxd <= 1'b1;
                    i <= i + 1;
                end
               end
            4: begin 
                if(cnt1 == 4'd4)begin 
                    uart_rxd <= 1'b0;
                    i <= i + 1;
                end
               end
            5: begin 
                if(cnt1 == 4'd5)begin 
                    uart_rxd <= 1'b0;
                    i <= i + 1;
                end
               end
            6: begin 
                if(cnt1 == 4'd6)begin 
                    uart_rxd <= 1'b1;
                    i <= i + 1;
                end
               end
            7: begin 
                if(cnt1 == 4'd7)begin 
                    uart_rxd <= 1'b1;
                    i <= i + 1;
                end
               end
            8: begin 
                if(cnt1 == 4'd8)begin 
                    uart_rxd <= 1'b0;
                    i <= i + 1;
                end
               end
            9: begin 
                if(cnt1 == 4'd9)begin 
                    uart_rxd <= 1'b0;
                    i <= i + 1;
                end
               end
            10: begin 
                if(cnt1 == 4'd10)begin 
                    uart_rxd <= 1'b1;
                    i <= i + 1;
                end
               end
            11: i<= 0; 
            default:;
        endcase
    end 
end 
endmodule

在这里插入图片描述
到此结束了,因为笔者比较匆忙,而是没写过写过几次,见谅!

  • 9
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值