FPGA——UART串口通信

news2024/11/19 3:37:31

文章目录

    • 前言
    • 一、UART通信协议
      • 1.1 通信格式
      • 2.2 MSB或LSB
      • 2.3 奇偶校验位
      • 2.4 UART传输速率
    • 二、UART通信回环
      • 2.1 系统架构设计
      • 2.2 fsm_key
      • 2.3 baud
      • 2.4 sel_seg
      • 2.5 fifo
      • 2.6 uart_rx
      • 2.7 uart_tx
      • 2.8 top_uart
      • 2.9 发送模块时序分析
      • 2.10 接收模块的时序分析
      • 2.11 FIFO控制模块时序分析
    • 三、仿真
      • 3.1 testbench
      • 3.2 接收模块仿真分析
      • 3.3 发送模块仿真分析
      • 3.4 FIFO控制模块仿真分析
    • 四、上板验证
    • 五、总结

前言

本篇博客的实验内容是实现uart串口通信发送数据和接收数据,并加入按键模块调整波特率,数码管模块显示波特率,实现不同波特率下的PC端与FPGA板子的数据回环,并加入FIFO IP核对数据进行缓存,当数据大于等于8个的时候,一次性发送8个数据出去。
实验环境:quartus 18.1 modelsim Cyclone IV开发板

一、UART通信协议

UART 即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),是一种串行、异步、全双工的通信协议。特点是通信线路简单,适用于远距离通信,但传输速度慢。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

1.1 通信格式

在这里插入图片描述
它的一帧数据由4部分组成:
起始位(1bit)
数据位(6\7\8 bit)
奇偶校验位(1bit)
停止位(1bit\1.5bit\2bit)
注意:它在空闲状态下为高电平,起始位为低电平,停止位为高电平。它的数据位甚至可以为5位或者4位,但是不能为9位及以上。
为什么起始位是低电平?
因为它的空闲位为高电平,必须有一个由高到低的下降沿变化才能知道后面要传输数据了,不然如果起始位为高电平那它怎么知道你何时传数据呢?

2.2 MSB或LSB

它的数据位传输顺序可以采用MSB也可以采用LSB,由通信双方决定。
MSB:数据高位先发送
LSB:数据低位先发送
在本次实验的工程里面采用的是LSB的格式传输。

2.3 奇偶校验位

奇校验:当实际数据中“1”的个数为奇数的时候,这个校验位就是“0”,否则这个校验位就是“1”;
偶校验:当实际数据中“1”的个数为偶数的时候,这个校验位就是“0”,否则这个校验位就是“1”。
注意:奇偶校验只能检验奇数个数据的错误,对于偶数个数据的错误无法检测。
本次实验没有用到奇偶校验,因此在这里单独进行说明:
在接收模块中接收串行数据的校验方法:
奇校验实现方式:校验位默认为高电平,每检测到1,则状态翻转
偶校验实现方式:校验位默认为低电平,每检测到1,则状态翻转
在这里插入图片描述
核心代码:
在这里插入图片描述
在接收模块接收并行数据进行校验:
偶校验:将输入数据按位异或
奇校验:将输入数据按位异或再取反(与偶校验相反)
核心代码:
在这里插入图片描述

2.4 UART传输速率

UART的数据传输速率用波特率(baud)表示,常见的波特率有9600、19200、38400、57600、115200,最常用的是9600和115200。
通信双方必须保持一致的波特率,不然数据就会出错,这个在最后我们的上板验证会体现。
以9600为例,它代表我们传输1bit数据所需要的时间是1/9600=104160ns,我们的时钟周期为20ns,所以传输1bit数据需要104160/20 = 5208个时钟周期。
注意波特率和比特率的区别
比特率:是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,每秒传送数据就越多。
波特率:在电子通信领域,波特(Baud)即调制速率,指的是有效数据信号调制载波的速率,即单位时间内载波调制状态变化的次数。它是对符号传输速率的一种度量,1波特即指每秒传输1个符号,而通过不同的调制方式,可以在一个码元符号上负载多个bit位信息。
在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是传输通道频宽的指标。
比特率=波特率x单个调制状态对应的二进制位数。

二、UART通信回环

2.1 系统架构设计

