文章目录[隐藏]
概述
串口是一种支持串行通信协议的接口泛称,串行通信协议包括UART,CAN,SPI等,本文介绍的UART只是其中的异步串行通信协议,即数据通信时没有伴随时钟信号的传输,而是通过约定相同的波特率,配合起始结束位实现数据通信。也是一种点对点通信协议,支持全双工通信。
1、UART硬件
1.1、逻辑电平
UART支持的逻辑电平有TTL/RS232/RS485等,RS232由于噪声容限较大,抗干扰能力远优于TTL电平,但是速率不如TTL,RS458属于差分逻辑电平,具有较好的共模抑制能力,抗干扰性能优于RS232但是占用引脚数较多,在应用中常见的用RS232和TTL较多,一般TTL常用于芯片见互连,RS232常用于设备之间互连。
1.1、电平转换芯片
常见电平转换芯片,MAX3232实现TTL和RS232电平转换,CP2102和CH340等实现USB转TTL的功能,UART是一种协议,而逻辑电平是信号实际传输时的物理特性,注意区分看待。
2、UART协议
2.1、串口参数
在任意串口工具上可以看到,实现串行数据传输,首先需要配置协议的几个参数,分别是波特率、数据位、奇偶位和停止位,以下分别介绍这几位参数。
波特率:是收发双方约定好的bit传输速率,由于UART不使用时钟来同步接收发送端,而是通过起始位、停止位和波特率实现数据的正确发送和接收,这样收端和发端就可以通过各自内部的时钟对信号进行收发,约定好相同的波特率,依靠起始位和停止位开始或结束通信,而不依赖额外的随路时钟,这就是所谓的“异步”。
帧结构:约束好波特率之后,就可以开始数据包的传输,数据包格式并不是唯一的,具有一定的灵活性,其帧结构包括四个部分,起始位(必须)、数据位(5-9位可选)、校验位(可选)、停止位(必须)。
① 起始位:起始位默认空闲状态为高电平,当发送端发送数据是TX管脚从高拉低并保持一个bit周期,代表数据起始位。
②数据位:5-9位可选,常见传输一个字节,即8bit。
③校验位:通常使用奇偶校验,即检查数据位中1的个数,若为偶数,校验位为0,若为奇数,校验位为1。这样接收端可以通过对接收数据进行奇偶校验并和校验位进行比较,以此简单的判断数据传输的准确性。
④停止位:保持高电平1-2bit时间。
2.2、通信时序
UART异步通信时序如下所示,发端和收端接收端的信号皆如下所示,不同的是发端需要考虑,如何将一个数据按照以下时序发出,接收端需要考虑,如何在以下数据时序中提取出数据帧部分。
2.3、系统时钟
那么约定好波特率BAUD之后,没bit位宽时间就已经确定了1/BAUD,那么发端发送数据如何实现保持每位宽时间呢,这肯定是需要使用发端内部时钟进行计数计量的,比如系统时钟是SYS_CLK,每个时钟周期为1/SYS_CLK,每个位宽时间需要持续( 1/BAUD ) / (1/SYS_CLK)= SYS_CLK / BAUD;
按照这样计算得到计数量:
3、基于FPGA实现UART协议
3.1 UART Tx
基于FPGA实现UART发送:代码如下所示,核心是:1、根据波特率和系统时钟计算出每bit位宽,在有效发送数据到来时,将每bit数据保持一定时间宽度送出,2、TX线上空闲位均为高电平。
module testuarttx(
input clk_50m,
input arst_n,
//input valid tx data
input [7:0] uart_tx_data,
input uart_tx_data_valid,
//output uart data
output reg uart_txd
);
localparam baud_cnt = 'd2604-'d1;
localparam IDLE = 3'd0;
localparam DATATRANS = 3'd1;
reg [2:0] state;
reg [11:0] star_end_cnt;
reg [3:0] bit_cnt;
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
star_end_cnt <= 'd0;
else if((state == DATATRANS) && (star_end_cnt < baud_cnt))
star_end_cnt <= star_end_cnt +'d1;
else
star_end_cnt <= 'd0;
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
bit_cnt <= 'd0;
else if(state == IDLE)
bit_cnt <= 'd0;
else if((state == DATATRANS) && (star_end_cnt==baud_cnt))
bit_cnt <= bit_cnt +'d1;
else
bit_cnt <= bit_cnt;
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
state <= IDLE;
else
begin
case(state)
IDLE:begin
if(uart_tx_data_valid)
state <= DATATRANS;
else
state <= IDLE;
end
DATATRANS:begin
if((bit_cnt=='d9)&&(star_end_cnt ==baud_cnt))
state <= IDLE;
else
state <= DATATRANS;
end
endcase
end
end
// reg [7:0] uart_tx_data_reg ;
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
uart_txd <= 'd1;
else if((state == DATATRANS)&&(star_end_cnt==baud_cnt))
begin
case(bit_cnt)
'd0: uart_txd <= 'd0;
'd1: uart_txd <= uart_tx_data[0];
'd2: uart_txd <= uart_tx_data[1];
'd3: uart_txd <= uart_tx_data[2];
'd4: uart_txd <= uart_tx_data[3];
'd5: uart_txd <= uart_tx_data[4];
'd6: uart_txd <= uart_tx_data[5];
'd7: uart_txd <= uart_tx_data[6];
'd8: uart_txd <= uart_tx_data[7];
'd9: uart_txd <= 'd1;
default:uart_txd <= uart_txd;
endcase
end
else
uart_txd <= uart_txd;
end
endmodule
仿真结果:
3.2 UART Rx
基于FPGA实现UART接收:代码如下所示,核心是:1、找到第一个下降沿的位置,即表明起始位置;2、根据波特率和系统时钟计算出每bit数据宽度,并在每个数据中间部分接收数据,避免在边沿误判。
module uart_rx
(
input clk_50m,
input arst_n,
//input valid rx data
output reg [7:0] uart_rx_data,
output reg uart_rx_data_valid,
//output uart data
input uart_rxd
);
localparam baud_cnt = 'd2604-'d1;
localparam baud_cnt2 = 'd1302;
localparam IDLE = 3'd0;
localparam DATATRANS = 3'd1;
reg [2:0] state;
reg [11:0] star_end_cnt;
reg [3:0] bit_cnt;
reg uart_rxd1;
reg uart_rxd2;
always@(posedge clk_50m)
begin
uart_rxd1 <= uart_rxd;
uart_rxd2 <= uart_rxd1;
end
wire nedge_flag;
assign nedge_flag = uart_rxd2 && ~uart_rxd1 ;
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
star_end_cnt <= 'd0;
else if((state == DATATRANS) && (star_end_cnt < baud_cnt))
star_end_cnt <= star_end_cnt +'d1;
else
star_end_cnt <= 'd0;
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
bit_cnt <= 'd0;
else if(state == IDLE)
bit_cnt <= 'd0;
else if((state == DATATRANS) && (star_end_cnt==baud_cnt))
bit_cnt <= bit_cnt +'d1;
else
bit_cnt <= bit_cnt;
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
state <= IDLE;
else
begin
case(state)
IDLE:begin
if(nedge_flag)
state <= DATATRANS;
else
state <= IDLE;
end
DATATRANS:begin
if((bit_cnt=='d9)&&(star_end_cnt ==baud_cnt))
state <= IDLE;
else
state <= DATATRANS;
end
endcase
end
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
uart_rx_data <= 'd0;
else if((state == DATATRANS)&&(star_end_cnt==baud_cnt2))
begin
case(bit_cnt)
'd1: uart_rx_data[0] <= uart_rxd;
'd2: uart_rx_data[1] <= uart_rxd;
'd3: uart_rx_data[2] <= uart_rxd;
'd4: uart_rx_data[3] <= uart_rxd;
'd5: uart_rx_data[4] <= uart_rxd;
'd6: uart_rx_data[5] <= uart_rxd;
'd7: uart_rx_data[6] <= uart_rxd;
'd8: uart_rx_data[7] <= uart_rxd;
default:uart_rx_data <= uart_rx_data;
endcase
end
else
uart_rx_data <= uart_rx_data;
end
always @ (posedge clk_50m or negedge arst_n)
begin
if(!arst_n)
uart_rx_data_valid <= 'd0;
else if((bit_cnt=='d9)&&(star_end_cnt ==baud_cnt))
uart_rx_data_valid <= 'd1;
else
uart_rx_data_valid <= 'd0;
end
endmodule
仿真结果:
参考资料来源:
https://www.analog.com/media/cn/analog-dialogue/volume-54/number-4/uart-a-hardware-communication-protocol_cn.pdf
http://www.openpcba.com/web/contents/get?id=3736&tid=15
https://www.circuitbasics.com/basics-uart-communication/
版权声明:本文为CSDN博主「青豆哒哒」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43813325/article/details/121539492
暂无评论