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

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

[复制链接]
xiaojie0513 发布时间:2018-5-26 21:52
本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑
5 f+ a* ~+ G0 u* S5 ~! F" i% N+ p) _4 y
创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。
听首歌缓解一下心情。
. v8 K8 J, R* j% g) O9 p/ ~
开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。
1BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,. M0 }+ p  f1 @3 }* _. _
2                              const char * const pcName,' w4 F) ~* I; {( I
3                              uint16_t usStackDepth,* {8 g7 {$ I7 G" ^* ~  d$ z' t' [
4                              void *pvParameters,
0 x8 R5 I- j+ I% _% V; P/ G 5                              UBaseType_t uxPriority,* X8 Z3 E3 z! R9 ~' w% X
6                              TaskHandle_t *pvCreatedTask! `; {# x# e9 R$ O
7                          )
;
# [5 m+ u9 X( }# z9 _ 8创建任务中的堆栈大小问题,在task.h中有这样子的描述:
- t8 o7 ?! O* F6 P+ o+ ^7 p9 I+ m 9/**
# u# Q8 f: y5 A, }. m: a+ v% j10* @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  
3 O' x+ T, T9 |% a. j11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.
& r0 ]; M3 Q  _" g5 [12*/
代码可左右滑动

& a0 Q3 k, Y" e. g
  当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。
这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。
文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。
当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。
! t  M$ t% ^3 W$ K, k# S( o, y
  好啦,补充完毕。下面正式开始我们今天的主题。


# c' m+ k9 j8 }! t
  我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。
  其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。
+ m4 z: d# B7 `, u
按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。
1int main(void)
) ]$ `- s7 A; E1 b% f9 O1 F 2
{
3 W! A9 [( Y: q3 \% h1 [ 3    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4    1 k7 {7 d; M6 w5 _; R4 R
4    Delay_Init();                       //延时函数初始化     ' m2 J6 v2 b! r$ r0 X2 H7 f
5    Uart_Init(115200);                  //初始化串口
5 L, R4 u3 W. f 6    LED_Init();                     //初始化LED
" R% t* s! F! D" M5 A 7    KEY_Init(); 6 u. H8 ~5 G+ v  c+ A" ?
8    //创建开始任务5 K  j9 r1 }! g3 C9 R
9    xTaskCreate((TaskFunction_t )start_task,            //任务函数
& q! f: [3 d0 l6 k10                (const char*    )"start_task",          //任务名称1 K9 P5 o+ ?, M) B: Z: {* T) ?4 x
11                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
* d$ {" i' k# f" B8 U12                (void*          )NULL,                  //传递给任务函数的参数
8 A4 S& w6 O" N6 H/ ^0 I13                (UBaseType_t    )START_TASK_PRIO,       //任务优先级# E* q7 ~6 c  @# w! m; `
14                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              # W( X5 v8 D. v
15    vTaskStartScheduler();          //开启任务调度
. a% K  ~" u# w7 [& d/ p16}
5 f& V9 M1 j$ c# ?
# r) j3 l  p  O( U5 n1 U
  来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。
注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。
1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
# v$ e0 O, }9 c+ F& }4 ] 2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */& v- o) o7 W; I5 I1 ?
3            if( pxStack != NULL )
9 Z' Q4 w8 q  l  w% a 4            {4 p: F, ?# j9 d3 \
5                /* Allocate space for the TCB. */
+ O% B' e6 }9 j4 v 6                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
9 Z% p5 x1 Q/ C" o 7                /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
5 O3 ^0 f5 Q  r9 _/ E1 `/ u0 ? 8                if( pxNewTCB != NULL )+ }, P& F" I% J- M' M9 t
9                {
6 k5 M" z4 I+ }3 c: T* X  p10                    /* Store the stack location in the TCB. */3 b1 S0 X: S- y; f% g$ v# R% \6 s3 L
11                    pxNewTCB->pxStack = pxStack;
5 _% k/ d5 L; O: J, _1 Y6 m5 I* b12                }1 e$ o' v3 W5 o/ h6 f5 t5 J
13                else
5 n" v- M! o9 i3 J) A1 e14                {7 T5 s* y  ]% E# h. j/ C
15                    /* The stack cannot be used as the TCB was not created.  Free
7 ^3 s3 l; K2 y+ ^16                    it again. */

5 E0 y2 Z* p7 D2 s2 p: H17                    vPortFree( pxStack );* u# I3 u( u% l9 w" o
18                }
5 ~% T' v  n( ]. e2 y19            }! z. G' |! q/ D
20            else
& N, j/ `8 v6 T0 \+ A8 s21            {2 R9 ^4 |: K+ Y: G, X; b) T$ m
22                pxNewTCB = NULL;
% Y0 y+ _2 }9 {: y7 g23            }
) M3 p, }; i- z3 n- i) x24        }
  首先是利用pvPortMalloc给任务的堆栈分配空间,if( pxStack != NULL )如果内存申请成功,就接着给任务控制块申请内存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同样是使用pvPortMalloc();如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈的内存vPortFree( pxStack );
  然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中prvAddNewTaskToReadyList( pxNewTCB );
  最后返回任务的状态,如果是成功了就是pdPASS,假如失败了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;

