文章目录
- 一、UART概述
- 二、UART协议帧格式
- 2.1 波特率
- 2.2 奇校验ODD
- 2.3 偶校验EVEN
- 三、UART接收器设计
- 3.1 接收时序图
- 3.2 Verilog代码
- 3.3 仿真文件测试
- 3.4 仿真结果
- 3.5 上版测试
- 四、UART发送器设计
- 4.1 发送时序图
- 4.2 Verilog代码
- 4.3 仿真文件测试
- 4.4 仿真结果
- 4.5 上板测试
- 五、UART回环测试
- 5.1 系统框图
- 5.2 Verilog代码
- 5.3 仿真文件测试
- 5.4 仿真结果
- 5.5 上板测试
- 六、参考
一、UART概述
从《浅谈UART,TTL,RS-232,RS-485的区别》这篇文章,我们知道了UART是一种串行、异步、全双工的通信协议,属于协议层;传输过程一般采用RS-232,RS-485电平标准,将所需传输的数据一位接一位地传输;整体传输框架如下:
串口通信由发送端和接收端构成,两根信号线实现,一根数据发送,一根数据接收。
二、UART协议帧格式
在串口通信过程中,数据接收与发送双方没有共享时钟,因此,双方必须协商好数据传输波特率。根据双方协议好的波特率,接收端即可对发送端的数据进行采样。整个传输流程如下:
- 传输线空闲时为高电平,然后拉低一个码元时间表示起始位
- 接着传输 8 个数据位,先传输低位,再传输高位
- 然后传输一个校验位,用来检测本次传输的数据是否正确
- 最后输出高电平表示停止位,可以是 1 位、1.5 位、2 位的高电平,并且进入空闲状态,等待下一次的数据传输
协议帧格式如下:
2.1 波特率
根据双方协议好的传输速率,接收端即可对发送端的数据进行采样。如果波特率为 9600也就是相当于每秒中划分成了 9600 等份的码元时间。常见的波特率标准为 300bps,600bps,800bps,9600bps,19200bps,38400bps,115200bps 等。通常对串口进行数据采样,采用更高频的时钟。
一个码元的计算方法:例如我们采样时钟为 50M ,所使用的波特率为 115200,传输 1个码元需要的时间( 1 / 115200 ) = 8680ns
2.2 奇校验ODD
使完整编码(有效位和校验位)中的 “1” 的个数为奇数个。如果原来信息中 1 的个数为奇数个,则校验位为 0,这样所有信息中1的个数还是奇数 ;如果原来信息中 1 的个数为偶数个,则校验位为 1,这样所有信息中1的个数还是奇数。
常见的串口通信格式是(8 位数据位+1 位奇数校验位)。以发送字符:10010101和11010101 为例
数据第0位 | 数据第1位 | 数据第2位 | 数据第3位 | 数据第4位 | 数据第5位 | 数据第6位 | 数据第7位 | 校验位 |
---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
2.3 偶校验EVEN
使完整编码(有效位和校验位)中的 “1” 的个数为偶数个。如果原来信息中 1 的个数为奇数个,则校验位为 1,这样所有信息中1的个数还是偶数 ;如果原来信息中 1 的个数为偶数个,则校验位为 0,这样所有信息中1的个数还是偶数。
常见的串口通信格式是(8 位数据位+1 位奇数校验位)。以发送字符:10010101和11010101 为例
数据第0位 | 数据第1位 | 数据第2位 | 数据第3位 | 数据第4位 | 数据第5位 | 数据第6位 | 数据第7位 | 校验位 |
---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 0 |
1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 |
三、UART接收器设计
开发平台:vivado2021.1
开发芯片:xc7a100tfgg484-2
3.1 接收时序图
本次实现11500波特率,无奇偶校验位,时钟频率为50M,则每个码元计数次数为50000000/115200=434。时序图如下:
3.2 Verilog代码
`timescale 1ns/1ns
module uart_rx#
(
parameter CLK_FREQ = 'd50_000_000, //时钟频率
parameter UART_BPS = 'd115200 //波特率
)
(
input clk ,
input rst_n ,
input rx , //uart_rx
output reg [7:0] rx_data , //uart_rx_data
output reg rx_data_valid //uart_rx_data_valid
);
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;//一个码元的长度
localparam BAUD_CNT_MAX_div2 = BAUD_CNT_MAX/2 ;
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg rx_flag ;
reg [23:0] baud_cnt ;
reg [3:0] bit_cnt ;
reg bit_flag ;
//打两拍,消除亚稳态
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)begin
rx_reg1 <= 1'b1;
rx_reg2 <= 1'b1;
end
else begin
rx_reg1 <= rx;
rx_reg2 <= rx_reg1;
end
end
//再打一拍用于判断RX下降沿
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
end
//接收范围
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
rx_flag <= 1'b0;
else if (rx_reg3 == 1'b1 && rx_reg2 == 1'b0)
rx_flag <= 1'b1;
else if(bit_cnt == 'd8 && bit_flag ==1'b1)
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
//baud_cnt:波特率计数器计数,从 0 计数到 BAUD_CNT_MAX
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
baud_cnt <= 'd0;
else if(baud_cnt == BAUD_CNT_MAX || rx_flag == 1'b0)
baud_cnt <= 'd0;
else if(rx_flag == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= baud_cnt;
end
//bit_flag: baud_cnt 计数器计数到码元中间,时采样的数据最稳定
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX_div2)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
//bit_cnt:有效数据计数器, 8 个有效数据(不含起始位和停止位)
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
bit_cnt <= 'd0;
else if(bit_cnt == 'd8 && bit_flag == 1'b1)
bit_cnt <= 'd0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
//输出数据
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
rx_data <= 'd0;
else if(bit_cnt >= 'd1 && bit_flag == 1'b1)
rx_data <= {rx_reg3,rx_data [7:1]};
else
rx_data <= rx_data ;
end
//输出数据有效信号,当接受完8个数据位后拉高
always @(posedge clk, negedge rst_n) begin
if(rst_n == 1'b0)
rx_data_valid <= 1'b0;
else if(bit_cnt == 'd8 && bit_flag == 1'b1)
rx_data_valid <= 1'b1;
else
rx_data_valid <= 1'b0;
end
endmodule
3.3 仿真文件测试
这次仿真文件使用task任务来模拟uart发送数据
`timescale 1ns/1ns
module tb_uart_rx();
reg clk ;
reg rst_n ;
reg rx ;
wire [7:0] rx_data ;
wire rx_data_valid ;
initial begin
clk = 0;
rst_n = 0;
rx = 1;
#100
rst_n = 1;
end
//调用task函数,连续发送八个数据
initial begin
#300
tx_data(8'h6);
tx_data(8'h6);
tx_data(8'h1);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
tx_data(8'hE);
tx_data(8'hF);
end
//定义task函数
task tx_data(
input [7:0] tx_data
);
integer i;
for (i=0; i<10; i=i+1 ) begin
case(i)
0: rx <= 1'b0;
1: rx <= tx_data[0];
2: rx <= tx_data[1];
3: rx <= tx_data[2];
4: rx <= tx_data[3];
5: rx <= tx_data[4];
6: rx <= tx_data[5];
7: rx <= tx_data[6];
8: rx <= tx_data[7];
9: rx <= 1'b1;
endcase
#(434*20);//每发送完一个bit数据后,延迟一个码元的时间
end
endtask
always #10 clk = ~ clk;
uart_rx#(
.CLK_FREQ ( 'd50_000_000 ),
.UART_BPS ( 'd115200 )
)u1_uart_rx
(
.clk ( clk ),
.rst_n ( rst_n ),
.rx ( rx ),
.rx_data ( rx_data ),
.rx_data_valid ( rx_data_valid )
);
endmodule
3.4 仿真结果
由仿真结果可以看出,我们接收到的依次是(661ABCDEF)8个数据,和仿真文件给的一致。
3.5 上版测试
调用ila核,准备抓取rx_data,rx_data_valid信号。通过上位机发送数据,观察rx_data是否正确
上位机没发送数据时候,ila采集不到rx_data_valid信号拉高。
上位机发送一个9F时,ila采集到rx_data_valid信号拉高,并且输出rx_data为9F
四、UART发送器设计
4.1 发送时序图
4.2 Verilog代码
`timescale 1ns / 1ps
module uart_tx#
(
parameter CLK_FREQ = 'd50_000_000, //时钟频率
parameter UART_BPS = 'd115200 //波特率
)
(
input clk ,
input rst_n ,
input [7:0] tx_data , //发送数据
input tx_data_en , //发送数据有效信号
output reg tx
);
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;//待计数的码元周期最大值
reg [7:0] tx_data_reg ;
reg [23:0] baud_cnt ;
reg [3:0] bit_cnt ;
reg bit_flag ;
reg tx_flag ;
//将待发送的数据缓存起来
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx_data_reg <= 'd0;
else if(tx_data_en == 1'b1)
tx_data_reg <= tx_data;
else
tx_data_reg <= tx_data_reg;
end
//tx_flag拉高时刻
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx_flag <= 1'b0;
else if(tx_data_en == 1'b1)
tx_flag <= 1'b1;
else if(bit_cnt == 'd9 && bit_flag == 1'b1)
tx_flag <= 1'b0;
else
tx_flag <= tx_flag;
end
//baud_cnt 计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
baud_cnt <= 'd0;
else if(baud_cnt == BAUD_CNT_MAX)
baud_cnt <= 'd0;
else if(tx_flag == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 'd0;
end
//bit_flag拉高时刻
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == 'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
end
//bit_cnt 计数器
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
bit_cnt <= 'd0;
else if(bit_cnt == 'd9 && bit_flag == 1'b1)
bit_cnt <= 'd0;
else if(bit_flag == 1'b1)
bit_cnt <= bit_cnt + 1'b1;
else
bit_cnt <= bit_cnt;
end
//移位data,由低到高发送至tx
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
tx <= 1'b1;
else if(bit_flag == 1'b1)
case(bit_cnt)
0: tx <= 1'b0;
1: tx <= tx_data_reg[0];
2: tx <= tx_data_reg[1];
3: tx <= tx_data_reg[2];
4: tx <= tx_data_reg[3];
5: tx <= tx_data_reg[4];
6: tx <= tx_data_reg[5];
7: tx <= tx_data_reg[6];
8: tx <= tx_data_reg[7];
9: tx <= 1'b1;
default:tx <= 1'b1;
endcase
end
endmodule
4.3 仿真文件测试
模拟两个数据(1A,3B)的输入,让tx模块发送
`timescale 1ns / 1ns
module tb_uart_tx();
reg clk ;
reg rst_n ;
wire tx ;
reg [7:0] tx_data ;
reg tx_data_en ;
initial begin
clk = 0;
rst_n = 0;
tx_data = 8'd0;
tx_data_en = 0;
#100
rst_n = 1;
#300
tx_data = 8'h1a;
tx_data_en = 1;
#20
tx_data_en = 0;
#(434*20*10+100)//等待10个码元周期时间再等待100ns
tx_data = 8'h3b;
tx_data_en = 1;
#20
tx_data_en = 0;
#(434*20*10+100)
$stop;
end
always #10 clk = ~clk;
uart_tx#
(
.CLK_FREQ ( 'd50_000_000 ),
.UART_BPS ( 'd115200 )
)
u1_uart_tx
(
.clk ( clk ),
.rst_n ( rst_n ),
.tx_data ( tx_data ),
.tx_data_en ( tx_data_en ),
.tx ( tx )
);
endmodule
4.4 仿真结果
因为发送数据是(1A:00011010)uart先发送起始位,再发送数据位从低到高,最后拉高一个停止位。理论上tx发送的顺序就是0_01011000_1
从仿真结果来看,tx线上的顺序和理论一致,3B数据同理。
4.5 上板测试
我们创建一个顶层,调用uart_tx模块,在顶层上输入一个数据,让发送模块发出,然后在上位机上接送,观察和我们发送的数据是否一致,代码如下:
`timescale 1ns / 1ps
module uart_top(
input clk ,
input rst_n ,
output tx
);
reg [7:0] tx_data ;
reg tx_data_en ;
reg [1:0] cnt ;
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
cnt <= 'd0;
else if(cnt == 'd3)
cnt <= cnt;
else
cnt <= cnt +1'b1;
end
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)begin
tx_data <= 'd0;
tx_data_en <= 1'b0;
end
else if (cnt == 'd2)begin
tx_data <= 8'h6D;
tx_data_en <= 1'b1;
end
else begin
tx_data <= 'd0;
tx_data_en <= 1'b0;
end
end
uart_tx#
(
.CLK_FREQ ( 'd50_000_000 ),
.UART_BPS ( 'd115200 )
)
u_uart_tx
(
.clk ( clk ),
.rst_n ( rst_n ),
.tx_data ( tx_data ),
.tx_data_en ( tx_data_en ),
.tx ( tx )
);
endmodule
我们在顶层模块发送了一个 6D数据。
上板后,上位机收到了 6D数据
五、UART回环测试
5.1 系统框图
上面我们测试了UART发送和接收模块都正常,接下来我们将接收模块的输出信号接到发送模块的输入信号,在上位机上面发送数据和接收数据,看是否一致,系统框图如下:
5.2 Verilog代码
`timescale 1ns / 1ps
module uart_top(
input clk ,
input rst_n ,
input rx ,
output tx
);
wire [7:0] rx_data ;
wire rx_data_valid ;
uart_rx#
(
.CLK_FREQ ( 'd50_000_000 ),
.UART_BPS ( 'd115200 )
)
u_uart_rx(
.clk ( clk ),
.rst_n ( rst_n ),
.rx ( rx ),
.rx_data ( rx_data ),
.rx_data_valid ( rx_data_valid )
);
uart_tx#
(
.CLK_FREQ ( 'd50_000_000 ),
.UART_BPS ( 'd115200 )
)
u_uart_tx
(
.clk ( clk ),
.rst_n ( rst_n ),
.tx_data ( rx_data ),
.tx_data_en ( rx_data_valid ),
.tx ( tx )
);
endmodule
5.3 仿真文件测试
我们依然用uart_rx里的测试代码,调用task函数,发送661abcdef
`timescale 1ns / 1ps
module tb_uart_top();
reg clk ;
reg rst_n ;
reg rx ;
wire tx ;
initial begin
clk = 0;
rst_n = 0;
rx = 1;
#100
rst_n = 1;
end
//调用task函数,连续发送八个数据
initial begin
#300
tx_data(8'h6);
tx_data(8'h6);
tx_data(8'h1);
tx_data(8'hA);
tx_data(8'hB);
tx_data(8'hC);
tx_data(8'hD);
tx_data(8'hE);
tx_data(8'hF);
end
//定义task函数
task tx_data(
input [7:0] tx_data
);
integer i;
for (i=0; i<10; i=i+1 ) begin
case(i)
0: rx <= 1'b0;
1: rx <= tx_data[0];
2: rx <= tx_data[1];
3: rx <= tx_data[2];
4: rx <= tx_data[3];
5: rx <= tx_data[4];
6: rx <= tx_data[5];
7: rx <= tx_data[6];
8: rx <= tx_data[7];
9: rx <= 1'b1;
endcase
#(434*20);//每发送完一个bit数据后,延迟一个码元的时间
end
endtask
always #10 clk =~clk;
uart_top u_uart_top(
.clk ( clk ),
.rst_n ( rst_n ),
.rx ( rx ),
.tx ( tx )
);
endmodule
5.4 仿真结果
可以看出接收后的数据和发送的数据没问题
5.5 上板测试
可以看到发送和接收都正常
六、参考
UART介绍