UART串口通信——FPGA学习笔记9

一、数据通信基本概念

按数据通信方式分类:

串行通信、并行通信

按数据传输方向分类:

单工通信、半双工通信、全双工通信

按数据同步方式分类:

同步通信、异步通信

常见的串行通信接口:

二、串口通信:

UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

UART 串口通信需要两根信号线来实现, 一根用于串口发送,另外一根负责串口接收。 UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如图 19.1.1 所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束, 数据位是一帧数据中的有效数据。 校验位分为奇校验和偶校验, 用于检验数据在传输过程中是否出错。 奇校验时, 发送方应使数据位中 1 的个数与校验位中 1 的个数之和为奇数;接收方在接收数据时, 对 1 的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。 同样,偶校验则检查 1 的个数是否为偶数。

直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据。例如规定由起始位、数据位、奇偶校验位、停止位等。某些通讯中还需要双方约定数据的传输速率,以便更好地同步、

物理层:

UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为 5、 6、 7、 8 位,其中 8 位数据位是最常用的, 在实际应用中一般都选择 8 位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择 1 位(默认) , 1.5 或 2 位。 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒) ,常用的波特率有 9600、 19200、38400、 57600 以及 115200 等。

在设置好数据格式及传输速率之后, UART 负责完成数据的串并转换, 而信号的传输则由外部驱动电路实现。 电信号的传输过程有着不同的电平标准和接口规范, 针对异步串行通信的接口标准有 RS232、 RS422、RS485 等, 它们定义了接口不同的电气特性,如 RS-232 是单端输入输出,而 RS-422/485 为差分输入输出等。

RS232 接口标准出现较早, 可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过 15m) , RS232 是串行通信最常用的接口标准, RS-232 标准的串口最常见的接口类型为 DB9,样式如图 19.1.2 所示,工业控制领域中用到的工控机一般都配备多个串口, 很多老式台式机也都配有串口。

协议层:

UART通信协议

起始位:一帧的开始,必须保持一个比特位的低电平0

数据位:传输的有效数据,数据位可选5~8位;LSB(低位)在前,MSB(高位)在后

校验位:可选位,占用1个比特位,也可以没有校验

停止位:一帧的结束,必须有,可选占用0.5/1/1.5/2个比特位,保持逻辑高电平1

UART的传输速率:波特率

波特率(BaudRate):串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒)。常用的波特率有9600、19200、38400、57600以及115200等。

计算:

例如通信速率115200 那么一个比特位所占用的时间为

如果系统时钟为50MHz(周期20ns),那么每个周期占用时间为8680/20=434个时钟周期。

三、程序设计

1、实验任务

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

2、框图分析

接收模块要有接收完成标志

发送模块要求有忙信号标志

串口通信实现思维导图:

针对异步信号的同步处理------打三拍

什么是亚稳态?亚稳态是由于违背了触发器的建立和保持时间而产生的。寄存器采样需要满足一定的建立时间(setup)和保持时间(holdup),而异步电路没有办法保证建立时间(setup)和保持时间(holdup),所以会出现亚稳态。

消除亚稳态

3、波形分析

接收模块:

发送模块:

四、代码编写

1、 串口接收部分

