你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【连载】从单片机到操作系统④——FreeRTOS创建任务&开启...

[复制链接]
xiaojie0513 发布时间:2018-5-26 21:52
本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑
. f- w- K* |  h
* A8 a% K3 [% g1 E3 U3 l
创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。
听首歌缓解一下心情。

6 L) `3 c% Z* j. i1 |; P& k& [
开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。
1BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,
9 T* W9 J" b2 g7 S/ n 2                              const char * const pcName,0 B( T, y, X/ n& a/ _5 q9 w  F1 ]/ b
3                              uint16_t usStackDepth,; [% \1 f) k- l! U
4                              void *pvParameters,3 K4 u/ S1 v6 k# V
5                              UBaseType_t uxPriority,
7 q" N: K: R: e' d: I 6                              TaskHandle_t *pvCreatedTask
6 E9 U1 e% [, X/ B 7                          )
;
) S- h  l3 y6 I* R 8创建任务中的堆栈大小问题,在task.h中有这样子的描述:
' o! m' B' Z% E  h$ r 9/**
9 O; v2 X* E% Y, J6 \- U% h' m4 w10* @param usStackDepth The size of the task stack specified as the number of variables the stack * can hold - not the number of bytes.  For example, if the stack is 16 bits wide and  6 S& _$ O" h4 W( R' h' v
11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.
1 P( g5 G+ m4 z9 D" z0 y  }  o12*/
代码可左右滑动

+ d' R9 P/ g6 z* Z$ k- ?
  当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。
这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)
文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。
当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。
6 u. {) `9 X0 F$ n( Q( k
  好啦,补充完毕。下面正式开始我们今天的主题。


/ Q9 H/ J: U3 Q0 j) t4 t" ]/ a- G, K1 Y
  我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。
  其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。
% W& p5 t3 N) p
按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。
1int main(void)3 Z' r# d6 }+ D% {
2
{) {/ X, n  {. U, a; R9 |% v
3    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4    ! m; R, i' D$ u1 |
4    Delay_Init();                       //延时函数初始化     4 l+ t. d9 i, m1 J
5    Uart_Init(115200);                  //初始化串口- y% T/ B- E/ d2 ^$ z1 Q1 A, P
6    LED_Init();                     //初始化LED. N2 e& D% @; l; o* ?, C
7    KEY_Init();
( O- P, ^, x( { 8    //创建开始任务
+ z% V) i; J3 Z' a/ O: m 9    xTaskCreate((TaskFunction_t )start_task,            //任务函数
0 M) l# e- h; t- i, i) \8 b; w10                (const char*    )"start_task",          //任务名称
# y! M& K( U7 Z; s: U3 ]11                (uint16_t       )START_STK_SIZE,        //任务堆栈大小" U2 \6 S. z0 d3 q, A" @( r: h
12                (void*          )NULL,                  //传递给任务函数的参数& h9 {9 A8 b) R' c# u( z
13                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
1 p" ^- H! M" b' A: \/ G14                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
) Z! h2 C8 j4 ]- I15    vTaskStartScheduler();          //开启任务调度5 ~7 y. \6 j4 h0 S+ p8 l$ i: o' B, A
16}
, n* L) V& \, M2 @& Q+ \# n
2 _& X1 G* A3 a5 h
  来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。
