UART接口是一个通用的异步串口,应该是最“古老”的通讯协议之一,几乎每片MCU都有至少一个UART接口,介绍UART的书籍也比较多,所以这篇博文其实是没有必要的。只是最近有个项目刚好有用到,所以习惯性的作个梳理。
一、UART通信协议解析:从原理到实践
一)、UART基础概述
UART(通用异步收发器)作为最基础的串行通信协议,凭借其硬件简单、成本低廉的特性,成为嵌入式系统设备间通信的首选方案。其核心特点包括:
异步通信:无需共享时钟信号,通过预定义波特率同步
全双工传输:独立TX/RX线路实现双向数据交互
点对点连接:仅支持两个设备直接通信
二)、硬件架构与接口设计
2.1 物理层实现
UART硬件接口通常包含以下关键组件:
电平转换电路:支持TTL(0-3.3V/5V)与RS-232(±3-15V)等标准
收发器芯片:如MAX232(RS-232转换)、MAX485(差分信号)
终端电阻:RS-485总线需配置120Ω电阻抑制信号反射
2.2 典型连接方案
设备类型 |
连接方式 |
适用场景 |
---|---|---|
微控制器间通信 |
TXA-RXB/RXA-TXB + GND |
短距离(<1m) |
工业设备 |
RS-485总线+终端电阻 |
长距离(<1200m) |
计算机外设 |
USB转UART模块 |
标准串口扩展 |
三)、协议帧结构与时序分析
3.1 标准数据帧组成
UART数据帧采用起始位+数据位+校验位+停止位结构:
起始位:1位低电平(逻辑0)标志传输开始
数据位:5-9位有效数据(通常8位对应1字节)
校验位:奇校验/偶校验/无校验(可选)
停止位:1/1.5/2位高电平(逻辑1)标志帧结束
3.2 时序示例(8N1配置)
Bit 0 |
Bit 1 |
... |
Bit 7 |
Bit 8 |
Bit 9 |
Bit 10 |
---|---|---|---|---|---|---|
0 |
1 |
... |
1 |
1 |
1 |
1 |
起始位 |
D0 |
... |
D7 |
停止位 |
空闲位 |
四)、关键设计要点与优化策略
4.1 波特率配置
常见速率:9600bps(工业控制)、115200bps(调试接口)
误差控制:双方波特率偏差需<2%
4.2 抗干扰设计
电气隔离:使用光耦隔离芯片(如HCPL-0720)
差分信号:RS-485总线抗共模干扰能力达±12V
软件滤波:中值滤波算法消除偶发噪声
4.3 可靠性增强
错误检测:奇偶校验+CRC循环冗余校验
流量控制:RTS/CTS硬件流控避免数据溢出
超时重传:软件实现超时检测与自动重发
五)、典型应用案例
5.1 智能家居控制系统
架构:STM32主控(UART1)→ 蓝牙模块(透传模式)
参数:115200bps, 8N1, 硬件流控
优化:采用DMA传输降低CPU负载
5.2 工业传感器网络
拓扑:RS-485总线多节点通信
协议:Modbus RTU(基于UART)
挑战:解决信号反射与终端匹配问题
六)、常见问题排查
数据乱码:检查波特率/校验位/停止位配置
通信中断:验证GND共地及线路阻抗
速率受限:提升波特率需缩短走线长度
UART作为跨越数十年的通信标准,其简单性与灵活性使其持续活跃在物联网、工业控制等领域。理解其硬件原理与协议细节,是构建可靠串口通信系统的基石。随着FPGA实现方案的普及,UART在现代系统中仍焕发新的生命力。
二、沁恒蓝牙芯片的UART接口
1、UART简介
CH585和CH584芯片提供了4组全双工的异步串口UART0/1/2/3。支持全双工和半双工串口通讯, 其中UART0提供发送状态引脚用于切换RS485,并且支持MODEM调制解调器信号CTS、DSR、RI、DCD、 DTR、RTS。
2、 主要特性
兼容16C550异步串口并且有所增强
支持5、6、7或者8个数据位以及1或者2个停止位
支持奇、偶、无校验、空白0、标志1等校验方式
可编程通讯波特率,最高达9Mbps波特率
内置8个字节的FIFO先进先出缓冲器,支持4个FIFO触发级
UART0支持MODEM调制解调器信号CTS、DSR、RI、DCD、DTR、RTS
UART0支持硬件流控制信号CTS和RTS自动握手和自动传输速率控制,兼容TL16C550C
支持串口帧错误检测、支持Break线路间隔检测
支持全双工和半双工串口通讯,UART0提供发送状态引脚用于切换RS485
三、实例应用
/********************************** (C) COPYRIGHT *******************************
* File Name : Main.c
* Author : WCH
* Version : V1.0
* Date : 2020/08/06
* Description : 串口1收发演示
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#include "CH58x_common.h"
uint8_t TxBuff[] = "This is a tx exam\r\n";
uint8_t RxBuff[100];
uint8_t trigB;
/*********************************************************************
* @fn main
*
* @brief 主函数
*
* @return none
*/
int main()
{
uint8_t len;
HSECFG_Capacitance(HSECap_18p);
SetSysClock(CLK_SOURCE_HSE_PLL_62_4MHz);
/* 配置串口1:先配置IO口模式,再配置串口 */
GPIOB_SetBits(GPIO_Pin_7);
GPIOB_ModeCfg(GPIO_Pin_4, GPIO_ModeIN_PU); // RXD-配置上拉输入
GPIOB_ModeCfg(GPIO_Pin_7, GPIO_ModeOut_PP_5mA); // TXD-配置推挽输出,注意先让IO口输出高电平
UART0_DefInit();
#if 1 // 测试串口发送字符串
UART0_SendString(TxBuff, sizeof(TxBuff));
#endif
#if 1 // 查询方式:接收数据后发送出去
while(1)
{
len = UART0_RecvString(RxBuff);
if(len)
{
UART0_SendString(RxBuff, len);
}
}
#endif
#if 1 // 中断方式:接收数据后发送出去
UART0_ByteTrigCfg(UART_7BYTE_TRIG);
trigB = 7;
UART0_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
PFIC_EnableIRQ(UART0_IRQn);
#endif
while(1);
}
/*********************************************************************
* @fn UART1_IRQHandler
*
* @brief UART1中断函数
*
* @return none
*/
__INTERRUPT
__HIGH_CODE
void UART0_IRQHandler(void)
{
volatile uint8_t i;
switch(UART0_GetITFlag())
{
case UART_II_LINE_STAT: // 线路状态错误
{
UART0_GetLinSTA();
break;
}
case UART_II_RECV_RDY: // 数据达到设置触发点
for(i = 0; i != trigB; i++)
{
RxBuff[i] = UART0_RecvByte();
UART0_SendByte(RxBuff[i]);
}
break;
case UART_II_RECV_TOUT: // 接收超时,暂时一帧数据接收完成
i = UART0_RecvString(RxBuff);
UART0_SendString(RxBuff, i);
break;
case UART_II_THR_EMPTY: // 发送缓存区空,可继续发送
break;
case UART_II_MODEM_CHG: // 只支持串口0
break;
default:
break;
}
}
/********************************** (C) COPYRIGHT *******************************
* File Name : CH58x_uart0.c
* Author : WCH
* Version : V1.2
* Date : 2021/11/17
* Description : source file(ch585/ch584)
*********************************************************************************
* Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd.
* Attention: This software (modified or not) and binary are used for
* microcontroller manufactured by Nanjing Qinheng Microelectronics.
*******************************************************************************/
#include "CH58x_common.h"
/*********************************************************************
* @fn UART0_DefInit
*
* @brief 串口默认初始化配置
*
* @param none
*
* @return none
*/
void UART0_DefInit(void)
{
UART0_BaudRateCfg(115200);
R8_UART0_FCR = (2 << 6) | RB_FCR_TX_FIFO_CLR | RB_FCR_RX_FIFO_CLR | RB_FCR_FIFO_EN; // FIFO打开,触发点4字节
R8_UART0_LCR = RB_LCR_WORD_SZ;
R8_UART0_IER = RB_IER_TXD_EN;
R8_UART0_DIV = 1;
}
/*********************************************************************
* @fn UART0_BaudRateCfg
*
* @brief 串口波特率配置
*
* @param baudrate - 波特率
*
* @return none
*/
void UART0_BaudRateCfg(uint32_t baudrate)
{
uint32_t x;
x = 10 * GetSysClock() / 8 / baudrate;
x = (x + 5) / 10;
R16_UART0_DL = (uint16_t)x;
}
/*********************************************************************
* @fn UART0_ByteTrigCfg
*
* @brief 串口字节触发中断配置
*
* @param b - 触发字节数 refer to UARTByteTRIGTypeDef
*
* @return none
*/
void UART0_ByteTrigCfg(UARTByteTRIGTypeDef b)
{
R8_UART0_FCR = (R8_UART0_FCR & ~RB_FCR_FIFO_TRIG) | (b << 6);
}
/*********************************************************************
* @fn UART0_INTCfg
*
* @brief 串口中断配置
*
* @param s - 中断控制状态,是否使能相应中断
* @param i - 中断类型
* RB_IER_MODEM_CHG - 调制解调器输入状态变化中断使能位(仅 UART0 支持)
* RB_IER_LINE_STAT - 接收线路状态中断
* RB_IER_THR_EMPTY - 发送保持寄存器空中断
* RB_IER_RECV_RDY - 接收数据中断
*
* @return none
*/
void UART0_INTCfg(FunctionalState s, uint8_t i)
{
if(s)
{
R8_UART0_IER |= i;
R8_UART0_MCR |= RB_MCR_INT_OE;
}
else
{
R8_UART0_IER &= ~i;
}
}
/*********************************************************************
* @fn UART0_Reset
*
* @brief 串口软件复位
*
* @param none
*
* @return none
*/
void UART0_Reset(void)
{
R8_UART0_IER = RB_IER_RESET;
}
/*********************************************************************
* @fn UART0_SendString
*
* @brief 串口多字节发送
*
* @param buf - 待发送的数据内容首地址
* @param l - 待发送的数据长度
*
* @return none
*/
void UART0_SendString(uint8_t *buf, uint16_t l)
{
uint16_t len = l;
while(len)
{
if(R8_UART0_TFC != UART_FIFO_SIZE)
{
R8_UART0_THR = *buf++;
len--;
}
}
}
/*********************************************************************
* @fn UART0_RecvString
*
* @brief 串口读取多字节
*
* @param buf - 读取数据存放缓存区首地址
*
* @return 读取数据长度
*/
uint16_t UART0_RecvString(uint8_t *buf)
{
uint16_t len = 0;
while(R8_UART0_RFC)
{
*buf++ = R8_UART0_RBR;
len++;
}
return (len);
}
以上代码,基于官方提供的demo例程修改得到,可通过转TTL正常与PC机通讯。
沁恒的UART接口有个特点,使用的时候需要根据实际情况灵活配置:
UART接收中断可以配置为1字节、2字节、4字节及7字节触发。
/**
* @brief Configuration UART TrigByte num
*/
typedef enum
{
UART_1BYTE_TRIG = 0, // 1字节触发
UART_2BYTE_TRIG, // 2字节触发
UART_4BYTE_TRIG, // 4字节触发
UART_7BYTE_TRIG, // 7字节触发
} UARTByteTRIGTypeDef;
/*********************************************************************
* @fn UART0_ByteTrigCfg
*
* @brief 串口字节触发中断配置
*
* @param b - 触发字节数 refer to UARTByteTRIGTypeDef
*
* @return none
*/
void UART0_ByteTrigCfg(UARTByteTRIGTypeDef b)
{
R8_UART0_FCR = (R8_UART0_FCR & ~RB_FCR_FIFO_TRIG) | (b << 6);
}
#if 1 // 中断方式:接收数据后发送出去
UART0_ByteTrigCfg(UART_7BYTE_TRIG);
trigB = 7;
UART0_INTCfg(ENABLE, RB_IER_RECV_RDY | RB_IER_LINE_STAT);
PFIC_EnableIRQ(UART0_IRQn);
#endif