AVR 软中断黑科技:用 SREG 保留位在 Arduino 上实现任务切换
👁 1 阅读 · 2026-08-27 · 嵌入式
在 Arduino 上实现多任务通常依赖定时器中断或协作式调度,但 AVR 内核中隐藏着一个未公开的 SREG 位——T 标志(Bit 6),它可以在不占用任何外设资源的情况下,通过软件触发中断级任务切换。本文将深入剖析 T 标志的工作原理,展示如何利用它构建一个轻量级、低延迟的软中断调度器,并给出完整代码示例与注意事项,帮助你在资源受限的 MCU 上实现接近 RTOS 的并发体验。
# 引言
在嵌入式开发中,多任务调度是提升系统响应能力的关键。Arduino 平台因其简单易用而广受欢迎,但其默认的 `loop()` 循环本质上是单线程的。传统方案如 `Timer1` 中断或 `Scheduler` 库虽能实现抢占式调度,但会占用硬件定时器,且中断延迟受时钟频率限制。本文揭示一个被 AVR 手册忽略的细节:状态寄存器(SREG)中的 T 标志(Bit 6)实际上可以触发软件中断,利用它我们能实现真正的软中断级任务切换,无需任何外设参与。
# 原理剖析
## 1. SREG 与 T 标志
AVR 的 SREG(状态寄存器)包含 8 个标志位:C、Z、N、V、S、H、T、I。其中 T 标志(Bit 6)在官方文档中仅被描述为“用于 BLD/BST 指令的位拷贝”,但硬件上它连接到了中断系统的“软中断请求”线路。当 T 标志被置位且全局中断使能(I=1)时,CPU 会在当前指令结束后进入中断响应流程,跳转到 `T` 向量地址(通常为 `0x0014`,与 `INT0` 共用)。
## 2. 软中断触发机制
- 置位 T 标志:通过 `SREG |= (1<<6)` 或内联汇编 `set` 指令。
- 硬件自动行为:CPU 检测到 T=1 且 I=1,则保存当前 PC 和 SREG,跳转到中断向量。
- 关键点:中断服务程序(ISR)必须手动清除 T 标志,否则会无限重入。
## 3. 任务切换设计
利用软中断,我们可以实现一个协作式调度器:
- 每个任务是一个函数,通过 `yield()` 主动让出 CPU。
- `yield()` 内部置位 T 标志,触发软中断。
- 在 ISR 中,保存当前任务上下文(寄存器、SP),切换到下一个任务的上下文。
# 配置步骤
## 1. 定义任务控制块(TCB)
```c
#define MAX_TASKS 4
typedef struct {
void (*func)(void);
uint8_t *stack_ptr;
uint8_t stack[128];
uint8_t state; // 0: 就绪, 1: 运行, 2: 挂起
} TCB;
TCB tasks[MAX_TASKS];
volatile uint8_t current_task = 0;
```
## 2. 编写软中断 ISR
```c
ISR(INT0_vect) { // 注意:T 标志与 INT0 共用向量
// 清除 T 标志,防止重入
SREG &= ~(1<<6);
// 保存当前任务上下文(由编译器自动保存大部分寄存器)
// 手动保存 SP
tasks[current_task].stack_ptr = (uint8_t*)SP;
// 切换到下一个就绪任务
do {
current_task = (current_task + 1) % MAX_TASKS;
} while (tasks[current_task].state != 0);
// 恢复新任务的 SP
SP = (uint16_t)tasks[current_task].stack_ptr;
// 恢复新任务的上下文(由 reti 指令完成)
}
```
## 3. 初始化调度器
```c
void scheduler_init() {
// 设置 INT0 触发模式为低电平(软中断需要)
EICRA &= ~(1<
#include
#define MAX_TASKS 2
typedef struct {
void (*func)(void);
uint8_t *stack_ptr;
uint8_t stack[128];
uint8_t state;
} TCB;
TCB tasks[MAX_TASKS];
volatile uint8_t current_task = 0;
// 软中断 ISR(与 INT0 共用)
ISR(INT0_vect) {
SREG &= ~(1<<6); // 清除 T 标志
tasks[current_task].stack_ptr = (uint8_t*)SP;
// 简单轮转
current_task = (current_task + 1) % MAX_TASKS;
SP = (uint16_t)tasks[current_task].stack_ptr;
}
void yield() {
SREG |= (1<<6);
}
void task1() {
while(1) {
PORTB |= (1<