FPGA_UART

1.UART 概述 (通用异步收发传输器)


1. 基本定义

UART (Universal Asynchronous Receiver/Transmitter)是一种常见的串行通信协议 ,用于在设备间通过异步串行通信 传输数据。它不依赖独立的时钟信号,而是通过预定义的波特率(Baud Rate) 同步数据的收发。 核心特点

  • 异步通信:无需共享时钟信号,仅通过数据线传输。
  • 全双工模式:收发双方可同时发送和接收数据(需独立TX、RX线)。
  • 灵活配置:支持自定义波特率、数据位长度、校验位、停止位等。

2. 工作原理

(1) 数据传输格式

每一帧数据包含以下部分(以典型8N1格式为例):

起始位 数据位(8位) 校验位(可选) 停止位(1位)
1位,低电平 LSB先发 奇偶校验(可选) 1位,高电平
  • 起始位:低电平(0)表示数据传输开始。
  • 数据位 :传输的有效数据(5~9位),通常低位(LSB)先发送(如用户之前的案例)。
  • 校验位:用于简单错误检测(奇校验/偶校验/无校验)。
  • 停止位:高电平(1)表示一帧数据结束,并允许接收端校准时序。

(2) 异步同步机制

  • 波特率校准 :收发双方必须使用相同的波特率(如115200、9600)。 例如:

复制

波特率115200 → 每位持续时间 ≈ 1/115200 ≈ 8.68μs

  • 数据采样 :接收端在起始位下降沿触发,并在数据位中间点采样,抵消时钟偏移影响。

3. 典型应用场景

  • 微控制器与外围模块通信:如ESP8266(Wi-Fi)、GPS模块、传感器(温湿度)。
  • 调试接口:通过UART输出调试信息(常见于嵌入式开发板)。
  • 有线设备互联:旧式打印机、工业设备(如Modbus RTU协议)。
  • 信号转换:结合电平转换芯片(如MAX232)实现RS232、RS485等长距离通信。

4. 优缺点分析

优点 缺点
硬件简单(仅需两根数据线) 传输距离短(通常<1米)
成本低(无需复杂协议栈) 无硬件错误恢复机制(需软件纠错)
广泛兼容性(几乎所有MCU支持) 需严格匹配波特率(误差<3%)

5. 硬件实现关键点

  • 发送端(TX)
    • 将并行数据转为串行,按波特率逐位发送。
    • 使用分频器生成波特率时钟(如50MHz主频 → 115200波特率需分频系数:50e6 / 115200 ≈ 434)。
  • 接收端(RX)
    • 检测起始位下降沿,启动同步采样。
    • 通过移位寄存器重组串行数据为并行数据。

6. 常见问题与解决方案

  • 波特率失配: 若收发波特率差超过3%,会导致采样偏移,需重新校准。
  • 电磁干扰: 长距离使用需加屏蔽线或转换为差分信号(如RS485)。
  • 数据冲突: 全双工通信需避免同时发送,可通过流控信号(RTS/CTS)解决。

7. 主流扩展协议

  • RS-232:电平标准(±3~15V),支持更长距离(<15米)。
  • RS-485:差分信号,可多点通信(工业现场总线)。
  • USB转UART:通过芯片(如CH340、CP2102)实现USB与串口无缝衔接。

2.verilog编写

这里需要讲解下,这里使用到50mhz的时钟, 波特率为115200,这里的50mhz的时钟是在1秒内有50_000_000个周期的数据,波特率115200是在1秒内有115200bit的传输。

50_000_000/115200 指的是传输1bit需要传输多少个时钟周期

在写测试代码的时候,#8680 是因为 在 50_000_000/115200= 434 个时钟周期传输1bit, 而434个时钟周期每个时钟周期为20ns 434*20= 8680.

1,波形图

接收和发送都根据这个图编写就行

2.1接收模块代码

复制代码
module uart_rx (
input          clk,
input          rst,
input          rx_en,
input          data_in,
output reg [7:0]  data_out,
output         uart_rx_done
);

localparam   CLK   =  50_000_000,
             BOTE  = 115200,
             CNT   = CLK / BOTE ;

reg             rx_en_d1;
reg             rx_en_d2;
reg             rx_flag ;
reg             rx_valid;
reg   [3:0]     rx_cnt  ;
reg  [15:0]     clk_cnt ;
reg  [7:0]      data_out_r;
reg             uart_rx_done_r;

assign       uart_rx_done = uart_rx_done_r;

