您的位置 首页 动态

μC/OS-II 移植笔记 1(FreeScale 68HCS12 核单片机)

mu;C/OS-II移植笔记1(移植到FreeScale68HCS12核单片机,SmallMemoryModel)最近闲暇下来,花了些时间研究了如何将mu;C/OS-II…

μC/OS-II移植笔记 1(移植到FreeScale 68HCS12 核单片机,Small Memory Model)

最近空闲下来,花了些时刻研讨了如何将 μC/OS-II 移植到 FreeScale 68HCS12 内核的单片机。其实这个作业前年做过一次,其时是在网上找的附近的移植代码(68HC11核,Bank Memory Model,METROWERKS 编译器)自己做了些修正,内核现已跑起来了,可是在跑串口测验程序(ESBB书上的那个串口模块)时,程序运转一段时刻就会跑飞。其时调试了良久也没有找到问题。这次便是接着前次的作业持续深化的往下做,消除过错。其实前年调试时就现已模糊的想到了过错或许的当地,仅仅当年对 68HCS12 内核还有 CodeWarrior IDE 顺便的 C 编译器、汇编器了解的有限,尤其是对编译器的 C Calling Convention、还有 C 编译器对代码中内嵌汇编的处理简直彻底不知,这种水平下调试不出过错也是天经地义的。
这次咱们有了曾经的根底,这次着眼点直接就放在了编译器对我写的移植代码的处理上,在汇编言语的层面上对移植代码进行了分析,很快(其实也不算快了,花了我整整 2 天时刻)就找到了问题所在,并给出了一个开始的解决方案。现在的移植代码谈不上完美,但至少是正确的。
1. μC/OS-II 版别的挑选
这次的移植代码首要针对 μC/OS-II V2.52,2.52 之后的版别对移植代码的要求大体相同,因而这个移植代码稍加修正就应该能在新版别上运转,而且我信任修正的难度应该很小。 μC/OS-III还没有研讨过,因而移植代码是否合适 μC/OS-III 我就不得而知了。
之所以挑选μC/OS-II V2.52这个版别首要依据两个方面的考虑。首要,Jean J.Labrosse写的那本大名鼎鼎的《MicroC/OS-II The Real-Time Kernel Second Edition》便是依据这个版别写成的,移植过程中遇到问题至少能够翻翻书。别的,这个版别的确也称得上经典,每一行代码都经过经得起琢磨。因而,我挑选了这个版别。
2. 移植代码详解
μC/OS-II 移植首要需求重写 3 个代码文件:
OS_CPU.H
OS_CPU_C.C
OS_CPU_A.S
下面就对移植代码进行详细的阐明。开发环境选用:CodeWarrior Development Studio V5.9.0
2.1 OS_CPU.H
OS_CPU.H 中的代码首要有两部分,榜首部分 typedef 了一系列的根本数据类型和几个宏界说。详细代码如下:

  1. /*
  2. ******************************************************************************
  3. *DATATYPES
  4. ******************************************************************************
  5. */
  6. typedefunsignedcharBOOLEAN;
  7. typedefunsignedcharINT8U;/*Unsigned8bitquantity*/
  8. typedefsignedcharINT8S;/*Signed8bitquantity*/
  9. typedefunsignedintINT16U;/*Unsigned16bitquantity*/
  10. typedefsignedintINT16S;/*Signed16bitquantity*/
  11. typedefunsignedlongINT32U;/*Unsigned32bitquantity*/
  12. typedefsignedlongINT32S;/*Signed32bitquantity*/
  13. typedeffloatFP32;/*Singleprecisionfloatingpoint*/
  14. typedefdoubleFP64;/*Doubleprecisionfloatingpoint*/
  15. #defineBYTEINT8S/*Definedatatypesforbackwardcompatibility..*/
  16. typedefcharSBYTE;
  17. #defineUBYTEINT8U/*…touC/OSV1.xx*/
  18. #defineWORDINT16S
  19. #defineUWORDINT16U
  20. #defineLONGINT32S
  21. #defineULONGINT32U
  22. typedefunsignedcharOS_STK;/*Eachstackentryis8-bitwide*/
  23. typedefunsignedcharOS_CPU_SR;/*DefinesizeofCPUstatusregister(CCR=8bits)*/
  24. /*
  25. ******************************************************************************
  26. *CONSTANTS
  27. ******************************************************************************
  28. */
  29. #ifndefFALSE
  30. #defineFALSE0
  31. #endif
  32. #ifndefTRUE
  33. #defineTRUE1
  34. #endif