- \1 ~7 x7 E1 J2 q1 b; W( O  {- C4 Q 1prvInitialiseNewTask(     pxTaskCode,
' R/ u5 B% X* s8 @. L" `2 M/ q 2                          pcName,
$ |  Q; G1 X9 T& ]% @ 3                         ( uint32_t ) usStackDepth,0 @0 \5 ]' Q% I7 b2 X) T0 t
4                          pvParameters,
+ \3 W9 g) F* D1 o9 x) k( A, A) o 5                          uxPriority, ! z- L1 D! T+ X5 W( n5 R/ Y$ h/ v1 k
6                         pxCreatedTask,
1 V& ?! t. m( X" B1 b- p 7                          pxNewTCB,
5 Q6 Y2 O( e: P  o% c 8                         NULL );
; t- X' d$ }! n) W5 i, t+ A 9            prvAddNewTaskToReadyList( pxNewTCB );
9 k6 s6 G" r2 ?6 I/ C( ~( i10            xReturn = pdPASS;
, t7 i( W* o  `0 n: f11        }4 x+ u4 T, x2 X0 y# |$ e) Q% U
12        else
" F6 _, K) {1 M) U$ e8 @- b1 D7 k) x13        {
, M) @5 B8 l# G9 L) g$ h" X1 e14            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;9 {) k; `7 \/ }2 h9 }
15        }+ f& X# f: w" \$ c( O& l
16        return xReturn;
+ l' B8 C- G# h6 ~8 K7 F17    }
0 [& k6 `/ ?1 D7 H# D& N18// 相关宏定义( L+ ^& a# x  p) t
19#define pdPASS            ( pdTRUE )
: l; y  Z8 x4 e) M: G- n* O# u20#define pdTRUE            ( ( BaseType_t ) 1 )$ ~' c: {) `3 X! r" U/ g( U$ V  r7 Y, z
21/* FreeRTOS error definitions. */
  Q8 Y: w: v9 {9 G4 D' @22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY    ( -1 ): d3 t0 y2 T4 _
3 r1 g% q' ]3 ]" m4 [6 e
  具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。
$ F: ]( D* `2 T3 V, @
  因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。

1 j- F) z& [' n% w' N
创建完任务就开启任务调度了:
1vTaskStartScheduler();          //开启任务调度8 c0 t' y8 ~+ P2 G$ T4 N- F
( e6 d* o* b6 m0 `6 B) G8 _& f
在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的)
1xReturn = xTaskCreate(    prvIdleTask,8 q7 D  t6 y+ j- K; U+ p
2                          "IDLE", configMINIMAL_STACK_SIZE,( F. i% o/ Z# e! `, a3 ]% ^2 S
3                          ( void * ) NULL,% N9 v$ l4 c0 @6 k0 j+ s- S
4                          ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),. N$ Z$ h! l) |& O, ?, ^# n
5                          &xIdleTaskHandle ); 8 y+ x/ j5 G1 l3 ?- F4 c$ a
6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
' N+ w8 }* R: B! m' n* r- g 7    }+ L! T4 Q# t; u& L
8相关宏定义:$ k. I6 F1 c: u1 P( t
9#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U ), n6 j$ r/ t* T9 p0 X+ A
10#ifndef portPRIVILEGE_BIT
* J. \: T& r9 y3 S2 t11    #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
4 m6 i0 v1 A2 q* K6 |, t6 B6 g12#endif5 e) A' V+ G! q- w( K1 C/ d- H
13#define configUSE_TIMERS                        1                              
& y2 ]; o6 r$ }/ B% Z/ s( D0 m  q14 //为1时启用软件定时器0 u* a* h4 N( W# B' ^7 S; I
从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。
如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是:
1#if ( configUSE_TIMERS == 1 )
* N( a8 K& i$ \6 Q$ p+ Q2    BaseType_t xTimerCreateTimerTask( void )4 y. U5 A' M( ?% I
3

) j/ f2 X* @1 ?  k% q8 g9 n3 q
然后还要把中断关一下
1portDISABLE_INTERRUPTS();
' z& e( d& D7 y
至于为什么关中断,也有说明:
1/* Interrupts are turned off here, toensure a tick does not occur
+ y3 y) \) F: ` 2before or during the call toxPortStartScheduler().  The stacks of. F9 _0 @5 r) ?  C( W
3the created tasks contain a status wordwith interrupts switched on& A% s/ [. N9 W( ~
4so interrupts will automatically getre-enabled when the first task
# z$ K% T1 c$ w, p: z; P' r8 a 5starts to run. */
! `0 w8 R, H5 b5 @4 W% d$ k0 n
6/ *中断在这里被关闭,以确保不会发生滴答
, p  E" A; x  \$ F; m 7在调用xPortStartScheduler()之前或期间。堆栈
* h  o3 W2 q& N7 Q 8创建的任务包含一个打开中断的状态字. o! j) D# R4 z4 d2 v
9因此中断将在第一个任务时自动重新启用
+ A0 O, V: D, F# J/ t, C& @; i10开始运行。*/
; E2 E( A( _, z4 g# Y1 }6 }8 P1 q" M, o# Q! m9 a6 y' f
  L! H- p) z; {) `" r
那么如何打开中断呢????这是个很重要的问题
别担心,我们在SVC中断服务函数里面就会打开中断的
看代码:
1__asm void vPortSVCHandler( void )
, L, y7 X0 u5 U/ o; F4 F. \' q 2
{
3 u4 }2 T# Q# m9 Q+ ^ 3         PRESERVE8; L& o, t/ I! i2 v6 f/ w' `. r1 c
4         ldr    r3, =pxCurrentTCB  /* Restore the context. */% x" G& s1 z! g1 D+ n# M$ E
5         ldrr1, [r3]                            /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
  ~) s8 w0 p8 R, P1 H+ m9 F8 [ 6         ldrr0, [r1]                            /* Thefirst item in pxCurrentTCB is the task top of stack. */
  h% c' }3 h8 u9 u& y5 L 7         ldmiar0!, {r4-r11}             /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
7 v, ?3 C; U9 o# q- s 8         msrpsp, r0                                   /*Restore the task stack pointer. */. N7 A! K3 `0 w( J6 d  g9 N
9         isb+ O. s+ T# }' F3 `9 c
10         movr0, #08 t4 i$ q6 `; L$ @5 a
11         msr  basepri, r0
8 |( }# a' L1 C6 G0 s12         orrr14, #0xd5 M; T( N3 k4 c* k
13         bxr14
( M3 r" D3 Z4 N5 [+ D# g14}
  w3 [& I; h' W+ x) K7 K: R6 V8 i! ^
1msr  basepri, r0
) x( H' r# s5 E* q' h
  就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。

2 J* V9 u: t( i+ I% b; T2 a5 |( U1xSchedulerRunning = pdTRUE;
3 R% O3 B) O( h! u' i9 z! p
任务调度开始运行

' e% b( p  W5 I1/* If configGENERATE_RUN_TIME_STATS isdefined then the following  Q0 ?* A+ J# A
2macro must be defined to configure thetimer/counter used to generate- ?  b6 @$ o: \9 p6 t0 Y
3the run time counter time base. */

/ }( `# M" B! z4 H/ q4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();7 |5 f3 H5 O: Y) d8 N/ E0 _0 f
$ E. D( N; X) S! A0 O
如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。
% Q& k! \2 C/ j# n- [6 l) X
来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。
1if( xPortStartScheduler() != pdFALSE )
4 t4 w5 Y; }2 s4 b9 M# @2                   {5 A) l1 [$ u* X& [  U, m
3                            /*Should not reach here as if the scheduler is running the
0 ]0 R: `# `. C4                            functionwill not return. */( G9 E: w4 R' X, V
5                   }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。
! K- ^+ W6 t2 ^/ G9 {' g' \
开始任务就按照套路模板添加自己的代码就好啦,很简单的。
先创建任务:
1 xTaskCreate((TaskFunction_t )led0_task,    & h. L1 G: x: \# C3 s
2                (const char*    )"led0_task",  
$ I) ^* [1 p- E; E5 D4 c 3                (uint16_t       )LED0_STK_SIZE,$ f4 g/ b) M) B5 s4 V. }+ z0 a/ f
4                (void*          )NULL,                                    / N& _$ r+ W! F& Z* z
5                (UBaseType_t    )LED0_TASK_PRIO,   
" ]2 Q) K( i3 l( R) F6 r0 P- x 6                (TaskHandle_t*  )&LED0Task_Handler);  & {, `* [% B5 l+ J6 m
7   //创建LED1任务9 y; @0 _- J& p+ ^' u4 @  F
8   xTaskCreate((TaskFunction_t )led1_task,   
+ w; @3 Z# I2 t! C 9                (const char*    )"led1_task",  
" R: C+ w+ v0 w10                (uint16_t       )LED1_STK_SIZE,
& y" }2 V0 o6 v. l" T( y  X11                (void*          )NULL,
( e  n6 e" c! @# ~3 [12                (UBaseType_t    )LED1_TASK_PRIO,0 \$ }9 z. _* _$ Q6 ?+ {
13                (TaskHandle_t*  )&LED1Task_Handler);      9 {, p8 l) v$ s$ _8 c9 A0 S

7 h3 q) h, o) \+ B3 h: [3 w3 m
创建完任务就开启任务调度:
1vTaskStartScheduler();          //开启任务调度
5 O/ @( W2 A, a# N- a, `2 \/ {
然后具体实现任务函数:
' S. E4 _) K* S# b* Y3 p
1//LED0任务函数4 L7 \, A( ?3 C6 F
2void led0_task(void *pvParameters)
$ y4 Y; W3 D+ A; D" { 3
{
0 |4 h+ E, y7 M% C 4   while(1)
; b& B- z0 @* C 5    {
' R1 T% j& o/ C3 H8 X/ h 6       LED0=~LED0;
. |2 x# m2 X. b$ n/ S$ W% X 7       vTaskDelay(500);8 F; o, Q$ a$ g5 i! U% n* x4 y3 o% P
8    }6 Q( W0 i* ]# @+ c3 C+ u# v6 J
9}  9 J1 m8 w  R6 }" L- `
10//LED1任务函数" U1 y; r. M) N- Z; c$ I, ^+ P: s
11void led1_task(void *pvParameters)
2 z$ t. u# u1 a12
{
# G  @* k, R$ z0 |0 e( q3 `13   while(1), J% G' G( w5 t0 B5 A& f) r4 W' t
14    {* B; b8 ~+ G, w4 w# A
15       LED1=0;0 G; Y( h5 G: Q1 ]' V# d/ Z
16       vTaskDelay(200);( O* [' j+ f; z/ ?; h& [
17       LED1=1;$ ^. Y6 Y  ~9 ?- w" |: b4 m
18       vTaskDelay(800);! Z  C( t# k1 X: V
19    }! I4 a/ p9 ]0 c/ k
20}, N& G8 @3 M8 }6 J: m. U

1 Y" g0 h, F) O- {' E) ^
好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~
: Y) ^1 L, ^* W% g# S  ?9 K- ?
) K! E# z/ ~9 G5 ?
欢迎大家一起来讨论操作系统的知识
我们的群号是:783234154
! C# v, }- c  t+ |6 c) ?" F- e

/ |0 q% x( s1 ^0 T; m* q' U6 e
/ z( w1 Z( y" `) k7 ]9 O# U! m/ ?4 b  d7 c7 c5 K% h
创客飞梦空间是开源公众号
欢迎大家分享出去
也欢迎大家投稿

" t; H# ?' k+ x
- p* s/ a3 c, Y0 Y2 F' v* \6 g4 [; C$ |8 R- R0 i+ c1 C
赞 收藏 评论16 发布时间:2018-5-26 21:52

举报

16个回答
lilei900512 回答时间:2018-6-1 07:14:03
xiaojie0513 发表于 2018-5-31 18:05
2 }5 i: v# A+ o3 J, y3 v怎么样啊,杰杰水平有限,还要多多指教吖
4 `/ X# ~  _, _0 }9 T" C7 h
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
moviexm 回答时间:2018-6-14 08:56:04
          1 q* E( d4 o% i# l
参与/回复主题% ~+ k( ?& g* |8 M! x' L% {2 {
关闭4 Q# U6 N6 a% J6 }0 P& v& d5 w
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
xiaojie0513 回答时间:2018-5-31 18:05:34
lilei900512 发表于 2018-5-31 12:42  i0 L( w5 L* F+ Y
看看怎么样!
- Z, h" N8 Z4 I1 z- ]2 g% c1 Y
怎么样啊,杰杰水平有限,还要多多指教吖
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:33# k8 \/ r8 c; L! x5 ?
感谢楼主分享

: A. [4 s/ s+ s) y9 T1 _
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( ^. r- }) y& ^% W. k0 K
写的好
3 j5 o. C+ A9 s. W
谢谢支持
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:202 ~( F3 \, V6 {9 C' L$ }
支持楼主,好东西

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