新闻  |   论坛  |   博客  |   在线研讨会
ucos
mayer | 2009-05-23 14:11:55    阅读:1405   发布文章

ucos

 

§4.1  LPC2106的介绍

ARM7TDMI-S是通用的32位微处理器,它具有高性能和低功耗的特性。ARM结构是基于精简指令集计算机(RISC)原理而设计的,指令集和相关的译码机制比复杂指令集计算机要简单得多,这样使用一个小的、廉价的处理器核就可实现很高的指令吞吐量和实时的中断响应。

由于使用了流水线技术,处理和存储系统的所有部分都可连续工作。通常在执行一条指令的同时对下一条指令进行译码,并将第三条指令从存储器中取出。

ARM7TDMI-S处理器使用了一个被称为Thumb的独特结构化策略,它非常适用于那些对存储器有限制或者需要较高代码密度的大批量产品的应用。

在Thumb后面一个关键的概念是“超精简指令集”。基本上,ARM7TDMI-S处理器具有两个指令集:

 ·标准32位ARM指令集;

 ·16位Thumb指令集。

Thumb指令集的16位指令长度使其可以达到标准ARM代码两倍的密度,却仍然保持ARM的大多数性能上的优势,这些优势是使用16位寄存器的16位处理器所不具备的。因为Thumb代码和ARM代码一样,在相同的32位寄存器上进行操作。

Thumb代码仅为ARM代码规模的65%,但其性能却相当于连接到16位存储器系统的相同ARM处理器性能的160%。

LPC2106带有一个支持实时仿真和跟踪的 ARM7TDMI-S CPU,并嵌入了128KB高速Flash存储器。LPC2106将 ARM7TDMI-S配置为小端(little-endian)字节顺序。128位宽度的存储器接口和独特的加速结构使32位代码能够在最大时钟频率下运行。

LPC2106主要的特征如下:

·128 KB片内 Flash程序存储器,具有ISP和IAP功能;

· Flash编程时间: 1ms可编程512字节,扇区擦除或整片擦除只需400 ms;

·64KB静态 RAM;

·向量中断控制器;

·仿真跟踪模块,支持实时跟踪;

·RealMonitor模块支持实时调试;

·标准 ARM测试/调试接口,兼容现有工具;

·极小封装:TQFP48(7 mm x 7 mm);

·双UART,其中一个带有完全的调制解调器接口;

·I2 C串行接口;

·SPI串行接口;

·两个定时器,分别具有4路捕获/比较通道;

·多达6路输出的PWM单元;

·实时时钟;

·看门狗定时器;

·通用I/O口;

·CPU操作频率可达60 MHZ;

·双电源:

      -CPU操作电压范围:1.65~1.95 V,即 1.8(1±8.3%)V;

      -I/O电压范围:3.0~3.6 V,即 3.3(1±10%)V。

·两个低功耗模式:空闲和掉电;

·通过外部中断将处理器从掉电模式中唤醒;

·外设功能可单独使能/禁止,实现功耗最优化;

·片内晶振的操作频率范围:10~25 MHZ;

·片内PLL允许CPU以最大速度运行,可以在超过整个晶振操作频率范围的情况使用。

 

§4.2  LPC2106的启动代码

启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要是为运行C语言程序提供基本运行环境,如初始化存储器系统等。ARM公司只设计内核,不自己生产芯片,只是把内核授权给其它厂商,其它厂商购买了授权且加入自己的外设后生产出各具特色的芯片。这样就促进了基于ARM处理器核的芯片多元化,但也使得每一种芯片的启动代码差别很大,不易编写出统一的启动代码。ADS(针对ARM处理器核的C语言编译器)的策略是不提供完整的启动代码,启动代码不足部分或者由厂商提供,或者自己编写。启动代码划分为4个文件:Vectors.c、Init.s、Target.c、 Target.h。Vectors.c包含异常向量表、堆栈初始化及中断服务程序与C程序的接口。Init.s包含统初始化代码,并跳转到ADS提供的初始化代码。Target.c和 Target.h包含目标板特殊的代码,包括异常处理程序和目标板初始化程序。这样做的目的是为了尽量减少汇编代码,同时把不需要修改的代码独立出来以减少错误。

§4.2.1  Vectors.c文件的编写

§4.2.1.1  中断向量表

