UART通信协议

  1. 串口通信的信号线只需要两条线就可以完成,TX和RX TX发送端 RX为接收端。
  2. 起始位,数据线从高变低,低有效为0,数据传输开始。
  3. 数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB)在前,高位(MSB)在后。
  4. 校验位,校验位可以认为是一个特殊的数据位,通常使用的是奇偶校验,使用串口协议时通常取消奇偶校验位。
  5. 停止位,停止位高有效为1,他表示这一个个字节传输结束。
    6.位时间,起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。
  6. 空闲位,持续的高电平。
  7. 帧:从起始位开始到停止位结束的时间间隔称之为一帧。
START D0~D7 P STOP
起始位 数据位 校验位 停止位

模块编写

首先约定波特率为9600(可变),无校验位
将数据线打两拍,防止亚稳态并捕获其的下降沿信号(起始位0)
检测到下降沿后拉高数据传输标志,意味着数据开始接收;并在传输第九个数据(终止位1)的正中(数据比较稳定)将数据传输标志拉低,意味着数据接收完成
对时钟进行计数,每一个周期(在设定波特率条件下传输一位所需要的时间)对数据线进行采样并将数据寄存,同时记录传输个数(记录当前共接收了多少个数据)
接收到第九个数据(终止位1)后,拉高接收完成标志位。
将8位或者多位数据拆分为一位一位的发送出去的过程称为并转串。将一位一位接收的数据合并为8位或者多位数据的过程称为串转并。
对于串行通信设备来说,发送方都是在执行并转串,接收方都是在执行串转并。UART设备为串行通信设备。

代码

UART_RX

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
module uart_rx(
input sys_clk, //50M系统时钟
input sys_rst_n, //系统复位
input uart_rxd, //接收数据线
output reg uart_rx_done, //数据接收完成标志
output reg [7:0]uart_rx_data //接收到的数据
);
//常量化波特率,可更改
parameter BPS=9600; //波特率9600bps,可更改
parameter SYS_CLK_FRE=50_000_000; //50M系统时钟
localparam BPS_CNT=SYS_CLK_FRE/BPS; //传输一位数据所需要的时钟个数

reg uart_rx_d0; //寄存1拍
reg uart_rx_d1; //寄存2拍
reg [15:0] clk_cnt; //时钟计数器
reg [3:0] rx_cnt; //接收计数器
reg rx_flag; //接收标志位
reg [7:0] uart_rx_data_reg; //数据寄存

wire neg_uart_rx_data; //数据的下降沿