Haskell 复制代码
`timescale 1ns / 1ps

module uart_rx(
input               sys_clk,
input               sys_rst_n,
input               uart_rxd,                   //串口输入信号
output  reg         uart_rx_done,               //串口接收完成信号
output  reg  [7:0]  uart_rx_data                //串口接收数据
);


parameter  CLK_FREQ      = 50_000_000;        //时钟
parameter  UART_BPS      = 115200;            //波特率
localparam BAUD_CNT_MAX  = CLK_FREQ / UART_BPS;   //50Mhz 115200

reg             uart_rx_d0  ;       
reg             uart_rx_d1  ;
reg             uart_rx_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_rx_d2 &(!uart_rx_d1)&(!rx_flag);

//数据打拍
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if(!sys_rst_n)begin
        uart_rx_d0  <= 1'b0     ;
        uart_rx_d1  <= 1'b0     ;
        uart_rx_d2  <= 1'b0     ;
    end
    else begin
        uart_rx_d0  <= uart_rxd    ;
        uart_rx_d1  <= uart_rx_d0  ;
        uart_rx_d2  <= uart_rx_d1  ;
    end
end

//给接收标志rx_flag赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if (!sys_rst_n) begin
        rx_flag <= 1'b0;
    end
    else if (start_en == 1'b1) begin
        rx_flag <= 1'b1;
    end
    //计数到9,在停止位一半的时候拉低
    else if ((rx_cnt == 4'd9)&&(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)) begin
        rx_flag <= 1'b0;
    end
    else begin
        rx_flag <= rx_flag;
    end
end

//对波特率计数器baud_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if (!sys_rst_n) begin
        baud_cnt <= 16'd0;
    end
    else if (rx_flag == 1'b1) begin
        if(baud_cnt < BAUD_CNT_MAX - 1'b1)begin
            baud_cnt <= baud_cnt + 16'b1;
        end
        else begin
            baud_cnt <= 16'd0;
        end
    end
    else begin
        baud_cnt <= 16'd0;    
    end
end

//对数据计数器rx_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        rx_cnt <= 4'd0;
    end
    else if(rx_flag == 1'b1)begin
        if (baud_cnt == BAUD_CNT_MAX - 1'b1) begin
            rx_cnt <= rx_cnt + 4'b1;
        end
        else begin
            rx_cnt <= rx_cnt;
        end
    end
    else begin
        rx_cnt <= 4'd0;       
    end
end

//根据rx_cnt寄存输入数据
always @(posedge sys_clk or negedge sys_rst_n  ) begin
    if (!sys_rst_n) begin
        rx_data_t <= 8'd0;
    end
    else if (rx_flag == 1'b1) begin
        if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)begin
            case (rx_cnt)
                4'd1: rx_data_t[0] <= uart_rx_d2;
                4'd2: rx_data_t[1] <= uart_rx_d2;
                4'd3: rx_data_t[2] <= uart_rx_d2;
                4'd4: rx_data_t[3] <= uart_rx_d2;
                4'd5: rx_data_t[4] <= uart_rx_d2;
                4'd6: rx_data_t[5] <= uart_rx_d2;
                4'd7: rx_data_t[6] <= uart_rx_d2;
                4'd8: rx_data_t[7] <= uart_rx_d2;
                default: ;
            endcase
        end
        else begin
            rx_data_t <= rx_data_t;
        end
    end
    else begin
        rx_data_t <= 8'd0;   
    end
end

//给接收完成信号uart_rx_done 和接收uart_rx_data 赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if (!sys_rst_n) begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= 8'd0;

    end
    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;
    end
    else begin
        uart_rx_done <= 1'b0;
        uart_rx_data <= uart_rx_data;        
    end
end

endmodule

2、串口发送部分

Haskell 复制代码
`timescale 1ns / 1ps
module uart_tx(
input           sys_clk,
input           sys_rst_n,
input           uart_tx_en,             //串口发送数据使能
input   [7:0]   uart_tx_data,           //串口准备发送的数据
output  reg     uart_txd,               //串口输出
output  reg     uart_tx_busy            //串口忙的标志
);

parameter  CLK_FREQ      = 50_000_000;              //时钟
parameter  UART_BPS      = 115200;                  //波特率
localparam BAUD_CNT_MAX  = CLK_FREQ / UART_BPS;     //50Mhz 115200

reg     [7:0]   tx_data_t;
reg     [3:0]   tx_cnt;
reg     [15:0]  baud_cnt    ;       //波特率计数器

