简介
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位。
此外,串口发送数据的前提是有数据,所以要在输入口提供一个数据寄存器,同时还需要一个说明数据有效的标志位。
下面即为串口发送代码
`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的字符,但这里有个坑点,即把字符串存储为向量时,其顺序是自右向左的,为了让串口发送的内容被识别,需要调转字符串的方向。
`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】,其架构图如下
代码为
`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发送端口 |