一、什么是PendSV PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。
OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。 PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:* D7 p" u4 |' ^' Z( X/ I% ]
1、执行一个系统调用" x; Y" {% d0 W" h# [7 p, Q0 W
2、系统滴答定时器(SYSTICK)中断,(轮转调度中需要) 让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过SysTick异常启动上下文切换。但若在产生 SysTick 异常时正在响应一个中断,则 SysTick异常会抢占其 ISR。在这种情况下,OS是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这 种事。因此,在 CM3 中也是严禁没商量——如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法fault异常。 为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应 时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切 换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick在执行后不得作上下文切换,只能等待下 一次SysTick异常),尤其是当某中断源的频率和SysTick异常的频率比较接近时,会发生“共振”, 使上下文切换迟迟不能进行。现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到 其它的 ISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。如果 OS检测到某 IRQ正在活动并且被 SysTick抢占,它将悬起一个 PendSV异常,以便缓期执行 上下文切换。 使用 PendSV 控制上下文切换个中事件的流水账记录如下: 1. 任务 A呼叫 SVC来请求任务切换(例如,等待某些工作完成) 2. OS接收到请求,做好上下文切换的准备,并且悬起一个 PendSV异常。 3. 当 CPU退出 SVC后,它立即进入 PendSV,从而执行上下文切换。 4. 当 PendSV执行完毕后,将返回到任务 B,同时进入线程模式。 5. 发生了一个中断,并且中断服务程序开始执行 6. 在 ISR执行过程中,发生 SysTick异常,并且抢占了该 ISR。 7. OS执行必要的操作,然后悬起 PendSV异常以作好上下文切换的准备。 8. 当 SysTick退出后,回到先前被抢占的 ISR中,ISR继续执行 9. ISR执行完毕并退出后,PendSV服务例程开始执行,并且在里面执行上下文切换 10. 当 PendSV执行完毕后,回到任务 A,同时系统再次进入线程模式。
我们在uCOS的PendSV的处理代码中可以看到: - OS_CPU_PendSVHandler5 x) V# N$ W0 t! p# i& e: m
- CPSID I ; 关中断
# h; [ G9 L1 T' a - ;保存上文
" T( Z( Z$ F8 x1 n2 K: ]: L - ;....................... 3 d. R: C" q' P0 F3 w' u4 d8 i
- ;切换下文
O( e+ ^0 q. ?" M4 Q3 n - CPSIE I ;开中断0 Q' s! u, a( Y
- BX LR ;异常返回
复制代码 3 |' w9 |( k& d
它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分: - NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器; R" j' F: H9 ^% [6 |, r
- NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14).
; y7 A0 s3 V5 L0 C9 f; Y, Q4 j - NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值$ }1 a$ c, c6 Z& f9 b
- " A4 O) [1 f* C" ^
- ; 设置PendSV的异常中断优先级
7 Y: z. W% p4 i) F5 C - 4 Q) L/ f w; h
- LDR R0, =NVIC_SYSPRI14
' G9 A" g {) {. Z% T( c+ ?7 Z/ G - LDR R1, =NVIC_PENDSV_PRI
0 @; O( u/ a* _ - STRB R1, [R0] ; 触发PendSV异常
5 K* V5 K% X0 m5 Z0 T! u - LDR R0, =NVIC_INT_CTRL ( G# m J; ^. n
- LDR R1, =NVIC_PENDSVSET " B6 b. R1 ^8 \
- STR R1, [R0]
复制代码 + x8 Z: d) B* V
二、堆栈操作 Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP? - MSR PSP, R0 ; Load PSP with new process SP% i; h# A5 s+ V8 J7 i0 s2 c
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack
复制代码
# Q1 D3 i/ X6 [# S( S0 Y" q0 u很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。 写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP, - EXTERN OS_CPU_ExceptStkBase
8 W% O+ `4 u1 F* j - ;PSP清零,作为首次上下文切换的标志
( {: I$ X0 M3 f' J+ p - MOVS R0, #0
$ b; r0 i4 k" p6 C4 T+ @8 b: b - MSR PSP, R0
0 t8 p! H' q6 @7 ?! ? - ;将MSP设为我们为其分配的内存地址
, s+ p$ j/ w0 b6 N - LDR R0, =OS_CPU_ExceptStkBase
6 H0 {! t+ ]+ O5 O; m - LDR R1, [R0]' Q3 v# M" p0 c
- MSR MSP, R1
复制代码
4 g$ x) t; W" n: e然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。 - MRS R0, PSP
. j5 l0 ?( W! g - SUBS R0, R0, #0x20 ;压入R4-R11* d7 h# i3 |* s& I" Z1 J( Y1 `# _
- STM R0, {R4-R11}
6 N$ [2 }6 a% {' |( w# b" X - " D C3 j' q6 j9 N4 n$ Y# u
- LDR R1, =Cur_TCB_Point ;当前任务的指针
- v1 G" V, g& B1 Z - LDR R1, [R1]( ^ B: P A9 F) @7 I6 e; S
- STR R0, [R1] ; 更新任务堆栈指针
复制代码
3 R# t7 \ o" O出栈类似,但要注意顺序 - LDR R1, =TCB_Point ;要切换的任务指针
) p) M0 V) [# W/ V1 D! [3 R0 ^ - LDR R2, [R1]
8 _' b9 e( C/ l# B3 {+ ]1 | J2 q - LDR R0, [R2] ; R0为要切换的任务堆栈地址
+ [8 l" z5 z, x. ^9 I' l& c - $ |! h- o* H d
- LDM R0, {R4-R11} ; 弹出R4-R11. H; T* H ^0 O
- ADDS R0, R0, #0x20$ p% C& b. k a+ i9 R7 J
- ; k% p# c. n/ y) S9 D3 D# P, q; p% } e2 \
- MSR PSP, R0 ;更新PSP
复制代码 4 J+ ]# B" b0 a* _9 y
三、OS实战 新建os_port.asm文件,内容如下: - NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register.; @, }( j. {/ Q/ A" I
- NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).
& A$ G7 b6 Y* U/ ], i - NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest).
+ T0 |# O* d1 X8 ~) T4 ? - NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.
- K4 P W- L- O- ^; ~3 I% F# Q
9 W% U( s& r$ g& A8 z- RSEG CODE:CODE:NOROOT(2)
' ~. {& d$ V/ }9 x - THUMB
! v. j* l' z8 m+ M4 l5 M) D - 7 ~1 w& E4 {9 g
-
+ q: j$ W6 a+ p( [ - EXTERN g_OS_CPU_ExceptStkBase
7 ~& c c% t6 @+ q( K% N3 \ -
- W W. A( }7 b8 a - EXTERN g_OS_Tcb_CurP
# j: [% f( v* A" r2 T( h - EXTERN g_OS_Tcb_HighRdyP
* w6 V% ]; q6 _' E' c! @: @& h - 9 t; f/ W' p) Z$ z! E
- PUBLIC OSStart_Asm
* w% Y! {0 L5 M3 @ - PUBLIC PendSV_Handler
3 u, |, d; i7 H G! B - PUBLIC OSCtxSw
1 A% w0 O9 }% W. ^# b - , P/ ]- F& S# {; \8 ]2 Y/ h
- OSCtxSw
* u& Z8 W5 a2 k7 s0 ]% C - LDR R0, =NVIC_INT_CTRL
# k a5 R5 B2 s" [ - LDR R1, =NVIC_PENDSVSET
6 O0 B) C+ H9 i - STR R1, [R0]
+ k" [- K) ]* a9 a/ N - BX LR ; Enable interrupts at processor level e( C+ M# W3 O8 R$ c' d, A2 Q# F+ l
- ( |/ Z+ n, N# a+ Q' S* g1 I1 i
- OSStart_Asm
0 o% j3 n; M$ M" } - LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority: j" R2 s; I% i" e! ~! m
- LDR R1, =NVIC_PENDSV_PRI) s' B6 {8 @8 ~2 N
- STRB R1, [R0]
2 T. N( k$ F3 q/ r {- i6 }8 T
! f C) G3 Q# N- MOVS R0, #0 ; Set the PSP to 0 for initial context switch call: L2 O4 T6 G: o. W3 F; N5 @0 y1 u
- MSR PSP, R0$ O- Y/ i n. ~1 a. w
- * J# U$ I& y: [) ~! n
- LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
; L- ?7 J6 G% Q+ p2 {7 q8 N4 W - LDR R1, [R0]6 L8 h- F6 r7 g, @+ V' j
- MSR MSP, R1 B- P8 `+ W& I% X3 F
- # M* A j9 ^! b3 d, i
- LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
" K1 `& G& W2 p0 u8 b& z - LDR R1, =NVIC_PENDSVSET
7 v% ?+ ~) \# B: U( r - STR R1, [R0]4 Z$ l5 w$ u% ?& x
: |/ j E5 P! O/ i" }( v- CPSIE I ; Enable interrupts at processor level- m+ b% d a7 P* b! E8 P C. J2 B
* z/ y/ j+ U0 _, O- OSStartHang
5 k" L5 r' h$ C" E - B OSStartHang ; Should never get here6 T5 U2 j+ T/ n- T* B6 H6 s R5 j. q
- 2 | H/ W0 d( g2 p- T# h% T4 O8 S
-
, h# y4 A# L T8 P* a! T. v1 B. ^ - $ H& u) g6 f+ l% J& o) N+ b8 R' Z
- PendSV_Handler
, i& r V n" P( y# t% b7 F - CPSID I ; Prevent interruption during context switch
. c8 Y9 }9 J; W( j' f, ~ - MRS R0, PSP ; PSP is process stack pointer
& M, a5 N$ {1 B T) n+ j3 G - CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time3 u1 L0 `, S; X+ N( I
- 1 h. q6 B% u, R: m8 u9 A3 D
- SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack
8 R# j/ @# A e0 O& W: h; _4 d- | - STM R0, {R4-R11}9 Q$ k& j2 u S1 m+ D
! _; W3 u# `% a$ @+ s) A- LDR R1, =g_OS_Tcb_CurP ; OSTCBCur->OSTCBStkPtr = SP;% Z$ N5 W% b: S- L& f: s5 F: Y) y
- LDR R1, [R1]
7 J, C( a1 e3 n8 c2 u/ ^' g( \ - STR R0, [R1] ; R0 is SP of process being switched out- n; B/ Q! W( F4 D
- 5 Q: T Y. f7 j# g b
- ; At this point, entire context of process has been saved) M n6 o+ x4 V- {$ ^- F" m% y6 Q
- OS_CPU_PendSVHandler_nosave
. r5 r% n- |. P: Z# s. u; N - LDR R0, =g_OS_Tcb_CurP ; OSTCBCur = OSTCBHighRdy;. P2 M' X, w3 k" W) `' o
- LDR R1, =g_OS_Tcb_HighRdyP* i) r, M( O* n; G/ W
- LDR R2, [R1]
- t/ K$ {8 {. A9 V& g r3 f; M - STR R2, [R0]( ?7 K6 Y1 F/ `" D3 \. ^1 D) ^# S0 J
- 1 u1 h% [7 I/ v7 J- Q$ m
- LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;# h- S3 C) V4 o- _
-
! Z+ m, w* z' F - LDM R0, {R4-R11} ; Restore r4-11 from new process stack
1 B' q% o# Y" R) @- j! t - ADDS R0, R0, #0x203 Y9 @ M/ {- b) }5 Q
-
/ p) O$ W* a2 E+ a - MSR PSP, R0 ; Load PSP with new process SP7 C6 E7 o/ _0 C* R/ i/ j6 n( B( q
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack' x! u: N1 c4 \1 h/ p j+ S) ^
- + e0 L* U6 Z1 q( P; V
- CPSIE I
2 \$ O" }! P2 S - BX LR ; Exception return will restore remaining context% ?' X4 u8 Q9 \7 L: j5 ?; P- U
- # _( {9 P% `/ ?
- END
复制代码 8 o) k# e M& ^: t6 P9 z
main.c内容如下: - #include "stdio.h"# t' c8 d4 ?# `% q
- #define OS_EXCEPT_STK_SIZE 1024" \! A5 Z2 G7 i9 D
- #define TASK_1_STK_SIZE 1024
: R+ _& g! i# }% w. ` - #define TASK_2_STK_SIZE 10244 w& u0 F' P$ l5 \5 b5 s
- 0 C, E2 e6 I3 h7 d+ Z& F
- typedef unsigned int OS_STK;: ~ {! j9 C- S+ F6 m& B
- typedef void (*OS_TASK)(void);
! c9 C6 s0 u* d5 U - ; W6 r' m7 M2 f3 K! B9 }
- typedef struct OS_TCB" R* w; s D# \4 V* H, d2 G
- {" a" c1 V' C& M9 Y
- OS_STK *StkAddr;
4 w" X g5 r+ z1 s6 X - }OS_TCB,*OS_TCBP;
4 G0 c7 `7 ~1 {& f4 ~9 O) J* I - * w3 H! w' [' b* f+ v
" x8 o# [8 }2 D% h2 i0 o- OS_TCBP g_OS_Tcb_CurP; : r& ^( G* V+ d g3 J0 h
- OS_TCBP g_OS_Tcb_HighRdyP;* V! Z* \: W }; l% ^
- 9 G; k$ p5 r! c3 y
- static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];- [6 H4 F5 K% M) s1 x; E o
- OS_STK *g_OS_CPU_ExceptStkBase;1 `! a8 A0 L* x* H
- 9 B V( \6 w' ` j3 C
- static OS_TCB TCB_1;! A3 a2 C$ Z O, S# | k/ Y% H; X1 V2 `- c
- static OS_TCB TCB_2;# w9 ]* z1 J m7 J: ^6 ^
- static OS_STK TASK_1_STK[TASK_1_STK_SIZE];& `7 C3 J8 y5 B( l+ b2 z' p: n( Z) p- I
- static OS_STK TASK_2_STK[TASK_2_STK_SIZE];! k. Z2 J: d4 F6 B2 Y; T
8 S7 ~) T4 W7 ?3 b- extern void OSStart_Asm(void);$ N3 h6 `& ^1 \8 c( R) N4 Q
- extern void OSCtxSw(void);* o- s" N: c. w5 C- h
. q& r" U8 |8 T- void Task_Switch()
; h4 a3 G5 c6 f/ j* U6 j) R1 L( Z3 c - {& r7 G& E' i, \9 ]' w' ]
- if(g_OS_Tcb_CurP == &TCB_1)2 x- t- L. {, J0 r+ S6 L6 \8 `
- g_OS_Tcb_HighRdyP=&TCB_2;: ^( p5 Q9 v9 K+ g
- else
7 \% @. m- U# K4 U - g_OS_Tcb_HighRdyP=&TCB_1;( r8 d* [7 u1 [, g% a( ^* t
- ' X" n. h: j/ T& J
- OSCtxSw();
7 q7 h: ?& g! V; t+ K! a - }
' n4 D$ J" }3 w2 y3 B5 D' [; w6 P/ Y
' L# x( p- N4 L& i5 v- $ R- \+ G! D4 W, ^! X |9 R) u& j* ]
- void task_1()0 h) x( _- x& t' {: C; ?
- {- X1 ?- x& C, r F1 D( t
- printf("Task 1 Running!!!\n");
! o0 R& r. h* h# c2 _: { - Task_Switch();, s# V, I5 s7 {; a
- printf("Task 1 Running!!!\n");
+ p% T2 D* t) a% |; g& q - Task_Switch();
! x, I8 y$ w: ^( F - }
- ~) |" t& K. g! s* N - 9 H9 M2 ?3 W4 G; P4 M2 Q/ ]& r( j
- void task_2()
/ b# l, K5 c4 H y s' ? A - {8 J; q6 [8 q; m1 I0 H) ^
-
. U' F. [: W5 \4 c5 q$ `1 E - printf("Task 2 Running!!!\n");
* i2 u3 K( M% z+ H9 s - Task_Switch();$ }. M" k2 ~+ Q8 K, X' y4 P2 R% D$ c
- printf("Task 2 Running!!!\n");9 E- O2 T/ R! c3 U6 r; Q
- Task_Switch();
* U5 R3 a8 z3 R5 | - }
3 |$ M, E- O% O8 \5 O4 f$ o - % w" ?6 U" Z! [" @' b- ~2 U
- void Task_End(void)
5 m- }! X% }+ f" ~, \9 m b - {% X: F, q0 T1 G1 m
- printf("Task End\n");
* I" I7 I3 I# {: t* S - while(1)
3 q0 `4 E+ ?: L. ^8 U2 S - {}5 F/ ^: b8 ^! j: A5 f
- }
/ i! b7 @' N( {8 ]9 I4 |6 n) D
5 _2 ]7 }8 [& C8 e% z( A- void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk). u# z! m- p1 M! h. G. `; d
- {( @$ {1 s3 o, u1 D( r
- OS_STK *p_stk;
* q) }. [( b: _% Q# u - p_stk = stk;
, j& s1 M; {/ Y$ d9 O - p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);$ w5 w; H- {% P" e J
- # J: }+ D+ H3 \5 C0 m
- *(--p_stk) = (OS_STK)0x01000000uL; //xPSR+ w* s0 Z! q+ e, {7 D' M- [
- *(--p_stk) = (OS_STK)task; // Entry Point4 j7 K) J+ C$ G/ V* e
- *(--p_stk) = (OS_STK)Task_End; // R14 (LR)
0 T1 L8 G6 D F8 W) ], Q - *(--p_stk) = (OS_STK)0x12121212uL; // R12' Z' C& x+ Y' K z; I; I( m
- *(--p_stk) = (OS_STK)0x03030303uL; // R30 c$ a7 j" F7 V0 v4 u7 ?
- *(--p_stk) = (OS_STK)0x02020202uL; // R22 B( R R/ \* u
- *(--p_stk) = (OS_STK)0x01010101uL; // R1
. U1 A5 G1 O6 O0 e0 i - *(--p_stk) = (OS_STK)0x00000000u; // R01 l6 o* ^8 q* l1 m1 N8 ?( Q
-
# B' r p# v% K- z( P - *(--p_stk) = (OS_STK)0x11111111uL; // R11
0 A3 M) O/ b; Y, X* g y. p - *(--p_stk) = (OS_STK)0x10101010uL; // R10- W# L" |! x( v
- *(--p_stk) = (OS_STK)0x09090909uL; // R9
/ g+ F2 l$ ~5 U% U9 g - *(--p_stk) = (OS_STK)0x08080808uL; // R8
- j B# X8 x7 s; e Y5 @ - *(--p_stk) = (OS_STK)0x07070707uL; // R7; v U% O- e: B; t O Q
- *(--p_stk) = (OS_STK)0x06060606uL; // R6' y% m# V$ U! g0 G7 s! i/ m2 Z
- *(--p_stk) = (OS_STK)0x05050505uL; // R5
! {% v% l2 w1 |. [ U5 C+ N A - *(--p_stk) = (OS_STK)0x04040404uL; // R4, F3 C! o$ ^/ e' W) ~7 M
- 9 M, \( V9 L5 B. F& O' _2 k! {8 a
- tcb->StkAddr=p_stk;- X5 e9 d# m5 D
- }
x3 u" d6 u2 l: p9 K) @: y - & D6 r* @' _# X% U3 i& I7 f6 P4 j2 u
2 A) `1 \4 T+ ~& v- int main()
/ _, O9 J, _# W) C% J+ X - {
4 Q9 m; d7 I1 d* \, h% t' M - l& o2 M0 p' P) u6 L
- g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;5 h. @0 \& J0 A8 f, g- |
-
( `) o4 V8 g% t- `8 j5 z - Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);
. p) f/ v6 b4 D) {3 n - Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]); A3 ^- \* ^" `% u j
-
( p: }6 x2 U1 {; r. ?1 N - g_OS_Tcb_HighRdyP=&TCB_1;
+ p" q; f1 J7 `5 x4 ^ -
$ G9 x) N$ f. v$ J - OSStart_Asm();
& v. `: Z7 ?0 }( d. l6 X9 F+ A -
4 Y2 \9 b' a$ F$ n3 w - return 0;: j: P/ m/ ^! I V" Z$ L
- }
复制代码
* Z2 W4 Q" k- S. K* n编译下载并调试:
6 e! B5 A% M0 f# f z
5 Y: {0 s' I6 v8 a) O/ E' U
在此处设置断点 此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。 % q, e, U6 [" m7 S
5 @/ D8 H! c2 m* b$ \/ [- g
IO输出如下:
. f9 D/ [, n9 X/ T$ `0 [* X8 g' u) D4 d
! Z2 ]% V% v, O3 `
至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。
" N+ K- a- x0 B; V0 A |