根本数据类型的长度查编译器手册都有详细的阐明。
有点难度的是仓库和状况寄存器,需求查68HCS12内核手册,尽管68HCS12内核是16位的内核,可是仓库是以字节为单位的,所以有如下代码:
typedef unsigned char OS_STK;

68HCS12内核的状况寄存器称为 CCR,也是8位了,因而:
typedef unsigned char OS_CPU_SR;

OS_CPU.H 中还包括对临界区的处理:

  1. #defineOS_CRITICAL_METHOD3
  2. #ifOS_CRITICAL_METHOD==3
  3. OS_CPU_SROSCPUSaveSR(void);
  4. voidOSCPURestoreSR(OS_CPU_SRos_cpu_sr);
  5. #endif
  6. #ifOS_CRITICAL_METHOD==1
  7. #defineOS_ENTER_CRITICAL()__asmsei;
  8. #defineOS_EXIT_CRITICAL()__asmcli;
  9. #endif
  10. #ifOS_CRITICAL_METHOD==2
  11. #defineOS_ENTER_CRITICAL()__asmpshc;__asmsei;
  12. #defineOS_EXIT_CRITICAL()__asmpulc;
  13. #endif
  14. #ifOS_CRITICAL_METHOD==3
  15. #defineOS_ENTER_CRITICAL()(cpu_sr=OSCPUSaveSR())/*Disableinterrupts*/
  16. #defineOS_EXIT_CRITICAL()(OSCPURestoreSR(cpu_sr))/*Enableinterrupts*/

上述代码中尽管列出了三种对临界区的不同处理办法,但实践只要第三种是正确的。
榜首种办法直接开关中止,这种办法的问题在于退出临界区后中止就被强制性的翻开了,及时在进入临界区前中止是关着的。在使命级代码中这样做没有太大的问题,由于在执行使命代码是中止根本都是翻开的,中止简直只要在临界区中才是封闭的。可是在中止处理函数中状况就不是这样的了,68HCS12 内核在进入中止处理函数后中止是封闭的,中止处理函数中要调用 OSIntExit(),OSIntExit()中是有临界区的,一退出临界区就会导致中止翻开,CPU 处理新的中止,也便是构成所谓的中止嵌套。而中止嵌套是咱们不期望发生的,由于答应中止嵌套并不能明显提高系统的功能,还会导致各个使命的仓库运用量加大,,关于内存严重的单片机来说,肯定弊大于利。因而,在我的移植代码中不答应中止嵌套,也就否掉了榜首种临界区的处理办法。
第二种办法看似很好,进入临界区时先将 CCR 的值存入仓库然后封闭中止,退出临界区时直接将仓库的内容康复到 CCR。看似很完美的解决办法,实践上却行不通。C 编译器要运用仓库指针的地址来寻址局部变量,而汇编查办 pshc 却改动了仓库指针的指向,导致对局部变量的拜访发生了错位。
要想阐明这个问题还要从 C 编译器对局部变量的处理办法说起,我运用的 C 编译器将局部变量放到仓库上,运用仓库指针(SP)直接寻址局部变量。假如仓库指针指向的地址变了,拜访局部变量时就要出问题。下面用个比方来阐明:

volatile char a = 1;
volatile char b = 2;

__asm pshc; __asm sei;

a = 3;
b = 4;

__asm pulc;

将其转化为汇编代码后如下:

PSHD
volatile char a = 1;
LDAB #1
STAB 1,SP
volatile char b = 2;
LSLB
STAB 0,SP

__asm pshc; __asm sei;
PSHC
SEI
a = 3;
INCB
STAB 1,SP
b = 4;
INCB
STAB 0,SP
__asm pulc;
PULC

PSHD 指令调整仓库指针,在仓库上空出 2 个字节寄存 a 和 b 的值。a 的地址为[SP+1],b 的地址为[SP],然后给 a 和 b 赋初值。PSHC 首要调整仓库指针(SP=SP-1),然后将 CCR 寄存器的值存入仓库,这儿需求留意的是68HCS12核的仓库是倒生仓库,实栈顶(也便是说SP指向的是有数据的当地,而不是个空位),而老的68HC11内核是虚栈顶的(SP指向的是个空位)。这时 [SP] 中存的是 CCR 的值, [SP+1]指向 b,[SP+2]指向a。因而, STAB, 0,SP 将本来存的 CCR 的值改成了 4, b 的值却没有任何改动,阐明这款编译器无法感知仓库的变化生成正确的代码。因而第二种办法会导致过错的成果,不能选用。
这儿多说两句,了解 x86 系统的读者能够将 68HCS12 核与 x86 内核做个比照。这两个核都是冯诺依曼结构系统,杂乱指令集,从某种意义上能够说这两种内核具有某种神似。在 x86 内核上大多数编译器也是将局部变量寄存到仓库上,可是不同的是拜访局部变量时用的是 BSP 寄存器而不直接运用 ESP 寄存器,因而在 x86 内核上用第二种办法处理临界区是没有问题的,而且能够认为是一种相当好的办法。从这也能够看出来寄存器多一些的确是有优点的。