//当uart_tx_en为高时    寄存输入的并行数据   且拉高busy信号
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if (!sys_rst_n) begin
        tx_data_t       <= 8'd0;
        uart_tx_busy    <= 1'b0;
    end
    else if (uart_tx_en == 1'b1) begin
        tx_data_t       <= uart_tx_data;
        uart_tx_busy    <= 1'b1;
    end
    else if ((tx_cnt == 4'd9)&&(baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16)) begin
        tx_data_t       <= 8'd0;
        uart_tx_busy    <= 1'b0;        
    end
    else begin
        tx_data_t       <=  tx_data_t     ;
        uart_tx_busy    <=  uart_tx_busy  ;      
    end
end

//对波特率计数器baud_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
    if (!sys_rst_n) begin
        baud_cnt <= 16'd0;
    end
    else if (uart_tx_busy == 1'b1) begin
        if(baud_cnt < BAUD_CNT_MAX - 1'b1)begin
            baud_cnt <= baud_cnt + 16'b1;
        end
        else begin
            baud_cnt <= 16'd0;
        end
    end
    else begin
        baud_cnt <= 16'd0;    
    end
end

//对数据计数器tx_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        tx_cnt <= 4'd0;
    end
    else if(uart_tx_busy == 1'b1)begin
        if (baud_cnt == BAUD_CNT_MAX - 1'b1) begin
            tx_cnt <= tx_cnt + 4'b1;
        end
        else begin
            tx_cnt <= tx_cnt;
        end
    end
    else begin
        tx_cnt <= 4'd0;       
    end
end

//根据tx_cnt控制TXD信号
always @(posedge sys_clk or negedge sys_rst_n) begin
    if (!sys_rst_n) begin
        uart_txd <= 1'd1;
    end
    else if (uart_tx_busy == 1'b1) begin
        case (tx_cnt)
            4'd0: uart_txd <= 1'b0;             //起始位
            4'd1: uart_txd <= tx_data_t[0];     //d0
            4'd2: uart_txd <= tx_data_t[1];     //d1
            4'd3: uart_txd <= tx_data_t[2];     //d2
            4'd4: uart_txd <= tx_data_t[3];     //d3
            4'd5: uart_txd <= tx_data_t[4];     //d4
            4'd6: uart_txd <= tx_data_t[5];     //d5
            4'd7: uart_txd <= tx_data_t[6];     //d6
            4'd8: uart_txd <= tx_data_t[7];     //d7
            4'd9: uart_txd <= 1'b1;             //停止位
            default: uart_txd <= 1'b1;
        endcase
    end
    else begin
        uart_txd <= 1'b1;
    end
end
    
endmodule

3、顶层代码

Haskell 复制代码
`timescale 1ns / 1ps

module uart_loopback(
input           sys_clk,
input           sys_rst_n,

//UART端口
input           uart_rxd,
output          uart_txd

);

parameter  CLK_FREQ      = 50_000_000   ;              //时钟
parameter  UART_BPS      = 115200       ;                  //波特率

wire           uart_rx_done ;
wire    [7:0]  uart_rx_data ;

uart_rx #(
    .CLK_FREQ   (CLK_FREQ),
    .UART_BPS   (UART_BPS)  
    )
    u_uart_rx(
        .sys_clk        (sys_clk     )  ,
        .sys_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(
        .sys_clk        (sys_clk     )  ,
        .sys_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

4、tb代码

Haskell 复制代码
`timescale 1ns / 1ps

module tb_uart_loopbak( );

parameter       CLK_PERIOD = 20;

reg     sys_clk;
reg     sys_rst_n;
reg     uart_rxd;
wire    uart_txd;

initial begin
    sys_clk <= 1'b0;
    sys_rst_n <= 1'b0;
    uart_rxd <= 1'b1;   //空闲状态
    #200
    sys_rst_n <= 1'b1;
    #1000
    //发送0x55   8'b0101_0101
    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

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

5、管脚定义

五、仿真与程序验证

1、Modelsim仿真

仿真结果符合预期结果。

2、下载验证

最终结果符合预期。

六、源码获取

私聊笔者获取源码

相关推荐
MC何失眠16 分钟前
vulnhub靶场之stapler靶机
网络·学习·安全·web安全·网络安全
怀澈1221 小时前
【ElasticSearch】学习笔记
笔记·学习
修修修也4 小时前
算法手记3
数据结构·学习·算法·刷题
JM丫5 小时前
python基础
笔记·python
gaorenyusi7 小时前
PentestGPT 下载
学习·web安全
小王努力学编程7 小时前
元音辅音字符串计数leetcode3305,3306
开发语言·c++·学习·算法·leetcode
啥都想学的又啥都不会的研究生8 小时前
Redis设计与实现-数据持久化
java·数据库·redis·笔记·缓存·面试
hxung8 小时前
Linux 命令学习记录
linux·运维·学习
千千道8 小时前
FPGA 中 assign 和 always 区别
fpga开发
hunandede8 小时前
QT 学习一 paintEvent,QPainter ,QImage
开发语言·qt·学习