注意:下面说的创建任务均为xTaskCreate(动态创建而非静态创建。
1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); . e% M( x7 v0 p! D
2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */
3 h0 i# W7 ]7 [7 [# o* c 3            if( pxStack != NULL )
( ~$ ]$ Y5 b( J8 X4 W2 J 4            {8 E! |- s5 }, r& a  p
5                /* Allocate space for the TCB. */3 O+ P9 R& L" A# A: M- J: W% A
6                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
' v$ o2 i; z8 _0 V& L& V1 O. U 7                /*lint !e961 MISRA exception as the casts are only redundant for some paths. */: n- P! q( a# u- R2 Y: A+ x7 [! E6 H
8                if( pxNewTCB != NULL )" u1 }9 h+ d& Z4 D" q) ]0 Z' }$ I+ ~  w
9                {* P( m, M. x  Y$ K
10                    /* Store the stack location in the TCB. */, @- {, g8 J7 W- x
11                    pxNewTCB->pxStack = pxStack;
8 K$ N7 T6 |' ^2 o5 y3 }1 e12                }' c8 j. \. {. W4 L$ v; }
13                else
! @; k* P3 ]/ q4 }" V14                {4 \# C* @, D. Q. |) v
15                    /* The stack cannot be used as the TCB was not created.  Free
3 {% l  h) x" x& y16                    it again. */
; l# o# q2 Y4 s0 c; h8 l
17                    vPortFree( pxStack );
5 m' h# i$ W! `8 ]( ~! B7 q18                }6 w+ E, R; A: }8 z
19            }
( Y/ I7 ?0 x9 t6 W20            else
* s) z$ H" B+ x; j0 ^0 E" s21            {# S- f$ I* w2 g* M+ o9 u
22                pxNewTCB = NULL;4 U; C  l; e3 D8 P
23            }" A# _  v, c( L- s0 t
24        }
  首先是利用pvPortMalloc给任务的堆栈分配空间,if( pxStack != NULL )如果内存申请成功就接着给任务控制块申请内存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同样是使用pvPortMalloc();如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈的内存vPortFree( pxStack );
  然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中prvAddNewTaskToReadyList( pxNewTCB );
  最后返回任务的状态,如果是成功了就是pdPASS,假如失败了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;

3 g: I' P: |1 C  C* {: A 1prvInitialiseNewTask(     pxTaskCode, / s# E, }0 `, a% }4 f$ Q4 ^9 n
2                          pcName,
  a& l8 P, L1 j) W2 W3 ? 3                         ( uint32_t ) usStackDepth,
9 k2 x  r9 N3 \0 J2 Y/ O) o 4                          pvParameters,
; J* T7 _6 ?( B" i+ u 5                          uxPriority,
9 j# _/ [! E9 \" h 6                         pxCreatedTask,( g1 y1 k7 C. p, k
7                          pxNewTCB, ) i; Q$ Z/ s0 Z
8                         NULL );
9 g. O* H- B2 c. Q) f1 G' J' g 9            prvAddNewTaskToReadyList( pxNewTCB );, l' ~4 l. }; R# P/ E
10            xReturn = pdPASS;! y5 D6 [. O( M: e
11        }
1 f* J( \9 ^6 i/ m12        else9 s0 \; W* M2 [& N
13        {! x* k9 L# {$ Z0 J) x
14            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
. T* _2 c5 M0 K. O, w15        }* P. x% ^% ]  `" \9 _
16        return xReturn;9 @6 V/ ~! M2 I2 M7 j* h
17    }
# W7 S' C% ]' b; P) z18// 相关宏定义6 K# `# J2 ?( S. E
19#define pdPASS            ( pdTRUE )7 q  {9 S& \, f1 K: g2 m7 z
20#define pdTRUE            ( ( BaseType_t ) 1 )
. W' W" O' a; {- N, K) m8 R4 @$ _21/* FreeRTOS error definitions. */8 q! D! {6 [/ E
22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY    ( -1 )
3 B  l2 u" N0 Y
0 D0 D9 w, B9 F+ F
  具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。

8 y: X5 Q5 b  \7 H
  因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。

# D2 o, }4 O3 w
创建完任务就开启任务调度了:
1vTaskStartScheduler();          //开启任务调度
. {3 X) @' l6 `. F& u; T# D7 b

% ]! R! ?: B& I7 y) i+ m
在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的)
1xReturn = xTaskCreate(    prvIdleTask,# \8 `8 J0 V! w, ?' s- m* q
2                          "IDLE", configMINIMAL_STACK_SIZE,2 I; q% @* P: y! s* @2 ]. c1 B2 I
3                          ( void * ) NULL,
$ D: J5 K1 V! W2 u2 [/ s- r; n 4                          ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),+ M) H& i1 v9 q2 Z3 s
5                          &xIdleTaskHandle ); , x4 B: Z1 ?# X' Y& ]% U
6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */# L, t/ b* G; [
7    }  y/ @  K$ c. H. A
8相关宏定义:$ ^, M: O$ r. `, ^" |: Q
9#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )# d8 m7 B$ s! }  L2 s6 e
10#ifndef portPRIVILEGE_BIT
4 Y& B4 k! H3 V0 u$ I11    #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )9 ^/ ^$ o2 ?3 M4 |/ D$ b* h6 b
12#endif' d; P3 M. r; `) E
13#define configUSE_TIMERS                        1                              
6 b- r3 X: F2 \1 @' z14 //为1时启用软件定时器, h* ]- h. z8 b3 d" M9 U' O; x
从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。
如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是:
1#if ( configUSE_TIMERS == 1 )6 [$ F4 o# m) D/ u
2    BaseType_t xTimerCreateTimerTask( void )& N5 F  }$ K9 u* Y& X$ ?6 I9 Q$ ^
3