assign neg_uart_rx_data=uart_rx_d1 & (~uart_rx_d0); //捕获数据线的下降沿,用来标志数据传输开始
//将数据线打两拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:用以捕获下降沿
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_d0<=1'b0;
uart_rx_d1<=1'b0;
end
else begin
uart_rx_d0<=uart_rxd;
uart_rx_d1<=uart_rx_d0;
end
end
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
rx_flag<=1'b0;
else begin
if(neg_uart_rx_data)
rx_flag<=1'b1;
else if((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2))//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
rx_flag<=1'b0;
else
rx_flag<=rx_flag;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
rx_cnt<=4'd0;
clk_cnt<=16'd0;
end
else if(rx_flag)begin
if(clk_cnt<BPS_CNT-1'b1)begin
clk_cnt<=clk_cnt+1'b1;
rx_cnt<=rx_cnt;
end
else begin
clk_cnt<=16'd0;
rx_cnt<=rx_cnt+1'b1;
end
end
else begin
rx_cnt<=4'd0;
clk_cnt<=16'd0;
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_rx_data_reg<=8'd0;
else if(rx_flag)
if(clk_cnt==BPS_CNT/2) begin
case(rx_cnt)
4'd1:uart_rx_data_reg[0]<=uart_rxd;
4'd2:uart_rx_data_reg[1]<=uart_rxd;
4'd3:uart_rx_data_reg[2]<=uart_rxd;
4'd4:uart_rx_data_reg[3]<=uart_rxd;
4'd5:uart_rx_data_reg[4]<=uart_rxd;
4'd6:uart_rx_data_reg[5]<=uart_rxd;
4'd7:uart_rx_data_reg[6]<=uart_rxd;
4'd8:uart_rx_data_reg[7]<=uart_rxd;
default:;
endcase
end
else
uart_rx_data_reg<=uart_rx_data_reg;
else
uart_rx_data_reg<=8'd0;
end
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_rx_done<=1'b0;
uart_rx_data<=8'd0;
end
else if(rx_cnt==4'd9)begin
uart_rx_done<=1'b1;
uart_rx_data<=uart_rx_data_reg;
end
else begin
uart_rx_done<=1'b0;
uart_rx_data<=8'd0;
end
end
endmodule

UART_TX

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
module uart_tx(
input sys_clk, //50M系统时钟
input sys_rst_n, //系统复位
input [7:0] uart_data, //发送的8位置数据
input uart_tx_en, //发送使能信号
output reg uart_txd //串口发送数据线

);

parameter SYS_CLK_FRE=50_000_000; //50M系统时钟
parameter BPS=9_600; //波特率9600bps,可更改
localparam BPS_CNT=SYS_CLK_FRE/BPS; //传输一位数据所需要的时钟个数

reg uart_tx_en_d0; //寄存1拍
reg uart_tx_en_d1; //寄存2拍
reg tx_flag; //发送标志位
reg [7:0] uart_data_reg; //发送数据寄存器
reg [15:0] clk_cnt; //时钟计数器
reg [3:0] tx_cnt; //发送个数计数器

wire pos_uart_en_txd; //使能信号的上升沿
//捕捉使能端的上升沿信号,用来标志输出开始传输
assign pos_uart_en_txd= uart_tx_en_d0 && (~uart_tx_en_d1);

always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
uart_tx_en_d0<=1'b0;
uart_tx_en_d1<=1'b0;
end
else begin
uart_tx_en_d0<=uart_tx_en;
uart_tx_en_d1<=uart_tx_en_d0;
end
end
//捕获到使能端的上升沿信号,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
tx_flag<=1'b0;
uart_data_reg<=8'd0;
end
else if(pos_uart_en_txd)begin
uart_data_reg<=uart_data;
tx_flag<=1'b1;
end
else if((tx_cnt==4'd9) && (clk_cnt==BPS_CNT/2))begin//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
tx_flag<=1'b0;
uart_data_reg<=8'd0;
end
else begin
uart_data_reg<=uart_data_reg;
tx_flag<=tx_flag;
end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
clk_cnt<=16'd0;
tx_cnt <=4'd0;
end
else if(tx_flag) begin
if(clk_cnt<BPS_CNT-1)begin
clk_cnt<=clk_cnt+1'b1;
tx_cnt <=tx_cnt;
end
else begin
clk_cnt<=16'd0;
tx_cnt <=tx_cnt+1'b1;
end
end
else begin
clk_cnt<=16'd0;
tx_cnt<=4'd0;
end
end
//在每个数据的传输过程正中(数据比较稳定)将数据寄存器的数据赋值给数据线
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
uart_txd<=1'b1;
else if(tx_flag)
case(tx_cnt)
4'd0: uart_txd<=1'b0;
4'd1: uart_txd<=uart_data_reg[0];
4'd2: uart_txd<=uart_data_reg[1];
4'd3: uart_txd<=uart_data_reg[2];
4'd4: uart_txd<=uart_data_reg[3];
4'd5: uart_txd<=uart_data_reg[4];
4'd6: uart_txd<=uart_data_reg[5];
4'd7: uart_txd<=uart_data_reg[6];
4'd8: uart_txd<=uart_data_reg[7];
4'd9: uart_txd<=1'b1;
default:;
endcase
else
uart_txd<=1'b1;
end
endmodule

UART_TOP

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
module uart_top(
input sys_clk, //系统时钟
input sys_rst_n, //系统复位

input uart_rxd, //接收端口
output uart_txd //发送端口

);
parameter UART_BPS=9600; //波特率
parameter CLK_FREQ=50_000_000; //系统频率50M

wire uart_en_w;
wire [7:0] uart_data_w;

//例化发送模块
uart_tx#(
.BPS (UART_BPS),
.SYS_CLK_FRE (CLK_FREQ))
u_uart_tx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_tx_en (uart_en_w),
.uart_data (uart_data_w),
.uart_txd (uart_txd)
);
//例化接收模块
uart_rx #(
.BPS (UART_BPS),
.SYS_CLK_FRE (CLK_FREQ))
u_uart_rx(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),

.uart_rxd (uart_rxd),
.uart_rx_done (uart_en_w),
.uart_rx_data (uart_data_w)
);

