UART串口通信——FPGA学习笔记9
一、数据通信基本概念
按数据通信方式分类:
串行通信、并行通信
按数据传输方向分类:
单工通信、半双工通信、全双工通信
按数据同步方式分类:
同步通信、异步通信
常见的串行通信接口:
二、串口通信:
UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART 串口通信需要两根信号线来实现, 一根用于串口发送,另外一根负责串口接收。 UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位,如图 19.1.1 所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束, 数据位是一帧数据中的有效数据。 校验位分为奇校验和偶校验, 用于检验数据在传输过程中是否出错。 奇校验时, 发送方应使数据位中 1 的个数与校验位中 1 的个数之和为奇数;接收方在接收数据时, 对 1 的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。 同样,偶校验则检查 1 的个数是否为偶数。
直接在数据信号中穿插一些同步用的信号位,或者把主体数据进行打包,以数据帧的格式传输数据。例如规定由起始位、数据位、奇偶校验位、停止位等。某些通讯中还需要双方约定数据的传输速率,以便更好地同步、
物理层:
UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为 5、 6、 7、 8 位,其中 8 位数据位是最常用的, 在实际应用中一般都选择 8 位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择 1 位(默认) , 1.5 或 2 位。 串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是 bps(位/秒) ,常用的波特率有 9600、 19200、38400、 57600 以及 115200 等。
在设置好数据格式及传输速率之后, UART 负责完成数据的串并转换, 而信号的传输则由外部驱动电路实现。 电信号的传输过程有着不同的电平标准和接口规范, 针对异步串行通信的接口标准有 RS232、 RS422、RS485 等, 它们定义了接口不同的电气特性,如 RS-232 是单端输入输出,而 RS-422/485 为差分输入输出等。
RS232 接口标准出现较早, 可实现全双工工作方式,即数据发送和接收可以同时进行。在传输距离较短时(不超过 15m) , RS232 是串行通信最常用的接口标准, RS-232 标准的串口最常见的接口类型为 DB9,样式如图 19.1.2 所示,工业控制领域中用到的工控机一般都配备多个串口, 很多老式台式机也都配有串口。
协议层:
UART通信协议
起始位:一帧的开始,必须保持一个比特位的低电平0
数据位:传输的有效数据,数据位可选5~8位;LSB(低位)在前,MSB(高位)在后
校验位:可选位,占用1个比特位,也可以没有校验
停止位:一帧的结束,必须有,可选占用0.5/1/1.5/2个比特位,保持逻辑高电平1
UART的传输速率:波特率
波特率(BaudRate):串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位/秒)。常用的波特率有9600、19200、38400、57600以及115200等。
计算:
例如通信速率115200 那么一个比特位所占用的时间为,
如果系统时钟为50MHz(周期20ns),那么每个周期占用时间为8680/20=434个时钟周期。
三、程序设计
1、实验任务
本节的实验任务是上位机通过串口调试助手发送数据给开发板,开发板通过USB UART串口接收数据并将接收到的数据发送给上位机,完成串口数据环回。UART通信波特率:115200,停止位:1,数据位:8位,无校验位。
2、框图分析
接收模块要有接收完成标志
发送模块要求有忙信号标志
串口通信实现思维导图:
针对异步信号的同步处理——打三拍
什么是亚稳态?亚稳态是由于违背了触发器的建立和保持时间而产生的。寄存器采样需要满足一定的建立时间(setup)和保持时间(holdup),而异步电路没有办法保证建立时间(setup)和保持时间(holdup),所以会出现亚稳态。
消除亚稳态
3、波形分析
接收模块:
发送模块:
四、代码编写
1、 串口接收部分
`timescale 1ns / 1ps
module uart_rx(
input sys_clk,
input sys_rst_n,
input uart_rxd, //串口输入信号
output reg uart_rx_done, //串口接收完成信号
output reg [7:0] uart_rx_data //串口接收数据
);
parameter CLK_FREQ = 50_000_000; //时钟
parameter UART_BPS = 115200; //波特率
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; //50Mhz 115200
reg uart_rx_d0 ;
reg uart_rx_d1 ;
reg uart_rx_d2 ;
reg rx_flag ; //高 在进行读取
reg [3:0] rx_cnt ; //位数计数器
reg [15:0] baud_cnt ; //波特率计数器
reg [7:0] rx_data_t ; //临时寄存器
wire start_en ; //开始信号
assign start_en = uart_rx_d2 &(!uart_rx_d1)&(!rx_flag);
//数据打拍
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 ;
uart_rx_d2 <= 1'b0 ;
end
else begin
uart_rx_d0 <= uart_rxd ;
uart_rx_d1 <= uart_rx_d0 ;
uart_rx_d2 <= uart_rx_d1 ;
end
end
//给接收标志rx_flag赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
rx_flag <= 1'b0;
end
else if (start_en == 1'b1) begin
rx_flag <= 1'b1;
end
//计数到9,在停止位一半的时候拉低
else if ((rx_cnt == 4'd9)&&(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)) begin
rx_flag <= 1'b0;
end
else begin
rx_flag <= rx_flag;
end
end
//对波特率计数器baud_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
baud_cnt <= 16'd0;
end
else if (rx_flag == 1'b1) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)begin
baud_cnt <= baud_cnt + 16'b1;
end
else begin
baud_cnt <= 16'd0;
end
end
else begin
baud_cnt <= 16'd0;
end
end
//对数据计数器rx_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
rx_cnt <= 4'd0;
end
else if(rx_flag == 1'b1)begin
if (baud_cnt == BAUD_CNT_MAX - 1'b1) begin
rx_cnt <= rx_cnt + 4'b1;
end
else begin
rx_cnt <= rx_cnt;
end
end
else begin
rx_cnt <= 4'd0;
end
end
//根据rx_cnt寄存输入数据
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
rx_data_t <= 8'd0;
end
else if (rx_flag == 1'b1) begin
if(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)begin
case (rx_cnt)
4'd1: rx_data_t[0] <= uart_rx_d2;
4'd2: rx_data_t[1] <= uart_rx_d2;
4'd3: rx_data_t[2] <= uart_rx_d2;
4'd4: rx_data_t[3] <= uart_rx_d2;
4'd5: rx_data_t[4] <= uart_rx_d2;
4'd6: rx_data_t[5] <= uart_rx_d2;
4'd7: rx_data_t[6] <= uart_rx_d2;
4'd8: rx_data_t[7] <= uart_rx_d2;
default: ;
endcase
end
else begin
rx_data_t <= rx_data_t;
end
end
else begin
rx_data_t <= 8'd0;
end
end
//给接收完成信号uart_rx_done 和接收uart_rx_data 赋值
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)&&(baud_cnt == BAUD_CNT_MAX/2 - 1'b1)) begin
uart_rx_done <= 1'b1;
uart_rx_data <= rx_data_t;
end
else begin
uart_rx_done <= 1'b0;
uart_rx_data <= uart_rx_data;
end
end
endmodule
2、串口发送部分
`timescale 1ns / 1ps
module uart_tx(
input sys_clk,
input sys_rst_n,
input uart_tx_en, //串口发送数据使能
input [7:0] uart_tx_data, //串口准备发送的数据
output reg uart_txd, //串口输出
output reg uart_tx_busy //串口忙的标志
);
parameter CLK_FREQ = 50_000_000; //时钟
parameter UART_BPS = 115200; //波特率
localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; //50Mhz 115200
reg [7:0] tx_data_t;
reg [3:0] tx_cnt;
reg [15:0] baud_cnt ; //波特率计数器
//当uart_tx_en为高时 寄存输入的并行数据 且拉高busy信号
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
tx_data_t <= 8'd0;
uart_tx_busy <= 1'b0;
end
else if (uart_tx_en == 1'b1) begin
tx_data_t <= uart_tx_data;
uart_tx_busy <= 1'b1;
end
else if ((tx_cnt == 4'd9)&&(baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16)) begin
tx_data_t <= 8'd0;
uart_tx_busy <= 1'b0;
end
else begin
tx_data_t <= tx_data_t ;
uart_tx_busy <= uart_tx_busy ;
end
end
//对波特率计数器baud_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n ) begin
if (!sys_rst_n) begin
baud_cnt <= 16'd0;
end
else if (uart_tx_busy == 1'b1) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)begin
baud_cnt <= baud_cnt + 16'b1;
end
else begin
baud_cnt <= 16'd0;
end
end
else begin
baud_cnt <= 16'd0;
end
end
//对数据计数器tx_cnt 赋值
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_cnt <= 4'd0;
end
else if(uart_tx_busy == 1'b1)begin
if (baud_cnt == BAUD_CNT_MAX - 1'b1) begin
tx_cnt <= tx_cnt + 4'b1;
end
else begin
tx_cnt <= tx_cnt;
end
end
else begin
tx_cnt <= 4'd0;
end
end
//根据tx_cnt控制TXD信号
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_txd <= 1'd1;
end
else if (uart_tx_busy == 1'b1) begin
case (tx_cnt)
4'd0: uart_txd <= 1'b0; //起始位
4'd1: uart_txd <= tx_data_t[0]; //d0
4'd2: uart_txd <= tx_data_t[1]; //d1
4'd3: uart_txd <= tx_data_t[2]; //d2
4'd4: uart_txd <= tx_data_t[3]; //d3
4'd5: uart_txd <= tx_data_t[4]; //d4
4'd6: uart_txd <= tx_data_t[5]; //d5
4'd7: uart_txd <= tx_data_t[6]; //d6
4'd8: uart_txd <= tx_data_t[7]; //d7
4'd9: uart_txd <= 1'b1; //停止位
default: uart_txd <= 1'b1;
endcase
end
else begin
uart_txd <= 1'b1;
end
end
endmodule
3、顶层代码
`timescale 1ns / 1ps
module uart_loopback(
input sys_clk,
input sys_rst_n,
//UART端口
input uart_rxd,
output uart_txd
);
parameter CLK_FREQ = 50_000_000 ; //时钟
parameter UART_BPS = 115200 ; //波特率
wire uart_rx_done ;
wire [7:0] uart_rx_data ;
uart_rx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
u_uart_rx(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n ) ,
.uart_rxd (uart_rxd ) ,
.uart_rx_done (uart_rx_done) ,
.uart_rx_data (uart_rx_data)
);
uart_tx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
u_uart_tx(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n ) ,
.uart_tx_en (uart_rx_done) ,
.uart_tx_data (uart_rx_data) ,
.uart_txd (uart_txd ) ,
.uart_tx_busy ()
);
endmodule
4、tb代码
`timescale 1ns / 1ps
module tb_uart_loopbak( );
parameter CLK_PERIOD = 20;
reg sys_clk;
reg sys_rst_n;
reg uart_rxd;
wire uart_txd;
initial begin
sys_clk <= 1'b0;
sys_rst_n <= 1'b0;
uart_rxd <= 1'b1; //空闲状态
#200
sys_rst_n <= 1'b1;
#1000
//发送0x55 8'b0101_0101
uart_rxd <= 1'b0; //起始位
#8680
uart_rxd <= 1'b1; //d0
#8680
uart_rxd <= 1'b0; //d1
#8680
uart_rxd <= 1'b1; //d2
#8680
uart_rxd <= 1'b0; //d3
#8680
uart_rxd <= 1'b1; //d4
#8680
uart_rxd <= 1'b0; //d5
#8680
uart_rxd <= 1'b1; //d6
#8680
uart_rxd <= 1'b0; //d7
#8680
uart_rxd <= 1'b1; //停止位
#8680
uart_rxd <= 1'b1; //空闲状态
end
always #(CLK_PERIOD/2) sys_clk = !sys_clk;
uart_loopback u_uart_loopback(
.sys_clk (sys_clk ) ,
.sys_rst_n (sys_rst_n) ,
.uart_rxd (uart_rxd ) ,
.uart_txd (uart_txd )
);
endmodule
5、管脚定义
五、仿真与程序验证
1、Modelsim仿真
仿真结果符合预期结果。
2、下载验证
最终结果符合预期。
六、源码获取
私聊笔者获取源码