Arduino 非阻塞式软件串口:基于定时器捕获的位级采样技巧
👁 2 阅读 · 2026-08-27 · 嵌入式
在资源受限的嵌入式系统中,硬件串口数量往往不足,软件模拟串口成为扩展通信能力的常用方案。本文深入探讨如何在 Arduino 平台上实现非阻塞式软件串口,重点介绍基于定时器输入捕获的位级采样技巧,避免传统延时位采样带来的阻塞问题,提升系统实时性与多任务处理能力。通过原理剖析、配置步骤与完整代码示例,帮助开发者掌握这一高效实现方法。
# 引言
在嵌入式开发中,串口通信是基础且常用的接口。然而,许多微控制器(如 Arduino Uno 的 ATmega328P)仅提供 1-2 个硬件 UART,当需要连接多个串口设备(如 GPS、蓝牙、传感器模块)时,硬件资源捉襟见肘。软件串口(Software Serial)应运而生,它通过 GPIO 引脚和定时器模拟 UART 协议。
传统软件串口(如 Arduino 官方 SoftwareSerial 库)采用**阻塞式位采样**:发送或接收时,通过 `delayMicroseconds()` 精确延时,期间 CPU 被完全占用,无法处理其他任务。这在多任务场景下会引发严重问题,例如中断响应延迟、数据丢失等。
本文提出一种**非阻塞式软件串口**实现方案,利用定时器输入捕获(Input Capture)功能,在硬件层面自动记录引脚电平变化的时间戳,配合状态机解析,实现位级采样,从而释放 CPU 资源。
# 原理讲解
## UART 协议基础
UART 帧格式:起始位(低电平)、8 个数据位(LSB 先发)、可选校验位、停止位(高电平)。空闲时,TX 线保持高电平。接收端需要以波特率(如 9600bps)为周期采样每个位。
## 阻塞式 vs 非阻塞式
- **阻塞式**:接收时,检测到起始位下降沿后,循环延时采样每个位。期间无法响应其他中断,CPU 利用率低。
- **非阻塞式**:利用定时器输入捕获中断,每当引脚电平变化(上升沿或下降沿)时,硬件自动记录当前定时器计数值并触发中断。软件在中断中记录边沿时间,通过分析边沿间隔还原数据位。
## 定时器输入捕获原理
以 Arduino Uno(ATmega328P)为例,定时器 1 是 16 位定时器,支持输入捕获单元。将 RX 引脚连接到 ICP1(Pin 8),配置定时器 1 为普通模式,开启输入捕获中断。当引脚发生指定边沿(如下降沿)时,定时器计数器值被复制到 ICR1 寄存器,并触发中断。
通过记录连续边沿的时间差,可以判断每个位的持续时间。例如,波特率 9600 时,位时间约为 104.17μs。若检测到两个下降沿间隔约为 1 个位时间,则中间为 0;若间隔为 2 个位时间,则中间为连续的 0(如 0b00)。
# 配置步骤
1. **硬件连接**:将外部设备的 TX 引脚连接到 Arduino 的 Pin 8(ICP1),共地。
2. **定时器配置**:
- 设置定时器 1 为普通模式,预分频为 8(计数频率 2MHz,分辨率 0.5μs)。
- 开启输入捕获中断(TIMSK1 |= (1<>= 1; // LSB先发,所以右移
// 注意:当前位是0,无需置位
bit_count++;
}
}
// 当前下降沿对应的位是0(因为下降沿表示该位为0)
if (bit_count < 8) {
current_byte >>= 1;
bit_count++;
}
// 如果已接收8位,则等待停止位(上升沿)
if (bit_count == 8) {
// 停止位应为高电平,但这里我们通过下一次上升沿确认
// 简单处理:直接认为接收完成,实际应用中可校验停止位
rx_byte = current_byte;
rx_ready = 1;
state = 0; // 回到IDLE
}
}
}
void loop() {
if (rx_ready) {
rx_ready = 0;
Serial.print("收到: 0x");
Serial.println(rx_byte, HEX);
}
// 其他任务可以在这里执行,不会阻塞
}
```
# 注意事项
- **定时器溢出处理**:上述代码未处理定时器溢出(16位计数器溢出约32.7ms),在低波特率或长空闲时可能出错。建议在溢出中断中扩展高位,或使用更高预分频。
- **边沿检测**:本方案仅依赖下降沿,对于连续0的位(如0x00)需要计算间隔,但若出现连续多个0,间隔会较长,需确保定时器不溢出。
- **波特率误差**:晶振误差和预分频会导致位时间偏差,建议使用误差小于2%的晶振,并在代码中微调BIT_TIME。
- **中断优先级**:输入捕获中断应具有较高优先级,避免被其他中断延迟导致时间戳不准确。
- **发送方向**:本文仅实现接收,发送可类似使用定时器比较输出(CTC模式)或占用CPU,但通常发送不频繁,阻塞影响较小。
# 总结
基于定时器输入捕获的软件串口,利用硬件自动记录边沿时间,配合状态机解析,实现了非阻塞式接收。这种方法显著提高了 CPU 利用率,使 Arduino 能够同时处理多个串口通信和其他实时任务。开发者可根据实际需求扩展为完整收发功能,并优化溢出处理。掌握这一技巧,将极大提升嵌入式系统的通信设计能力。