当前位置:首页 > EDA > 电子设计自动化
[导读]UART串口是FPGA入门最经典的实战项目之一——它协议简单、调试直观、实用性强。用Verilog实现一个完整的UART收发器,能帮你深刻理解FPGA的时序逻辑、状态机和波特率分频三大核心概念。本文以115200波特率、8N1格式(8数据位、无校验、1停止位)为例,从波特率发生器到收发模块,给出可直接仿真验证的代码。


UART串口是FPGA入门最经典的实战项目之一——它协议简单、调试直观、实用性强。用Verilog实现一个完整的UART收发器,能帮你深刻理解FPGA的时序逻辑、状态机和波特率分频三大核心概念。本文以115200波特率、8N1格式(8数据位、无校验、1停止位)为例,从波特率发生器到收发模块,给出可直接仿真验证的代码。

UART帧格式

UART的一帧数据包含:1位起始位(低电平)、8位数据位(LSB先发)、1位停止位(高电平)。空闲时总线为高电平。发送和接收双方约定相同的波特率,通过定时采样恢复数据。

波特率发生器

FPGA内部时钟通常远高于波特率(如50MHz),需要通过计数器分频产生采样时钟。对于115200波特率,每位宽度约为8.68μs。若系统时钟50MHz(周期20ns),则每位需要的时钟周期数为:

50MHz ÷ 115200 ≈ 434

因此,计数器从0计数到433即完成一位的定时。接收端通常在每位的中点采样(即计数到217时),以获得最稳定的数据。

module baud_gen #(parameter CLK_FREQ = 50_000_000, BAUD = 115200) (

   input clk, rst_n,

   output reg tick

);

   localparam CNT_MAX = CLK_FREQ / BAUD - 1;

   localparam CNT_WIDTH = $clog2(CNT_MAX + 1);

   reg [CNT_WIDTH-1:0] cnt;


   always @(posedge clk or negedge rst_n) begin

       if (!rst_n) begin

           cnt <= 0;

           tick <= 0;

       end else if (cnt == CNT_MAX) begin

           cnt <= 0;

           tick <= 1;

       end else begin

           cnt <= cnt + 1;

           tick <= 0;

       end

   end

endmodule

UART发送模块

发送模块的核心是一个状态机:空闲态等待发送使能,然后依次发送起始位、8个数据位和停止位。

module uart_tx (

   input clk, rst_n,

   input [7:0] data_in,

   input tx_en,

   output reg txd,

   output reg tx_busy

);

   localparam IDLE = 0, START = 1, DATA = 2, STOP = 3;

   reg [1:0] state;

   reg [3:0] bit_cnt;  // 0-7数据位

   wire tick;


   baud_gen #(.CLK_FREQ(50_000_000), .BAUD(115200)) u_baud (.clk(clk), .rst_n(rst_n), .tick(tick));


   always @(posedge clk or negedge rst_n) begin

       if (!rst_n) begin

           state <= IDLE;

           txd <= 1;

           tx_busy <= 0;

           bit_cnt <= 0;

       end else if (tick) begin

           case (state)

               IDLE: begin

                   txd <= 1;

                   tx_busy <= 0;

                   if (tx_en) begin

                       state <= START;

                       tx_busy <= 1;

                   end

               end

               START: begin

                   txd <= 0;  // 起始位

                   state <= DATA;

                   bit_cnt <= 0;

               end

               DATA: begin

                   txd <= data_in[bit_cnt];  // LSB first

                   if (bit_cnt == 7) state <= STOP;

                   else bit_cnt <= bit_cnt + 1;

               end

               STOP: begin

                   txd <= 1;  // 停止位

                   state <= IDLE;

               end

           endcase

       end

   end

endmodule

UART接收模块

接收的关键在于在每位的中点采样。用计数器记录当前处于位周期的哪个位置,在中间位置读取rxd引脚的电平。

module uart_rx (

   input clk, rst_n,

   input rxd,

   output reg [7:0] data_out,

   output reg rx_done

);

   localparam IDLE = 0, START = 1, DATA = 2, STOP = 3;

   reg [1:0] state;

   reg [3:0] bit_cnt;

   reg [8:0] sample_cnt;  // 每位内的采样计数器

   wire tick;


   baud_gen u_baud (.clk(clk), .rst_n(rst_n), .tick(tick));


   always @(posedge clk or negedge rst_n) begin

       if (!rst_n) begin

           state <= IDLE;

           data_out <= 0;

           rx_done <= 0;

           sample_cnt <= 0;

       end else if (tick) begin

           case (state)

               IDLE: begin

                   rx_done <= 0;

                   if (!rxd) begin  // 检测到起始位

                       state <= START;

                       sample_cnt <= 0;

                   end

               end

               START: begin

                   if (sample_cnt == 7) begin  // 中点采样

                       if (!rxd) begin  // 确认起始位有效

                           state <= DATA;

                           bit_cnt <= 0;

                           sample_cnt <= 0;

                       end else state <= IDLE;

                   end else sample_cnt <= sample_cnt + 1;

               end

               DATA: begin

                   if (sample_cnt == 15) begin  // 每位的中点

                       data_out[bit_cnt] <= rxd;

                       if (bit_cnt == 7) state <= STOP;

                       else bit_cnt <= bit_cnt + 1;

                       sample_cnt <= 0;

                   end else sample_cnt <= sample_cnt + 1;

               end

               STOP: begin

                   if (sample_cnt == 15) begin

                       rx_done <= 1;

                       state <= IDLE;

                   end else sample_cnt <= sample_cnt + 1;

               end

           endcase

       end

   end

endmodule

注意:接收模块的采样计数器范围是0-15,即在每位内采样16次,在第8次(中点)读取数据。这是因为波特率发生器产生的tick信号每周期只持续一个时钟周期,接收模块需要在每位内做更细粒度的采样。

顶层模块与测试

将收发模块例化到顶层,形成一个简单的UART环路测试:

module uart_top (

   input clk, rst_n,

   input rxd,

   output txd,

   input [7:0] sw_data,

   input btn_send

);

   wire [7:0] rx_data;

   wire rx_done;


   uart_rx u_rx (.clk(clk), .rst_n(rst_n), .rxd(rxd),

                 .data_out(rx_data), .rx_done(rx_done));

   uart_tx u_tx (.clk(clk), .rst_n(rst_n), .data_in(rx_data),

                 .tx_en(rx_done), .txd(txd), .tx_busy());

endmodule

编写testbench,模拟发送0x55(01010101),观察txd波形是否符合UART时序。使用ModelSim或Vivado Simulator运行仿真,检查起始位、8个数据位和停止位的电平变化是否正确。

写在最后

UART收发器是FPGA入门的里程碑项目。通过这个实战,你亲手实现了波特率分频器、状态机、移位寄存器和边沿检测——这些模块是更复杂设计(SPI、I2C、以太网)的基础。当你在示波器上看到自己写的FPGA发出的UART波形与PC串口助手完美通信时,那种成就感是理论学习无法替代的。从UART出发,FPGA的世界才刚刚打开。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