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中,需要用时钟频率来完成波特率的设置过程。设时钟频率为fff,波特率为fbf_bfb,则每次电平拉高需要经历ffb\frac{f}{f_b}fbf个时钟频率。
这两个问题,要求我们创建两个计数器,分别记录波数和信号数。其中,信号数为10,需要4位;波数实在是比较多,暂且设为16位。
此外,串口发送数据的前提是有数据,所以要在输入口提供一个数据寄存器,同时还需要一个说明数据有效的标志位。
下面即为串口发送代码
`timescale 1ns / 1psmodule 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) beginif(!rst_n) begintx_data_t <= 8'b0;busy <= 1'b0;endelse if(tx_en) begintx_data_t <= data;busy <= 1'b1;endelse if(tx_cnt == 4'd9 && baud_cnt == (BAUD_CNT_MAX-16'b1)) begintx_data_t <= 8'b0;busy <= 1'b0;endelse begintx_data_t <= tx_data_t;busy <= busy;end
endalways @(posedge clk or negedge rst_n) beginif(!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;elsebaud_cnt <= 16'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n) tx_cnt <= 4'd0;else if(tx_en) tx_cnt <= 16'd0; else if(busy) beginif(baud_cnt == (BAUD_CNT_MAX-16'b1))tx_cnt <= tx_cnt + 1'b1;elsetx_cnt <= tx_cnt;endelsetx_cnt <= 4'd0;
endalways @(posedge clk or negedge rst_n) beginif(!rst_n) txd <= 1'b1;else if(busy) beginif(tx_cnt==4'd0)txd <= 1'b0;else if(tx_cnt>=4'd1 && tx_cnt<=4'd8)txd <= tx_data_t[tx_cnt-1];elsetxd <= 1'b1;endelsetxd <= 1'b1;
endendmodule
生成波形
我们所谓的波形,其实就是Hello world的字符,但这里有个坑点,即把字符串存储为向量时,其顺序是自右向左的,为了让串口发送的内容被识别,需要调转字符串的方向。
`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) beginif(!rst_n) baud_cnt <= 16'd0;else if(send_en)baud_cnt <= (baud_cnt + 16'b1)%DELAY_MAX;elsebaud_cnt <= 16'd0;
endalways @(posedge clk or negedge rst_n) beginif (!rst_n) beginindex <= 0;send_en <= 1;char <= 0;done <= 0;endelse if ((baud_cnt % DELAY_MAX)==0) beginif (index < 12) beginchar <= hw[index*8+:8];index <= index + 1;done <= 1;end elsesend_en <= 0;endelsedone <= 0;
end
endmodule
顶层代码
顶层代码的作用,就是将【uart_gen】生成的波形传递给【uart_tx】,其架构图如下
代码为
`timescale 1ns / 1psmodule 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发送端口 |