# 引言 在嵌入式开发中,多任务调度是提升系统响应能力的关键。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<