本帖最后由 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-bit boolean or logical true/false (TRUE/FALSE) */
- typedef unsigned char INT8U; /* Unsigned 8 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的中断入口方法。 |