找回密码
 立即注册
查看: 1708|回复: 18

【再造轮子】FreeRTOS、μC/OS-ⅱ在开源工具SDCC上的移植过程详解

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:455
  • 最近打卡:2025-05-01 08:13:06
已绑定手机

27

主题

341

回帖

1687

积分

金牌会员

机长

积分
1687
发表于 2024-6-16 18:05:57 | 显示全部楼层 |阅读模式
本帖最后由 hsrzq 于 2024-6-17 00:38 编辑

移植原因
我只是一个单纯爱好MCS-51单片机的外行,对于单片机多任务调度处理起来非常吃力,理所当然希望能有一个系统来减轻负担。在MCS-51 这种8位单片机上可选择的系统不多,基本上就只能是μC/OS-ⅱFreeRTOS这两个了。
实际上,面向MCS-51版的μC/OS-ⅱ移植已经非常多了,但都是基于Keil这套“古老”且昂贵的开发环境的。如果你也正在用或者即将用Keil这套软件,那本文你读到此处就可以了。而FreeRTOS更是连使用Keil版的移植都没有了。
我原本就只是一个外行的爱好者,为了爱好购买Keil似乎有些不值,而盗版用起来总感觉有些不踏实,(实际上最大原因还是我的主力电脑都是Mac和Linux系统,Windows很少用到),迫切想要一个可用sdcc编译的RTOS系统。
在各论坛、QQ群、微信群等等求了很长一段时间无果后,发现还是得靠自己。于是先花了有半年时间查资料学习做准备,断断续续花了一个多月写代码,首先将FreeRTOS移植成功,然后又用一周时间将μC/OS-ⅱ移植成功。
为什么先移植FreeRTOS?因此它自带另一款MCS-51单片机c8051f120在sdcc上的移植,虽然早已不能使用甚至还有语法错误,但大致思路仍然是值得参考的。


对比这两个系统的移植过程,二者实现思路甚至核心方法极其相似,而μC/OS-ⅱ移植要稍微更难一点点,并且μC/OS-ⅱ裁剪后要更小一些(可能我配置有误?),因此后文主要讲述μC/OS-ⅱ的移植过程。

FreeRTOS v10.4.1@STC8x8K - VSCode/eIDE+SDCC+stcgal(Windows/MacOS/Linux) - SDCC, IAR C++ for STC8, GCC, VSCode,Linux, MacOS 国芯技术交流网站 - STC全球32位8051爱好者互助交流社区 (stcaimcu.com)

μC/OS-ii v2.93.01@STC8x8K - VSCode/eIDE+SDCC+stcgal(Windows/MacOS/Linux) - SDCC, IAR C++ for STC8, GCC, VSCode,Linux, MacOS 国芯技术交流网站 - STC全球32位8051爱好者互助交流社区 (stcaimcu.com)


移植原理
所有系统上的多任务调度都是类似的:1. 保存旧任务所使用的寄存器值;2. 恢复新任务所使用的寄存器值;3. 切换程序计数器(PC)到新任务地址。
第1、2步中需要保存和恢复的寄存器主要有通用寄存器R0~R7,累加器ACC、B,程序状态寄存器PSW,数据指针寄存器DPTR等。而最简单的保存恢复方式是按特定顺序压入到栈中以及从栈中弹出。
第3步初看很难,因为MCS-51不允许直接访问PC寄存器。但千万别忘了函数返回指令RET的执行过程,“将堆栈顶的主程序断点地址送入程序计数器PC,继续执行主程序”,因此只要构造特定的栈顶内容后执行RET,就能改变PC的值了。当然中断返回指令RETI也是相似的。

根据以上过程分析发现,栈操作是本次移植的核心。但MCS-51可作为栈空间的RAM+IRAM总共才256Bytes,而且连这都还要让一部分出来给通用寄存器R0~R7、位变量和其它内部RAM变量等,不做处理的话肯定是不够用的。
而处理方案实际上道理很简单:当发生任务切换时,将旧任务的栈从RAM/IRAM复制到扩展内存XRAM中,将新任务的栈从扩展内存XRAM恢复到RAM/IRAM中,并适当调整一下SP指针的位置即可。


