hsrzq 发表于 2024-6-16 18:05:57

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

本帖最后由 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#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif

#include "STC8x8K.h"

/* 1. ========== ========== ========== ========== ========== ========== ========== ==========*/
typedef unsigned char BOOLEAN;   /* 8-bitboolean or logical true/false (TRUE/FALSE)   */
typedef unsigned char INT8U;       /* Unsigned8 bit quantity                            */
typedef signed char INT8S;         /* Signed    8 bit quantity                            */
typedef unsigned int INT16U;       /* Unsigned 16 bit quantity                            */
typedef signed int INT16S;         /* Signed   16 bit quantity                            */
typedef unsigned long INT32U;      /* Unsigned 32 bit quantity                            */
typedef signed long INT32S;      /* Signed   32 bit quantity                            */
typedef unsigned long long INT64U; /* Unsigned 64 bit quantity                            */
typedef signed long long INT64S;   /* Signed   64 bit quantity                            */
typedef float FP32;                /* Single precision floating point                     */

typedef unsigned char OS_STK;    /* Each stack entry is 8-bit wide                      */
typedef unsigned char OS_CPU_SR; /* Define size of CPU status register (IE = 8 bits)   */

/*2. ========== ========== ========== ========== ========== ========== ========== ==========*/
#define OS_CRITICAL_METHOD 1

#if OS_CRITICAL_METHOD == 1
#define OS_ENTER_CRITICAL() \
    do {                  \
      EA = 0;             \
    } while (0)
#define OS_EXIT_CRITICAL() \
    do {                   \
      EA = 1;            \
    } while (0)
#elif OS_CRITICAL_METHOD == 2
#error "OS_CRITICAL_METHOD == 2 is NOT supported."
#elif OS_CRITICAL_METHOD == 3
#error "OS_CRITICAL_METHOD == 3 is NOT supported."
#endif

/*3. ========== ========== ========== ========== ========== ========== ========== ==========*/
#define OS_STK_GROWTH 0 /* Stack grows from LOW to HIGH memory on MCS-51 */
#define OS_TASK_SW()OSCtxSw()

/*4. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSStartHighRdy(void) __naked;
void OSCtxSw(void) __naked;
void OSIntCtxSw(void) __naked;

/*5. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSTimerInit(void);
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的中断入口方法。

Qinluoyao 发表于 2024-6-16 19:53:52

{:4_197:}

soma 发表于 2024-6-16 20:08:48

如果能介绍怎么移植就更好了。楼主威武啊

hsrzq 发表于 2024-6-17 00:33:11


os_cpu_c.c
#include "includes.h"

/*1. ========== ========== ========== ========== ========== ========== ========== ==========*/
#define PSH_ALL_REGISTERS()   \
    do {                      \
      __asm__("pushacc"); \
      __asm__("pushdpl"); \
      __asm__("pushdph"); \
      __asm__("pushb");   \
      __asm__("pushar2"); \
      __asm__("pushar3"); \
      __asm__("pushar4"); \
      __asm__("pushar5"); \
      __asm__("pushar6"); \
      __asm__("pushar7"); \
      __asm__("pushar0"); \
      __asm__("pushar1"); \
      __asm__("pushpsw"); \
      __asm__("clr   psw"); \
      __asm__("push_bp"); \
    } while (0)

#define POP_ALL_REGISTERS()   \
    do {                      \
      __asm__("pop   _bp"); \
      __asm__("pop   psw"); \
      __asm__("pop   ar1"); \
      __asm__("pop   ar0"); \
      __asm__("pop   ar7"); \
      __asm__("pop   ar6"); \
      __asm__("pop   ar5"); \
      __asm__("pop   ar4"); \
      __asm__("pop   ar3"); \
      __asm__("pop   ar2"); \
      __asm__("pop   b");   \
      __asm__("pop   dph"); \
      __asm__("pop   dpl"); \
      __asm__("pop   acc"); \
    } while (0)

/*2. ========== ========== ========== ========== ========== ========== ========== ==========*/
#define COPY_STACK_TO_XRAM()                           \
    do {                                                 \
      /* stkPTOS = (OS_STK *) (__start__stack - 1); */ \
      __asm__("mov _stkPTOS, #(__start__stack - 1)");\
      stkXRAM= OSTCBCur->OSTCBStkPtr;                \
      stkSize= SP - (OS_STK)stkPTOS;               \
      *stkXRAM = stkSize;                              \
      while (stkSize--) {                              \
            *(++stkXRAM) = *(++stkPTOS);               \
      }                                                \
    } while (0)

#define COPY_XRAM_TO_STACK()                           \
    do {                                                 \
      /* stkPTOS = (OS_STK *) (__start__stack - 1); */ \
      __asm__("mov _stkPTOS, #(__start__stack - 1)");\
      stkXRAM = OSTCBCur->OSTCBStkPtr;               \
      stkSize = pxXRAMStack;                        \
      while (stkSize--) {                              \
            *(++stkPTOS) = *(++stkXRAM);               \
      }                                                \
      SP = (OS_STK)stkPTOS;                            \
    } while (0)

/*3. ========== ========== ========== ========== ========== ========== ========== ==========*/
static uint8_t stkSize;
static OS_STK *stkPTOS;
static OS_STK *stkXRAM;