Vectors

        LDR     PC, ResetAddr

        LDR     PC, UndefinedAddr

        LDR     PC, SWI_Addr

        LDR     PC, PrefetchAddr

        LDR     PC, DataAbortAddr

        DCD     0xb9205f80

        LDR     PC, [PC, #-0xff0]

        LDR     PC, FIQ_Addr

 

ResetAddr           DCD     Reset

UndefinedAddr       DCD     Undefined

SWI_Addr           DCD     SoftwareInterrupt

PrefetchAddr         DCD     PrefetchAbort

DataAbortAddr       DCD     DataAbort

nouse               DCD     0

IRQ_Addr           DCD     IRQ_Handler

FIQ_Addr           DCD     FIQ_Handler

 

异常是由内部或外部源产生的以引起处理器处理的一个事件。ARM处理器核支持7种类型的异常。异常出现后,CPU强制从异常类型对应的固定存储地址开始执行程序。这个固定的地址就是异常向量。向量从上到下依次为复位、未定义指令异常、软件中断、预取指令中止、预取数据中止、保留的异常、IRQ和 FIQ。IRQ向量“LDR   PC, [PC, #-0xff0]” 使用的指令与其它向量不同。在正常情况下这条指令所在地址为0X00000018。当CPU执行这条指令但还没有跳转时,PC的值为0X00000020,0X00000020减去 0X00000FF0为 0XFFFFF030,这是向量中断控制器(VIC)的特殊寄存器VICVectAddr。这个寄存器保存当前将要服务的IRQ的中断服务程序的入口,用这一条指令就可以直接跳转到需要的中断服务程序中。至于在保留的异常向量“DCD  0xb9205f80”位置填数据0xb9205f8是为了使向量表中所有的数据32位累加和为0。

§4.2.1.2  初始化CPU堆栈

 

InitStack   

        MOV     R0, LR  

        MSR     CPSR_c, #0xd2  ;设置中断模式堆栈

        LDR     SP, StackIrq  

        MSR     CPSR_c, #0xd1 ;设置快速中断模式堆栈

        LDR     SP, StackFiq 

        MSR     CPSR_c, #0xd7 ;设置中止模式堆栈

        LDR     SP, StackAbt 

        MSR     CPSR_c, #0xdb ;设置未定义模式堆栈

        LDR     SP, StackUnd 

        MSR     CPSR_c, #0xdf ;设置系统模式堆栈

        LDR     SP, StackSys

        MOV     PC, R0

 

StackIrq   DCD     (IrqStackSpace + IRQ_STACK_LEGTH * 4 - 4)

StackFiq   DCD     (FiqStackSpace + FIQ_STACK_LEGTH * 4 - 4)

StackAbt   DCD     (AbtStackSpace + ABT_STACK_LEGTH * 4 - 4)

StackUnd  DCD     (UndtStackSpace + UND_STACK_LEGTH * 4 - 4)

StackSys   DCD     (SysStackSpace + SYS_STACK_LEGTH * 4 -4 )

;/* 分配堆栈空间 */

        AREA    MyStacks, DATA, NOINIT

IrqStackSpace   SPACE  IRQ_STACK_LEGTH * 4 ;中断模式堆栈

FiqStackSpace   SPACE  FIQ_STACK_LEGTH * 4 ;快速中断模式堆栈

AbtStackSpace   SPACE  ABT_STACK_LEGTH * 4 ;中止义模式堆栈

UndtStackSpace  SPACE  UND_STACK_LEGTH * 4 ;未定义模式堆栈

SysStackSpace   SPACE  SYS_STACK_LEGTH * 4 ; 系统模式堆栈

 

因为程序需要切换模式,而且程序退出时CPU的模式已经不再是管理模式而是系统模式LR已经不再保存返回程序地址,所以程序首先把返回地址保存到  R0中,同时使用R0返回。然后程序把处理器模式转化为IRQ模式,并设置IRQ模式的堆栈指针。其中变量Stacklrq保存着IRQ模式的堆栈指针的初始值,Irqstackspace是分配给 IRQ模式的堆栈空间的开始地址,IRQ_STACK_LEGTH是用户定义的常量,用于设置 IRQ模式的堆栈空间的大小。程序使用同样的方法设置FIQ模式堆栈指针、中止模式堆栈指针、未定义堆栈指针和系统模式堆栈指针。

程序使用编译器分配的空间作为堆栈,而不是按照通常的做法把堆栈分配到  RAM的顶端,之所以这样是因为这样做不必知道RAM顶端位置,移植更加方便;编译器给出的占用RAM空间的大小就是实际占用的大小,便于控制RAM的分配。对于 LPC2106来说,中止模式是不需要分配堆栈空间的,这是因为 LPC2106没有外部总线,也没有虚拟内存机制,如果出现取数据中止或取指令中止肯定是程序有问题。而一般情况下也不需要模拟协处理器指令或扩充指令,未定义中止也就意味着程序有错误,也不需要分配堆栈空间。

 

§4.2.1.3   异常处理代码与C语言的接口程序

μC/OS-Ⅱ中断服务子程序流程图如图4-1所示:

图4-1 中断服务子程序流程图

异常处理代码与C语言的接口程序如下:

MACRO

$IRQ_Label HANDLER $IRQ_Exception

        EXPORT  $IRQ_Label                      ;输出的标号

        IMPORT  $IRQ_Exception                  ;引用的外部标号

 

$IRQ_Label

        SUB     LR, LR, #4                       ;计算返回地址

        STMFD   SP!, {R0-R3, R12, LR}            ;保存任务环境

        MRS     R3, SPSR                        ;保存状态

        STMFD   SP!, {R3}

 

        LDR     R2,  =OSIntNesting               ;OSIntNesting++

        LDRB    R1, [R2]

        ADD     R1, R1, #1

        STRB    R1, [R2]

       

        BL      $IRQ_Exception          ;调用c语言的中断处理程序

 

        MSR    CPSR_c, #0x92            ;关中断

        BL      OSIntExit

 

        LDR     R0, =OSTCBHighRdy

        LDR     R0, [R0]

        LDR     R1, =OSTCBCur

        LDR     R1, [R1]

        CMP     R0, R1

       

        LDMFD   SP!, {R3}

        MSR     SPSR_cxsf, R3

 

        LDMEQFD SP!, {R0-R3, R12, PC}^          ;不进行任务切换

        LDR     PC, =OSIntCtxSw                 ;进行任务切换

    MEND

Undefined                                      ;未定义指令

        b       Undefined

PrefetchAbort                                   ;取指令中止

        b       PrefetchAbort

DataAbort                                      ;取数据中止

        b       DataAbort

IRQ_Handler      HANDLER  IRQ_Exception         ;中断

FIQ_Handler                                    ;快速中断

        b       FIQ_Handler

Timer0_Handler  HANDLER  Timer0              ;定时器0中断

 

未定义指令异常、取指令中止异常、取数据中止异常均是死循环,其中原因在上一小节已经说明。而快速中断在本应用中并未使用,所以也设置为死循环。LPC2106使用向量中断控制器,各个 IRQ中断的人口不一样,所以使用了一个宏来简化中断服务程序与C语言的接口编写。由ARM处理器核的文档可知,处理器进入IRQ中断服务程序时(LR-4)的值为中断返回地址,为了使任务无论在主动放弃CPU时还是中断时堆栈结构都一样,在这里先把LR减4。其它的部分与μC/OS-Ⅱ要求的基本一致。ARM处理核在进入中断服务程序时处理器模式变为IRQ模式,与任务的模式不同,它们的堆栈指针SP也不一样,而寄存器应当保存到用户的堆栈中,为了减少不必要的CPU时间和RAM空间的浪费,本移植仅在必要时将处理器的寄存器保存到用户的堆栈中,其它时候还是保存到IRQ模式的堆栈中。同时,从编译器的函数调用规范可知,C语言函数返回时,寄存器R4—R11、SP不会改变,所以只需要保存CPSR、R0—R3、R12和返回地址LR,在后面保存 CPSR是为了必要时将寄存器保存到用户堆栈比较方便。

在异常处理代码与C语言的接口程序中没有与中断服务子程序流程图中的判断语句对应的语句。判断语句是为了避免在函数OSIntCtxsw()调整堆栈指针,这个调整量是与编译器、编译器选项、μC/OS -Ⅱ配置选项都相关的变量。在这里进行这些处理相对其它处理器结构可能增加的处理器时间很少,但对于ARM来说,由于中断(IRQ)有独立的堆栈,在这里这样做就需要把所有寄存器从中断的堆栈拷贝到任务的堆栈,需要花费比较多的额外时间。而变量OSIntNesting为0时,并不一定会进行任务切换,所以本移植没有与之对应的程序,而在函数OSIntCtxsw()中做这一项工作。这样,仅在需要时才处理这些事物,程序效率得以提高。

在中断调用后,如果需要任务切换,则变量OSTCBHighRdy和变量OSTCBCur的值不同;如果不需要任务切换这两个变量则相同。本移植通过判断这两个变量来决定是进行任务切换,还是不进行任务切换。通过比较,如果需要任务切换则执行“LDR  PC, =OSIntCtxSw”跳转到OSIntCtxSw处进行任务切换;如果不需要任务切换则执行“LDMEQFD  SP!, {R0-R3, R12, PC}^”中断返回。

这里需要对“MSR    CPSR_c, #0x92”说明下,这条指令的作用是关IRQ中断。因为中断(IRQ)模式的LR寄存器在处理器响应中断时用于保存中断返回地址,所以在处理器响应中断时中断(IRQ)模式的LR寄存器不能保存有效数据。而BL指令要用LR寄存器保存BL下一条指令的位置,所以在中断(IRQ)模式时,在BL指令之前必须关中断,在保存LR后才能开中断。

 

§4.2.2  Target.c文件的编写

为了使系统基本能够工作,必须在进人 main()函数前对系统进行一些基本的初始化工作,这些工作由函数TargetResetInit()完成。

 

void TargetResetInit(void)

{

    uint32 i;

    uint32 *cp1;

    uint32 *cp2;

    extern void Vectors(void) ;

 

     /* 拷贝向量表,保证在flash和ram中程序均可正确运行 */ 

    cp1 = (uint32 *)Vectors;

    cp2 = (uint32 *)0x40000000;

    for (i = 0; i < 2 * 8; i++)

    {

        *cp2++ = *cp1++;

    }

   

 

   MEMMAP = 0x2;                  

  

PINSEL0 = (PINSEL0 & 0xFFFF0000) | UART0_PCB_PINSEL_CFG | 0x50;

 

  PLLCON = 1;                 /* 设置系统各部分时钟 */

    VPBDIV = 0;

    PLLCFG =0x23;

    PLLFEED = 0xaa;

    PLLFEED = 0x55;

    while((PLLSTAT & (1 << 10)) = = 0) ;

    PLLCON = 3;

    PLLFEED = 0xaa;

    PLLFEED = 0x55;

 

    MAMCR = 2;        /* 设置存储器加速模块 */

#if Fcclk < 20000000

    MAMTIM = 1;

#else

#if Fcclk < 40000000

    MAMTIM = 2;

#else

    MAMTIM = 3;

#endif

#endif

   

首先向量表拷贝到RAM底部,加上这部分是为了代码无论从Flash基地址开始编译还是从RAM基地址开始编译程序均运行正确。而把RAM底部映射到向量表“MEMMAP = 0x2”也是为了同一个目的。至于复制16个字而不是8个字,是因为后8个字存储跳转的地址是通过 PC指针间接寻址的,它们与对应指令(在向量表中)相对位置是不能变化的。

因为在进入多任务环境前使用了一些外设,部分外设使用了芯片的引脚,而 LPC2106的所有引脚都是多功能的,所以需要设置引脚功能。同时串口也进行了设置。时钟是芯片各部分正常工作的基础,虽然时钟可以在任何时候设置,但为了避免混乱,最好在进入 main()函数前设置。程序首先使能PLL但不连接PLL,然后设置外设时钟(VPB时钟pclk)与系统时钟(cclk)的分频比。接着设置PLL的乘因子和除因子。设置完成后,使用“PLLFEED = 0xaa;  PLLFEED = 0x55;”的访问序列把数据正确写人硬件,并等待PLL跟踪完成。最后,使能PLL,并使PLL联上系统。本应用外接的晶振频率(Fosc)为11.0592MHz,倍增器的值M=4,所以处理器时钟(Fcclk)为44.2368 MHz。为了使电流控制振荡器频率(Fcco)满足156-320MHz,所以分频器的值P=2,使得Fcco= Fcclk×2×P=176.9472 MHz。取VPB分频器的分频值为1/4,所以外设时钟(Fpclk)= Fcclk/4=11.0592 MHz,则记数周期为0.09042μs,定时0.2ms,则记数值为2212个,这些时钟的定义都在config.h文件中。

用户程序最终是要在Flash中运行的,而系统复位时Flash是以最低速度运行,这对发挥芯片的性能极其不利。虽然存储器加速模块可以在任何时候设置,但为了避免混乱,最好在进入 main()函数前设置。首先使存储器加速模块全速工作,然后根据系统主时钟利用条件编译将Flash的访问时钟设置到合适的值。

 

§4.2.3  Init.s文件的编写

由于LPC2106微控制器的存储系统比较简单,所以系统初始化代码也比较简单,代码如下:

 

Reset

        BL      InitStack              ;初始化堆栈

        BL      TargetResetInit         ;目标板基本初始化

 B       __main              ;跳转到c语言入口

 

在芯片复位在芯片复位时程序会跳转到标号Reset处,程序首先调用Initstack初始化各种模式的堆栈,然后调用TargetResetlnit对系统进行基本初始化,最后跳转到ADS提供的启动代码__main。_main是 ADS提供的启动代码起始位置,它初始化库并最终引导CPU进入main函数。

 

 

§4.3  移植μC/OS-Ⅱ

 

点击看大图

图4-1  μC/OS-Ⅱ硬件/软件体系结构

 

图4-1说明了μC/OS-Ⅱ的结构以及它与硬件的关系,由此可知与处理器相关的代码在OS_CPU.H(包括用#define设置一些常量的值,声明的数据类型和用#define声明的宏),OS_CPU_C.C(用C语言编写的简单函数)和 OS_CPU_A.ASM(编写的汇编语言函数)。下面逐一介绍这3个文件中的关键部分程序段。

 

§4.3.1  OS_CPU.H文件的编写

§4.3.1.1  定义与编译器无关的数据类型

typedef unsigned char BOOLEAN;        /* 布尔变量*/                                                      typedef  unsigned  char  INT8U;            /* 无符号8位整型变量*/     

typedef  signed    char  INT8S;           /* 有符号8位整型变量*/                       

typedef  unsigned short INT16U;           /* 无符号16位整型变量*/

typedef  signed   short INT16S;           /* 有符号16位整型变量 */                     

typedef  unsigned int   INT32U;          /* 无符号32位整型变量*/                       

typedef  signed   int   INT32S;          /* 有符号32位整型变量*/ typedef  float          FP32;       /* 单精度浮点数(32位长度)*/                

typedef  double         FP64;       /* 双精度浮点数(64位长度)*/                

typedef  INT32U         OS_STK;             /* 堆栈是32位宽度*/

 

§4.3.1.2  与ARM7体系结构相关的一些定义

 

#define  OS_CRITICAL_METHOD     3       /* 选择开、关中断的方式 */

 

实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,具体参见§4.3.2.2小节。

ARM处理器核具有2个指令集,用户任务还可以有2种处理器模式:用户模式和系统模式,组合起来具有4种方式,各种方式对系统资源有不同的访问控制权限。为了使底层接口函数与处理器状态无关,同时使任务调用相应的函数不需要知道函数位置,本移植使用软中断指令SWI作为底层接口,使用不同的功能号区分不同的函数,同时预留挂接μC/OS-Ⅱ系统服务函数的接口。软中断功能号分配如表4-1所列,未列出的为保留。

 

功能号

接口函数

简述

0x00

void OS_TASK_SW(void)

任务级任务切换函数

0x01

_OSStartHighRdy(void)

运行优先级最高的函数

0x02

void OS_ENTER_CRITICAL(void)

开终端

0x03

void OS_EXIT_CRITICAL(void)

关中断

表4-1  软中断功能号分配

 

底层接口函数声明如下:

 

  __swi(0x00) void OS_TASK_SW(void);           /*  任务级任务切换函数*/

  __swi(0x01) void _OSStartHighRdy(void);         /*  运行优先级最高的任*/

__swi(0x02) void OS_ENTER_CRITICAL(void);    /*  关中断*/

__swi(0x03) void OS_EXIT_CRITICAL(void);      /*  开中断*/

 

  用软件中断作为操作系统的底层接口就需要在C语言中使用SWI指令。在ADS中,有一个关键字__swi,用它声明一个不存在的函数,调用这个函数就在调用这个函数的地方插入一条SWI指令,并且可以指定功能号。

 

#define OS_STK_GROWTH    1                   /*  堆栈是从上往下/

 

μC/OS -Ⅱ使用结构常量OS_STKG_ROWTH中指定堆栈的生长方式,置 OS_STKG_ROWTH为 0表示堆栈从下往上长;置OS_STKG_ROWTH为1表示堆栈从上往下长。虽然ARM处理器核对于两种方式均支持,但ADS的C语言编译器仅支持一种方式,即从上往下长,并且是必须是满递减堆栈,所以OS_STKG_ROWTH的值为 1。

 

#define     USR32Mode       0x10              /*  用户模式*/

#define     SYS32Mode       0x1f              /*  系统模式*/

#define     NoInt           0x80

#ifndef     USER_USING_MODE

#define     USER_USING_MODE USR32Mode         /*  任务缺省*/

#endif

 

用户任务具有两种处理器模式(用户模式和系统模式),可以使用两种指令集(ARM指令集和THUMB指令集),这些状态均保存在程序状态寄存器CPSR中,而USER_USING_MODE就是定义任务开始执行时默认的处理器模式和使用的指令集。

§4.3.2  OS_CPU_C.C文件的编写

§4.3.2.1  OSTaskStkInt()的编写

   OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInt()来初始

化任务的堆栈结构,因此,堆栈看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。

  OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata,

OS_STK *ptos, INT16U opt)

{

    OS_STK *stk;

opt   = opt;                          

stk   = ptos;                                     

    *stk = (OS_STK) task;                  /*  pc  */

    *--stk = (OS_STK) task;                /*  lr  */

    *--stk = 0;                            /*  r12  */

    *--stk = 0;                            /*  r11  */

    *--stk = 0;                            /*  r10  */

    *--stk = 0;                            /*  r9   */

    *--stk = 0;                            /*  r8   */

    *--stk = 0;                            /*  r7   */

    *--stk = 0;                            /*  r6   */

    *--stk = 0;                            /*  r5   */

    *--stk = 0;                            /*  r4   */

    *--stk = 0;                            /*  r3   */

    *--stk = 0;                            /*  r2   */

    *--stk = 0;                            /*  r1   */

    *--stk = (unsigned int) pdata;        /*  r0,第一个参数使用r0传递*/ 

    *--stk = (USER_USING_MODE|0x00);/*  spsr,允许IRQ, FIQ 中断   */

*--stk = 0;                    /*  关中断计数器OsEnterSum;    */

 

    return (stk);

}

 

图4-2所示为本移植堆栈结构,对照图4-2很容易理解OSTaskStkInt()的代码,这里就不做过多的说明了。

图4-2  任务堆栈结构

 

 

§4.3.2.2   SWI_ Exception的编写

 

由软中断的汇编接口程序SoftwareInterrupt(具体参见§4.3.3.1小节)可知,在发生软中断时,除了功能号0和1的软中断,其他软中断都要跳转到软件中断的C语言函数SWI_ Exception处,其结构是一个switch语句把各个功能分开,使各个功能相对独立,程序清单如下所示:

 

 void SWI_Exception(int SWI_Num, int *Regs)

{

 OS_TCB   *ptcb;

  switch(SWI_Num)

    {

     //case 0x00: /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件 */

     //  break;

     //case 0x01: /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */

    //   break;

     case 0x02:  /* 关中断函数OS_ENTER_CRITICAL() */

            __asm

            {

                MRS     R0, SPSR

                ORR     R0, R0, #NoInt

                MSR     SPSR_c, R0

            }

            OsEnterSum++;

            break;

        case 0x03:   /* 开中断函数OS_EXIT_CRITICAL()*/

            if (--OsEnterSum == 0)

            {

                __asm

                {

                    MRS     R0, SPSR

                    BIC     R0, R0, #NoInt

                    MSR     SPSR_c, R0

                }

            }

            break;

    }

}

对于任务切换函数OS_TASK_SW和启动任务函数OSStartHighRdy在os_cpu_s.s文件中介绍。这里需要对关中断函数和开中断函数作些解释。与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断。这就使得μC/OS-Ⅱ能够保护临界段代码免受多任务或中断服务破坏。实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,即在OS_CPU.H文件中使OS_CRITICAL_MEHTOD等于3,这种方法是利用某些编译器提供的扩展功能,用户可以得到当前处理器状态字的值,并将其保存在C函数的局部变量之中,这个变量可以用于恢复PSW,而本ARM内核关中断和开中断时,是通过改变程序状态寄存器CPSR中的相应控制位实现。由于使用了软件中断,程序状态寄存器 CPSR保存到程序状态保存寄存器 SPSR中,软件中断退出时会将SPSR恢复到CPSR中。所以程序只要改变程序状态保存寄存器SPSR中相应的控制位就可以了。

§4.3.3   OS_CPU_A.ASM文件的编写

§4.3.3.1   SoftwareInterrupt的编写

当发生软件中断时,程序通过异常向量表跳转到软中断的汇编与C接口程序SoftwareInterrupt处,图4-3为SoftwareInterrupt的流程图。

图4-3 软件中断代码的汇编部分流程图

 

SoftwareInterrupt

        LDR     SP, StackSvc            ; 重新设置堆栈指针

        STMFD   SP!, {R0-R3, R12, LR}

        MOV     R1, SP                ; R1指向参数存储位置

        MRS     R3, SPSR

        TST     R3, #T_bit              ; 中断前是否是Thumb状态

        LDRNEH  R0, [LR,#-2]          ; 是: 取得Thumb状态SWI号

        BICNE   R0, R0, #0xff00

        LDREQ   R0, [LR,#-4]          ; 否: 取得arm状态SWI号

        BICEQ   R0, R0, #0xFF000000

                                  ; r0 = SWI号,R1指向参数存储位置

        CMP      R0, #1

        LDRLO   PC, =OSIntCtxSw

        LDREQ   PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换

        BL       SWI_Exception

        LDMFD   SP!, {R0-R3, R12, PC}^

StackSvc DCD     (SvcStackSpace+SVC_STACK_LENGTH* 4-4)

 

因为执行任务切换时堆栈指针会指向用户的堆栈,这样下一次进入管理模式就会破坏用户堆栈,从而导致程序执行不正确。所以程序在一开始设置堆栈指针。软中断指令使处理器进入管理模式,而用户程序处于系统/用户模式,其它异常也有自己的处理器模式,都有各自的堆栈指针,不会因为给堆栈指针赋值而破坏其它处理器模式的堆栈而影响其它程序的执行。返回的地址已经存储在连接寄存器LR中而不是存储在堆栈中。由于进人管理模式自动关中断,所以这段程序不会被其它程序同时调用,设置的堆栈指针指向的位置肯定是空闲位置,后一次调用不会影响前一次调用。这样就可以保证“LDR  SP, StackSvc”进行正确的堆栈指针设置。

软中断的功能号是包含在SWI指令当中的。程序通过读取该条指令的相应位段获得。由于ARM处理器核具有两个指令集,两个指令集的指令长度不同,SWI指令的功能号的位段也不同,所以程序先判断在进入软中断前处理器是在什么指令集状态,根据指令集状态的不同取出相应的SWI指令的功能号。然后,程序用功能号与1比较,当功能号符号小于1即为 0时,就跳转到任务切换函数OSIntCtxSw处。当功能号等于1时,就跳转到第一次任务切换OSStartHighRdy处。其它部分是给软件中断的C语言处理函数处理。这里有两个参数,第一个就是功能号,存于R0中;第二个是保存参数和返回值的指针,也就是堆栈中存储用户函数R0—R4的位置,就是当前堆栈指针的值,它存于R1中。

这里对功能号0说明一下,功能号0跳转到OSIntCtxsw程序段,而这段程序实际是实现了OS_TASK_SW()函数的功能。OS_TASK_SW()是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务又可能不在特权模式(不能改变CPSR)。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0实现OS_TASK_SW()的功能,即使用SWI 0X00代替 OSTASKSW(),具体实现方法见下一小节。

 

§4.3.3.2  OSIntCtxSw的编写

  在μC/OS-Ⅱ中,任务切换只是简单的将处理器寄存器保存到将被挂起的任务的堆栈中,并且将更高优先级的任务从堆栈中恢复出来。处于就绪状态的任务的堆栈结构看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。换句话说,μC/OS-Ⅱ要运行处于就绪状态的任务必须要做的事就是将所有处理器寄存器从任务堆栈中恢复出来,并且执行中断的返回。

在μC/OS-Ⅱ中,用户级任务调度时会调用宏(或者函数)OS_TASK_SW(),它是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,μC/OS-Ⅱ建议OS_TASK_SW()通过某种途径最终调用函数OSCtxSw()。函数OSCtxSw()是与系统相关的,μC/OS-Ⅱ提供的OSCtxSw()函数原型如下:

 

OSCtxSw()原型的程序清单

void OSCtxSw(void)

{

保存处理器寄存器;

将当前任务的堆栈指针保存到当前任务的OS_TCB中;

OSTCBCur->OSTCBStkPtr = Stack pointer;

  调用用户定义的OSTaskSwHook();

  OSTCBCur  = OSTCBHighRdy;

  OSPrioCur = OSPrioHighRdy;

  得到需要恢复的任务的堆栈指针;

    Stack pointer = OSTCBHighRdy->OSTCBStkPtr;

  将所有处理器寄存器从新任务的堆栈中恢复出来;

  执行中断返回指令;

}

 

在μC/OS-Ⅱ中,函数OSIntExit( )被用来在ISR使得更高优先级任务处于就绪状态时,执行任务切换功能。中断退出函数通过调OSIntCtxSw()来从ISR中执行切换功能。函数OSIntCtxSw()是与系统相关的,μC/OS-Ⅱ提供的OSIntCtxSw()函数原型如下:

 

OSIntCtxSw( )原型的程序清单

void OSIntCtxSw(void)

 {

    调用用户定义的OSTaskSwHook( ) ;

    OSTCBCur  = OSTCBHighRdy ;

    OSPrioCur = OSPrioHighRdy ;

    得到需要恢复的任务的堆栈指针 ;

    堆栈指针 = OSTCBHighRdy->OSTCBStkPtr ;

    将所有处理器寄存器从新任务的堆栈中恢复出来;

    执行中断返回指令;

 }

 

对比两个函数原型,除OSCtxSw()原型的程序清单比OSIntCtxSw( )原型的程序清单多了两句外,其它都是一样的。由异常处理代码与C语言的接口程序可知,OS_TASK_SW()实质是软件中断的功能号0,在软件中断中已经把除变量OsEntersum外的所有寄存器保存到管理模式的堆栈中。而由程序可知,当程序执行函数 OSIntCtxsw()时,变量 OsEnterSum也没有保存到 IRQ模式的堆栈中。也就是说,两种情况需要做的工作一样,都可用同一段代码实现。

 

OSIntCtxSw

                                    ;下面为保存任务环境

        LDR     R2, [SP, #20]                  ;获取PC   (1)

        LDR     R12, [SP, #16]                 ;获取R12  (2)

        MRS     R0, CPSR                               (3)

 

        MSR     CPSR_c, #(NoInt | SYS32Mode)            (4)

        MOV     R1, LR                                (5)

        STMFD   SP!, {R1-R2}                  ;保存LR,PC (6)

        STMFD   SP!, {R4-R12}                 ;保存R4-R12 (7)

 

        MSR     CPSR_c, R0                               (8)

        LDMFD   SP!, {R4-R7}                  ;获取R0-R3 (9)

        ADD     SP, SP, #8                     ;出栈R12,PC (10)

       

        MSR     CPSR_c, #(NoInt | SYS32Mode)              (11)

        STMFD   SP!, {R4-R7}                   ;保存R0-R3 (12)

        LDR     R1, =OsEnterSum            ;获取OsEnterSum (13)

        LDR     R2, [R1]                                   (14)

        STMFD   SP!, {R2, R3}         ;保存CPSR,OsEnterSum (15)

 

                              ;保存当前任务堆栈指针到当前任务的TCB

        LDR     R1, =OSTCBCur                            (16)

        LDR     R1, [R1]                                   (17)

        STR     SP, [R1]                                    (18)

 

        BL      OSTaskSwHook                ;调用子函数   (19)

                                        

        LDR     R4, =OSPrioCur                          (20)

        LDR     R5, =OSPrioHighRdy                     (21)

        LDRB    R6, [R5]                                (22)

        STRB    R6, [R4]                                (23)

                                          

        LDR     R6, =OSTCBHighRdy                     (24)

        LDR     R6, [R6]                                (25)

        LDR     R4, =OSTCBCur                         (26)

        STR     R6, [R4]                                 (27)

OSIntCtxSw_1                                     

                                                ;获取新任务堆栈指针

        LDR     R4, [R6]                                (28)

        ADD     SP, R4, #68                             (29)

        LDR     LR, [SP, #-8]                            (30)

        MSR     CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式(31)

        MOV     SP, R4                     ;设置堆栈指针(32)

 

        LDMFD SP!, {R4, R5}        ;CPSR,OsEnterSum    (33)

                                   ;恢复新任务的OsEnterSum

        LDR     R3, =OsEnterSum                       (34)

        STR     R4, [R3]                               (35)

        MSR     SPSR_cxsf, R5              ;恢复CPSR (36)

        LDMFD   SP!, {R0-R12, LR, PC }^     ;运行新任务(37)

 

这部分代码基本按照μC/OS-Ⅱ提供的函数原型编写的,其中程序清单(1)—(18)部分与OSCtxSw()和OSIntCtxSw( )的原型是没有对应语句的,寄存器应当保存到任务的堆栈中,但为了节省CPU的时间和RAM的空间,仅在必要的时候才将寄存器保存到任务堆栈。OSTCBCur->OSTCBStkPtr=SP也是在必要的时候才执行的。这部分正是在处理这两件事情,其流程图见图4-4。

图4-4  OSIntCtxSw部分代码流程图

 

由软中断的汇编与C接口程序可知,在调用OS_TASK_SW( )(即软件中断的功能号0)时,寄存器R0—R3、R12、PC已经保存到当前模式的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存,但不是保存到任务的堆栈中。同样由异常处理代码与C语言的接口程序可知,在调用OSIntCtxSw( )时,寄存器R0—R3、R12、PC已经保存到当前模式的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存,但不是保存到任务的堆栈中。当前处理器模式的堆栈结构如图4-5所示。

图4-5  当前处理器模式堆栈结构图

在执行(1)—(18)这部分程序时的寄存器和存储器之间的具体关系如图4-6所示,图中的标号为对应的程序段,这里是以调用OS_TASK_SW( )为例子来说明,调用OSIntCtxSw( )的工作过程和调用OS_TASK_SW( ) 的工作过程是一样的,只不过调用OS_TASK_SW( )时处理器当前模式为管理模式,而调用OSIntCtxSw( )时处理器当前模式为IRQ模式。

图4-6-1

此时处理器处于管理模式,因为本移植是使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0来实现OS_TASK_SW( )的功能。具体参见软中断的汇编与C接口程序代码。

图4-6-2

此时是通过执行程序段(4),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入用户/系统模式此时需要注意的是处理器的可见寄存器与管理模式时是有区别的。在保存之后同样是通过执行程序段(8),利用MSR指令返回到管理模式。

图4-6-3

进入管理模式以后继续保存数据,当数据保存好以后,要注意调整当前模式堆栈指针因为当前模式的入栈比出栈的数据多,而在堆栈中剩余的数据(R12、PC)已经没有用处,所以要进行调整。完成指针调整的为程序段(10)。

图4-6-4

  

此时又使处理器进入用户/系统模式,继续把未保存的寄存器内容保存到任务堆栈,这里要注意的是R3保存着任务的CPSR(既当前模式的SPSR),这部分在异常处理代码与C语言的接口程序中完成的。至此,寄存器内容已经完全保存到任务堆栈里了。

从标号OSIntCtxSw_1处开始至程序的最后,是将所有处理器寄存器从新任务的堆栈中恢复出来。处理器执行这段代码时处于AR

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客