--------------------至此,理论准备工作全部完成--------------------


移植准备

首先,确定寄存器入栈顺序:ACC、DPL、DPH、B、R2、R3、R4、R5、R6、R7、R0、R1、PSW、_BP。后续移植完全依赖于该顺序。
特别注意一下这个_BP,它并不是MCS-51中的寄存器,是sdcc内部方便操作栈的变量,对于函数的参数传递等非常重要,因此在任务切换也需要入栈。

其次,--model-large--stack-auto为sdcc编译时必备参数,其它参数按需设置。
--model-large使用大内存模式,即所以变量都放在XRAM中,而RAM/IRAM则全部留给栈使用。
--stack-auto表示自动栈模式,该模式下所有方法都是可重入的,并不需要加__reentrant标识。

移植过程
按照μC/OS-ⅱ官方建议,移植主要是创建os_cpu.h、os_cpu_c.c、os_cpu_a.asm这三个文件,实现OSStartHighRdy()、OSIntCtxSw()、OSCtxSw()这几个方法,以及OS_ENTRY_CRITICAL()、OS_EXIT_CRITICAL()这两个宏等。当然了,也少不了一些与编译器相关的数据类型定和定时器中断处理函数等。由于sdcc支持内联汇编,所以os_cpu_a.asm这个文件完全可以不用,所有函数直接在os_cpu_c.c中实现即可。另外,本次移植以理解RTOS原理为主,性能并不是本移植主要考虑因素,所以以C语言为主尽量不用汇编。

os_cpu.h
  1. #ifdef OS_CPU_GLOBALS
  2. #define OS_CPU_EXT
  3. #else
  4. #define OS_CPU_EXT extern
  5. #endif
  6. #include "STC8x8K.h"
  7. /* 1. ========== ========== ========== ========== ========== ========== ========== ==========*/
  8. typedef unsigned char BOOLEAN;     /* 8-bit  boolean or logical true/false (TRUE/FALSE)   */
  9. typedef unsigned char INT8U;       /* Unsigned  8 bit quantity                            */
  10. typedef signed char INT8S;         /* Signed    8 bit quantity                            */
  11. typedef unsigned int INT16U;       /* Unsigned 16 bit quantity                            */
  12. typedef signed int INT16S;         /* Signed   16 bit quantity                            */
  13. typedef unsigned long INT32U;      /* Unsigned 32 bit quantity                            */
  14. typedef signed long INT32S;        /* Signed   32 bit quantity                            */
  15. typedef unsigned long long INT64U; /* Unsigned 64 bit quantity                            */
  16. typedef signed long long INT64S;   /* Signed   64 bit quantity                            */
  17. typedef float FP32;                /* Single precision floating point                     */
  18. typedef unsigned char OS_STK;    /* Each stack entry is 8-bit wide                      */
  19. typedef unsigned char OS_CPU_SR; /* Define size of CPU status register (IE = 8 bits)   */
  20. /*2. ========== ========== ========== ========== ========== ========== ========== ==========*/
  21. #define OS_CRITICAL_METHOD 1
  22. #if OS_CRITICAL_METHOD == 1
  23. #define OS_ENTER_CRITICAL() \
  24.     do {                    \
  25.         EA = 0;             \
  26.     } while (0)
  27. #define OS_EXIT_CRITICAL() \
  28.     do {                   \
  29.         EA = 1;            \
  30.     } while (0)
  31. #elif OS_CRITICAL_METHOD == 2
  32. #error "OS_CRITICAL_METHOD == 2 is NOT supported."
  33. #elif OS_CRITICAL_METHOD == 3
  34. #error "OS_CRITICAL_METHOD == 3 is NOT supported."
  35. #endif
  36. /*3. ========== ========== ========== ========== ========== ========== ========== ==========*/
  37. #define OS_STK_GROWTH 0 /* Stack grows from LOW to HIGH memory on MCS-51 */
  38. #define OS_TASK_SW()  OSCtxSw()
  39. /*4. ========== ========== ========== ========== ========== ========== ========== ==========*/
  40. void OSStartHighRdy(void) __naked;
  41. void OSCtxSw(void) __naked;
  42. void OSIntCtxSw(void) __naked;
  43. /*5. ========== ========== ========== ========== ========== ========== ========== ==========*/
  44. void OSTimerInit(void);
  45. void OSTimer0ISR(void) __interrupt(1) __naked;
