三大低速总线之一:UART
文章目录
- 三大低速总线之一:UART
- 前言
- 一、UART协议
- 二、设计
- 1.整体说明
- 2.rx波形设计
- 三 程序实现
前言
三大低速总线:UART、IIC、SPI,其中IIC和SPI是同步通讯,UART是异步的。
优点:
简单:tx和rx都是一根线,使用简单的帧结构传输。 UART通信协议相对简单,易于实现和调试。
适用性广泛:UART被广泛应用于各种设备之间的通信,具有较好的兼容性。
距离:UART通信距离较远,适用于需要长距离传输的场景。
缺点:
速度较低:UART通信速度相对较低,不适用于对速度要求较高的应用。
双工:UART通信是双工的,可以进行低速双工传输数据,进行数据的发送和接收。
不可靠:由于UART是异步通信,可能会受到噪声和干扰的影响,导致数据传输不可靠。
为我们的项目选择合适的协议:
通信速度:SPI 提供高速度,UART 提供高灵活性,I2C 适用于速度要求较低,接线简单的配置。
电路设计:I2C 可实现多个设备的高效空间管理,SPI 可实现大型设计中的性能,而 UART 可实现简单性和多功能性。
距离和通信环境:UART 在长距离上具有稳定性,而 I2C 更适合短距离。
双工要求:SPI 和 UART 提供全双工功能,而 I2C 仅限于半双工。
小结:
- 使用帧传输,没有时钟的适合长距离,比如UART(如果是差分就更好了),但是帧传输会有冗余信息,传输相对较慢(但这个只针对一般接口,对于高速IO接口的工作频率会很高就不存在这个问题)
- 带有随路时钟的,因为可能存在各种干扰,对时钟不友好,不适合长距离。
- 使用哪种协议也要看使用的io资源,spi协议使用的端口就比较多,但是速度快
应用案例:
应用案例:
uart:
微控制器和外设之间的连接:用于简单直接的数据交换。
GPS 模块和与计算机的串行接口:用于可靠、低复杂性的通信。
工业机器:UART 通常用于工业设备中以实现稳定的通信。
使用 RS 标准(例如 RS-232、RS-485):这些标准支持更长距离的 UART 通信,并提供使用适当的收发器创建多从属网络的可能性,从而增加 UART 应用的灵活性和广度。
微控制器和外设之间的连接:用于简单直接的数据交换。
GPS 模块和与计算机的串行接口:用于可靠、低复杂性的通信。
工业机器:UART 通常用于工业设备中以实现稳定的通信。
使用 RS 标准(例如 RS-232、RS-485):这些标准支持更长距离的 UART 通信,并提供使用适当的收发器创建多从属网络的可能性,从而增加 UART 应用的灵活性和广度。
spi:
SPI 非常适合需要快速可靠的数据传输的情况,例如 TFT 显示器、SD 存储卡和无线通信模块。然而,在具有许多从站的复杂系统中,其有效性会降低。
IIC:
就其应用而言,连接方面,I2C在需要简单且经济的通信环境中表现出色。它尤其擅长在小型传感器、LCD 屏幕和 RTC(实时时钟)模块中使用。此外,I2C 由于其在紧凑电路中的效率,在温度控制设备、电池管理系统和 LED 控制器中很有用。但是,在需要快速或长距离数据传输的项目中,最好选择其他协议
一、UART协议
特点:
- 低速协议
- rx 和 tx两根线
帧结构
二、设计
1.整体说明
整体框图
模块说明
2.rx波形设计
TX波形设计
三 程序实现
RX设计思想:根据上面时序图的端口顺序
- 同步
- 帧起始位判断(边沿检测,并用中间控制信号en代替flag,只检测一次)
- 帧比特率计数(其他的都是依靠这个衍生的)
- bit标志位
- bit_cnt
- 每bit的数据接受输出
- 稳定数据输出(不输出正在接受变化的数据,而是接受完毕后,在用数据接受完毕的flag来输出最后的数据)
- 稳定数据flag输出(需要延迟一拍rx_flag)
需要注意的点有下面几个:
同步的时候需要两拍,但是用这两拍判断帧的沿是不稳定的,需要用第二排和第三拍的输出
数据采样的时候依靠flag,但是flag为高的时候,才的数据是flag变化之前的,即实际上是从1bit开始采样,采样到8的时候,有效数据采集完毕,起始和结束是没有采样的
变化的数据不能传递给下一个模块,要稳定的时候去传递,因此rx_data,只是中间数据,po_data才是最后的数据,同时po_plag也要同步域po_data(相对于rx_flag沿延迟一拍)
`timescale 1ns/1ns
module uart_rx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire rx , //串口接收数据
output reg [7:0] po_data , //串转并后的8bit数据
output reg po_flag //串转并后的数据有效标志信号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg rx_reg1 ;
reg rx_reg2 ;
reg rx_reg3 ;
reg start_nedge ;
reg work_en ;
reg [12:0] baud_cnt ;
reg bit_flag ;
reg [3:0] bit_cnt ;
reg [7:0] rx_data ;
reg rx_flag ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//插入两级寄存器进行数据同步,用来消除亚稳态
//rx_reg1:第一级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg1 <= 1'b1;
else
rx_reg1 <= rx;
//rx_reg2:第二级寄存器,寄存器空闲状态复位为1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg2 <= 1'b1;
else
rx_reg2 <= rx_reg1;
//rx_reg3:第三级寄存器和第二级寄存器共同构成下降沿检测
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_reg3 <= 1'b1;
else
rx_reg3 <= rx_reg2;
//start_nedge:检测到下降沿时start_nedge产生一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
start_nedge <= 1'b0;
else if((~rx_reg2) && (rx_reg3))
start_nedge <= 1'b1;
else
start_nedge <= 1'b0;
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(start_nedge == 1'b1)
work_en <= 1'b1;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到中间数时采样的数据最稳定,
//此时拉高一个标志信号表示数据可以被取走
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == BAUD_CNT_MAX/2 - 1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:有效数据个数计数器,当8个有效数据(不含起始位和停止位)
//都接收完成后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
bit_cnt <= 4'b0;
else if(bit_flag ==1'b1)
bit_cnt <= bit_cnt + 1'b1;
//rx_data:输入数据进行移位
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_data <= 8'b0;
else if((bit_cnt >= 4'd1)&&(bit_cnt <= 4'd8)&&(bit_flag == 1'b1))
rx_data <= {rx_reg3, rx_data[7:1]};
//rx_flag:输入数据移位完成时rx_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rx_flag <= 1'b0;
else if((bit_cnt == 4'd8) && (bit_flag == 1'b1))
rx_flag <= 1'b1;
else
rx_flag <= 1'b0;
//po_data:输出完整的8位有效数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_data <= 8'b0;
else if(rx_flag == 1'b1)
po_data <= rx_data;
//po_flag:输出数据有效标志(比rx_flag延后一个时钟周期,为了和po_data同步)
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
po_flag <= 1'b0;
else
po_flag <= rx_flag;
endmodule
**TX设计思想:**根据上面时序图的端口顺序
- 判断有效数据输入标志flag
- 进行baud率计数器计数
- 一般不在边界进行采样或者发送,这个选择计数后的1作为1bit发送的标志信号(在发送的时候也使用这个信号)
- 生成bit_cnt
- 发送数据帧,以flag信号为标志进行发送(注意:bit_cnt加1是延迟一拍的,发送的时候使用flag发送的,比如采样到flag为1的时候,实际上cnt_bit是0,然后下一排cnt_bit才+1,即实际上当前发送的是第0,即起始位)
`timescale 1ns/1ns
module uart_tx
#(
parameter UART_BPS = 'd9600, //串口波特率
parameter CLK_FREQ = 'd50_000_000 //时钟频率
)
(
input wire sys_clk , //系统时钟50MHz
input wire sys_rst_n , //全局复位
input wire [7:0] pi_data , //模块输入的8bit数据
input wire pi_flag , //并行数据有效标志信号
output reg tx //串转并后的1bit数据
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//localparam define
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS ;
//reg define
reg [12:0] baud_cnt;
reg bit_flag;
reg [3:0] bit_cnt ;
reg work_en ;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//work_en:接收数据工作使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
work_en <= 1'b0;
else if(pi_flag == 1'b1)
work_en <= 1'b1;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
work_en <= 1'b0;
//baud_cnt:波特率计数器计数,从0计数到BAUD_CNT_MAX - 1
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
baud_cnt <= 13'b0;
else if((baud_cnt == BAUD_CNT_MAX - 1) || (work_en == 1'b0))
baud_cnt <= 13'b0;
else if(work_en == 1'b1)
baud_cnt <= baud_cnt + 1'b1;
//bit_flag:当baud_cnt计数器计数到1时让bit_flag拉高一个时钟的高电平
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_flag <= 1'b0;
else if(baud_cnt == 13'd1)
bit_flag <= 1'b1;
else
bit_flag <= 1'b0;
//bit_cnt:数据位数个数计数,10个有效数据(含起始位和停止位)到来后计数器清零
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (bit_cnt == 4'd9))
bit_cnt <= 4'b0;
else if((bit_flag == 1'b1) && (work_en == 1'b1))
bit_cnt <= bit_cnt + 1'b1;
//tx:输出数据在满足rs232协议(起始位为0,停止位为1)的情况下一位一位输出
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
tx <= 1'b1; //空闲状态时为高电平
else if(bit_flag == 1'b1)
case(bit_cnt)
0 : tx <= 1'b0;
1 : tx <= pi_data[0];
2 : tx <= pi_data[1];
3 : tx <= pi_data[2];
4 : tx <= pi_data[3];
5 : tx <= pi_data[4];
6 : tx <= pi_data[5];
7 : tx <= pi_data[6];
8 : tx <= pi_data[7];
9 : tx <= 1'b1;
default : tx <= 1'b1;
endcase
endmodule
(1)奇偶校验的检错率只有50%,因为只有奇数个数据位发生变化能检测到,如果偶数个数据位发生变化则难以发现;
(2)奇偶校验每传输一个字节都需要加一位校验位,对传输效率影响很大。
(3)奇偶校验只能发现错误,但不能纠正错误。