/*4. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSInitHookBegin(void)
{
}

void OSInitHookEnd(void)
{
    OSTimerInit();
}

void OSTaskIdleHook(void)
{
}

void OSTCBInitHook(OS_TCB *ptcb)
{
}

void OSTaskCreateHook(OS_TCB *ptcb)
{
}

void OSTaskReturnHook(OS_TCB *ptcb)
{
}

void OSTaskSwHook(void)
{
}

#if (OS_TIME_TICK_HOOK_EN > 0)
void OSTimeTickHook(void)
{
}
#endif

#if OS_CPU_HOOKS_EN > 0
void OSTaskStatHook(void)
{
}
#endif

/*5. ========== ========== ========== ========== ========== ========== ========== ==========*/
OS_STK *OSTaskStkInit(void (*task)(void *p_arg), void *p_arg, OS_STK *ptos, INT16U opt)
{
    opt;
    uint32_t ulAddress;
    OS_STK *stk = ptos;

    ulAddress = (uint32_t)task;             // 任务入口地址
    *(++stk)= (OS_STK)(ulAddress & 0xFF); // 任务入口地址低字节
    *(++stk)= (OS_STK)(ulAddress >> 8);   // 任务入口地址高字节
    *(++stk)= 0x00;                     // ACC
    ulAddress = (uint32_t)p_arg;            // 任务参数地址
    *(++stk)= (OS_STK)(ulAddress & 0xFF); // DPL
    *(++stk)= (OS_STK)(ulAddress >> 8);   // DPH
    *(++stk)= (OS_STK)(ulAddress >> 16);// B
    *(++stk)= 0x00;                     // R2
    *(++stk)= 0x00;                     // R3
    *(++stk)= 0x00;                     // R4
    *(++stk)= 0x00;                     // R5
    *(++stk)= 0x00;                     // R6
    *(++stk)= 0x00;                     // R7
    *(++stk)= 0x00;                     // R0
    *(++stk)= 0x00;                     // R1
    *(++stk)= 0x00;                     // PSW
    *(++stk)= 0x00;                     // BP
    *ptos   = (OS_STK)(stk - ptos);       // 任务栈长度

    return ptos;
}

/*6. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSStartHighRdy(void)
{
    OSTaskSwHook();
    OSRunning = OS_TRUE;
    COPY_XRAM_TO_STACK();
    POP_ALL_REGISTERS();
    __asm__("ret");
}

/*7. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSCtxSw(void)
{
    PSH_ALL_REGISTERS();
    COPY_STACK_TO_XRAM();
    OSTaskSwHook();
    OSTCBCur= OSTCBHighRdy;
    OSPrioCur = OSPrioHighRdy;
    COPY_XRAM_TO_STACK();
    POP_ALL_REGISTERS();
    __asm__("ret");
}

/*8. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSIntCtxSw(void)
{
    SP = SP - 4;

    COPY_STACK_TO_XRAM();
    OSTaskSwHook();
    OSTCBCur= OSTCBHighRdy;
    OSPrioCur = OSPrioHighRdy;
    COPY_XRAM_TO_STACK();
    POP_ALL_REGISTERS();
    __asm__("reti");
}

/*9. ========== ========== ========== ========== ========== ========== ========== ==========*/
void OSTimerInit(void)
{
#if (OS_TICK_TIMER_1T == 1)
    AUXR |= 0x80; // 定时器时钟1T模式
    const uint16_t usTick = OS_CPU_MAIN_CLOCK / OS_TICKS_PER_SEC;
#else
    AUXR &= 0x7F; // 定时器时钟12T模式
    const uint16_t usTick = OS_CPU_MAIN_CLOCK / OS_TICKS_PER_SEC / 12;
#endif
    const uint16_t usInit = 0x10000 - usTick;

    TMR0 = usInit; // 定时器自动重装初值
    TMOD = 0x00;   // 定时器0工作在模式0
    ET0= 1;      // 使能定时器中断
    TR0= 1;      // 启动定时器
    EA   = 1;      // 模式3无需打开总中断
}

void OSTimer0ISR(void) __interrupt(1) __naked
{
    PSH_ALL_REGISTERS();
    OSIntEnter();
    OSTimeTick();
    OSIntExit();
    POP_ALL_REGISTERS();
    __asm__("reti");
}


第一部分:定义保存和恢复寄存器的宏,这个没啥好说的,主要是需要注意寄存器顺序,可参考“移植准备”章节。
第二部分:定义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。
第九部分:定时器初始化和定时器中断,没啥好说的了。

大锤子 发表于 2024-6-24 17:34:55

大佬,有没有兴趣研究一下platformio。 将freeRtos 上传到platformio的仓库方便一键安装使用

大锤子 发表于 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

hsrzq 发表于 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的,这两可不能混一起用……

大锤子 发表于 2024-8-23 14:24:51

hsrzq 发表于 2024-8-23 09:45
你这是不是哪里整岔了吧,看上层目录你是想搞μC/OS,但报错的内容是FreeRTOS的,这两可不能混一起用…… ...

论坛下载的这个文件 :STC8x8K-uCOSii

hsrzq 发表于 2024-8-23 14:32:59

大锤子 发表于 2024-8-23 14:24
论坛下载的这个文件 :STC8x8K-uCOSii

你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量

大锤子 发表于 2024-8-23 15:23:04

hsrzq 发表于 2024-8-23 14:32
你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量

嗯确实两个都下载了。我重新搞一个工程试一下
页: [1] 2
查看完整版本: 【再造轮子】FreeRTOS、μC/OS-ⅱ在开源工具SDCC上的移植过程详解