复制代码
第一部分:基本类型定义,这个没啥好说的。
第二部分:进入临界代码块的实现。μC/OS-ⅱ官方要求应当有三种实现方案,这里暂时只实现最简单的方案1,即进入临界块时关中断(EA=0),退出临界块时开中断(EA=1)。这个方案肯定不优雅,如果有嵌套就完蛋,但胜在简单。
第三部分:系统相关配置。OS_STK_GROWTH用来定义栈增长方向,0是向上增长;OS_TASK_SW()用来触发任务轮换,实际上,这个宏主要是设计给带自陷功能CPU的(即软中断),但MCS-51并不支持,只能生硬的调一遍OSCtxSw()了。
第四部分:系统移植核心方法的声明。特别注意一下__naked关键字,这表示进出该方法时,不会自动保存或恢复寄存器的值,这些都需要我们自己来手动操作。
第五部分:定义定时器初始化方法和定时器1的中断入口方法。
2 喜欢他/她就送朵鲜花吧,赠人玫瑰,手有余香!
业余撸代码,专业开飞机
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:316
  • 最近打卡:2025-04-14 12:35:14
已绑定手机

14

主题

41

回帖

563

积分

高级会员

积分
563
发表于 2024-6-16 19:53:52 | 显示全部楼层
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:422
  • 最近打卡:2025-05-01 09:54:52
已绑定手机

19

主题

3190

回帖

4866

积分

论坛元老

积分
4866
发表于 2024-6-16 20:08:48 来自手机 | 显示全部楼层
如果能介绍怎么移植就更好了。楼主威武啊
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:455
  • 最近打卡:2025-05-01 08:13:06
已绑定手机

27

主题

341

回帖

1687

积分

金牌会员

机长

积分
1687
发表于 2024-6-17 00:33:11 | 显示全部楼层