always @(posedge clk or negedge rst )begin
    if (rst == 1'b1)begin
        rx_en_d1 <= 1'b0;
        rx_en_d2 <= 1'b0;
    end
    else begin
        rx_en_d1 <= rx_en;
        rx_en_d2 <= rx_en_d1;
    end
end


always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        rx_flag <= 1'b0;
    else if (rx_en_d1 == 1'b1 && rx_en_d2 == 1'b0)
        rx_flag <= 1'b1;
    else 
        rx_flag <= 1'b0;
end

always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        rx_valid <= 1'b0;
    else if (rx_cnt == 4'd9 && clk_cnt == CNT /2 )
        rx_valid <= 1'b0;
    else if (rx_flag == 1'b1)
        rx_valid <= 1'b1;
    else;
end


always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        clk_cnt <= 16'd0;
    else if (clk_cnt == CNT )
        clk_cnt <= 16'd0;
    else if (rx_valid == 1'b1)
        clk_cnt <= clk_cnt +1'b1;
    else 
        clk_cnt <= 16'd0;
end

always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        rx_cnt <= 4'd0;
    else if (rx_en_d1 == 1'b1 && rx_en_d2 == 1'b0)
        rx_cnt <= 4'd0;
    else if (clk_cnt == CNT) 
        rx_cnt <= rx_cnt +1'b1;
    else;
end

always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        uart_rx_done_r <= 1'b0;
    else if (rx_valid == 1'b1) begin
            if (rx_cnt == 4'd9)
                uart_rx_done_r <= 1'b1;
            else 
                uart_rx_done_r <= 4'd0;
    end
    else
        uart_rx_done_r <= 1'b0;
end

always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        data_out_r <= 8'd0;
    else if (rx_valid == 1'b1   ) begin
            if (clk_cnt == CNT /4) 
                   case (rx_cnt )
                        4'd1  : data_out_r[0] = data_in ; 
                        4'd2  : data_out_r[1] = data_in ; 
                        4'd3  : data_out_r[2] = data_in ; 
                        4'd4  : data_out_r[3] = data_in ; 
                        4'd5  : data_out_r[4] = data_in ; 
                        4'd6  : data_out_r[5] = data_in ; 
                        4'd7  : data_out_r[6] = data_in ; 
                        4'd8  : data_out_r[7] = data_in ; 
                       default : ;
                   endcase
            else 
                data_out_r <= data_out_r;
    end
    else 
        data_out_r <= 8'd0;  
      
end

always @ (posedge clk or negedge rst )begin
    if (rst == 1'b1)
        data_out <= 8'd0;
    else if (rx_cnt == 4'd9)
        data_out <= data_out_r;
    else 
        data_out <= 8'd0;
end

endmodule 

2.2发送模块代码

复制代码
module uart_tx(
input           clk,
input           rst,
    
input           tx_en,
input   [7:0]   data_din,
output          data_out,
output          uart_tx_done 

);
localparam  CLK  = 50_000_000, //时钟 50_000_000 一秒 50000000个时钟周期 数据 波特率 9600 一秒9600个数据bit  5000000/9600 一个bit需要多少时钟周期 
            BOTE =115200,  
            CNT  = CLK /BOTE ;

reg          tx_en_d1;
reg          tx_en_d2;
reg          start_flag;
reg          tx_valid;
reg [3:0]    tx_cnt;
reg [15:0]   clk_cnt;
reg          uart_done_r;
reg          uart_dout_r;
reg  [7:0]   data_din_r;

assign     data_out  = uart_dout_r;
assign     uart_tx_done = uart_done_r;

always @ (posedge clk or negedge rst) begin
   if (rst == 1'b1) begin
        tx_en_d1 <= 1'b0;
        tx_en_d2 <= 1'b0;
   end
   else begin
        tx_en_d1 <=tx_en;
        tx_en_d2 <= tx_en_d1;
   end
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        data_din_r <= 8'd0;
    else if (tx_cnt ==4'd9 && clk_cnt == CNT /2)
        data_din_r <= 8'd0;
    else if ( tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0 ) 
        data_din_r <= data_din;
    else;
end


always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        start_flag <= 1'b0;
    else if ( tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0 ) 
        start_flag <= 1'b1;
    else 
        start_flag <= 1'b0;
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        tx_valid <= 1'b0;
    else if (start_flag == 1'b1)
        tx_valid <= 1'b1;
    else if (tx_cnt ==4'd9 && clk_cnt == CNT /2 )
        tx_valid <= 1'b0;
    else;
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        clk_cnt <= 16'd0;
    else if (clk_cnt == CNT )
        clk_cnt <= 16'd0;
    else if (tx_valid == 1'b1)
        clk_cnt <= clk_cnt +1'b1;
    else 
        clk_cnt <= 16'd0;
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        tx_cnt <= 4'd0;
    else if (tx_en_d1 == 1'b1 && tx_en_d2 == 1'b0)
        tx_cnt <= 4'd0;
    else if (clk_cnt == CNT)
        tx_cnt <= tx_cnt +1'b1;
    else;
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        uart_done_r <= 1'b0;
    else if (tx_valid == 1'b1) begin
         if (tx_cnt == 4'd9) 
            uart_done_r <= 1'b1;
         else
            uart_done_r <= 1'b0;  
    end
    else
        uart_done_r <= 1'b0;
end

always @ (posedge clk or negedge rst) begin
    if (rst == 1'b1)
        uart_dout_r <= 1'b0;  
    else if (tx_valid == 1'b1) begin
        case (tx_cnt)
            4'd0  : uart_dout_r <= 1'b0;
            4'd1  : uart_dout_r <=data_din_r[0];    
            4'd2  : uart_dout_r <=data_din_r[1];
            4'd3  : uart_dout_r <=data_din_r[2];
            4'd4  : uart_dout_r <=data_din_r[3];
            4'd5  : uart_dout_r <=data_din_r[4];
            4'd6  : uart_dout_r <=data_din_r[5];
            4'd7  : uart_dout_r <=data_din_r[6];
            4'd8  : uart_dout_r <=data_din_r[7];       
            4'd9  : uart_dout_r <=1'b1;
          default  : ;
        endcase
    end
    else
        uart_dout_r <= 1'b1;  
end

   



endmodule 

2.3顶层模块代码

复制代码
module uart_top (
input          clk,
input          rst,
input          uart_data_rx,
input          rx_en,
output         uart_data_out
);

wire       uart_tx_done;
wire       uart_rx_done;
wire [7:0] data_out;


uart_rx uart_rx (
.clk          (clk           ),      //input                      
.rst          ( rst          ),      //input                      
.rx_en        ( rx_en ),      //input                        
.data_in      ( uart_data_rx      ),      //input                          
.data_out     ( data_out ),      //output reg [7:0]                
.uart_rx_done ( uart_rx_done )       //output                             
);


uart_tx uart_tx(
.clk          ( clk           ),  //input                      
.rst          ( rst           ),  //input                             
.tx_en        ( uart_rx_done  ),  //input                        
.data_din     ( data_out      ),  //input   [7:0]                    
.data_out     ( uart_data_out ),  //output                          
.uart_tx_done ( uart_tx_done  )  //output                              
                      
);



endmodule 

2.4仿真模块代码

复制代码
module uart_tb(

    );


reg clk;
reg rst;
reg  rx_en;
reg uart_data_rx;
wire uart_data_out;


initial begin
    rst = 1'b1;
    clk = 1'b1;
    uart_data_rx = 1'b1;
    rx_en = 1'b0;
    #20
    rst = 1'b0;

    #4340
    uart_data_rx = 1'b0;
    rx_en = 1'b1;

    #20
    rx_en = 1'b0;

    #8680  uart_data_rx = 1'b1;
    #8680  uart_data_rx = 1'b0;
    #8680  uart_data_rx = 1'b1;
    #8680  uart_data_rx = 1'b0;
    #8680  uart_data_rx = 1'b1;
    #8680  uart_data_rx = 1'b0;
    #8680  uart_data_rx = 1'b1;
    #8680  uart_data_rx = 1'b0;

    #8680  uart_data_rx = 1'b1;

end

always #10 clk = !clk;



uart_top  uart_top_inst (
  .clk(clk),
  .rst(rst),
  .rx_en(rx_en),
  .uart_data_rx(uart_data_rx),
  .uart_data_out(uart_data_out)
);
  

endmodule

3.仿真波形

3.1 接收模块仿真波形

数据输入先放到低位依次传入

3.2 发送模块仿真波形

发送的时候,把需要发送的数据低位先发

3.3 顶层模块仿真波形

相关推荐
cycf9 小时前
CRC校验
fpga开发
landyjzlai10 小时前
AMBA总线(15)关于AXI-stream(sg模式)
arm开发·fpga开发·amba
白狐_79811 小时前
Quartus Prime 新手完全使用指南
fpga开发
Aaron158821 小时前
三种主流接收机架构(超外差、零中频、射频直采)对比及发展趋势浅析
c语言·人工智能·算法·fpga开发·架构·硬件架构·信号处理
博览鸿蒙1 天前
一颗数字系统是如何在 FPGA 上“跑起来”的?
fpga开发
雨洛lhw1 天前
FPGA JTAG接口设计全解析
fpga开发·jtag
minglie11 天前
iverilog 配合 Makefile 搭建 Verilog 仿真工程
fpga开发
芒果树技术1 天前
MangoTree案例分享:基于AtomRIO FPGA平台,客户实现自适应主动减振
测试工具·fpga开发·模块测试
雨洛lhw2 天前
按键电路设计的细节
fpga开发
minglie12 天前
vio_uart的浏览器版上位机
fpga开发