endmodule

TestBench

由于涉及到的模块较少使用VScode进行简单的仿真,需要生成vcd波形文件,添加了额外的代码,一次八位数据为$01010101_2$即$55_{16}$。

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
`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_top_tb();
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;

wire uart_txd;

//例化被测试的接收模块
uart_top #(
.UART_BPS (9600), //波特率9600
.CLK_FREQ (50_000_000)//时钟频率50M
)
u_uart_top(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_txd (uart_txd)
);

localparam CNT=50_000_000/9600*20; //计算出传输每个时钟所需要的时间
initial begin //传输8位数据 8'b01010101
//初始时刻定义
sys_clk <=1'b0;
sys_rst_n<=1'b0;
uart_rxd<=1'b1;
#20 //系统开始工作
sys_rst_n<=1'b1;
#(CNT/2)
uart_rxd<=1'b0;//开始传输起始位
#CNT
uart_rxd<=1'b1;//传输最低位,第1位
#CNT
uart_rxd<=1'b0;//传输第2位
#CNT
uart_rxd<=1'b1;//传输第3位
#CNT
uart_rxd<=1'b0; //传输第4位
#CNT
uart_rxd<=1'b1;//传输第5位
#CNT
uart_rxd<=1'b0;//传输第6位
#CNT
uart_rxd<=1'b1;//传输第7位
#CNT
uart_rxd<=1'b0; //传输最高位,第8位
#CNT
uart_rxd<=1'b1; //传输终止位
#20000
$finish;
end

always begin
#10 sys_clk=~sys_clk; //时钟20ns,50M
end
initial begin
$dumpfile("wave.vcd"); //指定生成的vcd文件名称为wave.vcd,仿真信息将记录到此文件
$dumpvars(0, uart_top_tb); //指定层次数为0,则tb模块及其下面各层次的所有信号将被记录
end
endmodule

图1 仿真结果

全双工通信、半双工通信和单工通信

  • 全双工通信是指在同一时刻信息可以进行双向传输。例如:打电话,说的同时也能听。
  • 半双工通信是指在同一时刻信息只能单向传输,双方都可以进行发送和接收,但是不能够同时发送和接收。例如:对讲机通信。
  • 单工通信是指在通信过程中,只能够设备A发送,设备B接收。例如:听收音机。

UART的通信电平标准

两个设备之间能够互相通信的基础条件为电平标准相同。UART的接口标准有很多,有RS232、RS485等等。台式PC上一般会有一个DB9,接口标准为RS232。
此接口在各个工业板上也有很多。随着技术的发展,PC上的DB9的接口逐渐被淘汰,换成了USB接口。
现如今的开发板上多为USB接口,便于和PC进行通信。
FPGA芯片是无法(较为复杂)发出对应的电平标准,如:RS485、RS232、USB接口电平等。在大多数板卡设计时,都会在FPGA外围添加电平转换器,将FPGA的电平标准转换为通信的电平标准。