当8051遇上纯C协程:手撸一个极轻量级RTOS
本创作用于给有足够单片机资源的8位单片机提供RTOS的操作方式。这套基于 8051 的纯 C 协程式 RTOS虽然代码不长但设计非常精妙。纯 C 黑科技不用内嵌汇编用RTOS_DoSwitch(u8 sp) { SP sp; }配合RET指令直接偷天换日完成上下文切换。内存绝对隔离将 8051 宝贵的 RAM 一分为二0x14~0x7F留给系统/中断栈0x80~0xFF划给任务栈彻底告别栈溢出踩踏。静态分配法用静态二维数组替代了不靠谱的malloc完美压榨了 CA51M151 的 512 字节物理 RAM。后续开发避坑指南非常重要既然你已经把这个 RTOS 跑起来了后续在写任务函数时请务必记住以下几条规定否则容易遇到玄学死机1. 任务里的局部变量要省着点用因为你的任务栈被限制在RTOS_STACK_BASE(0x80) 到0xFF之间总共 128 字节。千万不要在任务里定义巨大的局部数组比如u8 buf[100];这会直接导致栈溢出把其他任务的变量踩坏。如果需要大缓冲区请加上xdata关键字或者static关键字把它挪到全局区去cvoid LED_Task(void) { xdata u8 big_buf[100]; // 这样写是安全的放在外部 RAM // ... }2. 中断里绝对不能调用延时或 Yield你在定时器中断Timer0_ISR里调用的RTOS_Scheduler()是安全的因为它只减Tick不切任务。但在其他硬件中断如串口、外部中断里绝对不能调用RTOS_TaskDelay()、RTOS_Yield()或RTOS_SemTake()。因为中断有自己的栈帧强行在里面切 SP 会导致中断返回时彻底崩溃。3. 尽量不要使用reentrant可重入函数Keil C51 的reentrant模式会使用一个模拟栈指针?C_XBP。目前我们的 RTOS 切换只保存了硬件SP并没有保存这个模拟指针。如果你大量使用了reentrant函数多任务并发调用时可能会出问题。普通函数无reentrant是绝对安全的。4. 善用信号量和消息队列你现在已经拥有了RTOS_SemTake和RTOS_MsgSend。如果你要做串口接收可以用一个串口任务专门读数据然后通过RTOS_MsgSend发给处理任务这样能让你的代码逻辑变得非常清晰彻底告别while(1)里套一堆if的屎山代码。附上本人完全免费开源的代码首先是rtos.c文件/** ****************************************************************************** * file rtos.c * brief RTOS 实现协程式上下文切换版8051/Keil C51 * * note 上下文切换原理 * 1. 任务调用 RTOS_Yield()/RTOS_TaskDelay() 等主动让出点 * 2. 最终进入 RTOS_TaskSwitch() * 3. 保存硬件 SP用 C 把 idata 栈区 [RTOS_STACK_BASE .. SP] * 拷贝到当前任务的 xdata 软件栈 * 4. 选出下一个任务把它的软件栈内容拷回 idata * 5. 用纯 C 语言函数设置 SP 为新任务的 SP然后 return * 6. C51 生成 RET 从新栈弹出返回地址自动跳回新任务上次 * RTOS_TaskSwitch() 的返回点即 Yield/Delay 之后继续执行。 * * 定时器中断中的 RTOS_Scheduler() 只做 TickHandler不切换任务 * 避免在中断栈帧上改 SP 导致崩溃。 ****************************************************************************** */ #include RTOS/rtos.h #include include/ca51m151sfr.h /* */ /* 私有全局变量 */ /* */ static RTOS_TaskTCB xdata TaskTable[RTOS_MAX_TASKS]; /* 新增静态分配所有任务的软件栈避免 malloc 失败风险 */ static u8 xdata TaskStacks[RTOS_MAX_TASKS][RTOS_STACK_SIZE]; static u8 data CurrentTaskID 0; static u8 data TaskCount 0; static u8 data SchedulerLock 0; static u16 data SysTick 0; /* */ /* 私有函数栈拷贝 */ /* */ /** * brief 把 idata 栈区 [RTOS_STACK_BASE .. sp] 拷贝到 xdata 软件栈 */ static void RTOS_SaveStack(u8 xdata *dst, u8 sp) { u8 idata *src; u16 n; if (sp RTOS_STACK_BASE) return; src (u8 idata *)RTOS_STACK_BASE; n (u16)(sp - RTOS_STACK_BASE 1); while (n--) { *dst *src; } } /** * brief 把 xdata 软件栈内容恢复到 idata 栈区 [RTOS_STACK_BASE .. sp] */ static void RTOS_RestoreStack(u8 xdata *src, u8 sp) { u8 idata *dst; u16 n; if (sp RTOS_STACK_BASE) return; dst (u8 idata *)RTOS_STACK_BASE; n (u16)(sp - RTOS_STACK_BASE 1); while (n--) { *dst *src; } } /* */ /* 私有函数选择下一个任务 */ /* */ /** * brief 选择优先级最高的就绪任务 * return 任务 ID0xFF 表示没有就绪任务 */ static u8 RTOS_SelectNext(void) { u8 i; u8 best 0xFF; u8 best_prio 0; for (i 0; i TaskCount; i) { if (TaskTable[i].State TASK_DELAYED) { if (TaskTable[i].DelayTick 0) continue; TaskTable[i].State TASK_READY; } if (TaskTable[i].State TASK_READY) { if (best 0xFF || TaskTable[i].Priority best_prio) { best i; best_prio TaskTable[i].Priority; } } } return best; } /* */ /* 私有函数任务入口包装 */ /* */ /** * brief 新任务的启动入口 */ static void RTOS_TaskEntry(void) { TaskTable[CurrentTaskID].Func(); TaskTable[CurrentTaskID].State TASK_SUSPENDED; RTOS_TaskSwitch(); while (1); } /* */ /* 任务切换核心 (纯 C 语言实现) */ /* */ /* * 这个跳板函数只有一句代码编译器会将其编译为 MOV SP, R7 然后直接 RET。 * 当该函数 RET 时硬件会自动从新栈弹出返回地址完成无缝跳转。 */ static void RTOS_DoSwitch(u8 sp) { SP sp; } void RTOS_TaskSwitch(void) { u8 next; u8 sp; u8 cur_state; /* 1. 保存当前硬件 SP */ sp SP; /* 2. 当前 RUNNING 临时改为 READY参与优先级比较 */ cur_state TaskTable[CurrentTaskID].State; if (cur_state TASK_RUNNING) { TaskTable[CurrentTaskID].State TASK_READY; } /* 3. 选下一个 */ next RTOS_SelectNext(); if (next 0xFF || next CurrentTaskID) { /* 不切换恢复状态后直接返回 */ if (cur_state TASK_RUNNING) { TaskTable[CurrentTaskID].State TASK_RUNNING; } return; } /* 4. 保存当前任务上下文 */ TaskTable[CurrentTaskID].SP sp; RTOS_SaveStack(TaskTable[CurrentTaskID].Stack, sp); /* 5. 恢复下一个任务上下文 */ sp TaskTable[next].SP; RTOS_RestoreStack(TaskTable[next].Stack, sp); TaskTable[next].State TASK_RUNNING; CurrentTaskID next; /* 6. 触发跳板函数进行切换 */ RTOS_DoSwitch(sp); } /* */ /* 系统初始化与启动 */ /* */ void RTOS_Init(void) { u8 i; for (i 0; i RTOS_MAX_TASKS; i) { TaskTable[i].Func 0; TaskTable[i].Priority 0; TaskTable[i].State TASK_SUSPENDED; TaskTable[i].DelayTick 0; TaskTable[i].TaskID i; TaskTable[i].SP 0; TaskTable[i].Stack 0; } TaskCount 0; CurrentTaskID 0; SchedulerLock 0; SysTick 0; } u8 RTOS_TaskCreate(TaskFunc func, u8 priority) { u8 id; u16 entry; if (TaskCount RTOS_MAX_TASKS) return 0xFF; if (func 0) return 0xFF; id TaskCount; TaskTable[id].Func func; TaskTable[id].Priority priority; TaskTable[id].State TASK_READY; TaskTable[id].DelayTick 0; TaskTable[id].TaskID id; /* 使用静态分配的栈彻底告别 malloc 失败风险 */ TaskTable[id].Stack TaskStacks[id]; /* 构造初始栈 */ entry (u16)RTOS_TaskEntry; TaskTable[id].Stack[0] 0x00; /* 栈底占位 */ TaskTable[id].Stack[1] (u8)entry; /* 返回地址低字节 */ TaskTable[id].Stack[2] (u8)(entry 8); /* 返回地址高字节 */ TaskTable[id].SP RTOS_STACK_BASE 2; TaskCount; return id; } void RTOS_Start(void) { u8 i; /* 找到第一个就绪任务作为初始运行任务 */ for (i 0; i TaskCount; i) { if (TaskTable[i].State TASK_READY) { CurrentTaskID i; TaskTable[i].State TASK_RUNNING; break; } } if (i TaskCount) { while (1); } /* 恢复第一个任务的栈设置 SPreturn 时 RET 跳到 RTOS_TaskEntry */ RTOS_RestoreStack(TaskTable[CurrentTaskID].Stack, TaskTable[CurrentTaskID].SP); RTOS_DoSwitch(TaskTable[CurrentTaskID].SP); /* RTOS_DoSwitch 执行后不会回到这里 */ while(1); } /* */ /* 任务控制 API */ /* */ void RTOS_Yield(void) { if (SchedulerLock 0) { RTOS_TaskSwitch(); } } void RTOS_TaskDelay(u16 ticks) { if (ticks 0) { RTOS_Yield(); return; } if (CurrentTaskID TaskCount) return; TaskTable[CurrentTaskID].DelayTick ticks; TaskTable[CurrentTaskID].State TASK_DELAYED; /* 循环直到延迟到期或被恢复 */ while (TaskTable[CurrentTaskID].State TASK_DELAYED) { RTOS_TaskSwitch(); } TaskTable[CurrentTaskID].State TASK_RUNNING; } void RTOS_TaskSuspend(u8 task_id) { if (task_id TaskCount) return; TaskTable[task_id].State TASK_SUSPENDED; if (task_id CurrentTaskID) { while (TaskTable[CurrentTaskID].State TASK_SUSPENDED) { RTOS_TaskSwitch(); } TaskTable[CurrentTaskID].State TASK_RUNNING; } } void RTOS_TaskResume(u8 task_id) { if (task_id TaskCount) return; if (TaskTable[task_id].State TASK_SUSPENDED) { TaskTable[task_id].State TASK_READY; } } /* */ /* 临界区 */ /* */ void RTOS_EnterCritical(void) { SchedulerLock 1; } void RTOS_ExitCritical(void) { SchedulerLock 0; } /* */ /* 信号量 */ /* */ void RTOS_SemCreate(RTOS_Semaphore *sem, u8 init_val) { if (sem) { sem-Count init_val; sem-MaxCount RTOS_SEM_COUNT_MAX; } } RTOS_Status RTOS_SemTake(RTOS_Semaphore *sem, u16 timeout) { u16 waited 0; if (sem 0) return RTOS_ERR_PARAM; while (sem-Count 0) { if (timeout 0) return RTOS_ERR_TIMEOUT; if (timeout ! 0xFFFF waited timeout) return RTOS_ERR_TIMEOUT; RTOS_TaskDelay(1); waited; } sem-Count--; return RTOS_OK; } void RTOS_SemGive(RTOS_Semaphore *sem) { if (sem) { if (sem-Count sem-MaxCount) { sem-Count; } } } /* */ /* 消息队列 */ /* */ void RTOS_MsgQueueCreate(RTOS_MsgQueue *q) { if (q) { q-Head 0; q-Tail 0; q-Count 0; } } RTOS_Status RTOS_MsgSend(RTOS_MsgQueue *q, u8 *pData, u16 timeout) { u16 waited 0; u8 i; if (q 0 || pData 0) return RTOS_ERR_PARAM; while (q-Count RTOS_MSG_QUEUE_SIZE) { if (timeout 0) return RTOS_ERR_TIMEOUT; if (timeout ! 0xFFFF waited timeout) return RTOS_ERR_TIMEOUT; RTOS_TaskDelay(1); waited; } for (i 0; i RTOS_MSG_QUEUE_LEN; i) { q-Buf[q-Tail][i] pData[i]; } q-Tail; if (q-Tail RTOS_MSG_QUEUE_SIZE) q-Tail 0; q-Count; return RTOS_OK; } RTOS_Status RTOS_MsgReceive(RTOS_MsgQueue *q, u8 *pData, u16 timeout) { u16 waited 0; u8 i; if (q 0 || pData 0) return RTOS_ERR_PARAM; while (q-Count 0) { if (timeout 0) return RTOS_ERR_TIMEOUT; if (timeout ! 0xFFFF waited timeout) return RTOS_ERR_TIMEOUT; RTOS_TaskDelay(1); waited; } for (i 0; i RTOS_MSG_QUEUE_LEN; i) { pData[i] q-Buf[q-Head][i]; } q-Head; if (q-Head RTOS_MSG_QUEUE_SIZE) q-Head 0; q-Count--; return RTOS_OK; } /* */ /* 调度器与系统信息 */ /* */ void RTOS_TickHandler(void) { u8 i; SysTick; for (i 0; i TaskCount; i) { if (TaskTable[i].State TASK_DELAYED) { if (TaskTable[i].DelayTick 0) { TaskTable[i].DelayTick--; if (TaskTable[i].DelayTick 0) { TaskTable[i].State TASK_READY; } } } } } /** * brief 定时器中断中调用只处理 tick不切换任务 */ void RTOS_Scheduler(void) { RTOS_TickHandler(); } u8 RTOS_GetTaskCount(void) { return TaskCount; } u8 RTOS_GetCurrentTaskID(void) { return CurrentTaskID; } u16 RTOS_GetTick(void) { return SysTick; }其次是rtos.h文件#ifndef __RTOS_H__ #define __RTOS_H__ #include RTOS/rtos_config.h typedef unsigned char u8; typedef unsigned int u16; typedef unsigned long u32; typedef void (*TaskFunc)(void); typedef enum { RTOS_OK 0, RTOS_ERR_FULL, RTOS_ERR_EMPTY, RTOS_ERR_PARAM, RTOS_ERR_TIMEOUT } RTOS_Status; typedef enum { TASK_READY 0, TASK_RUNNING, TASK_DELAYED, TASK_BLOCKED, TASK_SUSPENDED } TaskState; typedef struct { TaskFunc Func; /** 任务入口函数 */ u8 Priority; /** 优先级数值越大越高 */ u8 State; /** 任务状态 */ u16 DelayTick; /** 剩余延迟 tick */ u8 TaskID; /** 任务 ID */ u8 SP; /** 挂起时保存的硬件 SP */ u8 xdata *Stack; /** 软件栈指针保存 idata 栈区内容 */ } RTOS_TaskTCB; typedef struct { u8 Count; u8 MaxCount; } RTOS_Semaphore; typedef struct { u8 Buf[RTOS_MSG_QUEUE_SIZE][RTOS_MSG_QUEUE_LEN]; u8 Head; u8 Tail; u8 Count; } RTOS_MsgQueue; /* ---- 系统 ---- */ void RTOS_Init(void); u8 RTOS_TaskCreate(TaskFunc func, u8 priority); void RTOS_Start(void); /* ---- 任务控制 ---- */ void RTOS_Yield(void); void RTOS_TaskDelay(u16 ticks); void RTOS_TaskSuspend(u8 task_id); void RTOS_TaskResume(u8 task_id); /* ---- 临界区 ---- */ void RTOS_EnterCritical(void); void RTOS_ExitCritical(void); /* ---- 信号量 ---- */ void RTOS_SemCreate(RTOS_Semaphore *sem, u8 init_val); RTOS_Status RTOS_SemTake(RTOS_Semaphore *sem, u16 timeout); void RTOS_SemGive(RTOS_Semaphore *sem); /* ---- 消息队列 ---- */ void RTOS_MsgQueueCreate(RTOS_MsgQueue *q); RTOS_Status RTOS_MsgSend(RTOS_MsgQueue *q, u8 *pData, u16 timeout); RTOS_Status RTOS_MsgReceive(RTOS_MsgQueue *q, u8 *pData, u16 timeout); /* ---- 调度器 ---- */ void RTOS_Scheduler(void); void RTOS_TickHandler(void); void RTOS_TaskSwitch(void); /* -- 必须在这里声明否则会报重定义错误 */ u8 RTOS_GetTaskCount(void); u8 RTOS_GetCurrentTaskID(void); u16 RTOS_GetTick(void); #endif最后是 rtos_config.h配置文件/** ****************************************************************************** * file rtos_config.h * brief RTOS 配置文件协程式上下文切换版 * * note 重要RTOS_STACK_BASE 必须 Keil C51 链接器分配的 ?STACK 地址。 * 请查看 .map 文件中的 ?STACK 符号或修改 STARTUP.A51 把栈底 * 固定到 RTOS_STACK_BASE。默认 0x80适用于 idata 为 256 字节、 * 全局变量不超过 0x80 的工程。 ****************************************************************************** */ #ifndef __RTOS_CONFIG_H__ #define __RTOS_CONFIG_H__ /** 最大任务数受 xdata 容量限制每个任务软件栈 RTOS_STACK_SIZE 字节 */ #define RTOS_MAX_TASKS 2 /** 系统心跳周期ms由定时器中断调用 RTOS_Scheduler() 提供 */ #define RTOS_TICK_MS 1 /** * brief idata 硬件栈区起始地址 * note 必须 链接器 ?STACK 符号的值。栈区范围 [RTOS_STACK_BASE, 0xFF]。 * 每个任务的软件栈大小 0x100 - RTOS_STACK_BASE。 */ #define RTOS_STACK_BASE 0x80 /** 每个任务的软件栈大小字节自动计算 */ #define RTOS_STACK_SIZE (0x100 - RTOS_STACK_BASE) /** 信号量最大计数值 */ #define RTOS_SEM_COUNT_MAX 255 /** 消息队列深度 */ #define RTOS_MSG_QUEUE_SIZE 8 /** 每条消息长度字节 */ #define RTOS_MSG_QUEUE_LEN 4 #endif使用示例#ifndef _MAIN_C_ #define _MAIN_C_ /*********************************************************************************************************************/ #include include/ca51m151_config.h #include include/ca51m151sfr.h #include include/ca51m151xsfr.h #include include/gpiodef_m151.h #include include/system_clock.h #include include/uart.h #include include/delay.h /*********************************************************************************************************************/ #include RTOS/rtos.h /*********************************************************************************************************************/ #define INT_TIME 100 //定时时间单位为us #define T_VAL (unsigned char)(0x100 - (INT_TIME*(FOSC/1000))/12000) void Timer0_Init(void) { TMOD (TMOD0xFC)|0x02; //模式选择: 定时器0模式2。 TH0 T_VAL; //装载重载值 TL0 T_VAL; //装载计数初值 TR0 1; //定时器0使能 ET0 1; //定时器0中断使能 // PT0 1; //设置定时器0中断优先级为高优先级 } /*********************************************************************************************************************/ void LED_Task(void) { while (1) { P02 ~P02; RTOS_TaskDelay(500); /* 真正保存 SP 切走500 tick 后从这行之后继续 */ } } void Key_Task(void) { while (1) { if (P03 0) { RTOS_TaskDelay(20); /* 消抖阻塞式写法 */ if (P03 0) { /* 确认按下 */ while (P03 0) RTOS_TaskDelay(10); /* 等释放代码线性直观 */ } } RTOS_Yield(); } } /*********************************************************************************************************************/ void System_Init(void) { LVDCON 0xE2; //开启LVD设置为低电压复位模式检测电压为2.7V #ifdef SYSCLK_8MHZ CKDIV 0; //系统时钟上电默认为IRCH的二分频(4MHz),运行8MHz,则CKDIV设置为0 #endif #ifdef UART1_EN Uart1_Initial(UART1_BAUTRATE); #endif #ifdef UART2_EN Uart2_Initial(UART2_BAUTRATE); #endif GPIO_Init(P02F,OUTPUT); GPIO_Init(P03F,INPUT); } void main(void) { /* 硬件初始化 */ System_Init(); EA 1; //开全局中断 #ifdef PRINT_EN uart_printf(Timer0 Mode2 Demo Code\n); #endif Timer0_Init(); RTOS_Init(); RTOS_TaskCreate(LED_Task, 2); RTOS_TaskCreate(Key_Task, 1); RTOS_Start(); /* 不会返回 */ } /* Timer0 1ms 中断 */ void Timer0_ISR(void) interrupt 1 { static u8 tick_cnt 0; /* 累加10次凑够 1ms提供给 RTOS 作为心跳 */ if (tick_cnt 10) { tick_cnt 0; RTOS_Scheduler(); /* 只递减 DelayTick不切换任务 */ } } #endif示波器测试led翻转需要工程源码的评论区艾特我我看到会给。一般来说不需要了因为我贴的代码已经是完整体了。