我觉得FPGA的学习不能够只停留在理论的仿真这一层面上,诚然理论仿真已经能够教会我们很多东西,但板级验证还能提供不一样的bug供我们学习(\doge),因此我重启了去年购买的小梅哥ACX720开发板来进一步学习FPGA,这次主要复现并学习了小梅哥Xilinx FPGA自学教程中的串口发送模块的设计与验证。

异步串行通信原理

串口通信是通信系统中常用的一种通信方式,又称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter, UART),此种通信方式在数据发送时将并行数据转换成串行数据来传输,再将数据接收到的串行数据转换成并行数据,以此来实现全双工传输和接收。它包括了RS232、RS449、RS423、RS422和RS485等接口标准规范和总线标准规范,这些规范规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容,本次学习主要是基于RS-232接口标准。

在RS-232标准中,最常用的配置是8N1(即8个数据位、无奇偶校验位、一个停止位),其发送一个字节时序图如下图所示:

UART发送一个字节时序图

RS232硬件电路设计

RS232早期使用的方案为RS23转TTL,这时需要MAX232或者SP3232等电平转换芯片来进行数据转换,具体的硬件设计原理图如下图所示:

RS232转TTL

随着系统集成度越来越高,如今大多数系统已经转用USB转串口的方式来实现RS232,此时需要CH340E来实现对应的USB转串口,具体的电路原理图如下所示(怪不得我以前做课设需要安装CH340的驱动原来是用来驱动串口通信的):

USB转串口

UART Verilog 异步串行发送模块设计与实现

本次复现的串口发送模块的整体框图如下所示:

单字节串口发送模块输入输出端口

其中输入端口clk为系统的50M时钟的接入端口,reset_n为系统低电平复位信号,data_byte为待发送的八位二进制数据,send_en为系统高电平使能信号,baud_set为串口波特率设定信号,以上端口均为输入端口。输出端口则包括:单比特串行发送信号uart_tx,串口发送完毕指示信号tx_done,串口发送状态信号uart_state。

具体硬件验证时,采用了虚拟IO(vio)IP核来实现八位data_byte数据的输入,并设置一虚拟按钮来实现模块使能信号的输入,然后用串口调试助手接收FPGA发送的串行数据来验证发送结果。

串口单字节发送验证效果如下所示:

系统顶层模块Verilog代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
module uart_tx_test( clk,reset_n,uart_tx,led );

input clk; //系统时钟50MHz
input reset_n; //低电平有效复位信号
output uart_tx; //串口输出信号
output led; //led指示数据发送状态

wire send_en; //发送使能
wire [7:0]data_byte; //待传输8bit数据
wire test_en; //按键标志信号
reg test_en_dly1;
reg test_en_dly2;

assign reset=~reset_n;

always @(posedge clk) begin
test_en_dly1 <= test_en;
test_en_dly2 <= test_en_dly1;
end

assign send_en=test_en_dly1 & !test_en_dly2;

vio_0 vio_0_inst1 (
.clk(clk), // input wire clk
.probe_out0(test_en), // output wire [0 : 0] probe_out0
.probe_out1(data_byte) // output wire [7 : 0] probe_out1
);

uart_tx uart_tx_inst1 (
.clk (clk ),
.reset_n (reset_n ),
.data_byte (data_byte),
.send_en (send_en ),
.baud_set (3'd0 ),
.uart_tx (uart_tx ),
.tx_done ( ),
.uart_state (led)
);

endmodule

串口单字节发送模块的Verilog代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
module uart_tx( 
clk,
reset_n,
data_byte,
send_en,
baud_set,

uart_tx,
tx_done,
uart_state );

input clk; //模块全局时钟50MHz
input reset_n; //模块全局复位信号(低电平有效)
input [7:0]data_byte; //待传输8bit数据
input send_en; //发送使能信号(高电平使能)
input [2:0]baud_set; //波特率设置信号
output reg uart_tx; //串口发送信号输出(串行)
output reg tx_done; //发送结束信号,一个时钟周期高电平
output reg uart_state; //发送状态,处于发送状态时为1

reg [15:0]bps_DR; //波特率计数周期寄存器
reg bps_clk; //波特率时钟
reg [15:0]div_cnt; //波特率时钟计数器计数值
reg [3:0]bps_cnt; //发送数据位计数
reg [7:0]data_byte_reg; //data_byte缓存(为什么要缓存?不缓存可以吗?)

assign reset = ~reset_n;
localparam START_BIT = 1'b0; //开始比特
localparam STOP_BIT = 1'b1; //结束比特

//通过baud_set设置波特率计数周期
always @(posedge clk or posedge reset) begin
if(reset)
bps_DR <= 16'd5207;
else begin
case (baud_set)
0: bps_DR <= 16'd5207; //波特率为9600
1: bps_DR <= 16'd2603; //波特率为19200
2: bps_DR <= 16'd1301; //波特率为38400
3: bps_DR <= 16'd867; //波特率为57600
4: bps_DR <= 16'd433; //波特率为115200
default: bps_DR <= 16'd5207;
endcase
end
end

//波特率周期计数器
always @(posedge clk or posedge reset)
if(reset)
div_cnt <= 16'd0;
else if (uart_state) begin
if(div_cnt == bps_DR)
div_cnt <= 16'd0;
else
div_cnt <= div_cnt+1'b1;
end
else
div_cnt <= 16'd0;

//波特率时钟生成
always @(posedge clk or posedge reset)
if(reset)
bps_clk <= 1'b0;
else if(div_cnt==16'd1) //这里要注意
bps_clk <= 1'b1;
else
bps_clk <= 1'b0;

//通过bps_cnt进行发送比特计数
always @(posedge clk or posedge reset)
if(reset)
bps_cnt <= 4'd0;
else if(bps_cnt == 4'd11)
bps_cnt <= 4'd0;
else if(bps_clk)
bps_cnt <= bps_cnt+1'b1;
else
bps_cnt <= bps_cnt;

//发送字节状态输出
always @(posedge clk or posedge reset)
if(reset)
tx_done <= 1'b0;
else if(bps_cnt==4'd11)
tx_done <= 1'b1;
else
tx_done <= 1'b0;

//串口发送状态输出
always @(posedge clk or posedge reset)
if(reset)
uart_state <= 1'b0;
else if(send_en)
uart_state <= 1'b1;
else if(bps_cnt == 4'd11)
uart_state <= 1'b0;
else
uart_state <= uart_state;

//串口使能进行发送字节缓存
always @(posedge clk or posedge reset)
if(reset)
data_byte_reg <= 8'd0;
else if(send_en)
data_byte_reg <= data_byte;
else
data_byte_reg <= data_byte_reg;

//串行数据发送
always @(posedge clk or posedge reset) begin
if(reset)
uart_tx <= 1'b1;
else begin
case (bps_cnt)
0:uart_tx <= 1'b1;
1:uart_tx <= START_BIT;
2:uart_tx <= data_byte_reg[0];
3:uart_tx <= data_byte_reg[1];
4:uart_tx <= data_byte_reg[2];
5:uart_tx <= data_byte_reg[3];
6:uart_tx <= data_byte_reg[4];
7:uart_tx <= data_byte_reg[5];
8:uart_tx <= data_byte_reg[6];
9:uart_tx <= data_byte_reg[7];
10:uart_tx <= STOP_BIT;
default: uart_tx <= 1'b1;
endcase
end
end

endmodule