FPGA实战:用PL端串口发送Hello world

文章目录

简介

UART,即universal asynchronous receiver-transmitter,异步收发传输器,特点是有两路信号线,一路负责信号发送,一路负责信号接收,二者互不干扰。

UART的每帧数据共有10位,由4部分组成:

  • 起始位:当UART传输数据时,传输线会从高电平被拉到低电平并保持1个数据位,此即起始位。
  • 数据位:即数据帧实际传输的数据。
  • 奇偶校验位:统计数据中1的个数。若有奇数个1,则校验位是1,否则校验位是0。校验位可以不设置。
  • 停止位:与起始位相反,当UART传完一帧后,传输线会跃迁到高电平,并保持1个数据位,此即停止位。

UART在传输信号时,每一位的时长通过波特率来调控,波特率的单位是bps,即位/秒。

领航者开发板有一个type-c接口,实际上是USB转UART接口,在连接到电脑之后,可以通过串口调试工具查看到。

考虑到我们无法确认FPGA是否收到了上位机发送的数据,所以本文的目的,是测试FPGA的串口发送功能,具体来说,就是当按下复位键的时候,让FPGA向上位机发送"hello world"。

串口发送

在实现串口发送模块时,需要注意以下两点

  • UART协议发送的内容不全都是数据,而要包含起始位和停止位。所以,在数据发送过程中,需要以10为周期,确保起始位和截止位发送正确。
  • 串口传输过程中,FPGA和上位机需要有着同样的波特率。上位机可以直接设置波特率,而在FPGA中,需要用时钟频率来完成波特率的设置过程。设时钟频率为 f f f,波特率为 f b f_b fb,则每次电平拉高需要经历 f f b \frac{f}{f_b} fbf个时钟频率。

这两个问题,要求我们创建两个计数器,分别记录波数和信号数。其中,信号数为10,需要4位;波数实在是比较多,暂且设为16位。

此外,串口发送数据的前提是有数据,所以要在输入口提供一个数据寄存器,同时还需要一个说明数据有效的标志位。

下面即为串口发送代码

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

module uart_tx(
    input               clk  ,
    input               rst_n,
    input               tx_en,
    input     [7:0]     data ,
    output  reg         txd  ,
    );

parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;

reg  [7:0]  tx_data_t;
reg  [3:0]  tx_cnt   ;
reg  [15:0] baud_cnt ;
reg         busy     ; 

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        tx_data_t <= 8'b0;
        busy <= 1'b0;
    end
    else if(tx_en) begin
        tx_data_t <= data;
        busy <= 1'b1;
    end
    else if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX-16'b1)) begin
        tx_data_t <= 8'b0;
        busy <= 1'b0;
    end
    else begin
        tx_data_t <= tx_data_t;
        busy <= busy;
    end
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        baud_cnt <= 16'd0;
    else if(tx_en)
        baud_cnt <= 16'd0;      
    else if(busy)
        baud_cnt <= (baud_cnt + 16'b1) % BAUD_CNT_MAX;
    else
        baud_cnt <= 16'd0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        tx_cnt <= 4'd0;
    else if(tx_en)  
        tx_cnt <= 16'd0;         
    else if(busy) begin
        if(baud_cnt == (BAUD_CNT_MAX-16'b1))
            tx_cnt <= tx_cnt + 1'b1;
        else
            tx_cnt <= tx_cnt;
    end
    else
        tx_cnt <= 4'd0;
end

always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        txd <= 1'b1;
    else if(busy) begin
        if(tx_cnt==4'd0)
            txd <= 1'b0;
        else if(tx_cnt>=4'd1 && tx_cnt<=4'd8)
            txd <= tx_data_t[tx_cnt-1];
        else
            txd <= 1'b1;
    end
    else
        txd <= 1'b1;
end

endmodule

生成波形

我们所谓的波形,其实就是Hello world的字符,但这里有个坑点,即把字符串存储为向量时,其顺序是自右向左的,为了让串口发送的内容被识别,需要调转字符串的方向。

verilog 复制代码
`timescale 1ns / 1ps
module uart_gen(
    input            clk,
    input            rst_n,
    output reg       done,
    output reg [7:0] char
    );

parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;
localparam DELAY_MAX = BAUD_CNT_MAX * 10;

parameter [12*8-1:0] hw = "!dlrow olleH";

reg [4:0] index;
reg  [15:0] baud_cnt ;
reg send_en;


always @(posedge clk or negedge rst_n) begin
    if(!rst_n) 
        baud_cnt <= 16'd0;
    else if(send_en)
        baud_cnt <= (baud_cnt + 16'b1)%DELAY_MAX;
    else
        baud_cnt <= 16'd0;
end


always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        index <= 0;
        send_en <= 1;
        char <= 0;
        done <= 0;
    end
    else if ((baud_cnt % DELAY_MAX)==0) begin
        if (index < 12) begin
            char <= hw[index*8+:8];
            index <= index + 1;
            done <= 1;
        end 
        else
            send_en <= 0;
    end
    else
        done <= 0;
end
endmodule

顶层代码

顶层代码的作用,就是将【uart_gen】生成的波形传递给【uart_tx】,其架构图如下

代码为

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


module hello_world(
    input sys_clk,
    input sys_rst_n,
    output uart_txd
    );
    
//parameter define
parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200  ;

wire  [7:0]  char;
wire         send_en;

uart_tx #(
    .CLK_FREQ  (CLK_FREQ),
    .UART_BPS  (UART_BPS)
    )    
    u_uart_tx(
    .clk       (sys_clk     ),
    .rst_n     (sys_rst_n   ),
    .tx_en     (send_en     ),
    .data      (char        ),
    .txd       (uart_txd    ),
    .busy      (            )
    );

uart_gen #(
    .CLK_FREQ (CLK_FREQ),
    .UART_BPS (UART_BPS)
    )
    u_uart_gen(
    .clk     (sys_clk  ),
    .rst_n   (sys_rst_n),
    .done    (send_en  ),
    .char    (char     )
    );


endmodule

实现

我所使用的开发板,其引脚如下

Name Package Pin I/O Std 说明
clk U18 LVCMOS33 系统时钟50MHz
rst N16 LVCMOS33 复位键,低电平有效
txd J15 LVCMOS33 UART发送端口