反向学习Uart串口通信

一、Uart串口通信的基本原理

 

         UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种常见的串行通信协议,用于在设备之间发送和接收数据。与并行通信方式不同,串口通信通过一个信号线(TX、RX)依次传输数据位,减少了线路的数量。

UART的基本工作原理

  1. 数据传输方式
    UART是一种异步通信协议。这意味着数据传输不需要时钟信号的同步,而是通过数据位的起始位、数据位、停止位等来确保传输的顺序和可靠性。

  2. 传输数据的组成
    每次通过UART发送的数据,都会被分为若干位(通常是8位)。这8位数据通过一条数据线(TX线)发送到接收设备。接收端会按照相同的格式(位、开始位、停止位)来接收和解析数据。

  3. 数据帧结构
    每个数据帧通常由以下几个部分组成:

    • 起始位:表示数据传输的开始,通常为一个低电平
    • 数据位实际传输的数据,常见为8位
    • 奇偶校验位(可选):用于错误检测,判断是否接收到正确的数据。
    • 停止位:表示数据传输的结束,通常为1位高电平
    • 下图为没有奇偶校验位的数据帧格式

4. 全双工与半双工
        UART通信支持全双工(Full Duplex)和半双工(Half Duplex)两种模式。在全双工模式下,发送和接收可以同时进行;而在半双工模式下,发送和接收是交替进行的。

UART的工作流程

  1. 发送端
    在发送端,数据通过TX(Transmit)线以串行形式发送出去。数据在传输时会被切分成位,先发送起始位,再发送数据位,最后发送停止位。

  2. 接收端
    接收端通过RX(Receive)线接收到数据。当接收端接收到数据时,它会首先读取起始位,然后逐位接收数据位,最后检查停止位。接收端会对数据进行校验,确保数据没有发生错误。

二、Uart串口发送

(一)设计代码

module uart_byte_tx(
Clk,
Reset_n,
Data,
Send_Go,
Baud_set,
uart_tx,
//Tx_done
);模块声明部分

input Clk;
input Reset_n;
input [7:0]Data;
input Send_Go;
input [2:0]Baud_set;
output reg uart_tx;外部端口声明

reg Send_en;
reg Tx_done;//output reg Tx_done;内部信号声明

always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Send_en <= 0;
else if(Send_Go)//当外部应用给一个脉冲Send_Go信号时,内部Send_en信号开始工作,检测到Tx_done发送完成信号,就停止工作。
Send_en <= 1;
else if(Tx_done)
Send_en <= 0;

reg [7:0]r_Data;//构建一个寄存器r_Data,用来存储信号,当Send_Go高电平信号来的时候,保证此刻的外部Data数据能够保留住。
always@(posedge Clk)
if(Send_Go)
r_Data <= Data;
else
r_Data <= r_Data;

//Baud_set = 0,就让波特率 = 9600;
//Baud_set = 1,就让波特率 = 19200;
//Baud_set = 2,就让波特率 = 38400;
//Baud_set = 3,就让波特率 = 57600;
//Baud_set = 4,就让波特率 = 115200;

reg [17:0]bps_DR;
always@(*)
case(Baud_set)
0:bps_DR = 1000000000/9600/20;
1:bps_DR = 1000000000/19200/20;
2:bps_DR = 1000000000/38400/20;
3:bps_DR = 1000000000/57600/20;
4:bps_DR = 1000000000/115200/20;
default:bps_DR = 1000000000/9600/20;
endcase

wire bps_clk;
assign bps_clk = (div_cnt == 1);

reg [17:0]div_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
div_cnt <=0;
else if(Send_en)begin
if(div_cnt == bps_DR – 1)
div_cnt <= 0;
else
div_cnt <= div_cnt + 1’b1;
end
else
div_cnt <=0;