第三种办法是完成两个函数 OSCPUSaveSR 和 OSCPURestoreSR。尽管这样会多些函数调用发生的额定开支,却没有了前两种办法的问题。这两个函数详细的完成能够放到OS_CPU_A.S 中,也能够在 OS_CPU_C.C。

假如用 C 言语完成,能够如下:

  1. OS_CPU_SROSCPUSaveSR(void)
  2. {
  3. __asm
  4. {
  5. tfrccr,b//copythevalueofCCRtotheregisterB
  6. sei//Disableinterrupts
  7. }
  8. }
  9. voidOSCPURestoreSR(OS_CPU_SRos_cpu_sr)
  10. {
  11. __asm
  12. {
  13. tfrb,ccr//BcontainstheCCRvaluetorestore,movetoCCR
  14. }
  15. }

想要了解上面代码,除了要知道汇编指令 tfr 和 sei 的意义。还要知道所谓的 C 言语调用约好,关于当时代码来说需求知道 C 言语编译器运用何种办法传递函数的参数和返回值。 这些常识在 编译器顺便的文档《S12(X)Build Tools Reference Manual》能够查到。我这儿只介绍直接相关的几条,其他的请自己查阅手册。

我所选用的编译器对参数固定的 C 函数选用 PASCAL 调用约好,参数选用仓库传递,传递次序为从左到右顺次入栈,假如最终一个参数小于 4 个字节则最终一个参数选用寄存器传递。1 字节的参数寄存到寄存器 B 中,OSCPURestoreSR 便是这种状况。函数的返回值假如也是 1 个字节,那么也经过 寄存器 B 传出来,OSCPUSaveSR 便是这种状况。关于这两个函数我就说这么多了。

假如想直接写汇编代码的话也很简单,下面是比方:

xdef OSCPUSaveSR
xdef OSCPURestoreSR
OSCPUSaveSR:
tfr ccr,b ; Its assumed that 8-bit return value is in register B
sei ; Disable interrupts
rts ; Return to caller with D containing the previous CCR

OSCPURestoreSR:
tfr b, ccr ; B contains the CCR value to restore, move to CCR
rts

和上面 C 函数的代码简直相同,没什么可介绍的了。

别的多说一句,我们或许觉得上面的代码能够直接写成内联汇编查办,就不必函数调用了。比方下面的代码:

#define OS_ENTER_CRITICAL() asm (“tpa; staa cpu_sr; sei”)
#define OS_EXIT_CRITICAL() asm (“ldaa cpu_sr; tap”)

上面的代码看似很好,进入临界区时将 CCR 的值放到 寄存器 a 中,然后存到 cpu_sr 中,再关中止。退出临界区是康复 CCR。问题在于 CodeWarrior Development Studio 中带的 C 编译器太弱智了,无法感知 寄存器 a 被改动了,更无法增加相应的处理代码。自己保存寄存器 a 的内容又很费事,不能直接放到仓库中,否则会影响局部变量的拜访,只能存在 C 编译器能够感知的当地。比方用下面的办法:
char tmp_a;
asm (“staa tmp_a, tpa; staa cpu_sr; sei; ldaa tmp_a”)
asm (“staa tmp_a, tpa; ldaa cpu_sr; tap; ldaa tmp_a”)

这样的开支也不小,不如直接写两个函数便利。

OS_CPU.H 还有几行代码:

  1. #defineOS_TASK_SW()__asmswi;
  2. #defineOS_STK_GROWTH/*Definestackgrowth:1=Down,0=Up*/
  3. #pragmaCODE_SEGNON_BANKED
  4. voidOSStartHighRdy(void);
  5. voidOSIntCtxSw(void);
  6. voidOSCtxSw(void);
  7. #pragmaCODE_SEGDEFAULT

都比较简单,OS_TASK_SW()将操作系统运用的中止指定为 SWI 中止,也便是 software interrupt。
仓库成长方向为向下成长。

至此,OS_CPU.H 就写完了。

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/news/dongtai/264129.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部