您好,欢迎来到华佗小知识。
搜索
您的当前位置:首页UCOS II

UCOS II

来源:华佗小知识
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; ·KB静态 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 LDRB R1, [R2] ADD R1, R1, #1 STRB R1, [R2]

BL $IRQ_Exception

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

;OSIntNesting++ 调用c语言的中断处理程序 ; ;关中断

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 FP; /* 双精度浮点数(位长度)*/

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 0x01 0x02 void OS_TASK_SW(void) _OSStartHighRdy(void) 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] ; LDR R12, [SP, #16] ; MRS R0, CPSR (3)

MSR CPSR_c, #(NoInt | SYS32Mode) (4) MOV R1, LR (5) STMFD SP!, {R1-R2} ; STMFD SP!, {R4-R12} ;

MSR CPSR_c, R0 (8) LDMFD SP!, {R4-R7} ; ADD SP, SP, #8 ;

MSR CPSR_c, #(NoInt | SYS32Mode) (11) STMFD SP!, {R4-R7} ; LDR R1, =OsEnterSum ; LDR R2, [R1] (14)

下面为保存任务环境 获取PC (1) 获取R12 (2) 保存LR,PC (6) 保存R4-R12 (7) 获取R0-R3 (9) 出栈R12,PC (10) 保存R0-R3 (12) 获取OsEnterSum (13) STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum (15)

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

LDR R1, =OSTCBCur (16) LDR R1, [R1] (17) STR SP, [R1] (18)

BL OSTaskSwHook ;

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)

调用子函数 (19) 获取新任务堆栈指 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处开始至程序的最后,是将所有处理器寄存器从新任务的堆栈中 恢复出来。处理器执行这段代码时处于ARM状态,但用户任务可能处于Thumb状态。而由ARM的相关资料可知,程序不可以直接改变程序状态寄存器的 CPSR的T位来改变处理器的状态。但“LDMFD SP!, {R0-R12, LR, PC }^ ” 异常返回指令是可以使用的(具体参见ARM的多寄存器加载指令),所以在恢复寄存器时,必须使处理器处于某种异常模式。由于FIQ模式和IRQ模式对应中 断,SP指针不能随意改变,而未定义模式和中止模式以后可能会用到,所以本移植选择为管理模式。

在执行(28)—(37)这部分程序时的寄存器和存储器之间的具体关系如图4-7所示,图中的标号为对应的程序段。

图4-7-1

由于 OSTCBHirtRdy->OSTCBStkPtr保存的是任务堆栈位置,而寄存器恢复后堆栈指针并不指向这里,所以要调整新任务堆栈指针,这是由程序段(29)完成的。

图4-7-2

然后通过执行程序段(31),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入管理模式,先恢复新任务OsEntersum,这是由程序段(34)(35)完成的,然后把新任务的 CPSR恢复到 SPSR这是由程序段(36)实现的。

图4-7-3

最后通过中断返回指令恢复R0—R12,把SPSR拷贝到CPSR(恢复用户任务的处理器模式和指令集)和执行用户任务(恢复PC指针),这是由程序段(37)实现的。这里需要注意的是程序段(29)和(32)中的SP是不同的处理器寄存器分别为R13和R13_SVC。

§4.3.3.3 OSStartHighRdy的编写

μC/OS-Ⅱ启动多任务环境的函数叫做OSStart()。用户在调用OSStart() 之前,必须已经建立了一个或更多任务。OSStart()最终调用OSStartHighRdy()函数运行多任务启动前优先级最高的任务。由软中断的汇 编与C接口程序SoftwareInterrupt可知,这是调用软中断的1号功能。这是因为ARM处理器核具有两个指令集,在执行Thumb指令的状态 时不是所有寄存器都可见(参考ARM的相关资料),而且任务可能不在模式。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和 ARM指令状态,并使用功能1实现OSStartHighRdy的功能。μC/OS-Ⅱ中调用函数 OSStartHighRdy

()之前,OSTCBHighRdy指向的是优先级最高的任务的任务控制块。μC/OS-Ⅱ要求处于就绪状态的任务的堆栈结 构看起来就像刚发生过中断,并将所有寄存器保存到堆栈中的情形一样。要想运行最高优先级任务,用户要做的是将所有处理器寄存器按顺序从任务堆栈中恢复出 来,并且执行中断的返回。μC/OS-Ⅱ提供的OSStartHighRdy函数原型如下:

OSStartHighRdy原型的程序清单 void OSStartHighRdy (void) {

调用用户函数OSTaskSwHook(); OSRunning=True;

获取堆栈指针SP=OSTCBHighRdy->OSTCBStkPtr: 从新任务堆栈中恢复所有寄存器; 执行中断返回指令; }

OSStartHighRdy的程序代码如下: OSStartHighRdy

MSR CPSR_c, #(NoInt | SYS32Mode)

;告诉uC/OS-II自身已经运行

LDR R4, =OSRunning MOV R5, #1 STRB R5, [R4]

BL OSTaskSwHook ;调用子函数

LDR R6, =OSTCBHighRdy LDR R6, [R6] B OSIntCtxSw_1

这部分代码是比较严格的按照μC/OS-Ⅱ提供的原型编写的,其中OSIntCtxSw_1的代码在上一小节已经详细介绍过。

这里需要对“MSR CPSR_c, #(NoInt | SYS32Mode)”作以说明,在μC/OS -Ⅱ中,需要用户提供周期性信号源,用于实现时间延时和确认超时。必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。换句 话说,在调用OSStart()之后做的第一件事是初始化定时器中断。通常,容易犯的错误是将允许时钟节拍器中断放在系统初始化函数OSInit()之 后,在调启动多任务系统启动函数OSStart()之前允许时钟节拍中断。μC/OS-Ⅱ的启动多任务函数OSStart()会在最后调用 OSStartHighRdy,而SStartHighRdy的第一条语句就允许中断。这里是将CPSR的第8位置1,允许IRQ中断。

至此已经将μC/OS-Ⅱ移植到了LPC2106,下面可以进行瞬时电压有效值检测的程序的编写。

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo0.cn 版权所有 湘ICP备2023017654号-2

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务