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

本文最初写于 2012-04-19 于 sohu 博客,这次博客搬家一起搬到这里来。

版权所有,转载请注明出处。

 

μ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 了一系列的基本数据类型和几个宏定义。具体代码如下:

/*
******************************************************************************
*                              DATA TYPES
******************************************************************************
*/

typedef unsigned char  BOOLEAN;
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 float          FP32;        /* Single precision floating point */
typedef double         FP64;        /* Double precision floating point */


#define BYTE    INT8S        /* Define data types for backward compatibility..*/
typedef char    SBYTE;
#define UBYTE   INT8U        /* ... to uC/OS V1.xx */
#define WORD    INT16S
#define UWORD   INT16U
#define LONG    INT32S
#define ULONG   INT32U

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

/*
******************************************************************************
*                               CONSTANTS
******************************************************************************
*/

#ifndef  FALSE
#define  FALSE    0
#endif

#ifndef  TRUE
#define  TRUE     1
#endif


基本数据类型的长度查编译器手册都有详细的说明。
有点难度的是堆栈和状态寄存器,需要查68HCS12内核手册,虽然68HCS12内核是16位的内核,但是堆栈是以字节为单位的,所以有如下代码:
typedef unsigned char  OS_STK;

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

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

#define  OS_CRITICAL_METHOD    3

#if      OS_CRITICAL_METHOD == 3
OS_CPU_SR OSCPUSaveSR(void);           
void OSCPURestoreSR(OS_CPU_SR os_cpu_sr);        
#endif

#if      OS_CRITICAL_METHOD == 1
#define  OS_ENTER_CRITICAL() __asm sei;
#define  OS_EXIT_CRITICAL()  __asm cli; 
#endif

#if      OS_CRITICAL_METHOD == 2 
#define  OS_ENTER_CRITICAL() __asm  pshc; __asm  sei;
#define  OS_EXIT_CRITICAL()   __asm  pulc;
#endif

#if      OS_CRITICAL_METHOD == 3 
#define  OS_ENTER_CRITICAL()  ( cpu_sr = OSCPUSaveSR() )    /* Disable interrupts                     */
#define  OS_EXIT_CRITICAL()   ( OSCPURestoreSR(cpu_sr) )    /* Enable  interrupts                     */


上述代码中虽然列出了三种对临界区的不同处理方法,但实际只有第三种是正确的。
第一种方法直接开关中断,这种方法的问题在于退出临界区后中断就被强制性的打开了,及时在进入临界区前中断是关着的。在任务级代码中这样做没有太大的问题,因为在执行任务代码是中断基本都是打开的,中断几乎只有在临界区中才是关闭的。但是在中断处理函数中情况就不是这样的了,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 语言实现,可以如下:

OS_CPU_SR OSCPUSaveSR( void ) 
{
__asm 
 {
          tfr ccr, b      // copy the value of CCR to the register B
          sei             // Disable interrupts
 }
}

void OSCPURestoreSR(OS_CPU_SR os_cpu_sr) 
{
__asm 
 {
   tfr  b, ccr  //B contains the CCR value to restore, move to CCR
 }
}


想要理解上面代码,除了要知道汇编指令 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   ; It's 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 还有几行代码:

#define  OS_TASK_SW() __asm swi;

#define  OS_STK_GROWTH    /* Define stack growth: 1 = Down, 0 = Up  */

#pragma CODE_SEG NON_BANKED
void          OSStartHighRdy          (void);
void          OSIntCtxSw              (void);
void          OSCtxSw                 (void);
#pragma CODE_SEG DEFAULT


都比较简单,OS_TASK_SW()将操作系统使用的中断指定为 SWI 中断,也就是 software interrupt。
堆栈生长方向为向下生长。

 

至此,OS_CPU.H 就写完了。

