FPGA知识基础之--uart串口通信协议,附RTL和Testbench代码


本篇学习博客部分参考了 原文连接
以及正点原子的官方文档

一、通信方式

1 定义

通信方式是指通信双方之间的工作方式或信号传输方式。 它涉及终端与其他设备(如其他终端、计算机和外部设备等)之间通过数据传输进行的通信。通信方式的选择取决于多种因素,包括传输距离、传输速度、成本以及通信环境的特性等。
在这里插入图片描述

2 分类

按数据传输方式分类:

串行通信:又称为点对点通信,是指利用一条传输线将数据一位位地顺序传送。这种方式通信线路简单,利用电话或电报线就可以实现通信,降低成本,适用于远距离通信,但传输速度相对较慢。
并行通信:是指利用多条传输线将一个数据的各位同时传送。这种方式传输速度快,适用于短距离通信,但所需的通信线路和设备成本较高。
在这里插入图片描述
![(https://i-blog.csdnimg.cn/direct/682ddf5d1ff6459e828923c35fb096cb.png#pic_center)

按数据的同步方式分类:

异步传送:在异步传送中,每个字符的传输是独立的,字符与字符之间的时间间隔可以是任意的。这种方式简单、易实现,但效率较低。异步通讯需要在数据信号之间穿插一些同步用的信号位,或者把主体数据打包 ,例如规定由起始位,数据位,奇偶校验位,停止位 。常见的有UART单总线
在这里插入图片描述

同步传送:在同步传送中,数据以数据块(或帧)为单位进行传输,数据块之间由特定的同步字符进行分隔。这种方式传输效率高,但需要额外的同步控制机制。通信双方会规定在是时钟信号的上升沿或者下降沿对数据进行采样。例如IIC(同步半双工),SPI通信接口(同步全双工)
在这里插入图片描述
同步通讯效率更高,因为传输的绝大部分都是有效数据,而异步通讯会包含有帧的各种标志符。
但是同步通讯双方的时钟误差允许小,异步双方允许的时钟误差大

按数据的传输方向与时间关系分类:

单工通信:只能实现单方向的数据传输,如广播、遥控、无线寻呼等。
半双工通信:可以实现双向的数据传输,但在同一时刻只能有一个方向的数据传输,如对讲机、收发报机等。
全双工通信:可以实现双向的数据传输,且在同一时刻两个方向的数据传输可以同时进行,如手机、电话等。

在这里插入图片描述

二、Uart串口通信

1.基本定义

UART是一种串行、异步、全双工的通信协议,用于在计算机和外部设备之间以串行方式传输数据。它支持异步通信,即发送端和接收端的时钟源可以不同,通过起始位和停止位来同步数据的发送和接收。

2. 物理层:接口类型

  • 常见的串行通信接口有RS232,RS485,RS422。其标准只对接口的电气特性(电压,阻抗)做出规定,而不涉及接插件、电缆或协议,在此基础上用户可以建立自己的高层通信协议。

  • 目前RS-232是PC机与通信工业中应用最广泛的一种串行接口。RS-232被定义为一种在低速率串行通讯中增加通讯距离的单端标准。采用三条信号线(接收线、发送线和信号地)能实现简单的全双工通信过程。

  • 传输距离短的另一原因是RS-232属单端信号传送,存在共地噪声和不能抑制共模干扰等问题,因此一般用于20m以内的通信。

电气特性

RS-232对电气特性、逻辑电平和各种信号功能都做了规定,如下:

在TXD和RXD数据线上:

(1)逻辑1的电平为-3V~-15V

(2)逻辑0的电平为+3~+15V的电压

规定逻辑“1”的电平为-5V~-15 V,逻辑“0”的电平为+5 V~+15 V。选用该电气标准的目的在于提高抗干扰能力,增大通信距离。RS -232的噪声容限为2V,接收器将能识别高至+3V的信号作为逻辑“0”,将低到-3 V的信号作为逻辑“1

机械特性

每种接头都有公头和母头之分,其中带针状的接头是公头,而带孔状的接头是母头。9针串口的外观如下图所示
在这里插入图片描述
管脚说明
在这里插入图片描述

本次实验以RS232 为例,我们用9针串口,即DB9,仅需要做简单的全双工通信,因此仅需要接收线、发送线和信号地

  • 接收数据( Received data,RXD)——通过RXD线终端接收从 MODEM发来的串行数据(DCE→DTE)。
    接收信号(RXD),数据终端设备(DTE)通过该信号线接收从数据通信设备(DCE)发来的串行数据。
  • 发送数据( Transmitted data,TXD)——通过TXD终端将串行数据发送到 MODEM(DTE→DCE)。
    发送数据(TXD),数据终端设备(DTE)通过该信号线将串行数据发送到数据通信设备(DCE)。
  • 地线-GND。
    地线(SG、PG),分别表示信号地和保护地信号线。

需要注意的是:接收数据时串行数据转为并行数据,发送数据时并行数据转为串行数据

因为电平标准和TTL,CMOS电平标准不一样,所以需要有电平转换芯片才能实现设备间的通信

有DB9接口的情况
在这里插入图片描述
没有DB9接口的情况
在这里插入图片描述
要在PC端下载CH340USB虚拟串口驱动,来将USB映射为驱动

转换电路

USB转串口的转换电路如下
在这里插入图片描述
电源转换芯片在这里插入图片描述

3.协议层:UART通信协议

在这里插入图片描述

1.内容

起始位:检测到下降沿时,即传输开始,占用一位;
数据位:一般是5到8为,LSB在前,MSB在后;
检验位:可选,占用1位;
停止位:可占用0.5,1,1.5,2,个比特位,保持高电平;

2.波特率

串口通信时每秒钟可以传输多少个二进制位,常见的波特率有9600,19200,38400,57600,115200,单位是bps,在FPGA串口通信中计算波特率计数器的最大值时,可通过频率除以波特率来计算

再次强调一遍!!
接收数据时串行数据转为并行数据,发送数据时并行数据转为串行数据总之出去的一定是串的,因为这是串行通信呀老哥!
波特率高的优缺点
优点:

  • 更高的数据传输速度:波特率越高,数据传输速度越快,能够在单位时间内传输更多数据。
  • 减少延迟:高波特率能够降低通信延迟,对于实时性要求高的应用场景非常有利。

缺点:

  • 提升误码率:波特率越大,传输速度越快,对信号完整性的要求越高,容易受噪声和危害,造成误码率的提升。
  • 硬件限定:部分系统配置对高波特率的适用性有限,可能不能稳定工作。
  • 传输距离限定:在远程传输中,高波特率信号衰减和帧遗失更严重,主要用途有限。

因此,对波特率需要合理优化,并非越高越好

  • 根据应用需求选择合适的波特率:如果是数据量较大、传输距离较短且硬件性能较高的场景,可以选择较高的波特率;如果是长距离传输或对可靠性要求高的场景,则应选择较低的波特率。
  • -确保硬件支持:在设定波特率时,确保所选的波特率在通信双方的硬件设备上都能稳定运行。查阅设备数据手册,了解其支持的最大波特率范围。
  • 信号质量:在高波特率下,信号的完整性和质量至关重要。应使用质量较高的电缆和连接器,尽量避免长距离传输和复杂的电磁环境,以减少干扰和噪声对信号的影响。
  • 使用校验和纠错:为了提高数据传输的可靠性,尤其是在高波特率下,可以采用校验和纠错技术,如奇偶校验、CRC校验等。这些方法可以检测并纠正传输过程中的错误,提高通信的准确性。
  • 测试和验证:在实际应用中,应通过测试和验证确定最佳波特率设置。通过测试不同波特率下的误码率和传输性能,选择合适的波特率。

三、实验任务

上位机通过串口调试助手,发送数据给开发板,开发板通过USB UART串口接收数据,并将接收到的数据发送给上位机,完成串口数据环回,波特率为:115200.停止位:1,数据位:8位,无校验位

四、程序设计

1.模块

接收模块
在这里插入图片描述
发送模块
在这里插入图片描述
系统模块
在这里插入图片描述
笔者在第一遍看正点原子视频时其实也是很迷糊的,很多概念是云里雾里,所以读者看框图时觉得有点难是正常的

2.时序分析

接收模块时序
在这里插入图片描述
发送模块时序
在这里插入图片描述

3.RTL代码

接收模块

module uart_rx(
    input                   rx_clk,
    input                   rst_n,
    input                   uart_rxd,

    output reg              uart_rx_done,
    output reg  [7:0]       uart_rx_data
);  

parameter CLK_FREQ = 50000000;              
parameter UART_BPS = 115200  ;              
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //根据波特率和时钟频率能算出波特率计数器最大值

reg             uart_rxd_d0;
reg             uart_rxd_d1;
reg             uart_rxd_d2;
reg             rx_flag ;
reg [3:0]       rx_cnt;
reg [15:0]      baud_cnt;
reg [7:0]       rx_data_t;

wire    start_en;


assign start_en = (!uart_rxd_d0)& uart_rxd_d1 & (!rx_flag);
//打两拍,以及检测下降沿
always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rxd_d0 <= 1'd0;
        uart_rxd_d1 <= 1'd0;
        uart_rxd_d2 <= 1'd0;    
    end
    else begin 
    uart_rxd_d0 <= uart_rxd;
    uart_rxd_d1 <= uart_rxd_d0;
    uart_rxd_d2 <= uart_rxd_d1; 
    end
end

//给接收标志赋值
always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n) 
        rx_flag <= 1'b0;
    else if(start_en)    //检测到起始位
        rx_flag <= 1'b1; //接收过程中,标志信号rx_flag拉高
    //在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低
    else if((rx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX/2 - 1'b1))
        rx_flag <= 1'b0;
    else
        rx_flag <= rx_flag;
end  

//给接受计数器赋值
always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n) 
        rx_cnt <= 4'd0;
    else if(rx_flag)
        if(baud_cnt == BAUD_CNT_MAX - 1'b1)
            rx_cnt <= rx_cnt + 16'd1;
        else
            rx_cnt <= rx_cnt;
    else  rx_cnt <=  4'd0;

end 

//给波特率赋值
always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n)
        baud_cnt <= 16'd0;
    else if(rx_flag && baud_cnt < (BAUD_CNT_MAX - 1)) 
        baud_cnt <= baud_cnt + 16'd1;
    else 
        baud_cnt <= 16'd0;
end


always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n)
        rx_data_t <= 8'd0;
   else if(rx_flag) begin                           //系统处于接收过程时
            if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin  //判断baud_cnt是否计数到数据位的中间
                case(rx_cnt)
                    
                            4'd1 : rx_data_t[0] <= uart_rxd_d2;   //寄存数据的最低位
                            4'd2 : rx_data_t[1] <= uart_rxd_d2;
                            4'd3 : rx_data_t[2] <= uart_rxd_d2;
                            4'd4 : rx_data_t[3] <= uart_rxd_d2;
                            4'd5 : rx_data_t[4] <= uart_rxd_d2;
                            4'd6 : rx_data_t[5] <= uart_rxd_d2;
                            4'd7 : rx_data_t[6] <= uart_rxd_d2;
                            4'd8 : rx_data_t[7] <= uart_rxd_d2;   //寄存数据的高低位
                        default : ;
                endcase
            end
            else rx_data_t <= rx_data_t;
                    end
    else 
        rx_data_t <= 8'b0;       
end

//给接收完成信号和接收到的数据赋值
always @(posedge rx_clk or negedge rst_n) begin
    if(!rst_n) begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= 8'b0;
    end
    //当接收数据计数器计数到停止位,且baud_cnt计数到停止位的中间时
    else if(rx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX/2 - 1'b1) begin
        uart_rx_done <= 1'b1     ;  //拉高接收完成信号
        uart_rx_data <= rx_data_t;  //并对UART接收到的数据进行赋值
    end    
    else begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= uart_rx_data;
    end
end

endmodule

发送模块

module uart_tx(
    input                   tx_clk,
    input                   rst_n,
    input                   uart_tx_en,
    input        [7:0]      uart_tx_data,

    output      reg         uart_txd,
    output      reg         uart_tx_busy
);      

reg [7:0]            tx_data_t;
reg [15:0]           baud_cnt;
reg [3:0]            tx_cnt;



parameter CLK_FREQ = 50000000;              
parameter UART_BPS = 115200  ;              
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //根据波特率和时钟频率能算出波特率最大值



//当uart_tx_en为高时,寄存输入的并行数据,并拉高BUSY信号
always @(posedge tx_clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_data_t <= 8'b0;
        uart_tx_busy <= 1'b0;
    end
    //发送使能时,寄存要发送的数据,并拉高BUSY信号
    else if(uart_tx_en) begin
        tx_data_t <= uart_tx_data;
        uart_tx_busy <= 1'b1;
    end
    //当计数到停止位结束时,停止发送过程
    else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1) begin
        tx_data_t <= 8'b0;     //清空发送数据寄存器
        uart_tx_busy <= 1'b0;  //并拉低BUSY信号
    end
    else begin
        tx_data_t <= tx_data_t;
        uart_tx_busy <= uart_tx_busy;
    end
end

//对波特计数器赋值
always @(posedge tx_clk or negedge rst_n) begin
    if(!rst_n)
        baud_cnt <= 16'd0;
    else if(uart_tx_en)
        baud_cnt <= 16'd0;  
    else if(uart_tx_busy) begin
            if (baud_cnt < (BAUD_CNT_MAX-1))
                baud_cnt <= baud_cnt + 16'd1; 
            else
                baud_cnt <= 16'd0;
    end
    else 
        baud_cnt <= 16'd0; 
end
//tx_cnt进行赋值
always @(posedge tx_clk or negedge rst_n) begin
    if(!rst_n) 
        tx_cnt <= 4'd0;
    else if(uart_tx_en)  
        tx_cnt <= 16'd0;         
    else if(uart_tx_busy) begin             //处于发送过程时tx_cnt才进行计数
        if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
            tx_cnt <= tx_cnt + 1'b1;        //发送数据计数器加1
        else
            tx_cnt <= tx_cnt;
    end
    else
        tx_cnt <= 4'd0;                     //发送过程结束时计数器清零
end



//对uart_txd赋值
always @(posedge tx_clk or negedge rst_n) begin
    if(!rst_n) 
        uart_txd <= 1'b1;
    else if(uart_tx_busy) begin
        case(tx_cnt) 
            4'd0 : uart_txd <= 1'b0        ; //起始位
            4'd1 : uart_txd <= tx_data_t[0]; //数据位最低位
            4'd2 : uart_txd <= tx_data_t[1];
            4'd3 : uart_txd <= tx_data_t[2];
            4'd4 : uart_txd <= tx_data_t[3];
            4'd5 : uart_txd <= tx_data_t[4];
            4'd6 : uart_txd <= tx_data_t[5];
            4'd7 : uart_txd <= tx_data_t[6];
            4'd8 : uart_txd <= tx_data_t[7]; //数据位最高位
            4'd9 : uart_txd <= 1'b1        ; //停止位
            default : uart_txd <= 1'b1;
        endcase
    end
    else
        uart_txd <= 1'b1;                    //空闲时发送端口为高电平
end


endmodule

顶层模块

module uart_loopback(
    input       sys_clk,
    input       sys_rst_n,
    input       uart_rxd,

    output      uart_txd
);

//parameter define
parameter CLK_FREQ = 50000000;    //定义系统时钟频率
parameter UART_BPS = 115200  ;    //定义串口波特率


wire         uart_rx_done;    //UART接收完成信号
wire  [7:0]  uart_rx_data;    //UART接收数据

uart_rx     #(
    .CLK_FREQ  (CLK_FREQ),
    .UART_BPS  (UART_BPS)
    )
    u_uart_rx(
        .rx_clk              (sys_clk),
        .rst_n               (sys_rst_n),
        .uart_rxd            (uart_rxd),
        .uart_rx_done        (uart_rx_done),
        .uart_rx_data        (uart_rx_data)
    
);

uart_tx #(
    .CLK_FREQ  (CLK_FREQ),
    .UART_BPS  (UART_BPS)
)
u_uart_tx(
        .tx_clk              (sys_clk),
        .rst_n               (sys_rst_n),
        .uart_tx_en          (uart_rx_done),                       
        .uart_tx_data        (uart_rx_data),
        .uart_txd            (uart_txd),
        .uart_tx_busy        ()
);


