在51单片机上实现一个简单的多任务操作系统(OS)需要考虑其有限的资源(如RAM和ROM),以下是分步实现的指南:
1. 系统设计思路
• 协作式多任务:任务主动释放CPU(适合资源有限的场景)。
• 抢占式多任务:通过定时器中断强制切换任务(实时性更好,但需处理上下文保存)。
• 任务控制块(TCB):记录任务状态(堆栈指针、任务状态等)。
• 堆栈管理:每个任务分配独立堆栈,用于保存上下文。
2. 关键实现步骤
步骤1:定义任务控制块(TCB)
#define MAX_TASKS 3 // 最大任务数(根据RAM调整)
typedef struct {
unsigned char id; // 任务ID
unsigned char sp; // 堆栈指针(保存到内部RAM地址)
void (*entry)(void); // 任务入口函数
} tcb;
tcb tasks[MAX_TASKS];
unsigned char current_task = 0; // 当前运行的任务ID
步骤2:初始化任务堆栈
每个任务的堆栈需初始化为“伪中断现场”,首次调度时通过RETI
跳转到任务函数。
void task_create(void (*entry)(void), unsigned char task_id, unsigned char *stack) {
// 初始化堆栈:模拟中断后的上下文
*stack-- = (unsigned int)entry & 0xFF; // 返回地址低字节
*stack-- = (unsigned int)entry >> 8; // 返回地址高字节
*stack-- = 0x00; // PSW(初始状态)
*stack-- = 0x00; // ACC
*stack-- = 0x00; // B
*stack-- = 0x00; // DPL
*stack-- = 0x00; // DPH
// 其他寄存器根据需要保存
tasks[task_id].sp = (unsigned char)stack; // 保存堆栈指针
}
步骤3:配置定时器中断(抢占式调度)
void configure_timer() {
TMOD |= 0x01; // Timer0模式1(16位定时器)
TH0 = 0xFC; // 定时1ms(假设12MHz晶振)
TL0 = 0x18;
ET0 = 1; // 允许Timer0中断
TR0 = 1; // 启动Timer0
}
步骤4:编写中断服务程序(汇编实现上下文切换)
在timer_isr
中保存当前任务上下文,加载下一个任务上下文:
; 定时器中断服务程序(Timer0)
TIMER_ISR:
; 保存当前任务上下文到堆栈
PUSH ACC
PUSH B
PUSH DPL
PUSH DPH
PUSH PSW
; 保存当前堆栈指针到TCB
MOV DPTR, #current_task
MOV A, @DPTR ; 获取current_task
MOV B, #sizeof(tcb)
MUL AB ; 计算TCB数组偏移量
ADD A, #tasks.sp ; 获取sp字段地址(需根据实际地址调整)
MOV DPL, A
MOV DPH, #0x00
MOV A, SP
MOVX @DPTR, A ; 保存SP到tasks[current_task].sp
; 切换到下一个任务
INC current_task ; current_task = (current_task + 1) % MAX_TASKS
MOV A, current_task
CJNE A, #MAX_TASKS, skip_reset
MOV current_task, #0
skip_reset:
; 加载新任务的SP
MOV A, current_task
MOV B, #sizeof(tcb)
MUL AB
ADD A, #tasks.sp ; 获取新任务的sp地址
MOV DPL, A
MOV DPH, #0x00
MOVX A, @DPTR
MOV SP, A ; 恢复新任务的SP
; 恢复新任务上下文
POP PSW
POP DPH
POP DPL
POP B
POP ACC
RETI ; 返回到新任务
步骤5:启动调度器
void main() {
// 初始化任务堆栈(假设stack1、stack2为预分配的堆栈空间)
task_create(task1, 0, stack1 + 50); // 每个堆栈50字节
task_create(task2, 1, stack2 + 50);
configure_timer(); // 配置定时器中断
EA = 1; // 开启全局中断
// 手动触发第一个任务调度
__asm
MOV SP, tasks[0].sp // 加载任务1的堆栈指针
RETI // 模拟中断返回,跳转到任务1
__endasm;
while(1); // 主循环(实际不会执行到这里)
}
3. 任务函数示例
void task1(void) {
while(1) {
P1 = 0xFF; // 任务1的代码(如点亮LED)
// 协作式调度需主动释放CPU
// task_yield();
}
}
void task2(void) {
while(1) {
P2 = 0x00; // 任务2的代码
}
}
4. 注意事项
• 堆栈分配:确保每个任务的堆栈不重叠(如stack1[50]
和stack2[50]
)。
• 资源竞争:共享资源(如串口)需通过临界区保护(关闭中断)。
• 任务切换频率:定时器中断间隔影响任务切换速度,需根据需求调整。
• 调试:可通过LED或串口输出观察任务切换情况。
5. 扩展功能
• 协作式调度:添加task_yield()
函数主动切换任务。
• 优先级调度:在TCB中添加优先级字段,调整调度逻辑。
• 信号量机制:实现简单的任务同步。