本文基于STM32F407ZGT6
——————————————
SVC异常:
SVC(系统服务调用,亦简称系统调用)用于产生系统函数的调用请求。
SVC 异常是必须立即得到响应的应用程序执行 SVC 时都是希望所需的请求立即得到响应。
在 UCOS 中并未使用 SVC 这个功能,了解一下即可。
在 UCOS 中并未使用 SVC 这个功能,了解一下即可。
在 UCOS 中并未使用 SVC 这个功能,了解一下即可。
PendSv异常:
- 由于SVC异常是必须立即得到响应的(若因优先级不比当前正处理的高,或是其它原因使之无法立即响应,将上访成硬 fault),应用程序执行SVC 时都是希望所需的请求立即得到响应。
- PendSV 则不同,它是可以像普通的中断一样被悬起的(不像 SVC 那样会上访)。OS
可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动作。
1、悬 起 PendSV 的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写 1。悬起后,如果优先级不够高,则将缓期等待执行。
2、PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先级的异常。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV异常,以便缓期执行上下文切换。
可以看到uCOS操作系统里(其实实时操作系统都一样)各类异常/中断的优先级关系:
SYSTICK异常>中断>PendSv异常
PendSV 异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。这样保证了中断的快速响应性又保证了操作系统的正常轮转。
一、将PendSV 异常设置为最低优先级(这两段代码在os_cpu_a.asm中定义)。
NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器 ; Interrupt control state register.
NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(2) ; System priority register (priority 14).
NVIC_PENDSV_PRI EQU 0xFFFF ; PendSV 中断优先级为最低 ; PendSV priority value (lowest).
NVIC_PENDSVSET EQU 0x10000000 ; 触发软件中断的值 ; Value to trigger PendSV exception.
NVIC_PENDSV_PRI EQU 0xFFFF ;这个语句把PendSV 中断优先级为了最低0xFFFF 。
OSStartHighRdy汇编函数:
OSStartHighRdy
LDR R0, =NVIC_SYSPRI14 ; 设置 PendSV 的优先级为最低 ; Set the PendSV exception priority
LDR R1, =NVIC_PENDSV_PRI
STRB R1, [R0]
MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
MSR PSP, R0
LDR R0, =OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
LDR R1, [R0]
MSR MSP, R1
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0] ;触发PenSv中断
CPSIE I ;开中断 ; Enable interrupts at processor level
OSStartHang
B OSStartHang ;死循环,应该不会到这里的 ; Should never get here
- LDR R1, =NVIC_PENDSV_PRI;这个语句把PendSV 中断优先级设置位0xFFFF,也就是最低。
- LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
;这两个语句实现了触发PendSv异常,也就是说进入OSStartHighRdy函数会触发PendSv异常,然后会进入PendSv异常服务函数进行任务切换。
OSStartHighRdy 是由 OSStart()调用,OSStart()用来开启多任务的,如果多任务开启正常则进入OSStartHighRdy 函数触发PendSv异常并马上进行任务切换;如果多任务开启失败的话就会进 入 OSStartHang函数(这里没有贴出来)。
二、OSStart()函数:
OSStart()函数在我们的main函数里创建第一个任务的时候发生调用。(好像也只是调用一次,用于启动系统)
void OSStart (OS_ERR *p_err)
{
#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
if (OSRunning == OS_STATE_OS_STOPPED) {
OSPrioHighRdy = OS_PrioGetHighest(); //找出目前最高优先级的函数 /* Find the highest priority */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
OSTCBCurPtr = OSTCBHighRdyPtr;
OSRunning = OS_STATE_OS_RUNNING; /*OSRunning变为1所以退出OSStart函数以后,系统开始跑*/
OSStartHighRdy(); //调用 OSStartHighRdy汇编函数 /* Execute target specific code to start task */
*p_err = OS_ERR_FATAL_RETURN; /* OSStart() is not supposed to return */
} else {
*p_err = OS_ERR_OS_RUNNING; /* OS is already running */
}
}
调用这个函数一般是在创建第一个任务时,那时系统还没开始跑;在程序里面OSRunning = OS_STATE_OS_RUNNING;把系统开始的标志位OSRunning设置为1。这个函数正常运行结束以后代表着操作系统真正开始跑起来了。
三、PendSV 异常服务函数:
PendSV 异常服务要完成两个工作:1、保存上文;2、切换下文
任务之间的切换就是发生在PendSV 异常服务函数里面。
可以理解为——触发了PendSV 异常->函数自动跳转到PendSV 异常服务函数->执行异常服务函数->实现任务的切换。
触发PendSV 异常的函数:(在os_cpu_a.asm中定义)
- OSStartHighRdy :用得很少,只是在任务开启时在OSStart()函数里调用
- OSCtxSw :实现任务级的任务切换
- OSIntCtxSw :实现中断级的任务切换
PendSV_Handler
CPSID I ;关中断 ; Prevent interruption during context switch
MRS R0, PSP ; 将 psp 的值加载到 R0 ; PSP is process stack pointer
; CBZ判0转移,判断 R0如果值为 0 ,则跳转到 OS_CPU_PendSVHandler_nosave
; 进行第一次任务切换的时候,R0 肯定为 0
CBZ R0, PendSVHandler_nosave ; Skip register save the first time
;判读是否使用FPU
;Is the task using the FPU context? If so, push high vfp registers.
TST R14, #0X10
IT EQ
VSTMDBEQ R0!,{S16-S31}
SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
;————————————保存上下文————————————————————
; 手动存储 CPU 寄存器 R4-R11 的值到当前任务的堆栈
STM R0, {R4-R11}
; 加载 OSTCBCurPtr 指针的地址到 R1
LDR R1, =OSTCBCurPtr ; OSTCBCurPtr->OSTCBStkPtr = SP;
LDR R1, [R1]
STR R0, [R1] ; R0 is SP of process being switched out
;—————————切换上下文—————————————
; 实现 OSTCBCurPtr = OSTCBHighRdyPtr
; 把下一个要运行的任务的堆栈OSPrioHighRdy加载到 CPU 寄存器中 ; At this point, entire context of process has been saved
PendSVHandler_nosave
PUSH {R14} ; Save LR exc_return value
LDR R0, =OSTaskSwHook ; OSTaskSwHook();
BLX R0
POP {R14}
LDR R0, =OSPrioCur ; OSPrioCur = OSPrioHighRdy;
LDR R1, =OSPrioHighRdy
LDRB R2, [R1]
STRB R2, [R0]
LDR R0, =OSTCBCurPtr ; OSTCBCurPtr = OSTCBHighRdyPtr;
LDR R1, =OSTCBHighRdyPtr
LDR R2, [R1]
STR R2, [R0]
; R0 is new process SP; SP = OSTCBHighRdyPtr->StkPtr;
LDM R0, {R4-R11} ; Restore r4-11 from new process stack
ADDS R0, R0, #0x20
;Is the task using the FPU context? If so, push high vfp registers.
TST R14, #0x10
IT EQ
VLDMIAEQ R0!, {S16-S31}
MSR PSP, R0 ; Load PSP with new process SP
ORR LR, LR, #0x04 ; Ensure exception return uses process stack
CPSIE I
BX LR ; Exception return will restore remaining context
END