7 r# g- ^& j# t, j8 @* V
然后还要把中断关一下
1portDISABLE_INTERRUPTS();: o. W9 B" k/ z5 {& s# ]" c
至于为什么关中断,也有说明:
1/* Interrupts are turned off here, toensure a tick does not occur
: s) @$ L1 p: R( m1 a- t! k 2before or during the call toxPortStartScheduler().  The stacks of& ?  ]* N. n0 y# i+ f
3the created tasks contain a status wordwith interrupts switched on* n, v) h/ x& I; Y
4so interrupts will automatically getre-enabled when the first task
) R, f. z4 _9 S: L) S 5starts to run. */

+ B& o& P6 n  @ 6/ *中断在这里被关闭,以确保不会发生滴答. y0 R0 M  z% R, b
7在调用xPortStartScheduler()之前或期间。堆栈
8 a* V% I/ L/ u& }6 O$ n+ `# k8 l 8创建的任务包含一个打开中断的状态字
% L4 e1 M& q2 {  k+ K  C 9因此中断将在第一个任务时自动重新启用9 J' r0 P( o3 n, W3 _' w! P
10开始运行。*/: k. s/ p% P. @- [, K' }. P
+ D; B( [, ?4 l1 _$ G; p4 U. {# P

2 y9 H! ^) o6 w7 H
那么如何打开中断呢????这是个很重要的问题
别担心,我们在SVC中断服务函数里面就会打开中断的
看代码:
1__asm void vPortSVCHandler( void )- @5 W# `5 g! ]3 A2 u
2
{3 s7 I, v1 r( @" c, O$ L8 @
3         PRESERVE8
* p$ p6 _/ z1 { 4         ldr    r3, =pxCurrentTCB  /* Restore the context. */4 d! U/ K( s$ k8 @  B" T
5         ldrr1, [r3]                            /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
7 ~% D+ y9 P5 y 6         ldrr0, [r1]                            /* Thefirst item in pxCurrentTCB is the task top of stack. */
: e4 I/ X9 A5 S; i9 b 7         ldmiar0!, {r4-r11}             /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
& _1 n) I) i6 |1 L% I6 L 8         msrpsp, r0                                   /*Restore the task stack pointer. */
+ J! o( B+ E7 S' Q9 w( ` 9         isb5 M) I1 F/ ^1 s* G, F
10         movr0, #01 K5 G& W' `* G, H. m
11         msr  basepri, r0; ]/ q/ o# G' Y" X: ?$ u* T" [
12         orrr14, #0xd
7 T# @; V& M6 x$ I9 M13         bxr14
5 B% }0 r( c! j) P14}9 T& G+ D# Q# ^& A: F# M/ K
! l  }5 r7 e) [- `
1msr  basepri, r0
) q1 J+ v4 G& R* n! w  @
  就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。

5 n. U4 a4 B" B. }1xSchedulerRunning = pdTRUE;
+ j) g2 N. I5 ^) \* W
任务调度开始运行

/ A5 m1 ^$ @& {5 t+ B1/* If configGENERATE_RUN_TIME_STATS isdefined then the following
9 q* V9 S6 o& Y9 X2macro must be defined to configure thetimer/counter used to generate
3 a6 a+ ^) P) _+ r; O6 F- }8 M3the run time counter time base. */
- x2 V' ~, K7 s' }
4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
: s' Y9 b! G; F% Z$ t( o! {3 I/ y) z  w+ U
如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。
2 M) u5 m: B0 |5 h) W0 ^
来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。
1if( xPortStartScheduler() != pdFALSE )4 c: B7 g3 h: z) k' Z3 S
2                   {2 r6 r/ u' g  o# e( ]6 B2 e; y  @
3                            /*Should not reach here as if the scheduler is running the
0 _* K+ ]: L6 ^8 ]0 h: C( k4                            functionwill not return. */
2 H1 P# v/ l/ X6 x4 L5                   }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。

; O: B* |5 T  C" k7 D: j
开始任务就按照套路模板添加自己的代码就好啦,很简单的。
创建任务
1 xTaskCreate((TaskFunction_t )led0_task,   
8 s% J, \! b5 h7 [ 2                (const char*    )"led0_task",  
2 C  m$ t$ K- l/ l: p+ N 3                (uint16_t       )LED0_STK_SIZE,
/ }1 u4 p; s1 @3 E' {1 e: _ 4                (void*          )NULL,                                    - G1 I' M/ ]+ x- J. s( T# J9 j0 g" D
5                (UBaseType_t    )LED0_TASK_PRIO,     Y! F: S3 K* K0 a+ C* g
6                (TaskHandle_t*  )&LED0Task_Handler);  " a7 r5 p9 Q5 C
7   //创建LED1任务
6 \: M. j. n( B' {9 J 8   xTaskCreate((TaskFunction_t )led1_task,    & v5 a: }; D1 [
9                (const char*    )"led1_task",  
8 ~$ I) W  v$ q8 u- w2 F10                (uint16_t       )LED1_STK_SIZE," V2 |  W  a! V: n, R
11                (void*          )NULL,
! C! H5 c$ u' _- v6 w" p12                (UBaseType_t    )LED1_TASK_PRIO," Q& S) _; b+ r) r7 R  D# @% I0 L
13                (TaskHandle_t*  )&LED1Task_Handler);      6 s0 o. d" Q; S( D' y" g2 t- Q  p
2 ^! D3 {, Q+ f2 v% P5 u- }
创建完任务就开启任务调度
1vTaskStartScheduler();          //开启任务调度
- ^5 ]( M6 ^) [6 ?
然后具体实现任务函数
( K4 n. w; U  [2 P
1//LED0任务函数
9 e+ T- ]$ y$ ?- @0 @0 ]; x 2void led0_task(void *pvParameters)+ G$ k- F7 f7 L
3
{0 d8 V- \  |. _" i, D
4   while(1)) M& ~/ J6 a8 {5 m. ^
5    {
& z- r$ d: G! K+ N 6       LED0=~LED0;
% E7 |# B. S' u: P4 c6 j 7       vTaskDelay(500);
! _! p% L/ ]; F4 U 8    }
- T* g' q; L, C* w 9}  6 [0 v3 B9 @1 K# H
10//LED1任务函数
# A8 j- o# d& x, k5 {11void led1_task(void *pvParameters)" p# }! x6 F  z1 Q1 Z# U
12
{% Q  l- S. Y: |% @
13   while(1)
' ^% l/ k  g* ^; X7 p, u14    {# b  H7 i8 i9 a" n: u9 U
15       LED1=0;
: L0 r% T5 y4 U+ w6 p3 G; M16       vTaskDelay(200);
- j3 g% c8 e* [1 t$ Z0 i& g17       LED1=1;- C2 F" V! T  p, a
18       vTaskDelay(800);9 c* K8 U3 ~' l" O/ c+ y& j
19    }
4 h0 A- H( h; Z20}
9 C: ~1 p5 r7 L9 n3 ^! f, ~5 P" X% R# B" W
好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~

, H& B" ^1 M; {( }* V
+ X5 [# Z" ~$ n3 F% m! l: ]. h
欢迎大家一起来讨论操作系统的知识
我们的群号是:783234154
% r1 S- G# r; z* E! f& ~

1 L- d$ ]$ t4 r! E5 o$ ^1 o4 U0 l& m/ v$ f; m' G# m# `. N

  G& u" w, c! U  m: Y, r2 p
创客飞梦空间是开源公众号
欢迎大家分享出去
也欢迎大家投稿
( c0 t2 V9 ^; R6 ~0 [; M6 k% V! v4 r3 I

; }: z/ l; }. O) H+ R7 t% E& r: ~) F/ Z
收藏 评论16 发布时间:2018-5-26 21:52