在这里插入图片描述
该系统分为了6个模块,分别是按键消抖模块(fsm_key)、波特率设置模块(baud)、数码管显示模块(sel_seg)、接收模块(uart_rx)、FIFO数据缓存模块(fifo)以及发送模块(uart_tx)。
首先是通过按键调整波特率:输入的按键信号进入按键消抖模块,然后将消抖后的按键信号传给波特率设置模块,通过按键信号可以设置不同的波特率,有常用的波特率选择:9600、19200、38400、57600、115200。默认情况下是9600。然后把设置好的波特率传给数码管显示模块进行波特率的显示。同时将设置好的波特率传给接收和发送模块,两个模块的波特率应该保持一致。同时接收模块接收了外界传入的串行数据后,将串行数据转成并行数据传给FIFO模块进行数据缓存,当FIFO里面缓存的数据大于等于8个数据时,FIFO模块将开启数据发送使能信号,然后把读取到的一个数据传给发送模块,然后发送模块再把并行数据转成串行数据一个一个的发送出去。每发送完一个数据后都拉高一个数据发送完成的使能信号传给FIFO模块,FIFO在根据这个使能信号再读一个数据传给发送模块进行数据发送,重复该步骤直到发送出8个数据为止。

2.2 fsm_key

//三段式状态机实现按键消抖
module fsm_key #(parameter  CNT_20MS = 26'd1_000_000,
                            KEY_NUM  = 3'd1)(//20MS计数器和按键的数量
    input                           clk     ,//时钟信号
    input                           rst_n   ,//复位信号
    input        [KEY_NUM-1:0]      key_in  ,//按键输入信号

    output   reg [KEY_NUM-1:0]      key_out  //按键输出信号
);

    parameter   IDLE = 4'b0001,     //空闲状态
                DOWN = 4'b0010,     //按键按下抖动状态
                HOLD = 4'b0100,     //消抖后保持低电平有效状态
                UP   = 4'b1000;     //释放抖动状态

    reg  [3:0]          state_c;//现态
    reg  [3:0]          state_n;//次态

    reg  [25:0]         cnt_20ms;//20ms计数寄存器
    wire                add_cnt_20ms;//开始计数
    wire                end_cnt_20ms;//结束计数

    reg  [KEY_NUM-1:0]  key_r0;//同步
    reg  [KEY_NUM-1:0]  key_r1;//打拍
    wire [KEY_NUM-1:0]  nedge;//下降沿
    wire [KEY_NUM-1:0]  podge;//上升沿

    //第一段状态机:时序逻辑,描述状态空间的切换
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          state_c <= IDLE;
        end
        else begin
          state_c <= state_n;
        end
    end

    //第二段状态机:组合逻辑,描述状态转移条件
    always @(*) begin
        case (state_c)
            IDLE:begin
                    if(nedge)begin //检测到下降沿进入抖动状态
                        state_n = DOWN;
                    end
                    else begin
                        state_n = state_c;
                    end
                end
            DOWN:begin
                    if(podge)begin //检测到上升沿证明是一个抖动回到空闲状态
                        state_n = IDLE;
                    end
                    else if(end_cnt_20ms && podge == 1'b0)begin//计时结束并且没有出现上升沿就证明是一个有效按下,进入下一个状态
                        state_n = HOLD;
                    end
                    else begin
                        state_n = state_c;
                    end
                end
            HOLD:begin
                    if(podge)begin//出现上升沿,进入释放抖动状态
                        state_n = UP;
                    end
                    else begin
                        state_n = state_c;
                    end
                end
            UP  :begin
                    if(end_cnt_20ms)begin//延时结束进入空闲状态
                        state_n = IDLE;
                    end
                    else begin
                        state_n = state_c;
                    end
                end 
            default: state_n = state_c;
        endcase
    end

    //第三段状态机,组合时序都可以,描述输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            key_out <= {KEY_NUM{1'b0}};
        end
        else if(state_c == DOWN && end_cnt_20ms)begin
            key_out <= ~key_r1;
        end
        else begin
            key_out <= {KEY_NUM{1'b0}};
        end
    end

    //打拍寄存
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            key_r0 <= {KEY_NUM{1'b1}}; //{3{1'b1}} -> 3'b111 {}拼接符号
            key_r1 <= {KEY_NUM{1'b1}};
        end
        else begin
            key_r0 <= key_in;
            key_r1 <= key_r0; 
        end
    end

    // always @(posedge Clk or negedge Rst_n)begin 
	// 	if(!Rst_n)begin
	// 		key_r <= 10'b11;
	// 	end  
	// 	else begin
	// 		key_r <= {key_r[0],&key_in}; //打拍寄存
	// 	end
	// end 

    assign nedge = ~key_r0 & key_r1;//要用按位与,因为不止一个按键信号
    assign podge = ~key_r1 & key_r0;

    //20MS计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_20ms <= 26'd0;
        end
        else if(add_cnt_20ms)begin
            if(end_cnt_20ms)begin
                cnt_20ms <= CNT_20MS;
            end
            else begin
                cnt_20ms <= cnt_20ms + 1'd1;
            end
        end
        else begin
            cnt_20ms <= 26'd0;
        end
    end

    assign add_cnt_20ms = state_c == DOWN || state_c ==  UP;
    assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == CNT_20MS - 1'd1);

endmodule

2.3 baud

module baud(
    input                    clk     ,
    input                    rst_n   ,
    input                    key     ,//按键输入信号

    output  reg  [16:0]      baud     //波特率
);

    reg     [2:0]   cnt;//设置波特率的计数器:0为9600,1为19200,2为38400,3为57600,4为115200

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt <= 3'd0;
        end
        else if(key)begin
            if(cnt == 3'd4)begin
                cnt <= 3'd0;
            end
            else begin
                cnt <= cnt + 1'd1;
            end
        end
        else begin
            cnt <= cnt;
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            baud <= 17'd9600;
        end
        else begin
            case (cnt)
                0: baud <= 17'd9600        ;
                1: baud <= 17'd19200    ;
                2: baud <= 17'd38400    ;
                3: baud <= 17'd57600    ;
                4: baud <= 17'd115200   ;                    
                default: baud <= 17'd9600  ;
            endcase
        end
    end

endmodule

2.4 sel_seg

module sel_seg(
    input               clk     ,//系统时钟
    input               rst_n   ,//复位信号
    input       [16:0]  baud    ,//波特率

    output  reg [5:0]   sel     ,//位选信号
    output  reg [7:0]   seg      //段选信号
);

    parameter   ZERO            =   8'b1100_0000    ,
                ONE             =   8'b1111_1001    ,
                TWO             =   8'b1010_0100    ,
                THREE           =   8'b1011_0000    ,
    	        FOUR            =   8'b1001_1001    ,
    	        FIVE            =   8'b1001_0010    ,
    	        SIX             =   8'b1000_0010    ,
    	        SEVEN           =   8'b1111_1000    ,
    	        EIGHT           =   8'b1000_0000    ,
    	        NINE            =   8'b1001_0000    ,
    	        A               =   8'b1000_1000    ,
    	        B               =   8'b1000_0011    ,
    	        C               =   8'b1100_0110    ,
    	        D               =   8'b1010_0001    ,
    	        E               =   8'b1000_0110    ,
    	        F               =   8'b1000_1110    ;
    parameter   CNT_20US = 10'd999  ;//20us需要1000个时钟周期
    reg [9:0]   cnt_20us            ;//20us计数寄存器
    wire        add_cnt_20us        ;//开始计数标志符
    wire        end_cnt_20us        ;//结束计数标志符
    reg [4:0]   number             ;//数码管要显示的数字

//20us计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_20us <= 10'd0;
        end
        else if(add_cnt_20us)begin
            if(end_cnt_20us)begin
                cnt_20us <= 10'd0;
            end
            else begin
                cnt_20us <= cnt_20us + 1'd1;
            end
        end
        else begin
            cnt_20us <= cnt_20us;
        end
    end

    assign  add_cnt_20us = 1'b1;
    assign  end_cnt_20us = add_cnt_20us && cnt_20us == CNT_20US;

//位选信号控制,每20us刷新一次
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            sel <= 6'b111_110;
        end
        else if(end_cnt_20us)begin
            sel <= {sel[4:0],sel[5]};
        end
        else begin
            sel <= sel;
        end
    end

//每个数码管需要显示的数字
    always @(*) begin
        case (sel)
            6'b111_110:     number = baud    / 100000           ;//最左侧数码管     
            6'b111_101:     number = baud    % 100000 / 10000   ;
            6'b111_011:     number = baud    % 10000  / 1000    ;
            6'b110_111:     number = baud    % 1000   / 100     ;
            6'b101_111:     number = baud    % 100    / 10      ; 
            6'b011_111:     number = baud    % 10               ;//最右侧数码管
            default:        number = 4'd0;
        endcase
    end

//段选信号控制显示
    always @(*) begin
        case (number)
            4'd0:   seg <= ZERO   ;
            4'd1:   seg <= ONE    ;
            4'd2:   seg <= TWO    ;  
            4'd3:   seg <= THREE  ;
            4'd4:   seg <= FOUR   ;
            4'd5:   seg <= FIVE   ;
            4'd6:   seg <= SIX    ;
            4'd7:   seg <= SEVEN  ;
            4'd8:   seg <= EIGHT  ;
            4'd9:   seg <= NINE   ;
            default:seg <= ZERO   ; 
        endcase
    end

endmodule

2.5 fifo

这里的FIFO是调用的IP核,使用的前显模式

module fifo(
    input               clk         ,
    input               rst_n       ,
    input     [7:0]     rx_dout     ,//接收并行数据
    input               dout_sign   ,//接收完成
    input               dout_sign_tx,

    output              tx_req      ,//发送请求拉高时开始发送
    output    [7:0]     tx_din      ,//输入并行数据
    output              empty       ,//空标志
    output              full        ,//满标志
    output    [6:0]     usedw        //已经用了多少空间
);
    reg                 tx_rd      ;//读数据信号寄存
    reg                 flag        ;//状态信号,为0时缓存数据,为1时发送数据,>=8时为1;
    reg       [3:0]     cnt_tx      ;//一次性发送8个数据出去
    wire                add_cnt_tx  ;//发送开始信号
    wire                end_cnt_tx  ;//发送结束信号 
    reg                 rdreq       ;//读取数据请求               

    fifo_128x8	    fifo_128x8_inst (
    .clock          ( clk       ),
    .data           ( rx_dout   ),
    .rdreq          ( rdreq     ),
    .wrreq          ( dout_sign ),

    .empty          ( empty     ),
    .full           ( full      ),
    .q              ( tx_din    ),
    .usedw          ( usedw     )
    );

//读取数据信号使能
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            rdreq <= 1'b0;
        end
        else if((tx_rd || dout_sign_tx) && !end_cnt_tx)begin
            rdreq <= 1'b1;
        end
        else begin
            rdreq <= 1'b0;
        end
    end

//当可度量达到标志时第一次读取数据使能信号
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_rd <= 1'b0;
        end
        else if(!flag && usedw >= 7'd8)begin
            tx_rd <= 1'b1;
        end
        else begin
            tx_rd <= 1'b0;
        end
    end

//状态信号,0为空闲状态,1为读取数据状态    
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            flag <= 1'b0;
        end
        else if(end_cnt_tx)begin
            flag <= 1'b0;
        end
        else if(!flag && usedw >= 7'd8)begin//空闲状态时检测剩余可读量达到标志
            flag <= 1'b1;
        end
        else begin
            flag <= flag;
        end
    end

//8个数据计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_tx <= 3'd0;
        end
        else if(add_cnt_tx)begin
            if(end_cnt_tx )begin
                cnt_tx <= 3'd0;
            end
            else begin
                cnt_tx <= cnt_tx + 1'd1;
            end
        end
        else begin
            cnt_tx <= cnt_tx;
        end
    end

    assign add_cnt_tx = flag && dout_sign_tx;
    assign end_cnt_tx = add_cnt_tx && cnt_tx == 3'd7;

    assign tx_req = rdreq;

endmodule

2.6 uart_rx

module uart_rx(
    input               clk     ,
    input               rst_n   ,
    input               rx_din  ,//输入串行数据
    input       [16:0]  baud    ,//波特率

    output      [7:0]   rx_dout ,//接收并行数据
    output     reg      dout_sign//接收完成
);
    parameter CNT_1S        = 26'd50_000_000;
    // parameter BAUD          = 14'd9600;

    reg  [12:0]  cnt_baud   ;//波特率计数器
    wire         add_baud   ;//波特率开始计数
    wire         end_baud   ;//波特率结束计数

    reg  [3:0]  cnt_bit     ;//比特计数器
    wire        add_bit     ;//比特开始计数
    wire        end_bit     ;//比特结束计数

    reg [9:0]   rx_data     ;//数据寄存器
    reg         rx_din_r0   ;//同步
    reg         rx_din_r1   ;//打拍
    wire         nedge      ;//起始位的下降沿
    reg         rx_flag     ;//开始接收数据标志

//波特率计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_baud <= 13'd0;
        end
        else if(rx_flag == 1'b0)begin
            cnt_baud <= 13'd0;
        end
        else if(add_baud)begin
            if(end_baud)begin
                cnt_baud <= 13'd0;
            end
            else begin
                cnt_baud <= cnt_baud + 1'd1;
            end
        end
        else begin
            cnt_baud <= cnt_baud;
        end
    end

    assign add_baud = rx_flag;
    assign end_baud = add_baud && cnt_baud == (CNT_1S/baud) - 1'd1;//根据波特率求得传输1bit所需要的时钟周期

//比特计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_bit <= 4'd0;
        end
        else if(rx_flag == 1'b0)begin
            cnt_bit <= 13'd0;
        end
        else if(add_bit)begin
            if(end_bit)begin
                cnt_bit <= 4'd0;
            end
            else begin
                cnt_bit <= cnt_bit + 1'd1;
            end
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end

    assign add_bit = end_baud;
    assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位

//同步打拍检测下降沿
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            rx_din_r0 <= 1'b1;
            rx_din_r1 <= 1'b1;
        end
        else begin
            rx_din_r0 <= rx_din;
            rx_din_r1 <= rx_din_r0;
        end
    end

    assign nedge = ~rx_din_r0 && rx_din_r1;//同步,打拍,检测到下降沿开始接收数据


//同步完成检测下降沿将开始的接收的标志置为1
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            rx_flag <= 1'b0;
        end
        else if(nedge)begin
            rx_flag <= 1'b1;
        end
        else if(rx_data[0] == 1'b1 || end_bit)begin//开始位为1,意外关闭
            rx_flag <= 1'b0;
        end
        else begin
          rx_flag <= rx_flag;
        end
    end


//缓存接收到的数据
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          rx_data <= 10'b0;
        end
        else if(rx_flag && cnt_baud == (CNT_1S/baud-1)/2)begin
          rx_data[cnt_bit] <= rx_din_r1;
        end
        else begin
          rx_data <= rx_data;
        end
    end

//结束位为1时才接收数据,否则让数据为0视为无效数据
    assign rx_dout[7:0] =  rx_data[8:1] ;

//接收到一个完整的有效数据后接收完成信号拉高
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
          dout_sign <= 1'b0;
        end
        else if(end_bit && rx_data[9] == 1'b1)begin
          dout_sign <= 1'b1;
        end
        else begin
          dout_sign <= 1'b0;
        end
    end

endmodule

2.7 uart_tx

module uart_tx (
    input           clk     ,
    input           rst_n   ,
    input           tx_req  ,//发送请求拉高时开始发送
    input  [7:0]    tx_din  ,//输入并行数据
    input  [16:0]   baud    ,//波特率

    output   reg    tx_dout,//发送串行数据,低位在前,高位在后
    output          dout_sign_tx //发送结束标志
);
    parameter CNT_1S        = 26'd50_000_000;
    // parameter BAUD          = 14'd9600;

    reg  [12:0]  cnt_baud   ;//波特率计数器
    wire         add_baud   ;//波特率开始计数
    wire         end_baud   ;//波特率结束计数

    reg  [9:0]  tx_data     ;//10bit数据
    reg         tx_flag     ;//发送标志

    reg  [3:0]  cnt_bit     ;//比特计数器
    wire        add_bit     ;//比特开始计数
    wire        end_bit     ;//比特结束计数

//波特率计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_baud <= 13'd0;
        end
        else if(add_baud)begin
            if(end_baud)begin
                cnt_baud <= 13'd0;
            end
            else begin
                cnt_baud <= cnt_baud + 1'd1;
            end
        end
        else begin
            cnt_baud <= cnt_baud;
        end
    end

    assign add_baud = tx_flag;
    assign end_baud = add_baud && cnt_baud == (CNT_1S/baud - 1'd1);//根据波特率求得传输1bit所需要的时钟周期

//比特计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_bit <= 4'd0;
        end
        else if(add_bit)begin
            if(end_bit)begin
                cnt_bit <= 4'd0;
            end
            else begin
                cnt_bit <= cnt_bit + 1'd1;
            end
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end

    assign add_bit = end_baud;
    assign end_bit = add_bit && cnt_bit == 4'd9;//一个数据有1bit起始位,8bit数据位,1bit结束位

//发送请求拉高时寄存数据
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_data <= 10'b0;
        end
        else if(tx_req)begin
            tx_data <= {1'b1,tx_din,1'b0};
        end
        else begin
            tx_data <= tx_data;
        end
    end

//发送请求拉高时保持发送信号拉高直到发送完毕
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_flag <= 1'b0;
        end
        else if (tx_req)begin
            tx_flag <= 1'b1;
        end
        else if(end_bit)begin
            tx_flag <= 1'b0;
        end
        else begin
            tx_flag <= tx_flag;
        end
    end

//串行数据输出
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            tx_dout <= 1'b1;
        end
        else if(tx_flag && cnt_baud)begin
            tx_dout <= tx_data[cnt_bit];
        end
        else begin
            tx_dout <= tx_dout;
        end
    end

    assign dout_sign_tx = end_bit;//直到没有再发送就结束了
endmodule

2.8 top_uart

module  top_uart(
    input               clk     ,
    input               rst_n   ,
    input               key     ,//按键输入信号
    input               rx      ,//输入串行数据

    output              tx      ,//输出串行数据
    output      [5:0]   sel     ,//位选信号
    output      [7:0]   seg      //段选信号  
);

    wire     [7:0]   rx_dout;//串转并数据
    wire             rx_dout_sign;
    wire             tx_dout_sign;
    wire             tx_req;
    wire     [7:0]   tx_din;
    wire             key_r;
    wire     [16:0]  baud_r;

    fsm_key     fsm_key_inst(
    .clk                (clk),//时钟信号
    .rst_n              (rst_n),//复位信号
    .key_in             (key),//按键输入信号

    .key_out            (key_r) //按键输出信号
    );

    baud        baud_inst(
    .clk                (clk),
    .rst_n              (rst_n),
    .key                (key_r),//按键输入信号

    .baud               (baud_r) //波特率
    );

    sel_seg     sel_seg_inst(
    .clk                (clk),//系统时钟
    .rst_n              (rst_n),//复位信号
    .baud               (baud_r),//波特率

    .sel                (sel),//位选信号
    .seg                (seg) //段选信号
    );

    uart_rx     uart_rx_inst(
        .clk            (clk     ),
        .rst_n          (rst_n   ),
        .rx_din         (rx      ),
        .baud           (baud_r  ),

        .rx_dout        (rx_dout ),
        .dout_sign      (rx_dout_sign)
    );

    fifo        fifo_inst(
        .clk            (clk),
        .rst_n          (rst_n),
        .rx_dout        (rx_dout),//接收并行数据
        .dout_sign      (rx_dout_sign),//接收完成
        .dout_sign_tx   (tx_dout_sign),

        .tx_req         (tx_req),//发送请求拉高时开始发送
        .tx_din         (tx_din) //输入并行数据
        // .empty          (),//空标志
        // .full           (),//满标志
        // .usedw          () //已经用了多少空间
    );

    uart_tx     uart_tx_inst(
        .clk            (clk     ),
        .rst_n          (rst_n   ),
        .tx_req         (tx_req  ),
        .tx_din         (tx_din  ),
        .baud           (baud_r  ),

        .tx_dout        (tx      ),
        .dout_sign_tx   (tx_dout_sign)
    );

endmodule

2.9 发送模块时序分析

在这里插入图片描述
从时序图看,这里想要发送的并行数据是8’b11010011。首先是发送请求信号拉高,此时发送标志信号拉高直到数据发送完成。采用一个10bit的寄存器对并行数据进行寄存,同时由于是低位在前,高位在后,开始位为0,停止位为1,因此寄存的数据为10’b1110100110。同时在发送标志信号拉高时,cnt_baud计数器开始计数,该计数器是计的发送1bit数据所需要的系统时钟周期。计满时cnt_bit计数器加1,然后发送的串行数据就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。

2.10 接收模块的时序分析

在这里插入图片描述
从这张时序图看,收到了一串的串行数据,首先需要对这一串数据进行同步打拍,通过同步打拍判断出下降沿,出现下降沿时开始接收数据的标志信号rx_flag拉高直到接收数据完成。然后依旧是两个计数器与发送模块的计数器功能一样,cnt_baud和cnt_bit。然后采用一个10bit的数据寄存器rx_data。为了接收到的数据稳定,因此采用当baud计数器过了一半时根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。

2.11 FIFO控制模块时序分析

在这里插入图片描述
当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。

三、仿真

3.1 testbench

`timescale 1ns/1ns
module top_uart_tb();
    reg             clk     ;
    reg             rst_n   ;
    reg             rx      ;
    reg             key_r   ;
    
    wire            tx      ;

    defparam uart_rx_inst.CNT_1S        = 26'd50;

    defparam uart_tx_inst.CNT_1S        = 26'd50;

    parameter CYCLE = 20;

    wire     [7:0]   rx_dout;//串转并数据
    wire             rx_dout_sign;
    wire             tx_dout_sign;
    wire             tx_req;
    wire     [7:0]   tx_din;
    wire     [6:0]   usedw ;
    wire     [16:0]  baud_r;
    reg      [7:0]   test  ;

    baud        baud_inst(
    .clk                (clk),
    .rst_n              (rst_n),
    .key                (key_r),//按键输入信号

    .baud               (baud_r) //波特率
    );

    uart_rx     uart_rx_inst(
        .clk            (clk     ),
        .rst_n          (rst_n   ),
        .rx_din         (rx      ),
        .baud           (baud_r  ),

        .rx_dout        (rx_dout ),
        .dout_sign      (rx_dout_sign)
    );

    fifo                fifo_inst(
        .clk            (clk),
        .rst_n          (rst_n),
        .rx_dout        (rx_dout),//接收并行数据
        .dout_sign      (rx_dout_sign),//接收完成
        .dout_sign_tx   (tx_dout_sign),

        .tx_req         (tx_req),//发送请求拉高时开始发送
        .tx_din         (tx_din), //输入并行数据
        // .empty          (),//空标志
        // .full           (),//满标志
        .usedw          (usedw) //已经用了多少空间
    );

    uart_tx     uart_tx_inst(
        .clk            (clk     ),
        .rst_n          (rst_n   ),
        .tx_req         (tx_req  ),
        .tx_din         (tx_din  ),
        .baud           (baud_r  ),

        .tx_dout        (tx      ),
        .dout_sign_tx   (tx_dout_sign)
    );

    always #(CYCLE/2) clk = ~clk;

    initial begin
        clk = 1'b0;
        rst_n = 1'b0;
        test = 8'b0;
        #(CYCLE/2  + 3);
        rst_n = 1'b1;

        repeat(8)begin
            test = test + 1'b1;
            rx = 1'b0;
            #(5*CYCLE);
            rx = test[0];
            #(5*CYCLE);
            rx = test[1];
            #(5*CYCLE);
            rx = test[2];
            #(5*CYCLE);
            rx = test[3];
            #(5*CYCLE);
            rx = test[4];
            #(5*CYCLE);
            rx = test[5];
            #(5*CYCLE);
            rx = test[6];
            #(5*CYCLE);
            rx = test[7];
            #(5*CYCLE);
            rx = 1'b1;
            #(30*CYCLE);
        end
        #(2000*CYCLE);
        $stop;
    end

endmodule

3.2 接收模块仿真分析

在这里插入图片描述
从这张仿真波形图可以看到,发送的标志信号拉高后,两个计数器正常工作,然后10bit的数据寄存器rx_data。在cnt_baud计数器过了一半时才会根据传入的串行数据打拍后的波形数据写入寄存器,同时判断了开始位为1和停止位为0的话都属于无效数据,不会接收无效数据。当数据有效时,最终接收到的并行数据rx_dout就为rx_data寄存器的[8:1]位。同时接收完毕后将接收完成的标志信号拉高。

3.3 发送模块仿真分析

在这里插入图片描述
从这张仿真波形图可以看到,当发送请求信号tx_req拉高后,发送标志信号tx_flag拉高直到发送完毕。同时10bit的数据寄存器将tx_din的并行数据存入并加上开始位0,停止位1。然后两个计数器开始正常计数。然后发送的串行数据tx_dout就是根据cnt_bit计数器对寄存的数据进行一个一个的发送。在出现发送停止位后就表明这个数据发送完成了,因此发送结束标志信号dout_sign就拉高。

3.4 FIFO控制模块仿真分析

在这里插入图片描述
从这张仿真波形图可以看出,当FIFO中缓存的数据大于等于8个数据时,开启读取数据的状态,开启读取第一个数据的使能信号,将读取到的数据发给数据发送模块,当数据发送模块将一个数据发送完成后,将数据发送完成的标志信号dout_sign_tx传给FIFO控制模块,cnt_tx计数器加1,同时重新读取一个新的数据传给数据发送模块进行发送,直到发送完8个数据才重新进入空闲状态。并且由于FIFO是使用的前显模式,因此当读取数据有效时读取的数据是前面显示的那个数据。

四、上板验证

在这里插入图片描述
从图片中可以看出,当发送数据小于8个时,它将数据接收并存入了FIFO缓冲器。当再次输入几个数据时,它会一次性输出8个数据,并且先输出之前存入的数据。当我们切换了波特率,PC端设置的波特率与FPGA板子上设置的波特率不同时,我们发送的数据就会解析混乱,变成一些乱码存入缓存器然后达到8个数据时输出。当我们重新调整波特率,使得双方波特率一致后,又能够收发同步了。

五、总结

因为该工程是分模块化的设计,因此如果只需要串口的发送和接收功能就只需要将这两个模块直接拿去用就可以了,大大方便了之后其他工程的设计。另外这只是我的一点学习笔记,如果有错误还请提出。

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

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

相关文章

java框架-Spring-事务

配置 配置事务管理器方法&#xff1a; Beanpublic PlatformTransactionManager platformTransactionManager(){return new DataSourceTransactionManager();}原理

多维时序 | MATLAB实现WOA-CNN-LSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-LSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-LSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基本描…

postman 自动升级后恢复collection数据

一、今天postman 自动升级了&#xff0c;导致一定要注册账号才能使用&#xff0c;登录账号后&#xff0c;发现之前的数据全部没有了。 找到目录&#xff1a;C:\Users\{{用户名}}\AppData\Roaming\Postman重新导入即可。 二、关闭自动更新&#xff1a;修改host&#xff0c;C:\W…

力扣-217.存在重复元素

Method 1 先对整个数组进行排序&#xff0c;然后从前往后开始遍历&#xff0c;判断前一个数 是否跟相邻的数相等 AC Code class Solution { public:bool containsDuplicate(vector<int>& nums) {sort(nums.begin(),nums.end());int front nums[0];for( int i 1; i…

Selenium自动化测试 —— 通过cookie绕过验证码的操作!

验证码的处理 对于web应用&#xff0c;很多地方比如登录、发帖都需要输入验证码&#xff0c;类型也多种多样&#xff1b;登录/核心操作过程中&#xff0c;系统会产生随机的验证码图片&#xff0c;进行验证才能进行后续操作 解决验证码的方法如下&#xff1a; 1、开发做个万能…

如何学习微服务Spring Cloud

简单来说&#xff0c;就是“三大功能&#xff0c;两大特性”。 三大功能是指微服务核心组件的功能维度&#xff0c;由浅入深层次递进&#xff1b;而两大特性是构建在每个服务组件之上的高可用性和高可扩展性。别看微服务框架组件多&#xff0c;其实你完全可以按照这三大功能模块…

外卖小程序开发指南:打造完美的点餐体验

第一步&#xff1a;项目设置和初始化 首先&#xff0c;您需要选择一个适合您的开发平台&#xff0c;例如微信小程序、支付宝小程序或其他移动应用平台。接下来&#xff0c;创建一个新的小程序项目&#xff0c;并初始化所需的文件和目录。 示例代码&#xff08;微信小程序&am…

项目进度网络图

概念 项目网络图是项目所有活动及其之间逻辑关系&#xff08;依赖关系&#xff09;的一个图解表示&#xff0c;并从左到右来表示项目的时间顺序。 可手工编制也可用计算机实现。可包括整个项目的全部细节&#xff0c;也可包含一个或多个概括性活动&#xff0c;还相应伴有一个…

向表中的指定列插入数据

MySQL从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129334507?spm1001.2014.3001.5502 练习:向test02表,所有列,插入数据 复习下前面的 mysql> #插入记录 mysql> insert into test02 values(1, 张三, 男, 100.5); Query OK, 1 ro…

【密码学补充知识】

&#x1f511;密码学&#x1f512;概述 &#x1f4d5; 1.基本概念 明文 &#xff1a; 要交换的信息 密文 &#xff1a; 明文经过一组规则变换成看似没有意义的随机消息。 加密 &#xff1a; 明文经过一组规则变换成密文的过程 解密 &#xff1a; 密文恢复出明文的过程 加…

【广州华锐互动】鱼卵孵化VR线上教学实训软件

随着科技的发展&#xff0c;教育方式也在不断地进行创新。VR研发公司广州华锐互动&#xff0c;为某院校开发了鱼卵孵化VR线上教学实训软件&#xff0c;可以帮助学生更好地理解鱼类繁殖和养殖的过程&#xff0c;还可以让他们在虚拟环境中进行实践操作&#xff0c;提高他们的技能…

HDFS编程实践-从HDFS中下载指定文件到本地

前言&#xff1a;Hadoop采用java语言开发&#xff0c;提供了Java Api与HDFS进行交互 先要把hadoop的jar包导入到idea中去 为了能编写一个与hdfs交互的java应用程序&#xff0c;一般需要向java工程中添加以下jar包 1&#xff09;/usr/local/hadoop/share/hadoop/common目录下…

Docker部署Nacos注册中心

文章目录 一、部署MySQL数据库并导入Nacos初始化SQL二、部署Nacos注册中心三、验证Nacos 一、部署MySQL数据库并导入Nacos初始化SQL 1、准备工作 docker pull mysql:8.0.27 Pwd"/data/software/mysql" mkdir ${Pwd}/{data,logs} -p chmod 777 ${Pwd}/logs2、添加配…

Matlab图像处理-彩色图像灰度化

图像预处理 预处理的具体操作是将车牌彩色图像灰度化&#xff0c;利用直方图均衡化、中值滤波、边缘提取、形态学运算等数字图像处理方法&#xff0c;确定车牌位置&#xff0c;提高车牌定位精确度及识别正确率。 彩色图像灰度化 图像灰度化本质就是通过一定的方法将彩色图像…

定时器的编码器接口

对应手册编码器接口14.3.12 实现代码 实现旋转编码器计次&#xff0c;与之前的在定时器中断时实现的旋转编码器计次实现内容相同&#xff0c;但是方式不同&#xff0c;之前的是通过触发外部中断&#xff0c;通过中断函数来实现手动计次加一&#xff1b;这次不同&#xff0c;是…

电脑版剪映怎么倒放?

1.打开一个素材 2.添加到时间轨道 3.右击轨道素材 弹出的选项钟选择&#xff0c;基础编辑》倒放&#xff01;

【Java开发】Redis位图实现统计日活周活月活

最近研究了使用 Redis 的位图功能统计日活周活等数据&#xff0c;特来和大家分享下&#xff0c;Redis 位图还可用于记录用户签到情况、判断某个元素是否存在于集合中等。 1 Redis 位图介绍 Redis 位图是一种特殊的数据结构&#xff0c;它由一系列位组成&#xff0c;每个位只能…

Spring Cloud Alibaba Gateway 全链路跟踪TraceId日志

前言 凡是文中需要注册到nacos的都需要这个jar包 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>凡是使用config jar包的都需要写bootstrap.prop…

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测(SE注意力机制)

多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09; 目录 多维时序 | MATLAB实现WOA-CNN-BiLSTM-Attention多变量时间序列预测&#xff08;SE注意力机制&#xff09;预测效果基本描述模型描述程序设计参考资料 预测效果 基…

Tuya MQTT 标准协议是什么?

TuyaLink 协议是涂鸦 IoT 开发平台面向物联网开发领域设计的一种数据交换规范&#xff0c;数据格式为 JSON&#xff0c;主要用于设备端和涂鸦 IoT 开发平台的双向通信&#xff0c;更便捷地实现了设备端和平台之间的业务数据交互。 设备的通信方式也是多种多样的。无线通信方式…