reg [3:0]bps_cnt;
always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
bps_cnt <=0;
else if(Send_en)begin
if(bps_clk)begin// if(div_cnt == bps_DR – 1)begin,bps_DR – 1的计满将导致bps_cnt计数不及时,进而导致uart_tx的起始位发送不及时。
if(bps_cnt == 11)
bps_cnt <= 0;
else
bps_cnt <= bps_cnt + 1’b1;
end
end
else
bps_cnt <= 0;

always@(posedge Clk or negedge Reset_n)
if(!Reset_n)begin
uart_tx <= 1’b1;
Tx_done <= 0;
end
else begin
case(bps_cnt)
1:uart_tx <= 0; //当bps_cnt为1时,uart_tx一直保持为0,这里发送起始位。
2:uart_tx <= r_Data[0];
3:uart_tx <= r_Data[1];
4:uart_tx <= r_Data[2];
5:uart_tx <= r_Data[3];
6:uart_tx <= r_Data[4];
7:uart_tx <= r_Data[5];
8:uart_tx <= r_Data[6];
9:uart_tx <= r_Data[7];
10:uart_tx <= 1;
11:uart_tx <= 1;
default:uart_tx <= 1;
endcase
end

always@(posedge Clk or negedge Reset_n)
if(!Reset_n)
Tx_done <= 0;
else if((bps_clk ==1) && (bps_cnt == 11))
Tx_done <= 1;
else
Tx_done <= 0;
// 0:begin uart_tx <= 0;Tx_done <=1’b0;end 当bps_cnt为0时,uart_tx一直保持为0(这不是我们想要的1)
// 1:uart_tx <= Data[0];
// 2:uart_tx <= Data[1];
// 3:uart_tx <= Data[2];
// 4:uart_tx <= Data[3];
// 5:uart_tx <= Data[4];
// 6:uart_tx <= Data[5];
// 7:uart_tx <= Data[6];
// 8:uart_tx <= Data[7];
// 9:uart_tx <= 1;
// 10:begin uart_tx <= 1;Tx_done <=1’b1;end
// default:uart_tx <= 1;功能逻辑部分

 
(二)Testbench验证

`timescale 1ns / 1ps仿真总时长1ps,仿真精度1ns

module uart_byte_tx_tb();模块定义
reg sys_clk;
reg sys_rst_n;
reg [7:0]Data;
reg Send_en;
wire [2:0]Baud_set;
wire Rx_Done;
wire [7:0]Data_all;信号声明

uart_loopback uart_loopback(
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.Send_en(Send_en),
.Baud_set(Baud_set),
.Data(Data),
.Data_all(Data_all),
.Rx_Done(Rx_Done)
// output Tx_done
//input uart_rx,
//output uart_tx
);被测试模块实例化(DUT)

//wire Tx_done;
reg uart_rx;
//wire uart_tx;

assign Baud_set = 3’d4;

initial sys_clk = 1;
always#10 sys_clk = ~sys_clk;时钟生成  使用initial块初始化系统时钟,并通过always块创建时钟周期,使时钟周期为20ns(即频率为50MHz)

initial begin   
sys_rst_n = 0;
Data = 0;
Send_en = 0;
#201;
sys_rst_n = 1;

#100;
uart_rx = 0;
Data = 8’h57;
Send_en = 1;
#2000;
@(posedge Rx_Done);
Send_en = 0;

#100;
uart_rx = 0;
Data = 8’h75;
Send_en = 1;
#2000;
@(posedge Rx_Done);
Send_en = 0;
uart_rx = 1;
$stop;
end输入激励信号的生成  通过initial块设置激励信号,模拟系统的不同输入状态。这里主要对sys_rst_nDataSend_enuart_rx等信号进行控制,以测试UART模块的不同功能

endmodule

三、Uart串口接收

(一)设计代码
`timescale 1ns / 1ps
 