剩下的内容明天继续。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 飞思卡尔 HCS12 系列单片机的 Bootloader 是一种程序,它允许用户通过特定的接口对单片机进行编程和调试,而不需要使用外部的编程器或者仿真器。Bootloader 可以通过串口、CAN、USB 等多种通信方式与单片机通信,通过读取或者写入特定的内存区域,实现对单片机程序的更新或者下载。 Bootloader 主要由以下几个部分组成: 1. 启动代码:负责初始化芯片、设置中断向量表、判断是否需要进入 Bootloader 模式等。 2. 通信协议:定义 Bootloader 与 PC 或者其他设备之间的通信协议,例如帧格式、数据传输方式等。 3. 数据处理程序:负责接收 PC 发送的数据,进行校验和解析,根据数据类型进行相应的处理。 4. 程序烧录程序:将接收到的数据写入指定的内存区域,实现程序的更新或者下载。 使用 Bootloader 有以下优点: 1. 省去外部编程器或者仿真器的成本和复杂性,提高开发效率。 2. 允许在线更新程序,方便远程维护。 3. 支持多种通信方式,具有较强的灵活性。 4. 可以通过修改 Bootloader 程序,实现自定义的功能,例如加密、签名等。 需要注意的是,由于 Bootloader 是一种开发人员自行编写的程序,因此需要谨慎设计和实现,避免出现安全漏洞或者其他问题。 ### 回答2: freescale飞思卡尔 hcs12 系列单片机是一种常用于嵌入式系统中的微处理器,其具有较高的性能和可信性。然而,在实际应用中,由于各种原因,可能需要在运行中更新单片机程序,这时候就需要用到bootloader。 bootloader是指系统启动的一段程序,其目的是负责加载操作系统或应用程序,同时还可以提供其他的系统维护和管理功能。在嵌入式系统中,bootloader可以用来升级设备的固件或软件版本。 freescale飞思卡尔 hcs12 系列单片机bootloader的主要作用是提供一种方便、安全的方式来更新单片机的程序,而不必拆下整个系统或者芯片。这样可以大大减少维护所需的时间和成本。 hcs12系列单片机bootloader的实现涉及到许多方面的知识,包括内存的分配、程序存储器的管理、通信协议等。在实现时,需要考虑到不同的应用场景,选择适合的通信方式和协议,同时还要保证系统的安全性,避免因误操作或人为破坏而带来的风险。 在使用hcs12系列单片机bootloader时,需要注意以下几个方面: 1.选择合适的bootloader协议。 常见的bootloader协议有IAP、CAN、UART等,每种协议都有自己的特点和适用范围。在选择时,需要根据具体情况进行评估和比较。 2.确保程序存储器的安全性。 程序存储器是单片机程序的心所在,如果被恶意修改或破坏,可能会导致系统运行不正常。因此,需要采取一些措施来保护程序存储器的安全性,例如加密、校验等。 3.保证通信的稳定性和可靠性。 如果通信不稳定或者出现错误,可能会导致程序烧录失败或者出现其他问题。因此,需要进行充分的测试和验证,确保通信的稳定性和可靠性。 总之,hcs12系列单片机bootloader是嵌入式系统中非常重要的组成部分,其实现涉及到多个方面的知识和技能。只有在充分理解和掌握相应的技术和方法之后,才能有效地应用到实际的开发和维护中。 ### 回答3: Freescale飞思卡尔hcs12系列单片机bootloader是一种运行在芯片上的小程序,它可以加载和升级单片机的程序代码。它的作用相当于计算机的BIOS,它使得单片机可以通过外部接口更新程序代码,而无需通过编程器进行操作,这使得单片机的开发和维护变得更加方便。 Freescale飞思卡尔hcs12系列单片机bootloader由两个部分组成,一个是引导程序,另一个是应用程序。引导程序是在芯片制造商出厂时预先生产的程序,它负责从外部接口读取应用程序,并将其加载到单片机中。应用程序则是用户编写的程序,它可以通过外部接口更新或升级,这使得单片机功能可以不断扩展和迭代。 Freescale飞思卡尔hcs12系列单片机bootloader支持多种通信接口,包括串口、CAN总线、以太网等。用户可以根据自己的需求选择合适的接口进行通信。通信时,用户需要发送特定的命令和数据,以达到读取或写入设备的目的。这些命令和数据需要符合Freescale飞思卡尔hcs12系列单片机bootloader的协议规定,否则将无法完成通信。 总之,Freescale飞思卡尔hcs12系列单片机bootloader是一种非常重要的工具,它使得单片机的开发和维护变得更加方便。了解和掌握这一工具的使用方法,对于单片机开发人员来说是非常重要的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值