一、什么是PendSV PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。
OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。 PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
7 d- L; b( L. [: @/ w1、执行一个系统调用/ x' R4 E: t$ E. H' [* g3 e* b
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_PendSVHandler
3 b3 R# m1 V0 k4 R: \- S* Z) e3 }. E - CPSID I ; 关中断$ p* b5 p5 x- y r! `& G
- ;保存上文 $ R, C1 V& N$ A1 [) O p! E
- ;....................... , [- ^+ D- j( R1 P Q
- ;切换下文 8 t( Z( b6 S& [0 }( `: I3 }! h
- CPSIE I ;开中断
# Y7 `" Q9 U' T. R# f7 v - BX LR ;异常返回
复制代码 & m, }* x0 C2 @. S# Q& n
它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分: - NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器/ a i/ [" P5 s" p
- NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14). ) b. ~2 T$ a8 t& v1 N8 u* g
- NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值, {4 ^( [/ z. n3 [3 k+ V
- " m8 w8 h, \9 G- t; ~6 e' @
- ; 设置PendSV的异常中断优先级
; e! V- u) c/ _/ }6 B- o9 ?! H
2 l: C- E6 L t* n( m0 P- LDR R0, =NVIC_SYSPRI14 ) E. X: ^/ t9 [# T
- LDR R1, =NVIC_PENDSV_PRI # i* n) E) H# c1 q
- STRB R1, [R0] ; 触发PendSV异常
& |8 x: o) ?% @; P+ Y( i& X1 f( U - LDR R0, =NVIC_INT_CTRL ) {( u$ v& Y9 j5 [4 v( w: \) N5 P2 p
- LDR R1, =NVIC_PENDSVSET
4 u6 u" q, Y" p# k, `, }: p8 j - STR R1, [R0]
复制代码
: h0 n; p: h2 T8 X9 t1 T 二、堆栈操作 Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP? - MSR PSP, R0 ; Load PSP with new process SP
; [9 s3 C, m0 \ |2 X - ORR LR, LR, #0x04 ; Ensure exception return uses process stack
复制代码
' A3 ~) X* T e* E% H. n9 z- R. K很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。 写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP, - EXTERN OS_CPU_ExceptStkBase) J; N% b* m# x0 i0 o0 O! ~3 a
- ;PSP清零,作为首次上下文切换的标志
" M4 q. P- A8 r" \9 d: j" E- i - MOVS R0, #0
+ _+ A! F3 ?3 ] - MSR PSP, R0
% i u: n* m2 ?4 v, f - ;将MSP设为我们为其分配的内存地址0 B2 V+ W+ W, M; V
- LDR R0, =OS_CPU_ExceptStkBase
; P* t" `7 p5 G0 @. D; } - LDR R1, [R0]) J9 @. c, r1 M' t. y" d# W
- MSR MSP, R1
复制代码
* T: U" ?, j" ^然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。 - MRS R0, PSP
8 L$ r0 |7 Z$ d7 V* q) S - SUBS R0, R0, #0x20 ;压入R4-R116 Z) z6 }8 Q7 S# u. ^" A9 C/ Z
- STM R0, {R4-R11}
4 i# I2 i6 M0 x& l5 J0 o7 k2 t6 { - 6 m% |5 e! Z( ~, Q/ E. [7 u
- LDR R1, =Cur_TCB_Point ;当前任务的指针
! _4 h" k7 X0 L1 Z! q4 B* o: Z, f - LDR R1, [R1]
8 R1 s9 J6 @* n4 v - STR R0, [R1] ; 更新任务堆栈指针
复制代码 5 ^* d% Y6 y$ P- P; C
出栈类似,但要注意顺序 - LDR R1, =TCB_Point ;要切换的任务指针
% O2 g1 E: x6 ?6 d1 g c5 _ - LDR R2, [R1]
* g2 c9 r4 w: u5 X& Z. a2 F9 O. V - LDR R0, [R2] ; R0为要切换的任务堆栈地址- B( L' o" l; u
- * F2 f+ x. W+ I, K. D
- LDM R0, {R4-R11} ; 弹出R4-R11& g# a3 p$ q& H' A
- ADDS R0, R0, #0x20/ l2 @1 y6 i9 e5 o
! Z4 f7 M# d: _. d; G7 B- MSR PSP, R0 ;更新PSP
复制代码 * D' K( M! m! o- J+ K
三、OS实战 新建os_port.asm文件,内容如下: - NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register." l1 t; \0 i/ W
- NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).
V- R3 W: e- ~. A - NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest).9 j- k- Y: A [8 T
- NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.
! {8 P# |! Z$ r0 \( c( t. P
5 r) V! J0 y8 |5 J' ^7 i- RSEG CODE:CODE:NOROOT(2)
4 b+ j9 {) a, J - THUMB
8 V/ U5 b& q4 H4 G- ]* \: a" p
1 R! S+ g0 K+ \4 s7 @) D$ X) ~, v-
' d$ f- c. j% Z9 d" f* a - EXTERN g_OS_CPU_ExceptStkBase8 r) x8 Z6 I6 ~ M
-
/ q/ ]/ @5 V# s; N$ Z3 e - EXTERN g_OS_Tcb_CurP. e7 [0 Z* n; k- f2 M7 M/ ^3 b' X
- EXTERN g_OS_Tcb_HighRdyP, h1 n4 p- X* Y% `- d
- ( c& B" l& {' F& r- `; d9 c( E
- PUBLIC OSStart_Asm: ?* g4 S4 ?) ~* u4 v' Y' G/ q
- PUBLIC PendSV_Handler
& ?( x! I! A3 g. d# u - PUBLIC OSCtxSw
' |' w9 V J- N7 t2 L" v$ L
7 I4 B2 B: m# H" K& B- OSCtxSw
1 Z2 v+ S$ z3 {" }: D - LDR R0, =NVIC_INT_CTRL
$ h; M; \% b X# F3 N - LDR R1, =NVIC_PENDSVSET
# Q6 K4 d t5 y, @/ Q; H0 @8 E - STR R1, [R0]0 g& l- A" i( b8 W
- BX LR ; Enable interrupts at processor level
0 j2 z" ^$ H! R, H
6 U2 S! @' g; T& ~/ U7 B- T- OSStart_Asm$ t. e0 x0 \9 d( v, h
- LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority6 A3 C% x4 J _2 w! r2 Z
- LDR R1, =NVIC_PENDSV_PRI
H' f* [4 l' o, x# m1 v - STRB R1, [R0]! `# K+ T2 s! ?
- ( D+ T. g) ?$ y1 D3 Q; D
- MOVS R0, #0 ; Set the PSP to 0 for initial context switch call) W& k. N- z& _9 n
- MSR PSP, R0- b8 g& B% n8 H& N# u1 T3 F& j$ L
: j0 U8 I* r S- LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
3 [) S+ `, z% t2 @ - LDR R1, [R0]
{/ u5 D& R4 s3 A - MSR MSP, R1
! n4 P5 g5 D% h# F* E# x3 n! G
0 x% ~2 t2 R; X6 T/ ?" h, L6 P& b- LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
( F6 C) g; Y! e [7 Q' Z - LDR R1, =NVIC_PENDSVSET
5 K" I J" s9 m - STR R1, [R0]
# ^3 M/ J* K5 W" t/ `& @ - ( r/ y& g6 b: v1 v: J
- CPSIE I ; Enable interrupts at processor level' z$ A% }7 I3 p3 H' e8 M
- ) }& L ?5 t T3 l7 c8 |
- OSStartHang8 c6 U% k# q8 i; b9 Z; j
- B OSStartHang ; Should never get here
$ X( L8 v# U: d9 c4 R -
- f: f: }" ] p4 A1 m% j- G - s x% g& I; R, C; V9 X4 {
7 z# w9 g3 c6 w- PendSV_Handler) {( F- [' P e2 P7 p" ^! c$ Z7 Z
- CPSID I ; Prevent interruption during context switch$ m7 j+ D4 {$ U, y
- MRS R0, PSP ; PSP is process stack pointer
R# A4 E8 i, G- r - CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time
- K5 m; G* K) P2 d -
$ V1 K9 ?/ i0 ?5 A5 f) H2 ? - SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack6 e3 R9 K( t* `0 X4 G. b. G% d
- STM R0, {R4-R11}9 E+ c$ S3 X2 p8 o4 o
- ; M) w7 Y. ]! k" K+ `
- LDR R1, =g_OS_Tcb_CurP ; OSTCBCur->OSTCBStkPtr = SP;
& q1 V" |7 K" ?. X- [2 w, C$ d - LDR R1, [R1]
V% @! {* e9 Y- L" _9 G - STR R0, [R1] ; R0 is SP of process being switched out+ @* P D6 i5 Y o) }4 w0 t! T9 f
4 |& w( `6 e: v% `5 H- ; At this point, entire context of process has been saved4 b" Y2 s" A! U4 s
- OS_CPU_PendSVHandler_nosave" H" d4 w% w5 {$ o, Y) @
- LDR R0, =g_OS_Tcb_CurP ; OSTCBCur = OSTCBHighRdy;
1 q, D& P4 U7 i3 }& a4 x9 H+ a - LDR R1, =g_OS_Tcb_HighRdyP! w; P% T7 T3 Z( y3 d, A' L0 y( H
- LDR R2, [R1]
/ L$ J D8 {) }- g$ o - STR R2, [R0]
/ Y& U1 R; _$ \: c8 T8 r - - S( d+ M4 u. Z' h. q4 C$ I: A
- LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;% p( o; \$ q$ ^+ B+ ?; @1 l; I
- ' R$ \0 l1 I/ P' _
- LDM R0, {R4-R11} ; Restore r4-11 from new process stack
2 R% Q* t2 E. Y% r' D - ADDS R0, R0, #0x20
( x5 N+ I& U) d/ v0 D& i5 m& p -
9 P& U* ?) T+ H: G! u7 w; `+ F) u - MSR PSP, R0 ; Load PSP with new process SP
- j' J, g x0 I& A- ]; K } - ORR LR, LR, #0x04 ; Ensure exception return uses process stack
) ?0 o) h( Y* r$ S -
9 j) N D- }! \/ @0 u4 k) H9 z5 E - CPSIE I
& \$ }" m8 ~1 s% x0 H4 D' h - BX LR ; Exception return will restore remaining context8 q/ Z1 v# d. f! a9 q2 @: x
-
9 W8 ~- U" a% } - END
复制代码 - @; s) a( i! c4 j
main.c内容如下: - #include "stdio.h"
0 q" U4 U& _6 T; X; G - #define OS_EXCEPT_STK_SIZE 10244 I. j) ~6 l6 b+ N7 }3 A
- #define TASK_1_STK_SIZE 1024
# ]1 a: D6 s2 r, d - #define TASK_2_STK_SIZE 1024
5 d+ i: U% ^- X+ L f+ a2 i - : c& @& q, k( c' U6 W; l7 u7 j
- typedef unsigned int OS_STK;; t7 q" V, _4 g2 v
- typedef void (*OS_TASK)(void);
v8 \6 l4 k0 U& n* | - ' i8 p- W, G6 w
- typedef struct OS_TCB
% a) q+ o& K( z9 M5 P - {
' k2 o4 }) M h+ ^3 Y6 e( Y( m/ c - OS_STK *StkAddr;
! q+ ~/ K4 H% Z3 u+ B - }OS_TCB,*OS_TCBP;
% ^4 p, f, p! X3 G' E$ h
& X h' M% f1 F1 b( n, ` Z- 0 Y# E5 t" P6 o) V0 s: `; ~( f
- OS_TCBP g_OS_Tcb_CurP; % f, K! |/ o% ` G
- OS_TCBP g_OS_Tcb_HighRdyP;2 D& f% ^/ I. f
- " I/ R: v+ s# S
- static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];4 Z* H! m8 q' m3 X4 L
- OS_STK *g_OS_CPU_ExceptStkBase;7 Q; E3 e6 X+ O6 [# |8 J
- ' i# c( G" N! X: A# B7 L1 E
- static OS_TCB TCB_1;
; O2 }- y/ s3 X - static OS_TCB TCB_2;
4 Z4 u" b/ ^$ B: e+ z7 r - static OS_STK TASK_1_STK[TASK_1_STK_SIZE];
( W, O0 d2 l' M* i! i5 [0 J- o - static OS_STK TASK_2_STK[TASK_2_STK_SIZE];1 q# _/ q$ H O- C3 ~) c
) v7 ]% n* p3 m- `% W, }2 C6 j- extern void OSStart_Asm(void);
" X7 k0 \7 y5 j6 B- |/ q: }7 ` - extern void OSCtxSw(void);
0 o9 f7 ~: b7 z6 q+ e E: U* H) p - G1 q! n4 F- e0 s
- void Task_Switch()# a" ^ p4 ]! T0 E' @6 k
- {
( U. [+ y' t! M/ K: a* Z9 P - if(g_OS_Tcb_CurP == &TCB_1)( V8 L$ ?! N; ?" a$ N) }0 J6 n+ J
- g_OS_Tcb_HighRdyP=&TCB_2;* \1 s! y0 d' h' B. Y/ y
- else
2 s T. Z8 n I% ?% v - g_OS_Tcb_HighRdyP=&TCB_1;9 _1 @ q8 K8 `( z: A- c
-
3 F& v# c5 Q) W8 D; n& v, ` - OSCtxSw();
9 H; s% e. z+ k5 S9 Q6 _ - }+ q/ K, v7 i6 H( {2 v
- A W% N% s/ E w
- D8 N. ]5 q( i9 b a$ }
- void task_1()
6 P) c& _& S z# T( u# s# g' Y - {
! P" y& O* x5 b i7 p7 {+ l - printf("Task 1 Running!!!\n");5 @2 Y; k# K# E; Q+ h- q; ~
- Task_Switch();2 f$ `) B, C @# M4 |7 ?) e
- printf("Task 1 Running!!!\n");
, G% f4 J/ T$ Z - Task_Switch();
5 y- \4 P9 Z9 q# i - }4 H3 \1 q" U3 W" ?
0 q7 B }' W/ o1 ?- void task_2()7 ~& `# l: a9 Y
- {
* W4 c' {& Q! Y' h" o3 \( H -
! L; N3 z0 ?2 Y% G8 T: o - printf("Task 2 Running!!!\n");0 R" h0 ]2 M4 f# M' i) m5 n
- Task_Switch();
- u' A3 o9 z6 j" D7 C, ^ - printf("Task 2 Running!!!\n");
4 w0 n8 Q3 z' D) t - Task_Switch();' i0 G* J8 r5 F7 s+ ~
- }, q; a9 T: u' m0 e* l
~0 f+ z, v) l/ ~5 F2 z- void Task_End(void)8 ]- Y. S" D8 d5 i( m E
- {/ Z) d, e1 V( X, u9 s* r
- printf("Task End\n");
# E3 ^, O0 P9 }: G1 g) G - while(1)
% E9 V: A1 t5 R6 [4 K - {}
0 |" k9 \5 s" r" Q% h+ W - }. n6 E; e; e$ ]5 m- N
8 p; `! U8 `- o- @. q- void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk)
- x. P& |- o9 _ - {
8 u9 L5 @# n% G. J" ^7 @5 q - OS_STK *p_stk;
) k- j- K0 k ]4 D* g - p_stk = stk;
- |* \+ K5 m" {8 S: T w - p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);) n& h5 R0 z8 z3 m5 q8 }- @. O0 a
- & k9 P% g7 c: A: _
- *(--p_stk) = (OS_STK)0x01000000uL; //xPSR: C& p/ @) _/ Z5 P" y. a
- *(--p_stk) = (OS_STK)task; // Entry Point
: u& i7 p, P- P+ t - *(--p_stk) = (OS_STK)Task_End; // R14 (LR)3 S q% q- W. p8 X# ~
- *(--p_stk) = (OS_STK)0x12121212uL; // R12! w2 D8 ~9 j! F3 f, e/ c: l
- *(--p_stk) = (OS_STK)0x03030303uL; // R3, t& x( S" g4 c# t. w. W5 \
- *(--p_stk) = (OS_STK)0x02020202uL; // R23 R0 H; ` i+ S @4 m! P& X8 o' _" p; t; X
- *(--p_stk) = (OS_STK)0x01010101uL; // R1
6 I6 n+ @3 R! U( N/ L' _ - *(--p_stk) = (OS_STK)0x00000000u; // R0
# g( N' N5 } ~& i; U8 n -
7 z: R' Z3 F; A! I, ^( u! Y - *(--p_stk) = (OS_STK)0x11111111uL; // R11
9 M) l3 t- R" v8 w - *(--p_stk) = (OS_STK)0x10101010uL; // R10( }, m3 e1 N: `9 r! Q
- *(--p_stk) = (OS_STK)0x09090909uL; // R9! I ~- i" k4 f- E4 ^5 }6 D
- *(--p_stk) = (OS_STK)0x08080808uL; // R85 e; K$ v$ ^( p V/ g7 l( g
- *(--p_stk) = (OS_STK)0x07070707uL; // R7
7 g$ [" f6 K( d3 `7 |5 m* X - *(--p_stk) = (OS_STK)0x06060606uL; // R6( k0 y! ^& A. I. Q+ M
- *(--p_stk) = (OS_STK)0x05050505uL; // R5: G9 k5 A7 `" X1 |" |
- *(--p_stk) = (OS_STK)0x04040404uL; // R4, D. R7 ? F# F0 L
-
* X2 \6 H; ]+ M4 ~% o - tcb->StkAddr=p_stk;
( ~5 r5 o# t, v% j# N - }
# o1 v$ M0 P! ]
6 X7 A% y" ]$ b2 }- ) q8 o+ o6 N( q
- int main()" o1 ]! `5 n% u- H% S
- {
8 w" e7 }6 A6 Y0 d - & Q+ y0 i0 w7 q2 \) z& ^
- g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;5 G- N# }9 s: N6 }3 a; _# `3 h/ V% D# c
- " h# K9 E* D n/ N& g4 W) ~
- Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);. @. c t6 P' n/ X! U% n
- Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]);
+ j/ E+ H3 H: h, l+ p- ?- _: j -
3 O! W+ f+ d" A3 j! d - g_OS_Tcb_HighRdyP=&TCB_1;- l) O5 }4 n; P: ^6 U
-
6 _$ u e: Q2 |0 k! @ - OSStart_Asm();, Q3 z" w7 T6 Y8 i8 H
-
* i- ?7 r4 O4 i3 A6 b { - return 0;
! a% M7 g3 `2 C% v: |0 [3 t" x# c) B - }
复制代码 . [6 l8 H# r9 A5 D6 x9 q2 Z
编译下载并调试: 0 u/ r* y5 |: Q( h4 g+ _3 F
/ V0 g Q9 f8 f8 N0 A
在此处设置断点 此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。
6 z3 _9 E! X# [! P, M" F3 a- E7 p
' H3 G6 n7 v: s- B
IO输出如下:
& |- b# {$ ~% X
. ~7 u; j+ h+ E G1 G0 E
至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。
- B' U3 K x5 Y/ F) V |