module uart_byte_rx(
    Clk,
    Reset_n,
    Baud_set,
    uart_rx,
    Data_all,
    Rx_Done,
    );模块声明部分
    
    input  Clk;
    input  Reset_n;
    input  [2:0]Baud_set;
    input  uart_rx;
    output  reg [7:0]Data_all;
    output  reg Rx_Done;外部端口声明
    
    reg [1:0]uart_rx_r;
    always@(posedge Clk)begin
        uart_rx_r[0] <= uart_rx;
        uart_rx_r[1] <= uart_rx_r[0];
    end
    
    wire pedge_uart_rx;
//  assign pedge_uart_rx = ((uart_rx_r[1]==0) && (uart_rx_r[0] == 1));
    assign pedge_uart_rx = (uart_rx_r == 2’b01);
    wire nedge_uart_rx;
//  assign nedge_uart_rx = ((uart_rx_r[1]==1) && (uart_rx_r[0] == 0));
    assign nedge_uart_rx = (uart_rx_r == 2’b10);
    
    reg [8:0] Bps_DR;
    always@(*)
        case(Baud_set)
            0:Bps_DR = 1000000000/9600/16/20 – 1;
            1:Bps_DR = 1000000000/19200/16/20 – 1;
            2:Bps_DR = 1000000000/38400/16/20 – 1;
            3:Bps_DR = 1000000000/57600/16/20 – 1;
            4:Bps_DR = 1000000000/115200/16/20 – 1;
            default:Bps_DR = 1000000000/9600/16/20 – 1;
        endcase
        
    wire bps_clk_16x;
    assign bps_clk_16x = (div_cnt == Bps_DR / 2);
    reg [2:0]sta_bit;
    reg [2:0]sto_bit;内部信号声明
    
    
    reg RX_EN;
    always@(posedge Clk or negedge Reset_n)功能逻辑部分
    if(!Reset_n)
        RX_EN <= 0;
    else if(nedge_uart_rx)
        RX_EN <= 1;
    else if (Rx_Done || (sta_bit >= 4))
        RX_EN <= 0;
    
    reg [8:0]div_cnt;
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)   
        div_cnt <= 0;
    else if(RX_EN)begin
        if(div_cnt == Bps_DR)
        div_cnt <= 0;
        else
        div_cnt <= div_cnt+1’b1;
    end
    else
        div_cnt <= 0;
    
    reg [7:0]bps_cnt;
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        bps_cnt <= 0;
    else if(RX_EN)begin
            if(bps_clk_16x)begin
                if(bps_cnt == 160)
                bps_cnt <= 0;
                else
                bps_cnt <= bps_cnt +1’b1;
            end
            else
                bps_cnt <= bps_cnt;
            end
    else 
        bps_cnt <= 0;
    
        
    //新语法:reg width number/depth
    reg [2:0]r_data[7:0];//用于收集数据?
  
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)begin
        sta_bit <= 0;
        sto_bit <= 0;
        r_data[0] <= 0; 
        r_data[1] <= 0; 
        r_data[2] <= 0; 
        r_data[3] <= 0; 
        r_data[4] <= 0; 
        r_data[5] <= 0; 
        r_data[6] <= 0; 
        r_data[7] <= 0; 
     end
     else if(bps_clk_16x)begin
        case(bps_cnt)
            0:begin
                sta_bit <= 0;
                sto_bit <= 0;
                r_data[0] <= 0; //由于有8位数据,因此分别定义8给寄存器r_data0-7,每个寄存器用于寄存一位数据中高电平的数量
                r_data[1] <= 0; 
                r_data[2] <= 0; 
                r_data[3] <= 0; 
                r_data[4] <= 0; 
                r_data[5] <= 0; 
                r_data[6] <= 0; 
                r_data[7] <= 0; 
              end
            5,6.7,8,9,10,11:sta_bit <= sta_bit + uart_rx;//由于有8位数据位,我们将每一位数据位分为16给部分,采样其中的7个部分
            21,22,23,24,25,26,27:r_data[0] <= r_data[0] + uart_rx;//将得到的高电平熟练寄存在r_data寄存器中
            37,38,39,40,41,42,43:r_data[1] <= r_data[1] + uart_rx;
            53,54,55,56,57,58,59:r_data[2] <= r_data[2] + uart_rx;
            69,70,71,72,73,74,75:r_data[3] <= r_data[3] + uart_rx;
            85,86,87,88,89,90,91:r_data[4] <= r_data[4] + uart_rx;
            101,102,103,104,105,106,107:r_data[5] <= r_data[5] + uart_rx;
            117,118,119,120,121,122,123:r_data[6] <= r_data[6] + uart_rx;
            133,134,135,136,137,138,139:r_data[7] <= r_data[7] + uart_rx;
            149,150,151,152,153,154,155:sto_bit <= sto_bit + uart_rx;
            default:;
        endcase
      end
      
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Data_all <= 0;
    else if(bps_clk_16x && (bps_cnt ==160))begin//判断寄存器r_data0-7,8个寄存器中高电平的数量是否大于等于4
        Data_all[0] <= (r_data[0] >=4)?1’b1:1’b0;//如果满足条件,则该位定义为高电平,否则定义为低电平。
        Data_all[1] <= (r_data[1] >=4)?1’b1:1’b0;
        Data_all[2] <= (r_data[2] >=4)?1’b1:1’b0;
        Data_all[3] <= (r_data[3] >=4)?1’b1:1’b0;
        Data_all[4] <= (r_data[4] >=4)?1’b1:1’b0;
        Data_all[5] <= (r_data[5] >=4)?1’b1:1’b0;
        Data_all[6] <= (r_data[6] >=4)?1’b1:1’b0;
        Data_all[7] <= (r_data[7] >=4)?1’b1:1’b0;
    end
    
    always@(posedge Clk or negedge Reset_n)
    if(!Reset_n)
        Rx_Done <= 0;
    else if((div_cnt == Bps_DR/2) && (bps_cnt == 160))
        Rx_Done <= 1;
    else
        Rx_Done <= 0;
    
