从电报到芯片:UART的百年进化史与异步通信的底层逻辑
在数字通信的浩瀚宇宙中,UART(通用异步收发传输器)如同一位沉默的巨人,承载着从19世纪电报时代到现代嵌入式系统的技术传承。它不仅是计算机与外部世界对话的桥梁,更是异步通信思想的完美体现。当我们今天在嵌入式系统中轻松配置UART接口时,很少意识到这背后跨越百年的技术演进——从莫尔斯电码的滴答声到现代芯片上高度集成的串行通信模块,这段历史充满了工程师们的智慧结晶和技术突破。
UART的核心魅力在于其"异步"特性:通信双方不需要共享时钟信号,仅通过事先约定的数据格式和传输速率就能实现可靠通信。这种简洁而高效的设计思想,使其在RS-232、RS-485等各种物理层标准中得以延续,成为工业控制、物联网设备、消费电子产品中最基础的通信方式之一。本文将带您深入UART的技术内核,探索其从电报起源到现代芯片实现的完整进化历程,揭示异步通信背后的底层逻辑和设计哲学。
1. 电报时代的技术基因:异步通信的起源
19世纪中叶,电报系统的出现标志着人类进入了远距离即时通信的时代。塞缪尔·莫尔斯发明的电报系统使用简单的开关控制电流脉冲,通过长短不同的信号组合(点与划)来表示字母和数字。这种通信方式已经具备了异步通信的基本特征:发送端和接收端独立运作,通过事先约定的编码规则和时序进行通信。
早期的电报系统采用机械式继电器和电磁铁作为收发装置,操作员需要手动控制电键发送信号,接收端则通过电磁铁驱动纸带打孔机或产生可听见的滴答声。这种系统的数据传输速率极低,通常只有每秒几个字符,但已经奠定了异步通信的两个关键要素:
- 起始同步机制:每个字符传输前都有明确的开始标志
- 定时恢复:接收方需要根据已知的传输速率来采样信号
电报操作员通过训练形成"肌肉记忆",能够以相当一致的速率发送莫尔斯电码,而接收方则通过识别字符间的停顿来区分不同的字符。这种人类参与的通信方式虽然原始,却包含了现代UART的核心思想——不需要全局时钟的自我同步机制。
20世纪初,随着电传打字机(Teletype)的出现,机械自动化开始取代人工操作。电传打字机使用5位的博多码(Baudot code)表示字符,传输速率提高到每秒10个字符左右。这些设备采用了更复杂的机电装置,包括:
- 旋转式同步机构:通过电机驱动保持基本定时
- 电磁离合器:控制字符传输的启停
- 穿孔纸带阅读器:实现离线消息准备和批量发送
典型电传打字机帧格式:
起始位(1位) + 数据位(5位) + 停止位(1.5位)
这种帧结构与现代UART的数据格式惊人地相似,只是数据位宽度和停止位长度有所不同。电传打字机的停止位通常为1.5位时间,这是为了让机械装置有足够时间复位准备接收下一个字符。
2. 半导体时代的革新:UART芯片的诞生与演进
随着半导体技术的发展,20世纪60年代末到70年代初,UART开始从离散逻辑电路向集成芯片转变。1971年Western Digital推出的WD1402A被认为是第一款商业化单芯片UART,它将之前需要数十个离散元件实现的功能集成到一个封装中,大大简化了串行通信接口的设计。
2.1 早期UART芯片的关键突破
第一代UART芯片如WD1402A和TI的TMS6010采用了PMOS工艺,工作电压较高(通常需要±12V),功耗也相对较大。但这些芯片实现了异步通信的所有基本功能:
- 并串转换:将并行数据转换为串行比特流
- 帧格式化:自动添加起始位、奇偶校验位和停止位
- 错误检测:奇偶校验和帧错误检测
- 波特率生成:可编程的时钟分频器
// 早期UART芯片的典型配置流程
void configure_uart(uint16_t baud_rate, uint8_t data_bits, uint8_t parity, uint8_t stop_bits) {
// 计算波特率分频值
uint16_t divisor = CLOCK_FREQ / (baud_rate * 16);
// 设置线路控制寄存器
uint8_t lcr = 0;
lcr |= (data_bits - 5) << 0; // 数据位长度
lcr |= (parity != 0) << 3; // 奇偶校验使能
lcr |= (parity == 1) << 4; // 奇偶校验类型
lcr |= (stop_bits - 1) << 2; // 停止位长度
// 写入配置寄存器
write_register(UART_LCR, lcr);
write_register(UART_DLL, divisor & 0xFF);
write_register(UART_DLM, divisor >> 8);
}
2.2 IBM PC与UART的标准化
1981年IBM PC的发布成为UART发展史上的重要里程碑。IBM选择了National Semiconductor的INS8250 UART作为串行接口的核心芯片,这一选择实际上确立了UART在个人计算机领域的标准地位。
INS8250虽然功能相对简单(只有1字节的缓冲),但其寄存器布局和编程模型成为了事实上的工业标准。后续的16450、16550等芯片都保持了向后兼容性,这使得软件驱动程序能够在不修改的情况下支持新一代硬件。
8250 UART主要特性对比:
| 特性 | INS8250 | 16450 | 16550A |
|---|---|---|---|
| 推出时间 | 1978年 | 1984年 | 1987年 |
| 缓冲区 | 1字节 | 1字节 | 16字节FIFO |
| 最高速率 | 9.6 kbps | 38.4 kbps | 115.2 kbps |
| 中断支持 | 基本 | 增强 | 增强 |
| DMA支持 | 无 | 有限 | 完整 |
8250系列UART的寄存器组设计极其精巧,通过8个主要寄存器控制所有功能:
- 接收缓冲寄存器 (RBR):存放接收到的数据
- 发送保持寄存器 (THR):存放待发送的数据
- 中断使能寄存器 (IER):控制中断产生条件
- 中断标识寄存器 (IIR):标识待处理的中断类型
- 线路控制寄存器 (LCR):配置数据格式
- 调制解调器控制寄存器 (MCR):控制MODEM信号
- 线路状态寄存器 (LSR):提供传输状态信息
- 调制解调器状态寄存器 (MSR):提供MODEM状态信息
3. 现代UART架构:FIFO、DMA与性能优化
随着计算机系统速度的不断提升,传统的U设计面临着新的挑战。当CPU主频从MHz跃升到GHz时,UART仍然工作在kbps到Mbps的速度范围,这种速度差异导致了效率问题:高速CPU需要频繁中断来处理相对低速的串行数据,造成了计算资源的浪费。
3.1 FIFO缓冲器的引入
16550A UART的16字节FIFO(先入先出)缓冲区是解决这一问题的关键创新。FIFO允许UART在积累足够多的数据后才向CPU发出中断,大大降低了中断频率。现代UART的FIFO深度不断增加,有些型号甚至提供了128字节或更大的缓冲区。
FIFO工作模式的优势:
- 减少中断开销:多个字符一次处理而非逐个处理
- 提高吞吐量:允许更高的波特率而不丢失数据
- 降低CPU负载:CPU可以更长时间处于低功耗状态
# 现代UART带FIFO的中断处理示例
def uart_interrupt_handler():
# 检查中断类型
iir = read_register(UART_IIR)
if iir & IIR_RX_TIMEOUT:
# FIFO超时中断:读取所有可用数据
while read_register(UART_LSR) & LSR_DR:
data = read_register(UART_RBR)
process_data(data)
elif iir & IIR_RX_AVAILABLE:
# 接收数据可用中断:读取达到触发水平的数据
fifo_count = estimate_fifo_level()
for i in range(fifo_count):
data = read_register(UART_RBR)
process_data(data)
elif iir & IIR_TX_EMPTY:
# 发送缓冲区空中断:填充更多数据
fill_tx_fifo()
3.2 DMA集成与高级功能
现代UART进一步集成了DMA(直接内存访问)控制器,允许数据直接在UART和内存之间传输而不需要CPU介入。这种架构特别适合高速数据采集和传输应用。
高级UART特性对比:
| 特性 | 16550A | 16750 | 16950 |
|---|---|---|---|
| FIFO深度 | 16字节 | 64字节 | 128字节 |
| 最高波特率 | 1.5 Mbps | 3 Mbps | 15 Mbps |
| DMA支持 | 需要外部控制器 | 集成DMA接口 | 集成DMA控制器 |
| 9位模式 | 不支持 | 不支持 | 支持 |
| 自动流量控制 | 基本 | 增强 | 完整 |
实践提示:在选择UART芯片时,除了关注FIFO深度和最高波特率外,还应考虑其中断触发级别的可配置性。合理的触发水平设置可以在延迟和CPU开销之间取得最佳平衡。
4. 嵌入式系统中的UART实现与应用
在现代嵌入式系统中,UART通常以IP核的形式集成在微控制器和SoC中。这种集成带来了新的优化机会,但也引入了新的设计挑战。
4.1 硬件流控制机制
硬件流控制通过RTS(请求发送)和CTS(清除发送)信号线防止数据丢失。当接收方缓冲区即将满时,通过CTS信号通知发送方暂停传输。这种机制在高速通信或处理能力不对称的系统中尤为重要。
流控制配置示例:
// 配置UART硬件流控制
void uart_config_flow_control(uart_port_t uart_num) {
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS, // 启用RTS/CTS流控制
.rx_flow_ctrl_thresh = 122, // 当RX缓冲区达到122字节时断言RTS
};
uart_param_config(uart_num, &uart_config);
uart_set_pin(uart_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE,
RTS_GPIO, CTS_GPIO);
}
4.2 低功耗设计与能效优化
物联网设备对功耗极其敏感,现代UART针对低功耗应用进行了多项优化:
- 自动睡眠模式:在检测到线路空闲时自动进入低功耗状态
- 时钟门控:当不需要收发数据时关闭内部时钟
- 智能唤醒:通过特定字符或模式匹配唤醒系统
ESP32 UART低功耗配置示例:
// ESP32 UART低功耗配置
void configure_low_power_uart() {
// 设置空闲超时检测
uart_set_rx_timeout(UART_NUM_1, 10); // 10个位时间空闲后触发超时
// 使能自动休眠
uart_enable_sleep_mode(UART_NUM_1);
// 配置唤醒条件:收到任何数据或特定唤醒字符
uart_wakeup_type_t wakeup_config = {
.wakeup_any_char = true,
.wakeup_char = 0x0A // 换行符作为附加唤醒条件
};
uart_config_wakeup(UART_NUM_1, &wakeup_config);
}
4.3 多协议支持与灵活性
现代UART通常支持多种工作模式和协议选项,增加了应用的灵活性:
- 9位数据模式:用于多机通信中的地址/数据区分
- 红外编码解码:支持IrDA物理层标准
- 单线半双工模式:节省引脚资源
9位模式多机通信示例:
# 9位模式UART多机通信
def uart_9bit_multidemo():
# 配置为主机模式
uart_config_9bit_mode(MASTER_ADDRESS)
# 向特定从机发送数据
def send_to_slave(slave_addr, data):
# 先发送地址帧(第9位=1)
uart_send_9bit(slave_addr, True)
# 发送数据帧(第9位=0)
for byte in data:
uart_send_9bit(byte, False)
# 配置为从机模式
def slave_listener():
uart_config_9bit_mode(SLAVE_ADDRESS)
while True:
# 等待地址帧
addr, is_address = uart_receive_9bit()
if is_address and addr == SLAVE_ADDRESS:
# 接收后续数据帧
while True:
data, is_address = uart_receive_9bit()
if is_address: # 收到新的地址帧,停止接收
break
process_data(data)
5. 异步通信的时钟同步与容错机制
UART异步通信的核心挑战是如何在缺乏全局时钟的情况下实现可靠的位定时和帧同步。这一节我们深入探讨UART如何解决时钟漂移、噪声干扰和时序容差问题。
5.1 位定时与采样策略
UART接收器使用过采样技术来可靠地检测和采样输入数据。通常以16倍于波特率的频率对输入信号进行采样,然后通过数字滤波和多数表决确定位的实际值。
理想UART位采样时序:
起始位检测:连续8个采样点为低电平确认起始位
位中心采样:在每个位的第7、8、9个采样点进行多数表决
帧错误检测:停止位应为高电平,否则报告帧错误
// 软件UART位采样实现(基于定时器中断)
void timer_isr(void) {
static uint8_t sample_count = 0;
static uint8_t bit_count = 0;
static uint8_t current_byte = 0;
static enum {IDLE, START, DATA, STOP} state = IDLE;
uint8_t rx_level = read_rx_pin();
switch (state) {
case IDLE:
if (rx_level == 0) { // 检测起始位下降沿
sample_count = 0;
state = START;
}
break;
case START:
if (++sample_count == 8) { // 起始位中点
if (rx_level == 0) { // 确认起始位
sample_count = 0;
bit_count = 0;
current_byte = 0;
state = DATA;
} else {
state = IDLE; // 虚假起始位
}
}
break;
case DATA:
if (++sample_count == 8) { // 位中点采样
current_byte |= (rx_level << bit_count);
if (++bit_count == 8) {
sample_count = 0;
state = STOP;
}
}
break;
case STOP:
if (++sample_count == 8) { // 停止位中点
if (rx_level == 1) { // 有效的停止位
receive_complete(current_byte);
} else {
frame_error(current_byte);
}
state = IDLE;
}
break;
}
}
5.2 时钟容差与波特率匹配
异步通信允许发送和接收双方时钟存在一定偏差,通常要求双方波特率差异不超过2-3%。现代UART使用分数波特率发生器来更精确地匹配目标波特率。
常见波特率时钟分频计算:
| 目标波特率 | 系统时钟 | 整数分频 | 实际波特率 | 误差(%) |
|---|---|---|---|---|
| 9600 | 16MHz | 104 | 9615 | 0.16% |
| 115200 | 48MHz | 26 | 115384 | 0.16% |
| 230400 | 80MHz | 217 | 230414 | 0.006% |
| 460800 | 120MHz | 163 | 460858 | 0.013% |
工程实践:在高可靠性应用中,建议使用晶体振荡器而非RC振荡器作为UART的时钟源,以确保波特率精度。对于需要与标准设备通信的应用,波特率误差应控制在2%以内。
5.3 错误检测与恢复机制
UART提供了多种错误检测机制,帮助系统识别和处理通信异常:
- 奇偶校验错误:数据位中1的个数与奇偶校验位不匹配
- 帧错误:在期望停止位的位置检测到低电平
- 溢出错误:新数据覆盖未读取的旧数据
- Break条件:接收线持续保持低电平状态
错误处理策略示例:
def uart_error_handler(uart_port):
status = uart_get_status(uart_port)
if status & PARITY_ERROR:
log_error("奇偶校验错误")
# 请求重传最后的数据帧
request_retransmission()
elif status & FRAME_ERROR:
log_error("帧错误")
# 重新同步通信
resynchronize()
elif status & OVERRUN_ERROR:
log_error("溢出错误")
# 清空缓冲区并调整流控制
uart_flush_buffer(uart_port)
adjust_flow_control()
elif status & BREAK_DETECTED:
log_event("检测到Break条件")
# 处理Break信号(如重置通信)
handle_break_condition()
6. 现代应用场景与性能优化技巧
UART在当今嵌入式系统中仍然扮演着重要角色,特别是在调试接口、设备配置和传感器数据采集等场景中。优化UART性能可以显著提升系统整体效率。
6.1 高速调试与日志输出
在开发阶段,UART常用于调试信息输出和系统日志记录。通过优化日志输出机制,可以减少调试对系统性能的影响。
高效日志输出实现:
// 基于DMA的高效日志输出系统
typedef struct {
uint8_t* buffer;
uint16_t size;
uint16_t write_index;
uint16_t read_index;
volatile bool dma_active;
} uart_dma_logger_t;
void log_message(uart_dma_logger_t* logger, const char* message) {
uint16_t len = strlen(message);
uint16_t free_space = calculate_free_space(logger);
if (len > free_space) {
// 等待DMA传输完成或丢弃旧数据
wait_for_dma_completion(logger);
}
// 将数据复制到循环缓冲区
copy_to_buffer(logger, message, len);
// 如果DMA未运行则启动传输
if (!logger->dma_active) {
start_dma_transfer(logger);
}
}
// DMA传输完成中断处理
void dma_complete_isr(void) {
uart_dma_logger_t* logger = get_logger_instance();
logger->dma_active = false;
// 检查是否有更多数据需要传输
if (logger->read_index != logger->write_index) {
start_dma_transfer(logger);
}
}
6.2 传感器数据采集与处理
UART常用于连接各种传感器模块,如GPS接收器、环境传感器和智能仪表。高效的数据采集策略对系统性能至关重要。
传感器数据解析优化:
# 高效UART传感器数据解析
class SensorDataParser:
def __init__(self, uart_port):
self.uart = uart_port
self.buffer = bytearray()
self.callbacks = {}
def add_protocol_handler(self, protocol_prefix, callback):
"""注册协议处理器"""
self.callbacks[protocol_prefix] = callback
def process_data(self):
"""处理接收到的数据"""
while self.uart.any():
self.buffer.extend(self.uart.read(256))
# 检查缓冲区是否包含完整帧
while self.contains_complete_frame():
frame = self.extract_frame()
self.dispatch_frame(frame)
# 防止缓冲区无限增长
if len(self.buffer) > 1024:
self.buffer = self.buffer[-512:] # 保留最近数据
def contains_complete_frame(self):
"""检查是否包含完整数据帧"""
# 查找帧头帧尾模式
start = self.buffer.find(b'$') # 示例帧头
if start == -1:
return False
end = self.buffer.find(b'\r\n', start)
return end != -1
def extract_frame(self):
"""提取完整数据帧"""
start = self.buffer.find(b'$')
end = self.buffer.find(b'\r\n', start)
frame = self.buffer[start:end+2]
self.buffer = self.buffer[end+2:]
return frame
def dispatch_frame(self, frame):
"""分发数据帧到相应的处理器"""
for prefix, callback in self.callbacks.items():
if frame.startswith(prefix):
callback(frame)
return
# 无匹配处理器
log_unknown_frame(frame)
6.3 多端口管理与资源分配
在需要多个UART端口的应用中,合理管理资源可以显著提高系统效率和可靠性。
多UART端口管理策略:
// 多UART端口管理系统
typedef struct {
uart_port_t port;
uint32_t baud_rate;
uart_parity_t parity;
uint8_t data_bits;
uint8_t stop_bits;
QueueHandle_t rx_queue;
TaskHandle_t task_handle;
volatile bool enabled;
} uart_manager_t;
// UART管理器实例数组
uart_manager_t uart_managers[UART_NUM_MAX];
// 统一的UART中断处理
void IRAM_ATTR uart_isr_handler(void* arg) {
uart_port_t uart_num = (uart_port_t)arg;
uart_manager_t* manager = &uart_managers[uart_num];
if (!manager->enabled) {
return;
}
uint32_t status = UART[uart_num].int_st;
// 处理接收中断
if (status & UART_RXFIFO_FULL_INT_ST ||
status & UART_RXFIFO_TOUT_INT_ST) {
size_t length = 0;
uart_get_buffered_data_len(uart_num, &length);
if (length > 0) {
uint8_t* data = malloc(length);
uart_read_bytes(uart_num, data, length, 0);
// 将数据发送到处理任务
xQueueSendFromISR(manager->rx_queue, data, NULL);
}
}
// 处理其他类型中断...
}
// UART管理任务
void uart_manager_task(void* pvParameters) {
uart_manager_t* manager = (uart_manager_t*)pvParameters;
uint8_t* rx_data;
while (1) {
if (xQueueReceive(manager->rx_queue, &rx_data, portMAX_DELAY)) {
// 处理接收到的数据
process_uart_data(manager->port, rx_data);
free(rx_data);
}
}
}
通过上述优化策略和实现技巧,UART在现代嵌入式系统中仍然能够提供高效可靠的通信能力。从简单的调试输出到复杂的数据采集系统,UART继续发挥着不可替代的作用。
UART的技术演进史就是一部嵌入式通信的发展史,从最简单的异步通信需求到现代高性能集成的解决方案,其核心设计理念经历了时间考验。在可预见的未来,随着物联网和边缘计算的快速发展,这种经典而高效的通信方式仍将继续发挥重要作用。
1万+



被折叠的 条评论
为什么被折叠?