endmodule

五、仿真

1.Testbench代码

我们用8‘d55来进行仿真验证’

`timescale  1ns/1ns   //仿真的单位/仿真的精度

module tb_uart_loopback();

//parameter define
parameter  CLK_PERIOD = 20;//时钟周期为20ns

//reg define
reg            sys_clk  ;  //时钟信号
reg            sys_rst_n;  //复位信号
reg            uart_rxd ;  //UART接收端口

//wire define
wire           uart_txd ;  //UART发送端口

//*****************************************************
//**                    main code
//*****************************************************

//发送8'h55  8'b0101_0101
initial begin
    sys_clk <= 1'b0;
    sys_rst_n <= 1'b0;
    uart_rxd <= 1'b1;
    #200
    sys_rst_n <= 1'b1;  
    #1000
    uart_rxd <= 1'b0;   //起始位
    #8680
    uart_rxd <= 1'b1;   //D0
    #8680
    uart_rxd <= 1'b0;   //D1
    #8680
    uart_rxd <= 1'b1;   //D2
    #8680
    uart_rxd <= 1'b0;   //D3
    #8680
    uart_rxd <= 1'b1;   //D4
    #8680
    uart_rxd <= 1'b0;   //D5
    #8680
    uart_rxd <= 1'b1;   //D6
    #8680
    uart_rxd <= 1'b0;   //D7 
    #8680
    uart_rxd <= 1'b1;   //停止位
    #8680
    uart_rxd <= 1'b1;   //空闲状态   
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

//例化顶层模块
uart_loopback  u_uart_loopback(
    .sys_clk      (sys_clk  ), 
    .sys_rst_n    (sys_rst_n),
    .uart_rxd     (uart_rxd ),
    .uart_txd     (uart_txd )
    );

endmodule

2.波形验证

在这里插入图片描述
在这里插入图片描述
验证正确!


  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值