举报

16个回答
lilei900512 回答时间:2018-6-1 07:14:03
xiaojie0513 发表于 2018-5-31 18:05
# m% m# o/ h7 s3 \1 x怎么样啊,杰杰水平有限,还要多多指教吖

! j' n$ {9 S4 e. p* _0 h+ `写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
moviexm 回答时间:2018-6-14 08:56:04
         
) I' n- f  i# w9 n参与/回复主题: R% {, s" t2 [8 k2 W5 g- O0 A/ S$ \
关闭
2 b* u& J$ A/ ^! @& R- cRE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
xiaojie0513 回答时间:2018-5-31 18:05:34
lilei900512 发表于 2018-5-31 12:429 }& m0 {+ X8 W5 u& v2 ?( k
看看怎么样!
( G# e; v; e# R/ Q/ r
怎么样啊,杰杰水平有限,还要多多指教吖
xiaojie0513 回答时间:2018-5-28 12:17:37
看完留个言啊
zero99 回答时间:2018-5-30 13:16:31
竟然看完了  

评分

参与人数 1ST金币 +2 收起 理由
xiaojie0513 + 2 给破总十个赞咯!!

查看全部评分

zero99 回答时间:2018-5-30 16:45:13
谢谢打赏~
king_lv 回答时间:2018-5-31 11:33:21
感谢楼主分享
xiaojie0513 回答时间:2018-5-31 12:14:21
king_lv 发表于 2018-5-31 11:337 O1 A& N0 O2 o2 d0 F
感谢楼主分享

# z7 h6 ~8 B+ V
lilei900512 回答时间:2018-5-31 12:42:59
看看怎么样!
heyv11 回答时间:2018-6-7 21:52:39
写的好
xiaojie0513 回答时间:2018-6-7 23:35:52
heyv11 发表于 2018-6-7 21:52; T% Q- J1 f8 p4 g
写的好

3 X) J3 P  P, ~- u# P谢谢支持
billy226-301299 回答时间:2018-6-13 11:20:13
支持楼主,好东西
电子星辰 回答时间:2018-6-13 11:34:49
学习一下
xiaojie0513 回答时间:2018-6-13 14:47:39
billy226-301299 发表于 2018-6-13 11:20
* f4 W5 P! m& g9 P$ D* `$ J支持楼主,好东西
. }% @- O+ @, r
谢谢支持& B) U' i5 ?; c8 a
谢谢支持
12下一页

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版