os_cpu_c.c
  1. #include "includes.h"
  2. /*1. ========== ========== ========== ========== ========== ========== ========== ==========*/
  3. #define PSH_ALL_REGISTERS()   \
  4.     do {                      \
  5.         __asm__("push  acc"); \
  6.         __asm__("push  dpl"); \
  7.         __asm__("push  dph"); \
  8.         __asm__("push  b");   \
  9.         __asm__("push  ar2"); \
  10.         __asm__("push  ar3"); \
  11.         __asm__("push  ar4"); \
  12.         __asm__("push  ar5"); \
  13.         __asm__("push  ar6"); \
  14.         __asm__("push  ar7"); \
  15.         __asm__("push  ar0"); \
  16.         __asm__("push  ar1"); \
  17.         __asm__("push  psw"); \
  18.         __asm__("clr   psw"); \
  19.         __asm__("push  _bp"); \
  20.     } while (0)
  21. #define POP_ALL_REGISTERS()   \
  22.     do {                      \
  23.         __asm__("pop   _bp"); \
  24.         __asm__("pop   psw"); \
  25.         __asm__("pop   ar1"); \
  26.         __asm__("pop   ar0"); \
  27.         __asm__("pop   ar7"); \
  28.         __asm__("pop   ar6"); \
  29.         __asm__("pop   ar5"); \
  30.         __asm__("pop   ar4"); \
  31.         __asm__("pop   ar3"); \
  32.         __asm__("pop   ar2"); \
  33.         __asm__("pop   b");   \
  34.         __asm__("pop   dph"); \
  35.         __asm__("pop   dpl"); \
  36.         __asm__("pop   acc"); \
  37.     } while (0)
  38. /*2. ========== ========== ========== ========== ========== ========== ========== ==========*/
  39. #define COPY_STACK_TO_XRAM()                             \
  40.     do {                                                 \
  41.         /* stkPTOS = (OS_STK *) (__start__stack - 1); */ \
  42.         __asm__("mov _stkPTOS, #(__start__stack - 1)");  \
  43.         stkXRAM  = OSTCBCur->OSTCBStkPtr;                \
  44.         stkSize  = SP - (OS_STK)stkPTOS;                 \
  45.         *stkXRAM = stkSize;                              \
  46.         while (stkSize--) {                              \
  47.             *(++stkXRAM) = *(++stkPTOS);                 \
  48.         }                                                \
  49.     } while (0)
  50. #define COPY_XRAM_TO_STACK()                             \
  51.     do {                                                 \
  52.         /* stkPTOS = (OS_STK *) (__start__stack - 1); */ \
  53.         __asm__("mov _stkPTOS, #(__start__stack - 1)");  \
  54.         stkXRAM = OSTCBCur->OSTCBStkPtr;                 \
  55.         stkSize = pxXRAMStack[0];                        \
  56.         while (stkSize--) {                              \
  57.             *(++stkPTOS) = *(++stkXRAM);                 \
  58.         }                                                \
  59.         SP = (OS_STK)stkPTOS;                            \
  60.     } while (0)
  61. /*3. ========== ========== ========== ========== ========== ========== ========== ==========*/
  62. static uint8_t stkSize;
  63. static OS_STK *stkPTOS;
  64. static OS_STK *stkXRAM;
  65. /*4. ========== ========== ========== ========== ========== ========== ========== ==========*/
  66. void OSInitHookBegin(void)
  67. {
  68. }
  69. void OSInitHookEnd(void)
  70. {
  71.     OSTimerInit();
  72. }
  73. void OSTaskIdleHook(void)
  74. {
  75. }
  76. void OSTCBInitHook(OS_TCB *ptcb)
  77. {
  78. }
  79. void OSTaskCreateHook(OS_TCB *ptcb)
  80. {
  81. }
  82. void OSTaskReturnHook(OS_TCB *ptcb)
  83. {
  84. }
  85. void OSTaskSwHook(void)
  86. {
  87. }
  88. #if (OS_TIME_TICK_HOOK_EN > 0)
  89. void OSTimeTickHook(void)
  90. {
  91. }
  92. #endif
  93. #if OS_CPU_HOOKS_EN > 0
  94. void OSTaskStatHook(void)
  95. {
  96. }
  97. #endif
  98. /*5. ========== ========== ========== ========== ========== ========== ========== ==========*/
  99. OS_STK *OSTaskStkInit(void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
  100. {
  101.     opt;
  102.     uint32_t ulAddress;
  103.     OS_STK *stk = ptos;
  104.     ulAddress = (uint32_t)task;             // 任务入口地址
  105.     *(++stk)  = (OS_STK)(ulAddress & 0xFF); // 任务入口地址低字节
  106.     *(++stk)  = (OS_STK)(ulAddress >> 8);   // 任务入口地址高字节
  107.     *(++stk)  = 0x00;                       // ACC
  108.     ulAddress = (uint32_t)p_arg;            // 任务参数地址
  109.     *(++stk)  = (OS_STK)(ulAddress & 0xFF); // DPL
  110.     *(++stk)  = (OS_STK)(ulAddress >> 8);   // DPH
  111.     *(++stk)  = (OS_STK)(ulAddress >> 16);  // B
  112.     *(++stk)  = 0x00;                       // R2
  113.     *(++stk)  = 0x00;                       // R3
  114.     *(++stk)  = 0x00;                       // R4
  115.     *(++stk)  = 0x00;                       // R5
  116.     *(++stk)  = 0x00;                       // R6
  117.     *(++stk)  = 0x00;                       // R7
  118.     *(++stk)  = 0x00;                       // R0
  119.     *(++stk)  = 0x00;                       // R1
  120.     *(++stk)  = 0x00;                       // PSW
  121.     *(++stk)  = 0x00;                       // BP
  122.     *ptos     = (OS_STK)(stk - ptos);       // 任务栈长度
  123.     return ptos;
  124. }
  125. /*6. ========== ========== ========== ========== ========== ========== ========== ==========*/
  126. void OSStartHighRdy(void)
  127. {
  128.     OSTaskSwHook();
  129.     OSRunning = OS_TRUE;
  130.     COPY_XRAM_TO_STACK();
  131.     POP_ALL_REGISTERS();
  132.     __asm__("ret");
  133. }
  134. /*7. ========== ========== ========== ========== ========== ========== ========== ==========*/
  135. void OSCtxSw(void)
  136. {
  137.     PSH_ALL_REGISTERS();
  138.     COPY_STACK_TO_XRAM();
  139.     OSTaskSwHook();
  140.     OSTCBCur  = OSTCBHighRdy;
  141.     OSPrioCur = OSPrioHighRdy;
  142.     COPY_XRAM_TO_STACK();
  143.     POP_ALL_REGISTERS();
  144.     __asm__("ret");
  145. }
  146. /*8. ========== ========== ========== ========== ========== ========== ========== ==========*/
  147. void OSIntCtxSw(void)
  148. {
  149.     SP = SP - 4;
  150.     COPY_STACK_TO_XRAM();
  151.     OSTaskSwHook();
  152.     OSTCBCur  = OSTCBHighRdy;
  153.     OSPrioCur = OSPrioHighRdy;
  154.     COPY_XRAM_TO_STACK();
  155.     POP_ALL_REGISTERS();
  156.     __asm__("reti");
  157. }
  158. /*9. ========== ========== ========== ========== ========== ========== ========== ==========*/
  159. void OSTimerInit(void)
  160. {
  161. #if (OS_TICK_TIMER_1T == 1)
  162.     AUXR |= 0x80; // 定时器时钟1T模式
  163.     const uint16_t usTick = OS_CPU_MAIN_CLOCK / OS_TICKS_PER_SEC;
  164. #else
  165.     AUXR &= 0x7F; // 定时器时钟12T模式
  166.     const uint16_t usTick = OS_CPU_MAIN_CLOCK / OS_TICKS_PER_SEC / 12;
  167. #endif
  168.     const uint16_t usInit = 0x10000 - usTick;
  169.     TMR0 = usInit; // 定时器自动重装初值
  170.     TMOD = 0x00;   // 定时器0工作在模式0
  171.     ET0  = 1;      // 使能定时器中断
  172.     TR0  = 1;      // 启动定时器
  173.     EA   = 1;      // 模式3无需打开总中断
  174. }
  175. void OSTimer0ISR(void) __interrupt(1) __naked
  176. {
  177.     PSH_ALL_REGISTERS();
  178.     OSIntEnter();
  179.     OSTimeTick();
  180.     OSIntExit();
  181.     POP_ALL_REGISTERS();
  182.     __asm__("reti");
  183. }
复制代码


第一部分:定义保存和恢复寄存器的宏,这个没啥好说的,主要是需要注意寄存器顺序,可参考“移植准备”章节。
第二部分:定义RAM/IRAM和XRAM互相复制的宏。这里有两点需要注意:1、__start__stack是sdcc内部变量,用于表示栈的起始位置,注意它会比SP初始值大1。2、而复制到XRAM中的栈数据,下标0是栈的长度信息,从下标1开始才是真正的数据。
第三部分:RAM/IRAM和XRAM互相复制的宏所要用到的变量。
第四部分:Hook方法。全部可以为空实现,但这里在OSInitHookEnd中启动了定时器。
第五部分:任务栈空间初始化。这里有两点要注意:1、注意初始化的寄存器顺序,参考“移植准备”章节,但比“移植准备”多了任务入口地址。2、当调用方法时,若第一个参数为通用指针时,则sdcc用DPL、DPH、B来传递,其中DTPR中为指针的地址,而B中为指针的类型。因此传递给任务的void*参数需要放在DPL、DPH和B中。
第六、第七、第八部分:移植的具体实现。主要注意第八部分OSIntCtxSw函数中的SP = SP - 4;,这是因为该函数的调用过程是 ISR -> OSIntExit() -> OSIntCtxSw(),需要将调用OSIntExit()和OSIntCtxSw()时入栈的返回地址去掉,而一个返回地址是2字节,所以这里减了4。
第九部分:定时器初始化和定时器中断,没啥好说的了。
业余撸代码,专业开飞机
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:468
  • 最近打卡:2025-05-01 10:58:55

44

主题

230

回帖

2841

积分

金牌会员

积分
2841
发表于 2024-6-24 17:34:55 | 显示全部楼层
大佬,有没有兴趣研究一下platformio。 将freeRtos 上传到platformio的仓库方便一键安装使用
不争是争
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:468
  • 最近打卡:2025-05-01 10:58:55

44

主题

230

回帖

2841

积分

金牌会员

积分
2841
发表于 2024-8-22 15:56:43 | 显示全部楼层
大佬, 我把所有的文件都放在同一个目录下可以吧。 编译提示:
lib/UC_OS/src/os_cpu_c.c:148: error 20: Undefined identifier 'pxXRAMStack'
lib/UC_OS/src/os_cpu_c.c:148: error 22: Array or pointer required for '[]' operation
不争是争
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:455
  • 最近打卡:2025-05-01 08:13:06
已绑定手机

27

主题

341

回帖

1687

积分

金牌会员

机长

积分
1687
发表于 2024-8-23 09:45:27 | 显示全部楼层
大*** 发表于 2024-8-22 15:56
大佬, 我把所有的文件都放在同一个目录下可以吧。 编译提示:
lib/UC_OS/src/os_cpu_c.c:148: error 20: U ...

你这是不是哪里整岔了吧,看上层目录你是想搞μC/OS,但报错的内容是FreeRTOS的,这两可不能混一起用……
业余撸代码,专业开飞机
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:468
  • 最近打卡:2025-05-01 10:58:55

44

主题

230

回帖

2841

积分

金牌会员

积分
2841
发表于 2024-8-23 14:24:51 | 显示全部楼层
hsr*** 发表于 2024-8-23 09:45
你这是不是哪里整岔了吧,看上层目录你是想搞μC/OS,但报错的内容是FreeRTOS的,这两可不能混一起用…… ...

论坛下载的这个文件 :STC8x8K-uCOSii
不争是争
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:455
  • 最近打卡:2025-05-01 08:13:06
已绑定手机

27

主题

341

回帖

1687

积分

金牌会员

机长

积分
1687
发表于 2024-8-23 14:32:59 | 显示全部楼层
大*** 发表于 2024-8-23 14:24
论坛下载的这个文件 :STC8x8K-uCOSii

你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量
业余撸代码,专业开飞机
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:468
  • 最近打卡:2025-05-01 10:58:55

44

主题

230

回帖

2841

积分

金牌会员

积分
2841
发表于 2024-8-23 15:23:04 | 显示全部楼层
hsr*** 发表于 2024-8-23 14:32
你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量

嗯确实两个都下载了。我重新搞一个工程试一下
不争是争
回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-5-2 04:23 , Processed in 0.202293 second(s), 141 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表