一、什么是PendSV PendSV是可悬起异常,如果我们把它配置最低优先级,那么如果同时有多个异常被触发,它会在其他异常执行完毕后再执行,而且任何异常都可以中断它。更详细的内容在《Cortex-M3 权威指南》里有介绍,下面我摘抄了一段。
OS 可以利用它“缓期执行”一个异常——直到其它重要的任务完成后才执行动 作。悬起 PendSV 的方法是:手工往 NVIC的 PendSV悬起寄存器中写 1。悬起后,如果优先级不够 高,则将缓期等待执行。 PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
$ F5 x9 F5 ]2 z) s9 E1 q, H1、执行一个系统调用
1 r3 K# A. c: Q' x+ L C2、系统滴答定时器(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
9 m+ H+ p. z$ o8 ^ - CPSID I ; 关中断
2 K6 P4 u, i1 t - ;保存上文 / N. F& Y. V. M( m
- ;.......................
1 }. e4 Q' w( T" p2 A- u - ;切换下文
; L) H/ J) ]0 H4 m5 j: J - CPSIE I ;开中断
( V/ m2 q$ t! q; Q - BX LR ;异常返回
复制代码 n4 _! C+ I; K( O1 | f
它在异常一开始就关闭了中端,结束时开启中断,中间的代码为临界区代码,即不可被中断的操作。PendSV异常是任务切换的堆栈部分的核心,由他来完成上下文切换。PendSV的操作也很简单,主要有设置优先级和触发异常两部分: - NVIC_INT_CTRL EQU 0xE000ED04 ; 中断控制寄存器$ B( K& n9 |$ ]! J6 t% }6 F
- NVIC_SYSPRI14 EQU 0xE000ED22 ; 系统优先级寄存器(优先级14).
0 \/ h5 k" M# B1 t; o - NVIC_PENDSV_PRI EQU 0xFF ; PendSV优先级(最低). NVIC_PENDSVSET EQU 0x10000000 ; PendSV触发值
" ^' s8 \$ p6 m9 k& h+ T9 O
4 f& K1 R) F' v8 A" S$ y- ; 设置PendSV的异常中断优先级
! Z" f& V% _! ^8 |3 n
9 l8 ?$ u+ E( ^5 s0 X# j- LDR R0, =NVIC_SYSPRI14 / O0 d7 M: d# P' f5 Q0 Q) ]8 o
- LDR R1, =NVIC_PENDSV_PRI
5 |; R' a5 O3 W3 n9 m4 b: O - STRB R1, [R0] ; 触发PendSV异常
, m5 e; H& D# C$ W) i% n4 s& _ - LDR R0, =NVIC_INT_CTRL
5 N; J4 Y$ j: g- V* b - LDR R1, =NVIC_PENDSVSET ' v! a0 C7 P3 `- ]* y" O
- STR R1, [R0]
复制代码 0 o0 t9 L6 b- q" `' D& ?
二、堆栈操作 Cortex M4有两个堆栈寄存器,主堆栈指针(MSP)与进程堆栈指针(PSP),而且任一时刻只能使用其中的一个。MSP为复位后缺省使用的堆栈指针,异常永远使用MSP,如果手动开启PSP,那么线程使用PSP,否则也使用MSP。怎么开启PSP? - MSR PSP, R0 ; Load PSP with new process SP! k8 o. [, ?" A8 k
- ORR LR, LR, #0x04 ; Ensure exception return uses process stack
复制代码 , K4 _* _1 D) j6 ~( t
很容易就看出来了,置LR的位2为1,那么异常返回后,线程使用PSP。 写OS首先要将内存分配搞明白,单片机内存本来就很小,所以我们当然要斤斤计较一下。在OS运行之前,我们首先要初始化MSP和PSP, - EXTERN OS_CPU_ExceptStkBase
" @. A" I( H1 ? - ;PSP清零,作为首次上下文切换的标志" [0 n& [5 l' x8 T H& x) J
- MOVS R0, #0 $ c: {, k+ `+ W3 Y( m
- MSR PSP, R0. a+ m% y, r- ?# _% Z
- ;将MSP设为我们为其分配的内存地址 @# L5 Y6 n3 U% U$ l% i
- LDR R0, =OS_CPU_ExceptStkBase
; e, ]4 J; o& N4 Z% W - LDR R1, [R0]8 z$ E5 S$ x. j ]% H) _8 h) j; c2 x
- MSR MSP, R1
复制代码 * T7 r' U' t/ g9 a R& E
然后就是PendSV上下文切换中的堆栈操作了,如果不使用FPU,则进入异常自动压栈xPSR,PC,LR,R12,R0-R3,我们还要把R4-R11入栈。如果开启了FPU,自动压栈的寄存器还有S0-S15,还需吧S16-S31压栈。 - MRS R0, PSP- K$ o7 g) v1 M6 y' u5 }
- SUBS R0, R0, #0x20 ;压入R4-R11
0 c8 s" l; G: t R+ E7 q: | - STM R0, {R4-R11}. A# B! i+ S( d
- - `, W4 R! |8 w0 W- l9 M( e
- LDR R1, =Cur_TCB_Point ;当前任务的指针
1 q" _" C" p6 Q: Z - LDR R1, [R1]. ^/ {& P9 ]( A
- STR R0, [R1] ; 更新任务堆栈指针
复制代码
% @& D4 N% T# E/ n" k出栈类似,但要注意顺序 - LDR R1, =TCB_Point ;要切换的任务指针
/ ^3 [0 E+ H" L3 Q8 t( \2 x- [ - LDR R2, [R1]
g: y2 U! p7 |1 J - LDR R0, [R2] ; R0为要切换的任务堆栈地址4 W+ K; b b; P1 \
-
2 x8 ]6 S0 [* _3 G" ?+ t - LDM R0, {R4-R11} ; 弹出R4-R118 a: w' R( ^5 r3 k& M
- ADDS R0, R0, #0x20
- Y3 w: s1 E* ]; O/ a( T2 ~ - % f, }4 G ^& g* H9 s5 ^
- MSR PSP, R0 ;更新PSP
复制代码
) k' D) ^7 K6 U/ O* A三、OS实战 新建os_port.asm文件,内容如下: - NVIC_INT_CTRL EQU 0xE000ED04 ; Interrupt control state register.8 k/ @4 ?2 n% E1 P t5 T
- NVIC_SYSPRI14 EQU 0xE000ED22 ; System priority register (priority 14).0 T+ \9 L; d! Q
- NVIC_PENDSV_PRI EQU 0xFF ; PendSV priority value (lowest).
5 r' m1 m) [6 v! [' ~ - NVIC_PENDSVSET EQU 0x10000000 ; Value to trigger PendSV exception.
2 b8 ]' t+ E* O) |: z1 q; L
, _) k1 A" w+ f- m5 r0 Z- RSEG CODE:CODE:NOROOT(2)$ U g2 h' S! A
- THUMB
+ x. @5 M/ l! n/ i1 q - 2 h, y' O5 h$ r! Q/ ~- ^
-
( }& D( v& W u+ G# t, K2 M - EXTERN g_OS_CPU_ExceptStkBase
* ^: e0 U0 M' O - 2 }' E& Y8 [) k0 x0 z
- EXTERN g_OS_Tcb_CurP0 N5 G' J5 ]: S" r, Q' Z
- EXTERN g_OS_Tcb_HighRdyP
+ D) t/ D* h; h% U8 d4 ? - 1 [3 k- I# h& B, \6 M
- PUBLIC OSStart_Asm* [! }/ k9 s8 L, p6 W c
- PUBLIC PendSV_Handler* g3 M3 Q7 l7 `7 [
- PUBLIC OSCtxSw1 E5 E4 @0 |$ A8 r! i0 f
# c! T- t5 D" W4 N* R! W- OSCtxSw
8 v8 c! D. a8 ` - LDR R0, =NVIC_INT_CTRL6 T% k# E1 | P3 B* f
- LDR R1, =NVIC_PENDSVSET
* k! |. j( z. S; x+ m3 N e0 Z - STR R1, [R0]
, h: w- [# ?% [' U1 F% ~ - BX LR ; Enable interrupts at processor level
2 a3 E h, R& i - 8 @, n4 [, w" ~. K( T0 D/ o
- OSStart_Asm! U' y0 u* J' D6 z& T
- LDR R0, =NVIC_SYSPRI14 ; Set the PendSV exception priority
7 f0 O1 c2 x% A# F2 v( K - LDR R1, =NVIC_PENDSV_PRI
4 U1 E* a6 U6 X4 s+ R, Z; ^. e - STRB R1, [R0]9 p' T% G; F: H9 u1 v7 F ^9 ?
- z2 t6 N0 ?) n9 N3 X" ]! g/ r/ o8 |
- MOVS R0, #0 ; Set the PSP to 0 for initial context switch call
/ k; f d; ]; }- m - MSR PSP, R0! [) G) ]7 o# a* a: o
- 7 b! x8 ]( E+ V) h$ Q+ u: Y
- LDR R0, =g_OS_CPU_ExceptStkBase ; Initialize the MSP to the OS_CPU_ExceptStkBase
/ E1 M: V5 n2 Q4 p* J. e - LDR R1, [R0]
/ }# T& I8 N d$ s+ [3 y9 l' A( ? - MSR MSP, R1 6 @! S: S/ ^! v1 K6 N' ?7 b. [6 q/ @
1 d3 S' x/ Z$ z5 q I6 H# R- LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)( Z7 Y& H# Z! x! V( F6 }$ ?
- LDR R1, =NVIC_PENDSVSET
2 i, Y1 s& D' e2 a# m# B - STR R1, [R0]
) S q9 p/ @$ z1 s; ~3 y: X - 7 p! O0 V3 T$ v( q$ [2 K5 Q
- CPSIE I ; Enable interrupts at processor level9 z: v$ g8 h- M/ O* F, A
2 f! S0 q! \# x- OSStartHang
# t3 m9 z" p; Y1 {- J - B OSStartHang ; Should never get here
+ I' P6 j Z; G5 u; S1 A; w -
1 @1 A, A X) {- @: T; P -
7 j, {3 c; x" J# _& _, T - ( R7 {; `4 c, |# Y4 C
- PendSV_Handler
6 ~/ Q* s7 W+ Y+ P+ H" U9 t - CPSID I ; Prevent interruption during context switch( a8 x6 N' E8 g* n9 m8 U4 c3 Y+ X' n
- MRS R0, PSP ; PSP is process stack pointer* Z9 ^9 D/ X5 h% D& k) N6 j7 P4 [
- CBZ R0, OS_CPU_PendSVHandler_nosave ; Skip register save the first time. A$ m3 r9 G2 W* I- f; j9 h
- ! o4 [4 `# O, w5 ~* R5 s- p* o# g
- SUBS R0, R0, #0x20 ; Save remaining regs r4-11 on process stack4 `% [4 [7 i5 A% z7 z: h
- STM R0, {R4-R11}
( p( ]( p$ L4 V1 t - - ?5 R* _, I, t" \; k
- LDR R1, =g_OS_Tcb_CurP ; OSTCBCur->OSTCBStkPtr = SP;
$ f4 t3 M- {! c" G6 J2 ~7 G8 c - LDR R1, [R1]
# H6 g5 D% d- {: H. b4 L2 ?& H! b8 ` - STR R0, [R1] ; R0 is SP of process being switched out' D# m4 D% q8 f |& |
m% D8 V" _- u" Y0 [1 i- ; At this point, entire context of process has been saved. u) ~' g: ?9 e& @! P; ~6 U# ]
- OS_CPU_PendSVHandler_nosave8 o; O! d: H" r5 @2 C1 A0 C% ?
- LDR R0, =g_OS_Tcb_CurP ; OSTCBCur = OSTCBHighRdy;
# G( J) r [) T5 H: @% c1 `; x - LDR R1, =g_OS_Tcb_HighRdyP
- W. U; c! `# d& Y' t0 C4 z - LDR R2, [R1]
2 w. v: E& j, z. h3 O! C - STR R2, [R0]' b) S6 T- K% I5 {
1 `+ X4 ^0 _, o H( }0 ]! q. O- LDR R0, [R2] ; R0 is new process SP; SP = OSTCBHighRdy->OSTCBStkPtr;
- n' o4 _* E. C2 O F - 0 ^; Y% V1 N* }: X
- LDM R0, {R4-R11} ; Restore r4-11 from new process stack
, D5 }2 z. z0 R - ADDS R0, R0, #0x20% N8 r8 c. x! H$ ]6 }3 G
-
" Z+ r! r) m' j* b# g4 W0 D - MSR PSP, R0 ; Load PSP with new process SP
8 |6 s! D( R: V! T2 P) o - ORR LR, LR, #0x04 ; Ensure exception return uses process stack; `- w; I$ S9 v
- ! A3 W7 R0 j9 z3 d+ l
- CPSIE I2 c9 D; @) P5 ?; y7 [- z& D3 [
- BX LR ; Exception return will restore remaining context
! z8 i$ o# D5 ^: } - 9 p$ T; I$ q. C3 V' K& I7 ~! k. S
- END
复制代码
: Y6 \& c" J5 Qmain.c内容如下: - #include "stdio.h"8 n$ C- t) Q4 \* L
- #define OS_EXCEPT_STK_SIZE 1024
O. @5 G7 n+ g5 c2 g, ^9 P - #define TASK_1_STK_SIZE 1024
) n& W) o* x x - #define TASK_2_STK_SIZE 1024
2 e. p# m* O( F K, ?' h
+ E W2 @/ v$ u( Q3 x- typedef unsigned int OS_STK;% g8 |* b9 \4 [% }' D* j$ _ w
- typedef void (*OS_TASK)(void); W1 [4 p8 n: L- Q2 w
. y& t8 i6 R! B1 Q- typedef struct OS_TCB" k" s" B& H5 N! b
- {
; S& c* ]4 e9 j \9 E# j( \; d - OS_STK *StkAddr;# ?2 Z4 n& A0 {1 `, }, U
- }OS_TCB,*OS_TCBP;. C' k5 E5 z( O4 X' |6 t0 v
- ' R4 o5 R4 v1 T3 j7 E9 ?
- + O7 L5 |" b* e4 V# S6 L' m" [
- OS_TCBP g_OS_Tcb_CurP; / ^7 ^0 w0 L; ^ K) {
- OS_TCBP g_OS_Tcb_HighRdyP;
' I# S5 |1 [5 m n8 ^
w5 N4 X1 n# s, f3 v+ u: S5 ]4 u- static OS_STK OS_CPU_ExceptStk[OS_EXCEPT_STK_SIZE];/ x/ Y5 B5 X+ i2 b! h7 \
- OS_STK *g_OS_CPU_ExceptStkBase;( z% k7 D3 y7 n+ h# `; v3 F
- ! Z7 [- O! M% H3 C V, G7 i! m
- static OS_TCB TCB_1;" Z# s" F3 F: T
- static OS_TCB TCB_2;* K. v( O) Y% O# l
- static OS_STK TASK_1_STK[TASK_1_STK_SIZE];3 T: P8 J, ^( y5 v
- static OS_STK TASK_2_STK[TASK_2_STK_SIZE];
; O% @, i: M+ l( i& z& b
* k# X0 V3 h; U! E- ^6 d6 ]) D- extern void OSStart_Asm(void);
3 _& C, ]7 [# v1 J& l) ]9 o - extern void OSCtxSw(void);- f6 T, F8 [' B3 b+ J5 h3 ]) p1 |, |
% x+ ? t) n/ \0 d- void Task_Switch()6 T8 |) `" Z: |8 f& m$ A
- {
# e/ }4 a" k0 u0 R - if(g_OS_Tcb_CurP == &TCB_1)
6 t+ E0 j. _# S, A7 h7 r - g_OS_Tcb_HighRdyP=&TCB_2;
2 H9 x! D% L+ }# I - else
2 U/ V1 h' u0 ? K - g_OS_Tcb_HighRdyP=&TCB_1;; q" ~4 W% y0 W" z
-
. B( v, {2 n* E - OSCtxSw();- l$ x# c% d3 T4 @6 T3 _# R! j
- }
% }) z' W) f+ U$ D$ S9 s6 W
1 {. I. e5 V) M4 D- ) {3 C1 U& r) @8 D6 ^
- void task_1()
& l+ W; m' J) z- z - {
! v2 ~0 x2 }- h7 k$ N: `. u- L - printf("Task 1 Running!!!\n");
+ L$ K1 {4 W% c9 A - Task_Switch();
, l5 x& ^+ g$ b7 c6 W - printf("Task 1 Running!!!\n");
7 W3 K* v# d3 q; m+ j - Task_Switch();
; Y6 s+ F0 O/ q4 G6 o# J - }. J- m, ]# @" p% g/ Y. F! V4 \! n
; D8 R+ d0 [ L9 }) H- void task_2()4 j; V5 S1 s) Q
- {3 z' Q) c1 |0 R7 x0 U
-
. G" U p. r9 z5 z$ A) B# m - printf("Task 2 Running!!!\n");
% P$ A6 a; I* J6 }4 D3 d - Task_Switch();. H, ], m/ o; P. P+ F; U) Y
- printf("Task 2 Running!!!\n");
% F* ^+ F* @: T6 O( h5 e5 v8 } v1 j - Task_Switch();
4 z8 i% ?) B/ t5 I# U - }
0 z, C, @$ l1 f# r8 f! V$ c P$ @; ~ - & A0 L8 ~# w l6 J3 B; K2 U6 Y
- void Task_End(void)1 }3 c% [' E, s3 N# m( M% O' x
- {$ F2 X; j: ], h: E$ m# ~9 J- T# v8 y
- printf("Task End\n");
- `4 E1 \0 H. s+ l - while(1)
% _" e* @ j6 G! ~. T5 t - {}# Y+ x& y# p: i$ T1 }0 l
- }
1 {" d0 X; s: E1 R2 q
* K! [/ Y9 }$ Q i8 K/ f- void Task_Create(OS_TCB *tcb,OS_TASK task,OS_STK *stk)
& q6 b9 E, J2 f8 F - {; ^8 M7 a* m% Z9 G4 O9 \5 a
- OS_STK *p_stk;& d8 f9 Y( H* X( K0 a1 z
- p_stk = stk;
, W7 k ^, x# c/ h7 C) `) K3 x# W9 f - p_stk = (OS_STK *)((OS_STK)(p_stk) & 0xFFFFFFF8u);# e3 K# Q- S8 c2 V
-
/ w( a$ G6 D' v: W; z - *(--p_stk) = (OS_STK)0x01000000uL; //xPSR
/ q# E+ O2 P# [3 O; c - *(--p_stk) = (OS_STK)task; // Entry Point
( U2 W6 [4 z/ Y) a, K - *(--p_stk) = (OS_STK)Task_End; // R14 (LR)
* g |+ u7 o5 T3 |4 p - *(--p_stk) = (OS_STK)0x12121212uL; // R125 `# i' Q/ |2 M6 ]6 x
- *(--p_stk) = (OS_STK)0x03030303uL; // R3. Q# m# t% d4 z' G" m
- *(--p_stk) = (OS_STK)0x02020202uL; // R24 ?8 D C. J# v7 d
- *(--p_stk) = (OS_STK)0x01010101uL; // R17 f' l6 s) a' E$ Q
- *(--p_stk) = (OS_STK)0x00000000u; // R0
: p) w; m( @( F) a% O# E - & l! d3 t- O/ [" Y* H9 Y& [
- *(--p_stk) = (OS_STK)0x11111111uL; // R11
, K0 M+ ~7 _# u) w7 M$ e$ | - *(--p_stk) = (OS_STK)0x10101010uL; // R10
, p# d# A" a0 d: F7 ~: M - *(--p_stk) = (OS_STK)0x09090909uL; // R9& ?1 J( A2 s. ]# R6 U' G1 W
- *(--p_stk) = (OS_STK)0x08080808uL; // R8
1 Q2 e, N+ r/ x/ p" _" G, U$ @ - *(--p_stk) = (OS_STK)0x07070707uL; // R7
0 \2 F" f$ {- R$ F- N: ?5 r - *(--p_stk) = (OS_STK)0x06060606uL; // R6
. J8 ?' H; a [4 ^5 M) e - *(--p_stk) = (OS_STK)0x05050505uL; // R5
: s/ T4 j% ]' O. i7 M ` - *(--p_stk) = (OS_STK)0x04040404uL; // R4" C& X' f) P, \
- + Z1 Z" E6 |8 e1 N' c& V
- tcb->StkAddr=p_stk;' ?2 A4 I& L; N; i; H8 q
- }
4 r1 ~& Z. U/ L& I9 h3 D5 N# v
$ ?' u* z6 S9 M: g: H9 M; ^
( v5 v! f1 U5 {; O+ G, S/ e7 O* v; \- int main()
, f9 _, ?' L- l8 j - { \7 O, T% u; d a* T0 m
-
3 q- i0 d) R( ]; |' Y - g_OS_CPU_ExceptStkBase = OS_CPU_ExceptStk + OS_EXCEPT_STK_SIZE - 1;
; N5 ~# r) f j1 l7 |/ a& O -
, k5 Q8 l! K0 |. ]4 ]$ y' g - Task_Create(&TCB_1,task_1,&TASK_1_STK[TASK_1_STK_SIZE-1]);, E; r0 d# p# t6 [
- Task_Create(&TCB_2,task_2,&TASK_2_STK[TASK_1_STK_SIZE-1]);
5 f0 C3 L( D3 I; z2 k# Z8 `/ u* } - 5 `7 c( }" a3 K( E
- g_OS_Tcb_HighRdyP=&TCB_1;9 f8 H1 l3 C4 W
- : a8 b2 o+ w) G, O" T
- OSStart_Asm();. ^% _; X: o, o' F
-
" L7 D: ^- |( E0 T. P3 E& k r5 N B - return 0;
+ Y7 ]/ ?8 v; T t - }
复制代码
# m% l; |; T, ]编译下载并调试:
# Q6 u+ p% r! {; m
4 |' f' q1 P9 E% p
在此处设置断点 此时寄存器的值,可以看到R4-R11正是我们给的值,单步运行几次,可以看到进入了我们的任务task_1或task_2,任务里打印信息,然后调用Task_Switch进行切换,OSCtxSw触发PendSV异常。
! m$ I% H" Q9 V& e
' S( c0 b0 d+ [ e' x
IO输出如下:
2 ^9 T1 e6 d( O) W* @. M1 V; m' a
. K$ O: X9 U6 H8 E+ _
至此我们成功实现了使用PenSV进行两个任务的互相切换。之后,我们使用使用SysTick实现比较完整的多任务切换。
- s8 {. ^# B. i8 l" W" h |