⑤这两个函数都用于任务切换,它们的本质都是触发PendSV中断,具体切换过程在PendSV的中断函数中进行,其中OSCtxSw是任务级切换,OSIntCtxSw是中断级切换,是从中断退出时切换到一个任务中,从中断切换到任务的过程中,CPU的寄存器入栈工作已经完成。
OSCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常
LDR R5, =NVIC_PENDSVSET
STR R5, [R4] ;向NVIC_INT_CTRL写入NVIC_PENDSVSET触发PendSV中断
POP {R4, R5}
BX LR
OSIntCtxSw
PUSH {R4, R5}
LDR R4, =NVIC_INT_CTRL ;触发PendSV异常
LDR R5, =NVIC_PENDSVSET
STR R5, [R4] ;向NVIC_INT_CTRL写入NVIC_PENDSVSET触发PendSV中断
POP {R4, R5}
BX LR
NOP
⑥这部分代码才是真正的任务切换函数,通过触发PendSV中断来进入该函数内进行任务切换
PendSV_Handler
CPSID I ;任务切换过程中必须关闭所有中断
MRS R0, PSP ;如果在用PSP堆栈,则可以忽略保存寄存器
CBZ R0, PendSV_Handler_Nosave ;如果PSP为0就转移到PendSV_Handler_Nosave
SUBS R0, R0, #0x20 ;R0-=20H
STM R0, {R4-R11}
LDR R1, =OSTCBCur
LDR R1, [R1]
STR R0, [R1]
PendSV_Handler_Nosave
PUSH {R14} ;保存R14的值
LDR R0, =OSTaskSwHook ;调用OSTaskSwHook()
BLX R0
POP {R14}
LDR R0, =OSPrioCur
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCur
LDR R1, =OSTCBHighRdy
LDR R2, [R1]
STR R2, [R0]
LDR R0, [R2] ;R0作为新任务的SP
LDM R0, {R4-R11} ;从堆栈中恢复R4-R11
ADDS R0, R0, #0x20
MSR PSP, R0 ;用新任务的SP加载PSP
ORR LR, LR, #0x04 ;确保LR的bit2为1,返回后使用进程堆栈
CPSIE I ;开启所有中断
BX LR ;中断返回
end
(2)os_cpu.h文件详解
①这部分主要用于定义一些数据类型,其中重点关注OS_STK这个数据类型,我们在定义任务堆栈的时候就是该类型数据,这是一个32位的数据类型,按字节算的话实际堆栈大小是我们定义的4倍。
typedef unsigned char BOOLEAN;
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned short INT16U;
typedef signed short INT16S;
typedef unsigned int INT32U;
typedef signed int INT32S;
typedef float FP32;
typedef double FP64;
typedef unsigned int OS_STK;
typedef unsigned int OS_CPU_SR;
②这部分代码定义了堆栈的增长方向,任务机切换的宏定义OS_TASK_SW,如果OS_CRITICAL_METHOD被定义为3的话那么进出临界段的宏定义分别为OS_ENTER_CRITICAL和OS_EXIT_CRITICAL,这两个函数都是用汇编语言编写的
//OS_CRITICAL_METHOD = 1 :直接使用处理器的开关中断指令来实现宏
//OS_CRITICAL_METHOD = 2 :利用堆栈保存和恢复CPU的状态
//OS_CRITICAL_METHOD = 3 :利用编译器扩展功能获得程序状态字,保存在局部变量cpu_sr
#define OS_CRITICAL_METHOD 3 //进入临界段的方法
#if OS_CRITICAL_METHOD == 3
#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();}
#define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}
#endif
void OSCtxSw(void);
void OSIntCtxSw(void);
void OSStartHighRdy(void);
void OSPendSV(void);
#if OS_CRITICAL_METHOD == 3u
OS_CPU_SR OS_CPU_SR_Save(void);
void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);
#endif
OS_CPU_EXT INT32U OSInterrputSum;
(3)sys.h文件修改
添加关于条件编译的定义,在文件中添加以下代码即可。
#define SYSTEM_SUPPORT_OS 1
当宏定义为1的时候,编译器在编译的时候会只编译满足条件的代码,当为0时,这部分代码不会被编译。
(4)delay.c文件修改
①添加Sys_Tick中断服务函数与函数定义
#include "includes.h"
//支持UCOSII
#ifdef OS_CRITICAL_METHOD
#define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
#define delay_ostickspersec OS_TICKS_PER_SEC //OS时钟节拍,即每秒调度次数
#define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
#endif
//systick中断服务函数,使用OS时用到
void SysTick_Handler()
{
//OS开始跑了,才执行正常的调度处理
if( delay_osrunning==1 )
{
OSIntEnter() ; //进入中断
OSTimeTick() ; //调用ucos的时钟服务程序
OSIntExit() ; //触发任务切换软中断
}
}
②时钟初始化函数修改
复制
void SysTick_Init( u8 SYSCLK )
{
#if SYSTEM_SUPPORT_OS
u32 reload;
#endif
SysTick->CTRL &= ~( 1<<2 ) ; //SYSTICK使用外部时钟源
fac_us = SYSCLK/8 ; //fac_us都需要使用
#if SYSTEM_SUPPORT_OS
reload = SYSCLK/8 ; //每秒钟的计数次数,单位为K
reload *= 1000000/delay_ostickspersec ; //根据delay_ostickspersec设定溢出时间
fac_ms = 1000/delay_ostickspersec ; //代表OS可以延时的最少单位
SysTick->CTRL |= 1<<1 ; //开启SYSTICK中断
SysTick->LOAD = reload ; //每1/delay_ostickspersec秒中断一次
SysTick->CTRL |= 1<<0 ; //开启SYSTICK
#else
fac_ms = ( u16 )fac_us*1000 ; //代表每个ms需要的systick时钟数
#endif
}