01. 概述7 }; @5 J" T( ]1 r% A9 b. W
在实际使用FreeRTOS的时候,我们常常需要根据自己的需求来配置FreeRTOS,而且不同的架构的MCU在使用的时候配置也不同。FreeRTOS的系统配置文件为FreeRTOSConfig.h,在该配置文件中可以完成FreeRTOS的裁剪和配置,这是非常重要的一个文件。
$ D4 J. t: N- S0 w& a
5 ?3 G* N) D2 o7 v; T3 _; ] FreeRTOS内核是高度可定制的,使用配置文件FreeRTOSConfig.h进行定制。每个FreeRTOS应用都必须包含这个头文件,用户根据实际应用来裁剪定制FreeRTOS内核。这个配置文件是针对用户程序的,而非内核,因此配置文件一般放在应用程序目录下,不要放在RTOS内核源码目录下。
" G# U( [; o) X8 k- ?6 K# X$ w: Y# {, K
在下载的FreeRTOS文件包中,每个演示例程都有一个FreeRTOSConfig.h文件。有些例程的配置文件是比较旧的版本,可能不会包含所有有效选项。如果没有在配置文件中指定某个选项,那么RTOS内核会使用默认值。典型的FreeRTOSConfig.h配置文件定义如下所示,随后会说明里面的每一个参数。8 V- F! x" D [! a* W. r) r
4 C0 W2 B/ Y2 Q" A8 h' S
02. FreeRTOS配置文件
7 U4 X( S: _6 k( T5 P- /*- U" E7 Z3 @) Y
- * FreeRTOS Kernel V10.4.1. m; w& `8 S4 a% Y% d3 T
- * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.: k! H' C8 m0 [, J4 \( X4 F/ _" i2 Q' Q
- *
0 h/ C$ K" u, I8 w( I, A - * Permission is hereby granted, free of charge, to any person obtaining a copy of
& y: H; g6 S/ _- C+ |" A2 v6 @ - * this software and associated documentation files (the "Software"), to deal in$ {% C" i) q* Z9 o! o* E
- * the Software without restriction, including without limitation the rights to
& s- p0 L* P {, G' f1 m4 { - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 F/ L& A# E0 v( {& ~1 b/ U6 c - * the Software, and to permit persons to whom the Software is furnished to do so,
9 y# X# T' M# ~- u - * subject to the following conditions:! ~4 _8 @; y/ j
- *( C* A% s. S/ u# }2 p/ w/ b/ a' i
- * The above copyright notice and this permission notice shall be included in all# _$ U/ m# h. Z$ d8 n; S% ]" J9 H
- * copies or substantial portions of the Software.
' q+ p/ e2 G3 R4 r8 _9 O$ X! Z2 q+ \ - *2 G" h" l6 Y X" M, w! i j
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' P) O# P; y; n4 K- l* Z* ^ - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
! F2 L) o) g7 S# N1 W9 Z - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR9 H& V# v: Z: _+ A1 }
- * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER2 L* [; | U8 m9 Z& @# I
- * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN# M) @6 P; P0 K9 ^+ M
- * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
, U" |3 k+ w0 Z" S$ q - *
. n9 t8 }3 R6 r+ @
, k6 \* \% b; X5 V- *
0 F9 B+ }, Q5 g* o1 x - * 1 tab == 4 spaces!
O1 o" q. @. H$ o) r - */
( N: r8 a4 b3 t& B, k5 o9 t - 9 t: d1 l( r* D4 Q
7 [/ ]! G9 {% P# X5 G" c- #ifndef FREERTOS_CONFIG_H8 e+ i) Y! g& C T+ r' ~( ?7 `3 D
- #define FREERTOS_CONFIG_H8 c2 W$ J+ x) }0 a
. r+ @2 U$ I0 ]# S5 }- /*-----------------------------------------------------------
" t. T% `7 C2 g - * Application specific definitions.3 l" Q% t: Y: y/ O! q5 V
- *3 B( s# z# U* n
- * These definitions should be adjusted for your particular hardware and2 X# l% `7 v. M* ~) e
- * application requirements.
, Y2 L! c) }/ \6 v" p - *
7 t8 _4 b% P6 F - * THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
% {2 m0 h/ }) @. B$ [* l - * FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.' l. S0 K% c4 A2 }3 }
- *! N. E1 f! I3 I9 k. I4 M! q
- *----------------------------------------------------------*/* G( h* V" C. x' i8 H) P/ m ?
- ' V9 \( S% V8 B4 }- @0 d) e n
- /* Ensure stdint is only used by the compiler, and not the assembler. */; T5 D, b8 I, X' u# B9 ~ u- I
- #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)
& o5 v5 p+ V; u1 J/ w - #include <stdint.h>
& ]+ U1 T, e3 ? - extern uint32_t SystemCoreClock;
1 v( e7 \8 D' {, }( } - #endif9 j5 Z' } a7 P$ z& E% W
- 2 w" \! I2 C+ X4 a G- r
- #define configUSE_PREEMPTION 1
3 F% v X. Q2 `3 o& f! o1 M+ ~ - #define configUSE_IDLE_HOOK 0
0 y2 w4 Q: F+ j% w - #define configUSE_TICK_HOOK 01 _% O1 [9 P: A6 F! _6 Q4 w0 @+ p% W5 e
- #define configCPU_CLOCK_HZ ( SystemCoreClock )
! S5 |( S7 [. C5 @7 _* w+ h - #define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
- F( ^$ s5 Z% R - #define configMAX_PRIORITIES ( 5 ) \& C9 B( q/ L; H0 Q$ c% @# U
- #define configMINIMAL_STACK_SIZE ( ( unsigned short ) 130 )" v4 r" m) y5 S8 t" ]% N5 T# q
- #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 75 * 1024 ) ); l& `' j/ r5 U% S
- #define configMAX_TASK_NAME_LEN ( 10 )
' E) C, l5 F" ~8 p7 z8 a$ |5 c - #define configUSE_TRACE_FACILITY 1
; V; @/ h& S5 }( Y% {8 s! x6 Q - #define configUSE_16_BIT_TICKS 0
1 e. W- t3 a9 H# r' [ - #define configIDLE_SHOULD_YIELD 1
+ d' T+ X5 I' X# ?* e1 H9 w - #define configUSE_MUTEXES 17 N) I- F# z4 ]* s' j1 p% H$ c+ O
- #define configQUEUE_REGISTRY_SIZE 82 c# O& L8 R! C( `
- #define configCHECK_FOR_STACK_OVERFLOW 0( K9 H/ a" Q; H9 e$ @! d
- #define configUSE_RECURSIVE_MUTEXES 1 o" r. }3 A: l, A
- #define configUSE_MALLOC_FAILED_HOOK 0
* {/ r$ u# m5 U3 |* T - #define configUSE_APPLICATION_TASK_TAG 0
2 ^4 L# B1 E! P( b' O _3 v( v - #define configUSE_COUNTING_SEMAPHORES 1) o" L8 @4 H9 F" D, S8 l
- #define configGENERATE_RUN_TIME_STATS 0
0 } z+ e- ~7 ~: G4 |
: t& u( w& n) J0 G# H! |% z- /* Co-routine definitions. */
* x" q6 |% w# M# N4 k - #define configUSE_CO_ROUTINES 0
/ U! L, G/ w; d& u - #define configMAX_CO_ROUTINE_PRIORITIES ( 2 )! |3 t7 V. z" g9 D; q0 {& r
6 j# @. A* F* x* {! _" Y- /* Software timer definitions. */
5 L' @; G' C6 s4 W% {! }2 } - #define configUSE_TIMERS 1
; V& X8 P4 J- B - #define configTIMER_TASK_PRIORITY ( 2 )
8 L; [3 Z4 Z4 Z; |, J5 w% A - #define configTIMER_QUEUE_LENGTH 10$ O- `. \, F! Q! G
- #define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )7 B& r- S$ I2 L* q6 ]
- ; m' u+ {9 |. @5 k3 {" ?
- /* Set the following definitions to 1 to include the API function, or zero
F% Q! P, w. ~% J - to exclude the API function. */" d" x1 M& S1 z3 Y# O
- #define INCLUDE_vTaskPrioritySet 1
; u- [* u# V8 y+ }2 U) m - #define INCLUDE_uxTaskPriorityGet 1) |: j9 T! }3 S8 D6 x
- #define INCLUDE_vTaskDelete 1
. g# t1 k O- g# _' V" b - #define INCLUDE_vTaskCleanUpResources 1% V, ~/ s+ F" h6 j
- #define INCLUDE_vTaskSuspend 1
1 L" J; L6 \& L% M6 @3 A& K; J. W - #define INCLUDE_vTaskDelayUntil 1
$ R; v) N3 _( I! c - #define INCLUDE_vTaskDelay 1. H1 M5 H* V$ V1 r5 a( G
- & D6 C; F4 Z$ ^) U% x
- /* Cortex-M specific definitions. */" M# ]6 M# t2 e2 n( {+ Z+ I2 \( k: u
- #ifdef __NVIC_PRIO_BITS
0 B9 K/ ]% ~9 z4 K/ Z - /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */, |, `: v! [5 D5 p% k) K. `
- #define configPRIO_BITS __NVIC_PRIO_BITS7 H @7 g+ f( \0 U4 T
- #else$ _8 {2 c, ?* k k
- #define configPRIO_BITS 4 /* 15 priority levels */3 ~% S) l! P- H3 g6 W8 N9 t! ~
- #endif
/ C+ {0 u/ ~2 H0 o% u - 6 S+ [! z0 r% |$ }# ^, U! `
- /* The lowest interrupt priority that can be used in a call to a "set priority"
; u: C, r% [+ p% F) G; d8 C - function. */" [$ p! t: j: g
- #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
+ ~1 g- x% n! l7 o - 8 c3 o: w, O0 V1 D8 R; d5 W
- /* The highest interrupt priority that can be used by any interrupt service; F* u) X$ Q5 t- Y u# I9 Z
- routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL N: b! W% G6 e8 m% {) Q
- INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER7 b& l/ C7 y$ h# c* z
- PRIORITY THAN THIS! (higher priorities are lower numeric values. */' }: y) W1 X3 c* A7 z
- #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 50 m8 t% z" g4 y9 C
4 W" d1 m8 T( [) n+ I- /* Interrupt priorities used by the kernel port layer itself. These are generic
& h7 f+ I/ F: C4 \: R - to all Cortex-M ports, and do not rely on any particular library functions. */# U) ~3 [. t, R. q
- #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
0 @5 ~0 J4 k% w; u - /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!5 Z9 o. a; y7 \! P9 u
- See <a href="http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html." target="_blank">http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html.</a> */- Y1 `# m/ q# ]. C; l! }
- #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) Y ~( j# l4 O
- 4 Q) V. x7 Z! I$ M* L
- /* Normal assert() semantics without relying on the provision of an assert.h
0 r. K, M( r0 \ - header file. */! c! \/ ]! x- s, E2 ?7 ]8 Q! s
- #define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); for( ;; ); }
+ X% K$ H% c; g. ^3 Q - ! U: j' [- i' O- f4 S. P5 A% ?
- /* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
6 E; {/ x# ]$ v - standard names. */4 W4 P$ v3 K$ h* m2 m7 F
- #define vPortSVCHandler SVC_Handler- m) Y8 ]; L1 ^0 Y
- #define xPortPendSVHandler PendSV_Handler
`; E7 E0 v. i5 p- B& A - //#define xPortSysTickHandler SysTick_Handler: I# s4 ^- q; x* z- d. g
4 V7 `( s9 R7 \ E- Y- #endif /* FREERTOS_CONFIG_H */
复制代码
$ D% a }( S7 ~7 A. ~3 t03. INCLUDE开始的宏6 f. p, Q3 k7 Q x6 ~- ]
使用“INCLUDE_”开头的宏用来表示使能或者除能FreeRTOS中相应的API函数,作用就是用来配置FreeRTOS中可选的API函数。比如当宏INCLUDE_vTaskPrioritySet设置为0的时候表示不能使用函数vTaskPrioritySet(),当设置为1的时候就表示可以使用函数vTaskPrioritySet()。这个功能其实就是条件编译,在tasks.c中有所示的代码。
5 t* K+ ` Y& s" d5 U/ h; |2 P" ]# n, y) V" E
- /* Set the following definitions to 1 to include the API function, or zero% l8 K3 s+ e. U! O r
- to exclude the API function. */
9 @! V h J* ~& M6 u8 Q - #define INCLUDE_vTaskPrioritySet 1* K( j) E. g* x1 k9 c
- #if ( INCLUDE_vTaskPrioritySet == 1 )
# E5 {. F- a/ j% z* p
+ q+ ] E) ^) @- w* D U! z; o- void vTaskPrioritySet( TaskHandle_t xTask,
: t2 S( }7 @8 A - UBaseType_t uxNewPriority )
/ D. h O( C3 G1 [5 }* z$ v - {
' r( k4 M5 _2 P1 c+ z - TCB_t * pxTCB;. e8 s/ ?7 u. a) h) M# Q
- /*此处省略大部分代码*/9 C4 F0 ?# N* J C9 \
- }6 Z9 |' t9 X; C' O! D
% m; Z, i5 Z) i: z# G- #endif /* INCLUDE_vTaskPrioritySet */3 Z, m4 Z) b3 m8 ?! Q6 m$ l0 [
复制代码 4 U" B3 v! u4 Z# c* o# i& }3 a
3.1 INCLUDE_xTaskAbortDelay3 {: J' ]- E- ^% Q3 C% C$ G
/ L6 i% r0 V8 L/ z! w( O2 n
如果要使用函数xTaskAbortDelay()的话将宏INCLUDE_xTaskAbortDelay定义为1。8 k8 a1 s$ ?0 t6 B
, _! e/ p+ {' I1 l# d
3.2 INCLUDE_xTaskGetCurrentTaskHandle
5 q0 a3 m3 I0 d4 f* I0 z% ~6 T# A7 ]+ A6 ~4 l2 ]
如果要使用函数xTaskGetCurrentTaskHandle()的话需要将宏INCLUDE_xTaskGetCurrentTaskHandle定义为。
' ?/ U. Y5 Y5 f% d, d% h8 a) v8 i0 B! w
3.3 INCLUDE_xTaskGetHandle
; l5 `9 s# F: g5 \0 Y! V8 a, f6 m# ~+ k( d6 u: r$ A
如果要使用函数xTaskGetHandle的话需要将宏INCLUDE_xTaskGetHandle定义为1。# n+ \1 h% Z c/ F4 V1 b) H z
* u% t' H% j2 l. X B- R3.4 INCLUDE_xTaskGetIdleTaskHandle& e: s7 p% B, f+ k0 \( R" n
* Z2 ]4 \6 I) d* i5 U如果要使用函数xTaskGetIdleTaskHandle()的话需要将宏INCLUDE_xTaskGetIdleTaskHandle定义为1。
- |) D+ Z P% g% n6 ~% A$ y( Z; L: }6 o( e
3.5 INCLUDE_xTaskGetSchedulerState
7 e* o) A: g$ `' {3 F% U1 i' h$ k Z3 B
如果要使用函数xTaskGetSchedulerState()的话需要将宏INCLUDE_xTaskGetSchedulerState定义为1。
$ r* U" W3 s/ z2 p$ k; n' b* A3 d) o! B) X
3.6 INCLUDE_uxTaskGetStackHighWaterMark
4 ~- F, S0 c3 Y2 R9 l2 i. |# b( Z- C7 a; a( x' P
如果要使用函数uxTaskGetStackHighWaterMark()的话需要将宏INCLUDE_uxTaskGetStackHighWaterMark定义为1。4 l3 q& M- i3 R3 ]
2 {: a0 g+ J: x( e: s
3.7 INCLUDE_uxTaskPriorityGet8 k4 l) [4 d" V! {# S* O
6 \3 N1 G9 w' B8 G% i如果要使用函数uxTaskPriorityGet()的话需要将宏INCLUDE_uxTaskPriorityGet定义为1。0 u9 n b7 Z" X
+ |% M4 j( n) q: @6 j' |
3.8 INCLUDE_vTaskPrioritySet7 U# P8 T* Z7 ~, T" E9 x/ g/ g
: l0 }& a: K$ A* G7 a9 z& X如果要使用函数vTaskPrioritySet()的话需要将宏INCLUDE_vTaskPrioritySet定义为1。
6 P5 l$ q) @, L( F
7 C1 D+ e) i# l7 I s* ~, b) Y, j3.9 INCLUDE_xTaskResumeFromISR6 {- h! m h5 I' w$ X' m
- c' U& y/ I* G5 M如果要使用函数xTaskResumeFromISR()的话需要将宏INCLUDE_xTaskResumeFromISR和INCLUDE_vTaskSuspend都定义为1。, s7 J& ?9 S6 w3 _ n( S. I
+ ^ W; ^! ^1 e
3.10 INCLUDE_eTaskGetState; s I: ~- M4 g3 k& D# g, U
% J( i1 {' P& x$ y2 ^, O如果要使用函数eTaskGetState()的话需要将宏INCLUDE_eTaskGetState定义为1.
! b/ {0 H B7 S, T8 t" T4 }/ `3 c/ b1 L/ u6 h6 X2 U4 M0 Q
3.11 INCLUDE_vTaskSuspend
0 _; F+ Z' s4 O/ P1 `6 j
8 G; y- u5 S2 g9 Z9 J* n$ A如果要使用函数vTaskSuspend()、vTaskResume()、prvTaskIsTaskSuspended()、XTaskResumeFromISR()的话宏INCLUDE_vTaskSuspend要定义为1.0 J, }/ ]: v5 C5 ]& B& ?5 u6 ?
" z8 ]4 i/ y" x4 D7 E
如果要使用函数xTaskResumeFromISR()的话宏INCLUDE_xTaskResumeFromISR和INCLUDE_vTaskSuspend都必须定义为1.1 w( D, B7 J0 J) b' a ~5 L
7 z/ P" _/ { S0 T/ u. k
3.12 INCLUDE_vTaskDelete2 R' \/ P6 Y5 y3 C# u, i$ g
# V2 |0 f+ k6 p+ q y) h' i如果要使用函数vTaskDelete()就必须将INCLUDE_vTaskDelete该宏设置为1.2 G" D% V# H/ X' P
' J/ F( R& o( }7 J3.13 INCLUDE_vTaskCleanUpResources, }$ V6 \1 [8 S8 h6 Y* u' I3 f0 ?7 B- T
1 M9 p* F: N% i# U/ w
如果要使用函数vTaskCleanUpResources()函数就需要将宏INCLUDE_vTaskCleanUpResources设置为1.4 j' C9 I' P6 d4 X
, C1 W# J4 s) J
3.14 INCLUDE_vTaskSuspend
7 t' V, e; v* u' P/ y, z3 |. h" }0 W) H" E4 G2 K
如果要使用函数vTaskSuspend()就需要将宏INCLUDE_vTaskSuspend设置为1.
( ~) ^6 z8 I Z! ~7 v3 r/ G8 W& W9 q0 K8 d3 ^& Z7 A# @ ~/ \! k0 v! I
3.15 INCLUDE_vTaskDelayUntil
& N! a: ~( |3 J$ o+ y: E( r# [4 W& p7 W* d5 P
如果要使用函数vTaskDelayUntil()就需要将宏INCLUDE_vTaskDelayUntil设置为1.: k' k, N' Z. x* s2 D
; C. |7 e9 Z* }& ?0 V& o& F
3.16 INCLUDE_vTaskDelay* V* }4 [5 i5 u X
: Y+ \1 s/ ?: J2 M# z }/ j4 i
如果要使用函数vTaskDelay()就需要将宏INCLUDE_vTaskDelay设置为1.
5 q, {9 H& N$ p7 @6 u- W* q$ f2 ^! E' w+ A4 D+ o- E b
04. config开始的宏# S( i7 C2 W. o" _3 ?
config开始的宏和INCLUDE开始的宏一样,都是用来完成FreeRTOS的配置和裁剪的。
, q6 u7 N# {- H U9 Q, {. _2 y! b+ R5 h& D
4.1 configUSE_PREEMPTION( N) E4 |" ]; e! o' ]6 s3 K
/ e( f8 j" m8 d+ ]) v, T) T为1的时候使用抢占式调度器,为0的时候使用协程。如果使用抢占式调度器的话内核会在每个时钟节拍中断中进行任务切换,当使用协程的话会在如下地方进行任务切换。
: N' E5 q6 n7 o+ @3 L0 O4 Z5 ]$ E- L1 o+ `2 G& w4 A* a
一个任务调用了函数taskYIELD()。7 ^ E' d7 k% J4 V
) |- c/ Y) `1 D7 F& O( j1 l; I
一个任务调用了可以使任务进入阻塞态的API函数。
+ V- k; ^% s9 D. P& a0 C% Y7 ]7 @% u( V3 ~) H
应用程序明确定义了在中断中执行上下文切换。0 e4 E8 M+ p3 ~/ B/ i
8 n- o. }7 N: X a
4.2 configUSE_IDLE_HOOK7 [9 Y+ H; k2 Y6 T' P8 _
4 W R1 _1 d z3 J
设置为1使用空闲钩子(Idle Hook类似于回调函数),0忽略空闲钩子。; i3 ?2 D+ @+ j: s' V
3 m+ W( g1 N6 r
当RTOS调度器开始工作后,为了保证至少有一个任务在运行,空闲任务被自动创建,占用最低优先级(0优先级)。对于已经删除的RTOS任务,空闲任务可以释放分配给它们的堆栈内存。因此,在应用中应该注意,使用vTaskDelete()函数时要确保空闲任务获得一定的处理器时间。除此之外,空闲任务没有其它特殊功能,因此可以任意的剥夺空闲任务的处理器时间。9 `' T4 V& _7 W& O. @$ n
4 M! E7 m0 U/ ^* K- I
应用程序也可能和空闲任务共享同个优先级。
! { P, m0 t. B8 l/ ^" F9 {3 D4 R7 s3 v! r1 B. H
空闲任务钩子是一个函数,这个函数由用户来实现,RTOS规定了函数的名字和参数,这个函数在每个空闲任务周期都会被调用。
5 J0 Q& g- Y% j4 S& g9 M+ A$ Z4 h$ s" \" `3 ^8 k- ]: u
要创建一个空闲钩子:& J. |/ ]; z& G9 G/ H3 F, y6 S# R# C
2 \6 O+ \; h- U Q% T3 I5 t: t
设置FreeRTOSConfig.h 文件中的configUSE_IDLE_HOOK 为1;9 D. M7 C6 X: O+ Q; m
定义一个函数,函数名和参数如下所示:$ T; X4 v% G; T D- q4 _+ q, A8 ?4 K3 J
- void vApplicationIdleHook(void );
复制代码
& H2 s% X4 F3 e, A这个钩子函数不可以调用会引起空闲任务阻塞的API函数(例如:vTaskDelay()、带有阻塞时间的队列和信号量函数),在钩子函数内部使用协程是被允许的。
- p% e( Q, H& q" e X& p: U5 R8 |* K" N& X* c5 L
使用空闲钩子函数设置CPU进入省电模式是很常见的。0 N7 P* K w+ a2 E: U( e& x4 p! [
$ C5 q9 @, _& J; m) [; v4.3 configUSE_TICK_HOOK1 }/ i/ d0 P. p+ k% x& q
# u: b) M6 [) K5 \6 Y设置为1使用时间片钩子(Tick Hook),0忽略时间片钩子。
9 E8 G2 L; C3 L+ _; d- d/ X3 y9 M- m
注:时间片钩子函数(Tick Hook Function)
# T' L: G6 z+ y- j. P
# T9 J: s0 c9 y T6 r4 a% O时间片中断可以周期性的调用一个被称为钩子函数(回调函数)的应用程序。时间片钩子函数可以很方便的实现一个定时器功能。
# h1 `1 j7 Q) F+ C: K# m2 `5 y. W; |0 U' |5 X% o- o8 _
只有在FreeRTOSConfig.h中的configUSE_TICK_HOOK设置成1时才可以使用时间片钩子。一旦此值设置成1,就要定义钩子函数,函数名和参数如下所示:. g; W: j, V2 [! [8 i
$ i v( X4 B2 T1 _! U+ }# }- void vApplicationTickHook( void );
复制代码 1 _" [* h7 C5 {
vApplicationTickHook()函数在中断服务程序中执行,因此这个函数必须非常短小,不能大量使用堆栈,不能调用任何不是以”FromISR" 或 "FROM_ISR”结尾的API函数。
6 d5 k. _8 E% B) J7 S; c" W9 }# R1 c+ K
在FreeRTOSVx.x.x\FreeRTOS\Demo\Common\Minimal文件夹下的crhook.c文件中有使用时间片钩子函数的例程。- U4 n; O$ }% `, P: o5 i1 a
* j- N- K( @, X" ^4.4 configCPU_CLOCK_HZ7 t6 W; m! F/ _
, S9 o( @$ o; I
写入实际的CPU内核时钟频率,也就是CPU指令执行频率,通常称为Fcclk。配置此值是为了正确的配置系统节拍中断周期。
& H: n# J3 {8 N1 d# c
% N3 g+ T1 M( V4.5 configTICK_RATE_HZ, _' `. Y' h y9 g' d! n
# q0 D. D+ {8 i- B' [% g
RTOS 系统节拍中断的频率。即一秒中断的次数,每次中断RTOS都会进行任务调度。
4 @1 D" e' I! C7 n" U( H) H/ V; Q% E. b( G9 q- c' J. C. |$ [
系统节拍中断用来测量时间,因此,越高的测量频率意味着可测到越高的分辨率时间。但是,高的系统节拍中断频率也意味着RTOS内核占用更多的CPU时间,因此会降低效率。RTOS演示例程都是使用系统节拍中断频率为1000HZ,这是为了测试RTOS内核,比实际使用的要高。(实际使用时不用这么高的系统节拍中断频率)' E; a, v% A2 u3 Z" ?
0 ~9 P: `3 _9 _多个任务可以共享一个优先级,RTOS调度器为相同优先级的任务分享CPU时间,在每一个RTOS 系统节拍中断到来时进行任务切换。高的系统节拍中断频率会降低分配给每一个任务的“时间片”持续时间。
) u, }' `0 g h$ {! D0 [3 `+ f$ {! ~5 i: t' P( p
设置FreeRTOS的系统时钟节拍频率,单位为HZ,此频率就是抵达定时器的中断频率,需要使用此宏来配置滴答定时器的中断。这里我们一般设置为1000,周期就是1ms。
2 ]# @* f( ?3 W3 A8 y; g% d1 ]! v8 p: K3 z7 t
4.6 configMAX_PRIORITIES
3 F% I6 ?; ^, T" `7 t1 P% Y- k" c5 L; |9 F- x! N
配置应用程序有效的优先级数目。任何数量的任务都可以共享一个优先级,使用协程可以单独的给与它们优先权。见configMAX_CO_ROUTINE_PRIORITIES。1 B* X2 v& o0 J) P% e3 g8 ^% ^
7 o' X# h/ E! o在RTOS内核中,每个有效优先级都会消耗一定量的RAM,因此这个值不要超过你的应用实际需要的优先级数目。
8 \" @8 v' O) I$ ?% r' V/ X2 A2 _! {" z, m. L5 X/ @
每一个任务都会被分配一个优先级,优先级值从0~ (configMAX_PRIORITIES - 1)之间。低优先级数表示低优先级任务。空闲任务的优先级为0(tskIDLE_PRIORITY),因此它是最低优先级任务。
; t. T7 b5 p7 i( ^, d/ N0 Q5 u* Q, y* T! t2 D# Z! m+ Y8 I
FreeRTOS调度器将确保处于就绪状态(Ready)或运行状态(Running)的高优先级任务比同样处于就绪状态的低优先级任务优先获取处理器时间。换句话说,处于运行状态的任务永远是高优先级任务。
/ G" T# e8 j' C# n9 I5 K( c g7 T( C
处于就绪状态的相同优先级任务使用时间片调度机制共享处理器时间。4 o8 |: c7 N6 L
: Q/ _0 B4 A- b- b/ m0 g4.7 configMINIMAL_STACK_SIZE1 M; S; [2 e, w( L
6 {7 L% q- V9 ]
定义空闲任务使用的堆栈大小。通常此值不应小于对应处理器演示例程文件FreeRTOSConfig.h中定义的数值。
/ ?: o. A# ]- v$ D
/ R/ h# I9 h u就像xTaskCreate()函数的堆栈大小参数一样,堆栈大小不是以字节为单位而是以字为单位的,比如在32位架构下,栈大小为100表示栈内存占用400字节的空间。
: r9 I; R( b) Q! A% U" g* z. ]7 }% _) f. w8 h+ z) k
4.8 configTOTAL_HEAP_SIZE) U( D- P3 p5 u% @/ d
4 a2 w, b! {% ^; l( M
RTOS内核总计可用的有效的RAM大小。仅在你使用官方下载包中附带的内存分配策略时,才有可能用到此值。每当创建任务、队列、互斥量、软件定时器或信号量时,RTOS内核会为此分配RAM,这里的RAM都属于configTOTAL_HEAP_SIZE指定的内存区。后续的内存配置会详细讲到官方给出的内存分配策略。
5 ~7 H- V2 t' i; i* Q3 I. ~9 r7 O3 q
4.9 configMAX_TASK_NAME_LEN
6 _# h# x- M7 A) Y& x" N: q+ R1 }9 l7 r# W7 _: X4 l( Q$ q# N! Z! @
调用任务函数时,需要设置描述任务信息的字符串,这个宏用来定义该字符串的最大长度。这里定义的长度包括字符串结束符’\0’。
+ _4 O; r6 i& z1 g) K7 o
/ u3 d, g7 F4 ` W3 U' X2 |4.10 configUSE_TRACE_FACILITY
0 j1 R9 Z" @- P6 {, z
3 s, s+ S) q1 C/ _设置成1表示启动可视化跟踪调试,会激活一些附加的结构体成员和函数。! p. `9 z; O/ @ {5 L3 M. o3 U
$ X' {/ @# E; I
4.11 configUSE_16_BIT_TICKS
& Y9 R9 z, A, T9 Y& l; ~2 K0 v! {- j, d
定义系统节拍计数器的变量类型,即定义portTickType是表示16位变量还是32位变量。
+ ^( } w _; ]# K w2 Q2 t. s. Z+ b# z
3 X- x0 n( n" e定义configUSE_16_BIT_TICKS为1意味着portTickType代表16位无符号整形,定义configUSE_16_BIT_TICKS为0意味着portTickType代表32位无符号整形。
) e9 X, m/ w( a( R, p( g
& ^1 g' z% n5 D使用16位类型可以大大提高8位和16位架构微处理器的性能,但这也限制了最大时钟计数为65535个’Tick’。因此,如果Tick频率为250HZ(4MS中断一次),对于任务最大延时或阻塞时间,16位计数器是262秒,而32位是17179869秒。
r7 R& A3 l; p. ]# [ O
$ p7 _5 \ {8 D4.12 configIDLE_SHOULD_YIELD
v! U; G1 G6 a, P4 ~" X5 F) E0 Q% j6 w. e! Q
这个参数控制任务在空闲优先级中的行为。仅在满足下列条件后,才会起作用。
" F N8 _9 m b: j7 [5 z1.使用抢占式内核调度
. d5 u! Y' b& R4 \ t ^9 v2.用户任务使用空闲优先级。$ r8 t; E! M1 ^$ a7 G2 E; y% q _
通过时间片共享同一个优先级的多个任务,如果共享的优先级大于空闲优先级,并假设没有更高优先级任务,这些任务应该获得相同的处理器时间。
" y5 k: c* x: G3 m6 S; ~* ` J3 {( C+ ^9 w, T5 ?
但如果共享空闲优先级时,情况会稍微有些不同。当configIDLE_SHOULD_YIELD为1时,其它共享空闲优先级的用户任务就绪时,空闲任务立刻让出CPU,用户任务运行,这样确保了能最快响应用户任务。处于这种模式下也会有不良效果(取决于你的程序需要),描述如下:
! `1 v) [( }2 h1 P
+ J* Z" |7 w. d/ g8 f
$ e, z) `" D" ?" t
! Q/ E6 p& w' ]2 B! |) Z
图中描述了四个处于空闲优先级的任务,任务A、B和C是用户任务,任务I是空闲任务。上下文切换周期性的发生在T0、T1…T6时刻。当用户任务运行时,空闲任务立刻让出CPU,但是,空闲任务已经消耗了当前时间片中的一定时间。这样的结果就是空闲任务I和用户任务A共享一个时间片。用户任务B和用户任务C因此获得了比用户任务A更多的处理器时间。0 F& }' G. S& g$ E, M3 F6 f
8 ^( T) Y3 ]5 u; k' }: y: M, T
可以通过下面方法避免:7 U: n; z# m: y) C; Q
如果合适的话,将处于空闲优先级的各单独的任务放置到空闲钩子函数中;/ C% {( J! g* q8 u) o2 q
创建的用户任务优先级大于空闲优先级;+ D+ e( `0 G F0 h. }
设置IDLE_SHOULD_YIELD为0;
6 h$ L4 W5 X) T$ D" C& d" L7 [- B4 {( }9 W* Z2 ]1 `
设置configIDLE_SHOULD_YIELD为0将阻止空闲任务为用户任务让出CPU,直到空闲任务的时间片结束。这确保所有处在空闲优先级的任务分配到相同多的处理器时间,但是,这是以分配给空闲任务更高比例的处理器时间为代价的。
$ B2 j+ R U* v9 ?+ ]$ R
0 M5 a: H2 ~. E, |% {1 ^0 L4.13 configUSE_MUTEXES- ~5 ^. [/ v+ N8 K H7 I
) T9 ~! s' `9 ~4 B! o" d8 o; }设置为1表示使用互斥量,设置成0表示忽略互斥量。读者应该了解在FreeRTOS中互斥量和二进制信号量的区别。
; C7 e% x( r9 j) t- M" t& j8 a2 M8 c; X/ y/ r( L) \/ a
关于互斥量和二进制信号量简单说:$ w1 B; }: R' |; t2 w/ ]& E
1.互斥型信号量必须是同一个任务申请,同一个任务释放,其他任务释放无效。
; b2 b6 n* w0 |# T2.二进制信号量,一个任务申请成功后,可以由另一个任务释放。
+ `: ^1 z, l( Y6 {/ C% \4 _/ {3.互斥型信号量是二进制信号量的子集 j) _/ v5 d7 }
]& q% w! X# y' I2 `
4.14 configQUEUE_REGISTRY_SIZE2 _; G. J, o4 F( B& d
+ n2 a: l* M" k
队列记录有两个目的,都涉及到RTOS内核的调试:
/ c- ?* r3 i1 z# f1.它允许在调试GUI中使用一个队列的文本名称来简单识别队列;
$ W" L s' g& Z/ n2.包含调试器需要的每一个记录队列和信号量定位信息;' |4 K5 R, n; i" a8 {+ D
6 S* ? d8 ~) E9 ~" X2 |7 s除了进行内核调试外,队列记录没有其它任何目的。9 a% x* S+ K- H) z: Q
8 J4 J* H- m, |! {0 \9 i9 iconfigQUEUE_REGISTRY_SIZE定义可以记录的队列和信号量的最大数目。如果你想使用RTOS内核调试器查看队列和信号量信息,则必须先将这些队列和信号量进行注册,只有注册后的队列和信号量才可以使用RTOS内核调试器查看。查看API参考手册中的vQueueAddToRegistry() 和vQueueUnregisterQueue()函数获取更多信息。! K6 F4 b) I# \' [2 h5 i# S
$ j" N1 K1 i8 M: a1 W/ Q4.15 configCHECK_FOR_STACK_OVERFLOW
& X5 X5 r8 j# i) l/ k( g1 w! @$ K
. U, [3 X2 D- y每个任务维护自己的栈空间,任务创建时会自动分配任务需要的占内存,分配内存大小由创建任务函数(xTaskCreate())的一个参数指定。堆栈溢出是设备运行不稳定的最常见原因,因此FreeeRTOS提供了两个可选机制用来辅助检测和改正堆栈溢出。配置宏configCHECK_FOR_STACK_OVERFLOW为不同的常量来使用不同堆栈溢出检测机制。
2 P$ n" j/ L, J9 |/ u: M& t; W/ Q% q! d( x2 @4 B0 c3 k0 i" e
注意,这个选项仅适用于内存映射未分段的微处理器架构。并且,在RTOS检测到堆栈溢出发生之前,一些处理器可能先产生故障(fault)或异常(exception)来反映堆栈使用的恶化。如果宏configCHECK_FOR_STACK_OVERFLOW没有设置成0,用户必须提供一个栈溢出钩子函数,这个钩子函数的函数名和参数必须如下所示:
$ j6 Y6 j/ {+ {! U0 p Z1 [" ^9 g$ P* A! b6 o1 G- w+ V
- void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName );
复制代码
& ?3 C! h& z6 j' t; M参数xTask和pcTaskName为堆栈溢出任务的句柄和名字。请注意,如果溢出非常严重,这两个参数信息也可能是错误的!在这种情况下,可以直接检查pxCurrentTCb变量。( v; u3 L6 A. ]4 H( l
$ e. g6 S5 w" z& K5 u1 E8 _推荐仅在开发或测试阶段使用栈溢出检查,因为堆栈溢出检测会增大上下文切换开销。
- d% ?$ c' B. w4 v6 x) T1 x7 S7 q V1 m z/ B- z. i& n3 h% V9 W
任务切换出去后,该任务的上下文环境被保存到自己的堆栈空间,这时很可能堆栈的使用量达到了最大(最深)值。在这个时候,RTOS内核会检测堆栈指针是否还指向有效的堆栈空间。如果堆栈指针指向了有效堆栈空间之外的地方,堆栈溢出钩子函数会被调用。
- @! q6 H8 E& _" Q; U" v$ X+ j7 f0 }0 g, j( n" C+ v9 U
这个方法速度很快,但是不能检测到所有堆栈溢出情况(比如,堆栈溢出没有发生在上下文切换时)。设置configCHECK_FOR_STACK_OVERFLOW为1会使用这种方法。
* J1 n, b( s2 R& s- T$ x+ `5 y' X. W0 l8 v5 N* c
当堆栈首次创建时,在它的堆栈区中填充一些已知值(标记)。当任务切换时,RTOS内核会检测堆栈最后的16个字节,确保标记数据没有被覆盖。如果这16个字节有任何一个被改变,则调用堆栈溢出钩子函数。0 w0 N; {7 J" Q1 p& k: [
3 v l' H# v: R* ^
这个方法比第一种方法要慢,但也相当快了。它能有效捕捉堆栈溢出事件(即使堆栈溢出没有发生在上下文切换时),但是理论上它也不能百分百的捕捉到所有堆栈溢出(比如堆栈溢出的值和标记值相同,当然,这种情况发生的概率极小)。. X6 e, C/ O' p: L% \/ ?: P
) R+ q; \& q& v( Q7 \' p1 Z
使用这个方法需要设置configCHECK_FOR_STACK_OVERFLOW为2.
. y4 F' t; m. u8 m! t+ T0 r0 O4 |- W$ ~3 z- g5 S) T Q" @# z
4.16 configUSE_RECURSIVE_MUTEXES
3 a3 E) K6 Q( `
+ c9 z4 r- T8 j设置成1表示使用递归互斥量,设置成0表示不使用。- }* w+ p2 G7 c
) M$ W' m$ n; r4.17 configUSE_MALLOC_FAILED_HOOK
4 u* J/ |# W0 X: t
9 O: Z2 w1 V E# q; c* z: |- }6 E7 H每当一个任务、队列、信号量被创建时,内核使用一个名为pvPortMalloc()的函数来从堆中分配内存。官方的下载包中包含5个简单内存分配策略,分别保存在源文件heap_1.c、heap_2.c、heap_3.c、heap_4.c、heap_5.c中。 仅当使用这五个简单策略之一时,宏configUSE_MALLOC_FAILED_HOOK才有意义。9 o5 a( l9 k7 [; S' Q
4 @" J5 y9 ]/ ^ U5 X+ {4 a7 d+ T
如果定义并正确配置malloc()失败钩子函数,则这个函数会在pvPortMalloc()函数返回NULL时被调用。只有FreeRTOS在响应内存分配请求时发现堆内存不足才会返回NULL。# j& s; l5 t5 q o! x
( d: B% M3 w9 Y" }8 r6 N, g( J0 [如果宏configUSE_MALLOC_FAILED_HOOK设置为1,那么必须定义一个malloc()失败钩子函数,如果宏configUSE_MALLOC_FAILED_HOOK设置为0,malloc()失败钩子函数不会被调用,即便已经定义了这个函数。malloc()失败钩子函数的函数名和原型必须如下所示:
2 l7 ^7 t# a) h/ p( n% e9 p7 _7 R" ]
% F- g6 ^3 n9 _2 _- void vApplicationMallocFailedHook( void);
复制代码 6 ^7 T) U1 t# ^3 |' F
4.18 configUSE_APPLICATION_TASK_TAG
' U. y% a1 }& K3 e1 V& ?2 S2 i6 p# P* e$ q$ t
此宏设置为1的话函数configUSE_APPLICATION_TASK_TAGF()和xTaskCallApplicationTaskHook()就会被编译。
% M* `. c* g7 J# ~& l Q9 R: I2 V# W" B
4.19 configUSE_COUNTING_SEMAPHORES* F, T9 O$ q7 ~$ {) p
4 y( |: {" u2 v
设置成1表示使用计数信号量,设置成0表示不使用。
: Y0 E4 w2 e( S7 |
. M9 K7 X7 a! Q. [4.20 configGENERATE_RUN_TIME_STATS
) I. R; x ]& T4 G3 h9 k% U3 s# N/ G4 V) M9 l
设置宏configGENERATE_RUN_TIME_STATS为1使能运行时间统计功能。一旦设置为1,则下面两个宏必须被定义:# j6 g9 b: W6 N' _8 D) X$ `5 Q
3 _& q( E5 M3 g6 H" f
1.portCONFIGURE_TIMER_FOR_RUN_TIME_STATS():用户程序需要提供一个基准时钟函数,函数完成初始化基准时钟功能,这个函数要被define到宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()上。这是因为运行时间统计需要一个比系统节拍中断频率还要高分辨率的基准定时器,否则,统计可能不精确。基准定时器中断频率要比统节拍中断快10~100倍。基准定时器中断频率越快,统计越精准,但能统计的运行时间也越短(比如,基准定时器10ms中断一次,8位无符号整形变量可以计到2.55秒,但如果是1秒中断一次,8位无符号整形变量可以统计到255秒)。# v* G0 r- ^. @4 |: y/ I
+ ^4 i/ R! X1 o" b3 z
2.portGET_RUN_TIME_COUNTER_VALUE():用户程序需要提供一个返回基准时钟当前“时间”的函数,这个函数要被define到宏portGET_RUN_TIME_COUNTER_VALUE()上。
! v7 ~& [+ i$ A2 M- H* n# {' b9 h2 V4 z5 Z# U
举一个例子,假如我们配置了一个定时器,每500us中断一次。在定时器中断服务例程中简单的使长整形变量ulHighFrequencyTimerTicks自增。那么上面提到两个宏定义如下(可以在FreeRTOSConfig.h中添加):- k. a i$ N. X0 a- M' C- g2 R
- extern volatile unsigned longulHighFrequencyTimerTicks;9 E$ [0 d4 g# h Z" |2 b& f2 j: c7 E
- #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() ( ulHighFrequencyTimerTicks = 0UL )+ ?" w6 b) {7 _$ x; D$ v
- #define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
复制代码
+ Q- W. W5 m3 a) B, G0 t6 Q' K4.21 configUSE_CO_ROUTINES# \1 l+ k/ Y) U+ c
4 |; L) k' {$ k4 ]; s
设置成1表示使用协程,0表示不使用协程。如果使用协程,必须在工程中包含croutine.c文件。
: g& ?8 i$ H: ^+ Q9 B i4 ^7 \6 _7 s8 O& \/ G' ]2 t. {
注:协程(Co-routines)主要用于资源发非常受限的嵌入式系统(RAM非常少),通常不会用于32位微处理器。- G+ ]( ]. ~: c4 A8 L
* h; g/ v' F Z) R/ f2 u
在当前嵌入式硬件环境下,不建议使用协程,FreeRTOS的开发者早已经停止开发协程。4 Q" q, t8 d3 g0 @5 O0 \
% W- c& C- Z/ ]/ `8 ?& D1 Z4.22 configMAX_CO_ROUTINE_PRIORITIES
& P1 m |( v$ A6 d- {( N
/ p- Y3 H/ s8 _3 W. P应用程序协程(Co-routines)的有效优先级数目,任何数目的协程都可以共享一个优先级。使用协程可以单独的分配给任务优先级。见configMAX_PRIORITIES。; r. @& M+ w4 I
( \$ g& S2 I& q5 `+ u4.23 configUSE_TIMERS
" r2 S8 C% l) _8 D+ ]/ H* n8 T
设置成1使用软件定时器,为0不使用软件定时器功能。详细描述见FreeRTOS software timers 。1 f: M; Q3 s j5 Z3 k. s
: r y( L" K, x& @! l- l! T3 L4.24 configTIMER_TASK_PRIORITY& `/ t/ Q( i' I4 `) r" b
' K1 v s' J2 V: z
设置软件定时器服务/守护进程的优先级。详细描述见FreeRTOS software timers 。
! ~4 Q4 B3 y8 D3 {) F# r
5 D* B$ L* L1 i4 A7 W* m4.25 configTIMER_QUEUE_LENGTH
# Q# N& U# Y% [6 @1 t) C
; i9 Q6 K" i( F& Y, h& U5 p设置软件定时器命令队列的长度。详细描述见FreeRTOS software timers。" k& n9 f' C8 K; ]& v: x
9 w4 o" p" S) c( y, }4.26 configTIMER_TASK_STACK_DEPTH6 F/ P# h) x0 d( w0 F$ x& H1 G
* {% A& `; W+ y% G设置软件定时器服务/守护进程任务的堆栈深度,详细描述见FreeRTOS software timers 。' A0 B8 x1 g# {- \
' p( J8 L2 E7 \, q" c' C4.27 configKERNEL_INTERRUPT_PRIORITY、configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY
! ?6 g, L) g. [0 h$ U) n
8 I4 D2 k9 E6 D3 {0 h" }8 O& C这是移植和应用FreeRTOS出错最多的地方,所以需要打起精神仔细读懂。5 e- y( ?3 ]& D* @. ?. j% _( W7 r% q
8 W( i Z, r' K L" H+ A. V! L2 `
Cortex-M3、PIC24、dsPIC、PIC32、SuperH和RX600硬件设备需要设置宏configKERNEL_INTERRUPT_PRIORITY;PIC32、RX600和Cortex-M硬件设备需要设置宏configMAX_SYSCALL_INTERRUPT_PRIORITY。2 z4 g" r, i7 Q! u
( f3 ^' G5 W7 p# ] configMAX_SYSCALL_INTERRUPT_PRIORITY和configMAX_API_CALL_INTERRUPT_PRIORITY,这两个宏是等价的,后者是前者的新名字,用于更新的移植层代码。
% }* s q) r8 }6 G& L
- Z. y; T# \/ c8 c 注意下面的描述中,在中断服务例程中仅可以调用以“FromISR”结尾的API函数。
0 g9 E5 {4 \0 R! Q2 p* P O: C- m. X/ C9 q0 T3 P
仅需要设置configKERNEL_INTERRUPT_PRIORITY的硬件设备(也就是宏configMAX_SYSCALL_INTERRUPT_PRIORITY不会用到):configKERNEL_INTERRUPT_PRIORITY用来设置RTOS内核自己的中断优先级。调用API函数的中断必须运行在这个优先级;不调用API函数的中断,可以运行在更高的优先级,所以这些中断不会被因RTOS内核活动而延时。
2 o* s. J$ U, x- B. A/ Z& W% l$ c: Z' N5 P7 ]# O$ ~ O- |
configKERNEL_INTERRUPT_PRIORITY和configMAX_SYSCALL_INTERRUPT_PRIORITY都需要设置的硬件设备:configKERNEL_INTERRUPT_PRIORITY用来设置RTOS内核自己的中断优先级。因为RTOS内核中断不允许抢占用户使用的中断,因此这个宏一般定义为硬件最低优先级。configMAX_SYSCALL_INTERRUPT_PRIORITY用来设置可以在中断服务程序中安全调用FreeRTOS API函数的最高中断优先级。优先级小于等于这个宏所代表的优先级时,程序可以在中断服务程序中安全的调用FreeRTOS API函数;如果优先级大于这个宏所代表的优先级,表示FreeRTOS无法禁止这个中断,在这个中断服务程序中绝不可以调用任何API函数。- k/ p0 K; L1 c
3 z, V0 g' c. a9 a$ q* M通过设置configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级级别高于configKERNEL_INTERRUPT_PRIORITY可以实现完整的中断嵌套模式。这意味着FreeRTOS内核不能完全禁止中断,即使在临界区。此外,这对于分段内核架构的微处理器是有利的。请注意,当一个新中断发生后,某些微处理器架构会(在硬件上)禁止中断,这意味着从硬件响应中断到FreeRTOS重新使能中断之间的这段短时间内,中断是不可避免的被禁止的。1 [) f' I: C" Q; w" T* B& ]( s
* w1 s1 _! ?: r2 Y
不调用API的中断可以运行在比configMAX_SYSCALL_INTERRUPT_PRIORITY高的优先级,这些级别的中断不会被FreeRTOS禁止,因此不会因为执行RTOS内核而被延时。0 M3 j' Y. @+ @- F ^
! f" R. F6 J2 o6 W$ w' m: x
例如:假如一个微控制器有8个中断优先级别:0表示最低优先级,7表示最高优先级(Cortex-M3和Cortex-M4内核优先数和优先级别正好与之相反,后续文章会专门介绍它们)。当两个配置选项分别为4和0时,下图描述了每一个优先级别可以和不可做的事件:, A0 t' `6 {+ B$ Y2 n, V
configMAX_SYSCALL_INTERRUPT_PRIORITY=4
- l, d4 p$ P5 BconfigKERNEL_INTERRUPT_PRIORITY=0
[5 B; L7 _% r; A8 D% H/ ^
/ S6 m7 O0 q8 c; L( _# t
# `) C1 T1 _2 U& U! P/ h# P4 r- c9 t! N8 M" ^5 l1 t
这些配置参数允许非常灵活的中断处理:9 l2 P6 l4 u. C% b. a( i% j
2 F5 c2 C2 Y" l/ l+ I. G 在系统中可以像其它任务一样为中断处理任务分配优先级。这些任务通过一个相应中断唤醒。中断服务例程(ISR)内容应尽可能的精简—仅用于更新数据然后唤醒高优先级任务。ISR退出后,直接运行被唤醒的任务,因此中断处理(根据中断获取的数据来进行的相应处理)在时间上是连续的,就像ISR在完成这些工作。这样做的好处是当中断处理任务执行时,所有中断都可以处在使能状态。
7 P/ i; E b5 W
* o. K$ N3 K0 m3 @( p. d2 [ 中断、中断服务例程(ISR)和中断处理任务是三码事:当中断来临时会进入中断服务例程,中断服务例程做必要的数据收集(更新),之后唤醒高优先级任务。这个高优先级任务在中断服务例程结束后立即执行,它可能是其它任务也可能是中断处理任务,如果是中断处理任务,那么就可以根据中断服务例程中收集的数据做相应处理。4 V3 `% S2 A1 k, _
; P3 g! }. g1 i0 E9 E
configMAX_SYSCALL_INTERRUPT_PRIORITY接口有着更深一层的意义:在优先级介于RTOS内核中断优先级(等于configKERNEL_INTERRUPT_PRIORITY)和configMAX_SYSCALL_INTERRUPT_PRIORITY之间的中断允许全嵌套中断模式并允许调用API函数。大于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断优先级绝不会因为执行RTOS内核而延时。
% z& H" K6 C2 a8 @. Y" B2 ~5 y1 `0 U9 p; S( k5 q
运行在大于configMAX_SYSCALL_INTERRUPT_PRIORITY的优先级中断是不会被RTOS内核所屏蔽的,因此也不受RTOS内核功能影响。这主要用于非常高的实时需求中。比如执行电机转向。但是,这类中断的中断服务例程中绝不可以调用FreeRTOS的API函数。
$ K& U* r* r: O `1 M; O& o! A4 b& l. T* X. y! Q
为了使用这个方案,应用程序要必须符合以下规则:调用FreeRTOS API函数的任何中断,都必须和RTOS内核处于同一优先级(由宏configKERNEL_INTERRUPT_PRIORITY设置),或者小于等于宏configMAX_SYSCALL_INTERRUPT_PRIORITY定义的优先级。
3 X8 e# U- w) g" t3 ~1 K4 f. E7 Q" L N n
4.28 configASSERT# L1 Z* U0 Y7 k5 v4 _9 k
) O7 U' g- @! Z1 `8 M- [- D断言,调试时可以检查传入的参数是否合法。FreeRTOS内核代码的关键点都会调用configASSERT( x )函数,如果参数x为0,则会抛出一个错误。这个错误很可能是传递给FreeRTOS API函数的无效参数引起的。定义configASSERT()有助于调试时发现错误,但是,定义configASSERT()也会增大应用程序代码量,增大运行时间。推荐在开发阶段使用这个断言宏。
" ~& j- w" D+ N; q2 b. T$ A6 Q
$ `( X2 c/ D# j. K 举一个例子,我们想把非法参数所在的文件名和代码行数打印出来,可以先定义一个函数vAssertCalled,该函数有两个参数,分别接收触发configASSERT宏的文件名和该宏所在行,然后通过显示屏或者串口输出。代码如下:3 U4 T0 O5 W! Y# i( {8 @
8 A8 m/ C' W: l! z- @8 V; _
- #define configASSERT( ( x ) ) if( ( x ) == 0 )vAssertCalled( __FILE__, __LINE__ )
复制代码 2 L2 M% f* }7 H0 a/ F
这里__FILE__和__LINE__是大多数编译器预定义的宏,分别表示代码所在的文件名(字符串格式)和行数(整形)。0 P; W5 h( j% c+ Q
8 L: |4 R+ H# Z6 T这个例子虽然看起来很简单,但由于要把整形__LINE__转换成字符串再显示,在效率和实现上,都不能让人满意。我们可以使用C标准库assert的实现方法,这样函数vAssertCalled只需要接收一个字符串形式的参数(推荐仔细研读下面的代码并理解其中的技巧):- #define STR(x) VAL(x)9 b8 K" H- ^" t
- #define VAL(x) #x
- G# u$ V# j3 R& p1 g$ N" F: T* ? - #define configASSERT(x) ((x)?(void) 0 :xAssertCalld(__FILE__ ":" STR(__LINE__) " " #x"\n"))
3 N0 c3 X% g0 G8 P9 P+ f: w9 x
复制代码 6 A4 X1 T2 B; G) Z
这里稍微讲解一下,由于内置宏__LINE__是整数型的而不是字符串型,把它转化成字符串需要一个额外的处理层。宏STR和和宏VAL正是用来辅助完成这个转化。宏STR用来把整形行号替换掉__LINE__,宏VAL用来把这个整形行号字符串化。忽略宏STR和VAL中的任何一个,只能得到字符串”LINE”,这不是我们想要的。
2 L9 C6 m+ h0 y# i2 ~! e+ ?9 R$ [2 I8 {2 r1 a
这里使用三目运算符’?:’来代替参数判断if语句,这样可以接受任何参数或表达式,代码也更紧凑,更重要的是代码优化度更高,因为如果参数恒为真,在编译阶段就可以去掉不必要的输出语句。8 [) p) i- S+ m+ H4 o
0 ~/ n- P1 k( l( v, W E( {7 K4.29
+ v- }$ y, H8 W4 b5 `7 M8 q
3 m2 J* V5 I- t1 [2 H4.30
2 M d% u+ o+ M' Z$ b w, D* ?* R4 n6 ?" B$ D, y2 d
05. 其它# i( D% }# m$ A& ~
FreeRTOS目录结构2 Z0 l- R+ u% E$ G' A$ n
* a8 s# h* [+ X* T- ├─FreeRTOS
* v. y/ x6 ?& z: U0 A4 N/ u - │ ├─Demo // 各种开发工具的完整Demo,开发者可以方便的以此搭建出自己的项目,甚至直接使用& _" e g. m! B5 B. `1 j4 A
- │ │ ├─Common // 所有例程都可以使用的演示例程文件
' z& {$ _" f* I9 P7 U - │ │ └─其他 // 对应平台和开发工具的项目例程(命名:平台_开发工具,例如:CORTEX_M4F_M0_LPC43xx_Keil)1 ^$ |' k ]. G4 ~6 _# Q% u
- │ ├─License // 使用修改过的GPL
9 Q: A' s P7 f0 U - │ └─Source // FreeRTOS的源码8 _9 a2 F6 U& B& [. Y# M3 Y5 y
- │ ├─include // 源码对应的头文件8 d- H& O$ o# ^/ {5 Z
- │ └─portable // 每个支持的处理器架构需要一小段与处理器架构相关的RTOS代码。该目录下即为和开发平台相关的代码
' ~8 h* y% D: m; ]2 q$ C - │ ├─MemMang // FreeRTOS内存管理方案(一般要根据平台来选择以下5个之一)5 q% T& M B3 v) P
- │ │ heap_1.c4 x2 M5 o% J, ^& B; P2 o: i1 m4 y8 `1 `
- │ │ heap_2.c# z8 Z% U6 l* |9 f
- │ │ heap_3.c
9 O: R% a) y6 j3 `# T% F - │ │ heap_4.c
( Y. j' J0 b* p8 V' `% S - │ │ heap_5.c
, d* G. X) c% k! V; q - │ └─其他 // 其他开发工具相关的代码,需要根据自己的开发工具进行选择" X! H, K$ q: c+ W2 \5 g" K& f$ ]
- │ croutine.c // 协线程(协程)文件,和任务类似,在系统资源比较缺乏下使用9 R! @! N" t5 }& P- o
- │ event_groups.c // 事件标志组& d6 z6 n9 {" i5 t* n" S* J( [
- │ list.c // 列表结构描述,在内核整体控制上都使用了列表格式数据处理,一切数据结构的基础
$ }4 ]+ \% |% d2 g. ^ - │ queue.c // 队列,任务和任务之间的通讯处理
1 `) O9 Y+ j6 ^+ W7 @$ } - │ tasks.c // 所有任务相关函数 s) V' |7 f1 k
- │ timers.c // 软件定时器,以任务形式存在0 q3 V% v/ c# P+ x5 W3 E
- | stream_buffer.c // 10.0.0 新增# I2 @: Y6 L3 U/ ~6 g
- └─FreeRTOS-Plus // FreeRTOS+组件和演示例程
复制代码 " ]; p+ F: `- \( Z H$ E5 V _
FreeRTOS_Config.h文件1 J% T# Y& [' S3 D+ s+ h0 }2 ]8 K
5 E$ s2 }% O+ @; J ~# I- #ifndef FREERTOS_CONFIG_H
$ [6 W1 H" l# E" d( Z6 ]7 h& M - #define FREERTOS_CONFIG_H7 c4 k. d: R3 Z5 ?
- 8 F N& p) H5 w% T, O- ]4 [9 {
- #include "sys.h"
8 U; k. ?/ H9 Z) Q9 i - #include "usart.h"
* Q5 {! E1 A) S( t - //针对不同的编译器调用不同的stdint.h文件
/ M" [) ?: g# R q% \, O% t - #if defined(__ICCARM__) || defined(__CC_ARM) || defined(__GNUC__)7 P. h7 }0 Q; u8 B* k M3 P# o
- #include <stdint.h>
; S8 `" N( `# N. `3 @9 `9 E/ }3 R - extern uint32_t SystemCoreClock;6 ?. A' t. k& F
- #endif2 n8 q6 x' T7 f8 O
- 5 L, d: V* v# f" M8 k+ }
- //断言 p2 I0 I9 N. n* m. N( N
- #define vAssertCalled(char,int) printf("Error:%s,%d\r\n",char,int)0 D9 b0 a* [7 {( ~& A3 i0 _5 \" |' c
- #define configASSERT(x) if((x)==0) vAssertCalled(__FILE__,__LINE__)
" F5 c/ M& E5 w q - ( ]- x% D1 ?( N6 J( Y' K, S! O
- /***************************************************************************************************************/; d& m, @. G( ~- X5 S
- /* FreeRTOS基础配置配置选项 */
1 X% z- E1 ]5 X5 z/ H& x$ z5 I8 k - /***************************************************************************************************************/* N& U6 j% l: n8 E& m
- #define configUSE_PREEMPTION 1 //1使用抢占式内核,0使用协程: A4 _ ^& f$ e0 a$ d' Y( l
- #define configUSE_TIME_SLICING 1 //1使能时间片调度(默认式使能的)6 u# S% \* [. O4 c9 X
- #define configUSE_PORT_OPTIMISED_TASK_SELECTION 1 //1启用特殊方法来选择下一个要运行的任务, o J! A; c0 [3 b% H- ?' m
- //一般是硬件计算前导零指令,如果所使用的* J- p$ v( z. {4 Z" V$ e
- //MCU没有这些硬件指令的话此宏应该设置为0!1 N4 Y- U7 [/ ~) b9 b) i
- #define configUSE_TICKLESS_IDLE 0 //1启用低功耗tickless模式! z# { i1 p7 d
- #define configUSE_QUEUE_SETS 1 //为1时启用队列
1 p+ H6 k6 o* a5 G9 @ - #define configCPU_CLOCK_HZ (SystemCoreClock) //CPU频率
7 c% h+ J5 t& r, p! c- Y! T* L - #define configTICK_RATE_HZ (1000) //时钟节拍频率,这里设置为1000,周期就是1ms- }% H9 B [5 }# O0 w# f
- #define configMAX_PRIORITIES (32) //可使用的最大优先级
# ]; s- Z! w5 N5 m! S k - #define configMINIMAL_STACK_SIZE ((unsigned short)130) //空闲任务使用的堆栈大小8 s( j+ X! ]( Q/ V5 D; {- |
- #define configMAX_TASK_NAME_LEN (16) //任务名字字符串长度- @+ D& L% T& u" c; E
z4 Z$ ]$ k8 \2 M- #define configUSE_16_BIT_TICKS 0 //系统节拍计数器变量数据类型,
0 ]5 b: o) }8 t7 d2 n: _ - //1表示为16位无符号整形,0表示为32位无符号整形0 E4 s3 R8 ^ S( V* N( L S
- #define configIDLE_SHOULD_YIELD 1 //为1时空闲任务放弃CPU使用权给其他同优先级的用户任务- D( U5 i W' h7 d2 K( m
- #define configUSE_TASK_NOTIFICATIONS 1 //为1时开启任务通知功能,默认开启
6 Q& H# i, G1 T+ {/ O" M8 | - #define configUSE_MUTEXES 1 //为1时使用互斥信号量
: V* d9 l5 V2 o: O- |$ O/ A - #define configQUEUE_REGISTRY_SIZE 8 //不为0时表示启用队列记录,具体的值是可以$ f7 ^# C# E, P T# z0 G
- //记录的队列和信号量最大数目。
7 n6 I" k2 R8 j9 x. d - #define configCHECK_FOR_STACK_OVERFLOW 0 //大于0时启用堆栈溢出检测功能,如果使用此功能0 k; G* C2 z9 ^( K
- //用户必须提供一个栈溢出钩子函数,如果使用的话5 S4 A8 f- y2 h7 V; p, v) `* p, N
- //此值可以为1或者2,因为有两种栈溢出检测方法。2 Q9 C; ^* b3 i8 e
- #define configUSE_RECURSIVE_MUTEXES 1 //为1时使用递归互斥信号量( e1 l# t. M; V$ a" ~+ i
- #define configUSE_MALLOC_FAILED_HOOK 0 //1使用内存申请失败钩子函数5 I0 J3 W& D# K3 i1 z- n# }% `: [
- #define configUSE_APPLICATION_TASK_TAG 0
$ H& p/ E2 R, i5 v6 F+ U! C - #define configUSE_COUNTING_SEMAPHORES 1 //为1时使用计数信号量& D; H3 D+ a8 g0 T8 A) _
- 5 O' _4 b5 [2 Z7 \) i& o; R/ c
- /***************************************************************************************************************/$ g0 J$ t4 d3 L0 c' U }* d; r
- /* FreeRTOS与内存申请有关配置选项 */; y! q4 k" v2 x+ }
- /***************************************************************************************************************/
' z# D" k! C* F) I4 h' s - #define configSUPPORT_DYNAMIC_ALLOCATION 1 //支持动态内存申请
2 J# L$ t- I3 N - #define configTOTAL_HEAP_SIZE ((size_t)(20*1024)) //系统所有总的堆大小, \( v! E, X. h. T& }. \; j
- C$ R2 b) s1 U
- /***************************************************************************************************************/% a1 Q. Q2 h$ T+ }
- /* FreeRTOS与钩子函数有关的配置选项 */
" A6 B$ D: h; t* K+ U" L" b7 t1 l+ s& @ - /***************************************************************************************************************/
" O1 g8 f9 B# a7 N4 S - #define configUSE_IDLE_HOOK 0 //1,使用空闲钩子;0,不使用4 i) A. N$ o: ?# @- N1 k
- #define configUSE_TICK_HOOK 0 //1,使用时间片钩子;0,不使用
3 d/ r4 e0 i, ] - 7 I5 G# R& t8 r# e
- /***************************************************************************************************************/" s j C5 }! e+ M" A4 i
- /* FreeRTOS与运行时间和任务状态收集有关的配置选项 */
7 `$ B6 c' Y& _0 M& g - /***************************************************************************************************************/
, F# X9 |: P J& k( @ - #define configGENERATE_RUN_TIME_STATS 0 //为1时启用运行时间统计功能
5 t- r. p% y7 R, ~6 I - #define configUSE_TRACE_FACILITY 1 //为1启用可视化跟踪调试7 U. ?6 J* I8 E I+ Q/ Y3 e' ^
- #define configUSE_STATS_FORMATTING_FUNCTIONS 1 //与宏configUSE_TRACE_FACILITY同时为1时会编译下面3个函数- O+ `& e5 H) v+ _
- //prvWriteNameToBuffer(),vTaskList(),
$ C! l5 @+ \7 A: E - //vTaskGetRunTimeStats()
. r, u* w% {' x2 w" |+ E; U& Z - 0 g& L" T; M3 M
- /***************************************************************************************************************/" K. b. g! [7 k1 h% p
- /* FreeRTOS与协程有关的配置选项 */& H, q3 A8 c* L8 i, U
- /***************************************************************************************************************/
8 r p9 _% J) } U. |9 J. B - #define configUSE_CO_ROUTINES 0 //为1时启用协程,启用协程以后必须添加文件croutine.c
4 P9 q- n; }0 x. Z* \) a - #define configMAX_CO_ROUTINE_PRIORITIES ( 2 ) //协程的有效优先级数目% r; x& q& ~" c! u
- 7 L& d1 D: O8 ~1 K) o( Q |- w& \
- /***************************************************************************************************************/. g- g* Q% H5 N# U; B( {
- /* FreeRTOS与软件定时器有关的配置选项 */ ?% e5 _9 g0 ~: x; A, ]
- /***************************************************************************************************************/0 z) T) i$ o9 A$ z o
- #define configUSE_TIMERS 1 //为1时启用软件定时器
0 A$ l. O3 A% {+ A; o' h - #define configTIMER_TASK_PRIORITY (configMAX_PRIORITIES-1) //软件定时器优先级
9 r% v* g' H) L6 G - #define configTIMER_QUEUE_LENGTH 5 //软件定时器队列长度2 G' O/ @+ L+ F4 o X8 L Y. c, S
- #define configTIMER_TASK_STACK_DEPTH (configMINIMAL_STACK_SIZE*2) //软件定时器任务堆栈大小
/ w7 d2 ]1 W+ b: o, N
1 i9 R! C& c+ ~8 C2 F, @8 k- /***************************************************************************************************************/
3 K6 a& {7 v! { - /* FreeRTOS可选函数配置选项 */
4 Q, n$ R+ V: J* f$ ^* v: U/ K - /***************************************************************************************************************/7 b. {% L3 C( h( A8 r9 G g
- #define INCLUDE_xTaskGetSchedulerState 1 ' h( ~9 b8 u- B6 f2 R- N' t [
- #define INCLUDE_vTaskPrioritySet 1
. B1 z8 Y" K+ z4 k4 @! A - #define INCLUDE_uxTaskPriorityGet 1
1 `2 q$ x0 j6 [ [ - #define INCLUDE_vTaskDelete 1
6 w" j" |: ]* P5 ?5 S8 n - #define INCLUDE_vTaskCleanUpResources 15 P8 |2 @) y. s5 d
- #define INCLUDE_vTaskSuspend 1
2 {$ Y! x0 C( ~: c0 V4 ^- C - #define INCLUDE_vTaskDelayUntil 14 A; B E8 O8 f. j
- #define INCLUDE_vTaskDelay 1
' x9 d- [* p- k - #define INCLUDE_eTaskGetState 1# \8 G0 o2 b! D% ]
- #define INCLUDE_xTimerPendFunctionCall 1
; |0 r8 M( P& v) t& s - ( S2 V7 v) Q5 Q8 u/ u0 j) E
- /***************************************************************************************************************/% f; e! _* m \' E k$ H
- /* FreeRTOS与中断有关的配置选项 */1 J+ ~0 t5 H, Q6 @
- /***************************************************************************************************************/
3 \, v# T% I4 O3 s* _5 h - #ifdef __NVIC_PRIO_BITS: t9 R! @1 [" H. t( C0 K
- #define configPRIO_BITS __NVIC_PRIO_BITS* q. ], E( u. ]+ l- l# t$ a
- #else
6 X4 @5 I) X! ~ - #define configPRIO_BITS 4
3 |& H& k/ s' z* `, _; p - #endif
" K* B0 s9 H$ w# J' C; b$ r
?2 ~, V; `7 c9 G- #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 //中断最低优先级
! L( o0 |9 S2 H- t' @9 s& ~) ] - #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 //系统可管理的最高中断优先级; Q2 ~ D& e- t. Y
- #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
) Y( Y0 E X& P; e0 q - #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
, V& d* r- L2 U+ B7 e9 P! R - 8 w9 U; |" _" M3 C. H( Z3 |
- /***************************************************************************************************************/1 }# @3 {6 S5 V7 i7 C8 S" k( J* J
- /* FreeRTOS与中断服务函数有关的配置选项 */
& E8 Z' a% w6 g6 U; g - /***************************************************************************************************************/
- y: R/ {5 H Z' e- p7 o - #define xPortPendSVHandler PendSV_Handler! T" X2 Y8 ~ ^
- #define vPortSVCHandler SVC_Handler7 @4 [9 a# T O( F" j7 N
- & N' J5 }9 G- G$ B% l
- #endif /* FREERTOS_CONFIG_H */
复制代码
% }1 S8 M/ |. c* K& o: U8 X) N" y; d/ v4 m+ K) m
|