一、什么是PendSV PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。
OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。 PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
! Y. O9 y: x6 z# M% ~0 E1、执行一个系统调用
% X; W7 a4 u0 z$ K4 W' U% W" W6 e2、系统滴答定时器(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_PendSVHandler9 w: G$ H: j }" }3 y/ `8 z3 L
- CPSID I ; 关中断
0 @0 [; e2 D2 |: s2 ] - ;保存上文
" ^# p6 F8 W( d2 T# f0 c0 X/ } - ;....................... 9 K) P* H" b* ?& }( X' w$ X
- ;切换下文
6 ^6 g. q8 b. |; q: t - CPSIE I ;开中断$ W5 d( ~$ b5 g" }' S: ^
- BX LR ;异常返回
复制代码 , b$ `2 ?0 J0 g2 R
它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分: - NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器
$ l' g' ^. p) U - NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14).
4 H) l8 t6 O g5 F8 X9 ] - NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值! W. m& m* U# t' M0 M: }( }4 C, {
- ' M9 Q1 C' ^* }) J) d: s9 F* P" G7 \
- ; 设置PendSV的异常中断优先级. g6 g& `; P+ `' e
% I' b0 I% k s& A7 P2 u0 R) G- LDR R0, =NVIC_SYSPRI14 ( ~/ @4 p: m+ W# U- ]
- LDR R1, =NVIC_PENDSV_PRI
5 R6 [ n. N* n5 Q1 \ - STRB R1, [R0] ; 触发PendSV异常; |' l7 L' P$ `( y8 c
- LDR R0, =NVIC_INT_CTRL 8 d. {4 e) M4 f: c- ~
- LDR R1, =NVIC_PENDSVSET
: M) s0 d( ^" q3 i# U - STR R1, [R0]
复制代码
5 E! [% [. O' q) s6 O 二、堆栈操作 Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP? - MSR PSP, R0 ; Load PSP with new process SP9 ~$ ]7 f3 m1 e" L) P' T9 R% H
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack
复制代码 ) M" ^4 `' X7 C3 Z
很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。 写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP, - EXTERN OS_CPU_ExceptStkBase
. y& f0 T! X/ {" p! a - ;PSP清零,作为首次上下文切换的标志
f0 L# P& `2 K7 |' L" [. }2 Z - MOVS R0, #0
% k2 L! n- B9 [! _. y8 ` - MSR PSP, R0
7 d2 t5 P2 G! T0 L' }1 K; D: Z - ;将MSP设为我们为其分配的内存地址
, q% X4 Z& ]2 a9 P - LDR R0, =OS_CPU_ExceptStkBase I8 z0 f7 m' M) F) D7 w
- LDR R1, [R0]
9 [. ], b$ Y. Q) Z - MSR MSP, R1
复制代码 0 ?0 b! F* t* q6 w
然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。 - MRS R0, PSP
' W! F6 o, a; z! B# s: n - SUBS R0, R0, #0x20 ;压入R4-R115 V& B& r1 s' L, t+ V
- STM R0, {R4-R11}
$ R) M/ n$ j7 @! N! ~
( n- k8 V& Q( c8 M- LDR R1, =Cur_TCB_Point ;当前任务的指针6 A0 j; l! s6 i5 J+ \; i0 L
- LDR R1, [R1]& {6 P5 ^; z& ?) e
- STR R0, [R1] ; 更新任务堆栈指针
复制代码
0 o! g7 n/ h9 a1 w4 l- C9 d出栈类似,但要注意顺序 - LDR R1, =TCB_Point ;要切换的任务指针% x; P$ [+ `: M8 P7 f4 j W
- LDR R2, [R1]
; p. W2 ]# a% f6 I6 P% o0 i. { - LDR R0, [R2] ; R0为要切换的任务堆栈地址& t' E7 @. c: p' k# x+ Y0 \
-
6 D( `( ~! h Z. @8 e- D, s' a - LDM R0, {R4-R11} ; 弹出R4-R117 B2 N# X7 g( ?
- ADDS R0, R0, #0x20( D0 @- u: q( X3 h
- 1 `3 [+ p6 J- F" A
- MSR PSP, R0 ;更新PSP
复制代码 # D* B' i! o: j0 d ]: j
三、OS实战 新建os_port.asm文件,内容如下: - NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register.% S1 z, P/ j; z2 Q
- NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).4 C6 h0 k6 M7 M2 y. h4 i3 J
- NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest)./ f# {! j# p+ `' o6 c" |
- NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.
5 |$ w: h6 p! c! _3 Z
+ @1 e& l" j" V: o# R" ?) f- RSEG CODE:CODE:NOROOT(2)8 X% N4 w" v. `7 G6 T
- THUMB
% p0 i. P B e+ J z - / w4 ?& ?2 {9 r; a) t
-
0 ~- z+ U7 G! j - EXTERN g_OS_CPU_ExceptStkBase
. I8 e! F6 V2 }* [+ N: s1 b$ o% S+ R - ) x% g5 E- |2 ?1 n
- EXTERN g_OS_Tcb_CurP n8 ^4 C0 _9 \' z5 X5 W9 Y
- EXTERN g_OS_Tcb_HighRdyP- }- Z7 t- U k# ?0 I" P; _
- ! \& C9 d$ q: v7 W# R4 p
- PUBLIC OSStart_Asm
7 C& _" b- W. E& G* g$ O3 } - PUBLIC PendSV_Handler+ Y( d2 t1 f @$ J( K
- PUBLIC OSCtxSw
+ |# \. k2 s( F2 x% [" @
3 T7 N+ W$ u* J7 a- OSCtxSw) f% c- ?$ i4 o9 v5 q# i) [9 T5 s
- LDR R0, =NVIC_INT_CTRL
5 D% N6 k* }9 t$ e0 Q! g - LDR R1, =NVIC_PENDSVSET7 {. }% D: m8 n1 [* X+ n& F
- STR R1, [R0]
+ K( Z' Q! U& Y2 f$ f. ? - BX LR ; Enable interrupts at processor level |9 O" |) m6 H% G% A) v" m7 n7 O
- & {2 [! c1 s. ?
- OSStart_Asm
2 G0 j- @8 d+ h" i. P, z - LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
4 r- F! W+ D1 l" ~ - LDR R1, =NVIC_PENDSV_PRI3 t, o2 c8 [' ^7 F% M
- STRB R1, [R0]
) j6 c0 U$ l6 w" X: @8 w - % z# X8 P8 `9 |7 ~) Y
- MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
" Z5 R# J1 N8 D6 z; T - MSR PSP, R0$ U. I- N1 ]$ O8 e
- + c# R2 F5 g8 i3 @+ [ q8 m
- LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase: h4 u _, u$ w; q
- LDR R1, [R0]0 V# E3 j# ^2 {' `* \& L
- MSR MSP, R1
' ]" J# p9 A1 O5 o - ( g4 k, X! j+ @% v l% n/ E! F$ n4 O
- LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
& B$ A7 O+ f9 V# _ - LDR R1, =NVIC_PENDSVSET
% j, v* _% X( T - STR R1, [R0]
& ]$ p& |4 D$ F8 Q/ o D7 s% v9 S
0 i4 }2 T1 \1 l2 ~- CPSIE I ; Enable interrupts at processor level8 l: B. o2 l& X! w
) t0 C8 j9 I( \5 H- g- OSStartHang2 e; T' w k" T. v
- B OSStartHang ; Should never get here4 ]% m; p+ S$ F5 ` S& M: Z
- - _* l& x. Q8 p
-
0 |% ]/ B0 M6 _) {$ ` - 4 |. }! Y% a: \0 n& |
- PendSV_Handler
& ]$ D' ~$ _0 ?; a Q/ }4 K2 ? - CPSID I ; Prevent interruption during context switch5 Z# X" H' v0 l q9 ?6 p, s) h/ A
- MRS R0, PSP ; PSP is process stack pointer
1 \ r1 ?& {" c7 F* j- H! T - CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time1 k5 h, h2 A; p1 y; _, N
- % J- V/ c0 m" x5 f& j: G u, F
- SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack. f2 f3 G6 \- X
- STM R0, {R4-R11}/ f! O2 E+ U- x' Z% t# @
- C0 S9 j) ]& k* E/ g7 `3 c
- LDR R1, =g_OS_Tcb_CurP ; OSTCBCur->OSTCBStkPtr = SP;) `' j' i. }# S' C" X" K* F0 y6 Q
- LDR R1, [R1]- J- H- M% r; T1 d7 ~6 ?# }+ P
- STR R0, [R1] ; R0 is SP of process being switched out/ |4 s, L R# |/ p7 o: M: i2 Z
- 2 y e, Y6 E a; _) X" e
- ; At this point, entire context of process has been saved9 `; q* O+ J$ R4 t8 B; i
- OS_CPU_PendSVHandler_nosave
9 C* _! k/ \& M - LDR R0, =g_OS_Tcb_CurP ; OSTCBCur = OSTCBHighRdy;
6 s* }, e2 ~- _4 u& F7 H K+ | - LDR R1, =g_OS_Tcb_HighRdyP# g; i Q: Y+ g- x4 l4 d; j" x
- LDR R2, [R1]
( n9 A# Z8 ]2 U: k* W, h4 | - STR R2, [R0]
" h2 `7 m9 N* v; m, B - ) K4 m1 ] g4 h; V7 o" ]
- LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
: h+ r" g- V5 z3 h+ Q& n. \: ? b - ' B" M! ~: E3 K
- LDM R0, {R4-R11} ; Restore r4-11 from new process stack
& S1 L9 K$ L3 Q# z - ADDS R0, R0, #0x205 i% N1 X9 l! G6 I t5 f
- * R; o6 f( L' J$ a6 u
- MSR PSP, R0 ; Load PSP with new process SP" e4 U: u* x& V6 l4 x0 O
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack& r& v- q$ x- P% P1 \+ d7 f
- / W8 a. W7 F% s: g* o) Z# n' [) P
- CPSIE I
/ @ u7 e: A4 L, k - BX LR ; Exception return will restore remaining context
- o) o3 a- S( q -
- D: |5 K; L M! Q0 | H b - END
复制代码
9 K7 Q) l1 y. P. tmain.c内容如下: - #include "stdio.h"
# M) }# m$ u: x* @ - #define OS_EXCEPT_STK_SIZE 1024+ Q+ }6 e2 e: E C
- #define TASK_1_STK_SIZE 1024
: Y# k- W6 M' Z& l8 s* \ - #define TASK_2_STK_SIZE 10249 U- ]# j9 S+ K, c* C6 i- A/ f
- @) Y, S$ _$ I/ Q/ H0 s R
- typedef unsigned int OS_STK;# l x8 x5 w# U1 z$ c3 L# U
- typedef void (*OS_TASK)(void);; i4 `; w+ o: [
- * U N; e4 Y$ j: l
- typedef struct OS_TCB
5 y" K* w9 M( C/ I6 A0 J$ W$ i4 E - {
; k7 W& p! E9 L, ^2 \5 m% o - OS_STK *StkAddr;# g% z- R- m& I9 [
- }OS_TCB,*OS_TCBP;
4 _! w; b, e- C* g3 |; ` - 1 B! G. [$ v; _5 c5 L2 W
3 |1 E; z: V6 c- OS_TCBP g_OS_Tcb_CurP;
5 H5 g. r/ O3 G2 z0 \6 ]$ J4 N) G& B9 s - OS_TCBP g_OS_Tcb_HighRdyP;( }# F/ a# F- M5 g' R' [
/ U. P u, F; i+ l- static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];9 o# v& u4 m; f. ]- v/ i- T
- OS_STK *g_OS_CPU_ExceptStkBase;
! U+ G: {0 O% B& G; H; Q
! v2 ^9 _% m6 m6 l$ t; W- v- static OS_TCB TCB_1;
9 a" B% Y$ m. c( w/ V1 v - static OS_TCB TCB_2;) ]# b7 e* K: B: D0 R
- static OS_STK TASK_1_STK[TASK_1_STK_SIZE];
) F( ~. l( B8 X1 Q - static OS_STK TASK_2_STK[TASK_2_STK_SIZE];
, J) r/ i2 H1 N1 k# R
' ]6 d# X5 B6 w- I& g( F8 O- extern void OSStart_Asm(void);
% {2 d; C/ s/ W4 l - extern void OSCtxSw(void);2 J1 c7 ?! c2 y: h. S. t# O
- ; D1 g n! R5 P1 _0 O; o( S* E8 s
- void Task_Switch()
- t4 `6 m% }* z1 c3 N - {
. b) K5 ^8 \2 \2 g4 f - if(g_OS_Tcb_CurP == &TCB_1)
s9 ~9 N, } v, F0 i - g_OS_Tcb_HighRdyP=&TCB_2;
9 }1 Y% ]7 T- x( b7 Z - else
- b& H7 z3 |3 F6 |! Z' O - g_OS_Tcb_HighRdyP=&TCB_1;2 S8 H X9 u; E/ S6 H
- 7 X% j0 G6 @, T" G
- OSCtxSw();$ n) W$ O# p* a0 T. u
- }8 O1 y$ k [) r; o
- / h+ K2 Z$ n3 ~4 y
- u, y6 h, k- [! L: E9 q$ O7 ]; {) w- void task_1()- G# v5 Q3 G4 h4 c6 S
- {$ O( @4 ^: M# O5 q' @0 ^$ t
- printf("Task 1 Running!!!\n");
# a' L- P0 [1 ~4 N& G - Task_Switch();
! h, {5 p" Q! r - printf("Task 1 Running!!!\n");
, o1 J3 y G4 |9 G9 K - Task_Switch();; X9 D9 [) w" Z
- }9 M- g0 o% m# f+ i( G" l0 w
- 9 G8 F( C* ]2 Y" g
- void task_2()
7 V, K1 O) L* v% ]( z3 W7 P2 f' z - {0 E, ~, C- J% g3 J) B4 T
-
0 `8 U) t# v$ E- F0 b ]) I+ I ] - printf("Task 2 Running!!!\n");
" b4 q& r) `8 \3 T r- P3 [ - Task_Switch();1 `/ I: \; n h6 n
- printf("Task 2 Running!!!\n");2 a& {& ]' H! _( {: ]9 U
- Task_Switch();+ m( i9 Z* t! l* e- Q
- }" B Q" ]1 ]' ?$ T1 H$ U
- 5 {5 v# i* R/ X! j8 }/ s$ N% n" [3 a
- void Task_End(void)
. @1 a: J1 J6 n. Z, L2 { - {: J4 Y( U' _% x3 {
- printf("Task End\n");
7 k N- n- D' h2 ~! [ - while(1)
+ r0 [6 ?5 E: p9 U! X* w - {}
$ {6 w* Q5 P. ? - }5 G- ^9 G, Z' @4 }- X! e$ y! D0 i
- : a4 \4 f: G$ D, ^* ]' m
- void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk)
3 b4 e% {6 D. S4 ~1 J3 o# k$ w4 s - {+ v6 v0 B! E. N
- OS_STK *p_stk;
# ~" f6 k! z6 T( |- w7 x7 t7 _ - p_stk = stk;0 n* I7 F5 M4 a6 y( j6 y% D% B
- p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);
; X, n+ W* U/ x+ s! G -
9 {8 G. Q* C. K& M0 J% Q - *(--p_stk) = (OS_STK)0x01000000uL; //xPSR: @- b* J' D2 K5 y% m5 R% K
- *(--p_stk) = (OS_STK)task; // Entry Point
% |/ [& Q2 d2 J4 I4 c. Y' Q1 F - *(--p_stk) = (OS_STK)Task_End; // R14 (LR)/ j: g. I' l. @4 w
- *(--p_stk) = (OS_STK)0x12121212uL; // R12
0 m* j9 n- E1 h$ o - *(--p_stk) = (OS_STK)0x03030303uL; // R37 \& b- E1 S, s( w+ J6 y9 ?
- *(--p_stk) = (OS_STK)0x02020202uL; // R2
6 d( _) r9 H( `4 Y: D. a - *(--p_stk) = (OS_STK)0x01010101uL; // R1
/ V* E0 p L8 K: M - *(--p_stk) = (OS_STK)0x00000000u; // R0
9 Y; V6 w0 T9 F7 ~0 E -
+ Y5 w1 a- ]. j - *(--p_stk) = (OS_STK)0x11111111uL; // R119 I& F/ t, r6 a' w* v
- *(--p_stk) = (OS_STK)0x10101010uL; // R105 k" G+ r4 |* Y& {5 x: N
- *(--p_stk) = (OS_STK)0x09090909uL; // R93 S% S/ Q% C& m8 f# ^, V. j7 b
- *(--p_stk) = (OS_STK)0x08080808uL; // R8
5 E3 G( W& u! }8 G3 a - *(--p_stk) = (OS_STK)0x07070707uL; // R7
. T& w: R" m8 v& m& ^- e* J% \5 h - *(--p_stk) = (OS_STK)0x06060606uL; // R64 @7 `# {6 I0 N
- *(--p_stk) = (OS_STK)0x05050505uL; // R5
8 O" _9 s% O6 O- l8 J( _: J - *(--p_stk) = (OS_STK)0x04040404uL; // R4
# Q& ?: w) m! D; { - % g% Q# L. a1 X# g) E
- tcb->StkAddr=p_stk;& V8 K8 G4 v, z: s s* x8 ]
- }
) U! m/ W$ c, P - , G* C9 B9 `4 x0 T
- * K# k, Q& s4 x$ B! g# `2 a3 h
- int main()/ l2 b8 l! ?& D- y0 y- l) }
- {
: l1 D# t* M; e) z, L) L4 O -
4 O8 A+ B+ x: q3 l9 l0 n4 O. z9 `+ ? - g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;
3 h' M, w9 Y* u - / {6 r9 m5 v( P, a6 K( K9 H6 m
- Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);
0 x8 n: {) {+ F" f2 U6 n2 y - Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]);
3 y; w5 L+ W, `+ a* }; `1 E0 X -
" z! G7 q- P. J, P6 ` - g_OS_Tcb_HighRdyP=&TCB_1;
: ?/ S/ p9 z& M - & l" I7 x) t" F* y8 A) O
- OSStart_Asm();
) s9 T+ _. J M# a; c3 ]( J! H - / ^3 L. O: u s! s; n; W1 U/ O) r
- return 0;
. k! O T x* j+ x2 _ - }
复制代码
" t" m9 {% e3 k+ X6 S( a& N7 `编译下载并调试:
+ L, a/ |# c& K3 X4 A7 r. a6 l( c
2 i0 C. v7 \. s" Y! B9 ]+ P! d
在此处设置断点 此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。 9 `6 V, q& T1 R4 g
) ]5 X1 A& {1 N: s8 L2 `4 M
IO输出如下:
( n0 ?. c ?. X0 Z. U
7 f4 v& N: _ H& M0 O T
至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。
+ L- @" `- X2 X4 F8 _3 M |