endmodule
 
(二)Testbench验证
`timescale 1ns / 1ps仿真总时长1ps,仿真精度1ns
 
module uart_byte_rx_tb();
        reg  Clk;
        reg  Reset_n;
        wire  [2:0]Baud_Set;
        reg  uart_rx;
        wire [7:0]Data;
        wire Rx_Done;模块定义
        
        assign Baud_Set = 4;
 
       uart_byte_rx uart_byte_rx(
        .Clk(Clk),
        .Reset_n(Reset_n),
        .Baud_Set(Baud_Set),
        .uart_rx(uart_rx),
        .Data(Data),
        .Rx_Done(Rx_Done)
        );被测试模块实例化(DUT)
        
        initial Clk = 1;
        always#10 Clk = ~Clk;时钟生成使用initial块初始化系统时钟,并通过always块创建时钟周期,使时钟周期为20ns(即频率为50MHz)
        
        initial begin
        Reset_n = 0;
        uart_rx = 1;
        #201;
        Reset_n = 1;
        #200;
        uart_tx_byte(8’h5a);
        #90000;
        uart_tx_byte(8’ha5);
        #90000;
        uart_tx_byte(8’h86);
        #90000;
        $stop;
        end
        
        task uart_tx_byte;
            input [7:0]tx_data;
            begin
                uart_rx = 1;
                #20;
                uart_rx = 0;
                #8680;
                uart_rx = tx_data[0];
                #8680;
                uart_rx = tx_data[1];
                #8680;
                uart_rx = tx_data[2];
                #8680;
                uart_rx = tx_data[3];
                #8680;
                uart_rx = tx_data[4];
                #8680;
                uart_rx = tx_data[5];
                #8680;
                uart_rx = tx_data[6];
                #8680;
                uart_rx = tx_data[7];
                #8680;
                uart_rx = 1;
                #8680;
                end
            endtask输入激励信号的生成  
                
endmodule
 
 
 

留言

您的邮箱地址不会被公开。 必填项已用 * 标注