【再造轮子】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的中断入口方法。 {:4_197:} 如果能介绍怎么移植就更好了。楼主威武啊
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。
第九部分:定时器初始化和定时器中断,没啥好说的了。 大佬,有没有兴趣研究一下platformio。 将freeRtos 上传到platformio的仓库方便一键安装使用 大佬, 我把所有的文件都放在同一个目录下可以吧。 编译提示:
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 大锤子 发表于 2024-8-22 15:56
大佬, 我把所有的文件都放在同一个目录下可以吧。 编译提示:
lib/UC_OS/src/os_cpu_c.c:148: error 20: U ...
你这是不是哪里整岔了吧,看上层目录你是想搞μC/OS,但报错的内容是FreeRTOS的,这两可不能混一起用…… hsrzq 发表于 2024-8-23 09:45
你这是不是哪里整岔了吧,看上层目录你是想搞μC/OS,但报错的内容是FreeRTOS的,这两可不能混一起用…… ...
论坛下载的这个文件 :STC8x8K-uCOSii 大锤子 发表于 2024-8-23 14:24
论坛下载的这个文件 :STC8x8K-uCOSii
你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量 hsrzq 发表于 2024-8-23 14:32
你肯定两个都下了,并且还把两个搞混了,pxXRAMStack是FreeRTOS中的变量
嗯确实两个都下载了。我重新搞一个工程试一下
页:
[1]
2