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

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

[复制链接]
xiaojie0513 发布时间:2018-5-26 21:52
本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑
( k) A2 D: ^' D) b5 u$ Z' C/ Q1 v' n/ r7 \+ t& b- N7 m
创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。
听首歌缓解一下心情。

* B. i- Q5 _2 c* x  j
开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。
1BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,9 J: l5 g$ ]0 t- e" Z1 V# `
2                              const char * const pcName,
- x# U/ p" `% I/ S) a, i% ~/ d# H 3                              uint16_t usStackDepth,' p, M1 v; o/ r
4                              void *pvParameters,4 @& V9 x2 k, y" g7 c5 d9 n
5                              UBaseType_t uxPriority,5 w2 f' s% D- `6 p- {3 _) ^
6                              TaskHandle_t *pvCreatedTask$ [4 Y% h! W1 ^8 _) I. J
7                          )
;. i' B! k2 p8 L, ^( R6 F$ j
8创建任务中的堆栈大小问题,在task.h中有这样子的描述:
" ~* h- e. I; v4 h( y 9/**
; t3 t5 i- m% b! M" k10* @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  0 n! N# c) s5 c: Q) v
11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.) G- ]& [1 T& y+ y3 j, H3 T, l- \
12*/
代码可左右滑动

1 z6 P3 u* D, U+ }- C
  当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。
这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)
文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。
当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。
: C$ x- t7 g; Y4 [* g9 B, t0 m( C" X
  好啦,补充完毕。下面正式开始我们今天的主题。


7 y3 K5 Y8 U8 `( r$ I- S
  我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。
  其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。

  J0 `$ c# h- l' I  i( s9 W. T
按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。
1int main(void)
4 F7 d" e. P4 `# ^% b/ P 2
{' G# p2 M% W+ X; ?  j
3    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4    # i  u' X4 X6 F
4    Delay_Init();                       //延时函数初始化     - @! v3 P5 q# ]
5    Uart_Init(115200);                  //初始化串口
  W6 |# R' @) f7 j6 ? 6    LED_Init();                     //初始化LED
, y6 u+ z2 O; Z( h 7    KEY_Init(); 7 l) \* ^! I( }# z" s
8    //创建开始任务
7 R) z! I& ?$ \! o( o 9    xTaskCreate((TaskFunction_t )start_task,            //任务函数
7 \+ Y1 y; O9 @, y10                (const char*    )"start_task",          //任务名称
4 p8 s8 d& r: m# W5 c  T" l11                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
! ?# {6 H' e* @0 f" r12                (void*          )NULL,                  //传递给任务函数的参数
7 R* C* w- ]0 u" p; D13                (UBaseType_t    )START_TASK_PRIO,       //任务优先级0 O8 M7 ~8 z3 t) r  a1 z; `. l
14                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
& s6 R- G4 f: s. g15    vTaskStartScheduler();          //开启任务调度
4 C% h" g8 z# w. ?' q+ v16}
! B% k% {4 ^  l9 J- z; z

, k( ~- E; \% M. K
  来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。
注意:下面说的创建任务均为xTaskCreate(动态创建而非静态创建。
1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); * F( U" ^  R% @9 k) F: T% v& ]
2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */9 J% j2 ~1 Y' P2 w$ a
3            if( pxStack != NULL )8 E, q: u1 T& X# P6 h6 n3 x9 t& H
4            {( N0 \# w9 c+ V, J) X
5                /* Allocate space for the TCB. */
9 c7 w! `- d8 s7 {* s 6                pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );, O6 ]) u. R) b
7                /*lint !e961 MISRA exception as the casts are only redundant for some paths. */& ]' f0 b1 V- w( X- j
8                if( pxNewTCB != NULL )+ e/ {$ w" N1 F% I# [( X0 c7 X5 D
9                {
! y( \7 y4 l. H6 g, `10                    /* Store the stack location in the TCB. */
! o8 W! [+ r8 ^' @  o  _11                    pxNewTCB->pxStack = pxStack;
* g% o! A$ C7 M3 H3 e& R( |12                }
) Z, F, b5 J+ T0 l0 o# Y, i13                else  j% z6 ^0 D5 ]; e' F! `
14                {
( J+ O! D7 O6 \9 D15                    /* The stack cannot be used as the TCB was not created.  Free
5 Y" @7 j6 X2 F1 V+ h16                    it again. */

. B7 b. a1 o" I17                    vPortFree( pxStack );8 q0 ?9 F# s9 t# X! h! t
18                }4 K; b" F" c9 Q$ v! K/ x$ l
19            }
5 H" |; y( F9 @5 l$ [/ Q4 ^20            else2 x$ r+ Y- l1 b- L: c, J/ F/ J6 {
21            {) O3 j" u- w0 v
22                pxNewTCB = NULL;
, h$ A) B: T3 p( g3 s' _23            }
' A. M5 y- _8 d8 w: N8 p24        }
  首先是利用pvPortMalloc给任务的堆栈分配空间,if( pxStack != NULL )如果内存申请成功就接着给任务控制块申请内存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同样是使用pvPortMalloc();如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈的内存vPortFree( pxStack );
  然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中prvAddNewTaskToReadyList( pxNewTCB );
  最后返回任务的状态,如果是成功了就是pdPASS,假如失败了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
; n& \# l7 D2 a) \
1prvInitialiseNewTask(     pxTaskCode, $ x- K0 O" M4 ^: Q6 m/ B' H, U/ g& q
2                          pcName,
# {. ?; a$ H; G/ r2 b 3                         ( uint32_t ) usStackDepth,
$ k  }/ m" U* ^' H0 i4 P9 q 4                          pvParameters,- G5 o0 M# x% J
5                          uxPriority, " Q* @& P* W6 V
6                         pxCreatedTask,5 e5 n' d+ d4 z" N( U
7                          pxNewTCB, 9 z! a( a. _; x5 e& S
8                         NULL );
. ^% D6 B7 h  Z+ h 9            prvAddNewTaskToReadyList( pxNewTCB );
; g# g  x% @: d0 J/ C% P7 I* M10            xReturn = pdPASS;
& c8 f6 Y! H. V3 G3 `1 T! N11        }' e- p! m' }: V. Q9 S
12        else, R2 j9 K' x  }+ d
13        {
8 [- }' I6 i, Q5 \; g! s% r: P14            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;# f3 O8 k+ e3 V4 d  {% ^; e6 L, ^
15        }- i+ b4 P# Q  n& Z! \& u
16        return xReturn;
8 g$ T+ j  k, y2 F  N0 R17    }( n% c$ U' v4 E/ [
18// 相关宏定义
9 L9 g# Q; T- Y  D. G; z6 `19#define pdPASS            ( pdTRUE )
4 a% F( _) b6 D" G20#define pdTRUE            ( ( BaseType_t ) 1 )
; M! b7 {3 S8 s* G1 S) _* T21/* FreeRTOS error definitions. */
; D% h% `" w" u8 ]0 K+ G& j22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY    ( -1 )
4 }) v* q# |, E5 Y7 D) V8 v8 M  M' Y3 \/ d5 i3 a
  具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。

2 e# C* n1 Z2 J  v4 @& D5 q
  因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。
) n2 l! o, m( [
创建完任务就开启任务调度了:
1vTaskStartScheduler();          //开启任务调度
0 y! ^: B2 @5 L8 U! n$ D: f
8 b) M3 h) L0 R" f0 A2 {2 f; o
在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的)
1xReturn = xTaskCreate(    prvIdleTask,! r8 l3 M" ?# M" U
2                          "IDLE", configMINIMAL_STACK_SIZE,2 j3 o, |* N0 X( r1 v. E7 s
3                          ( void * ) NULL,
+ }  X/ B. @9 S( D+ V+ T* @8 L 4                          ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),; l7 x0 Q3 _/ m7 |8 u
5                          &xIdleTaskHandle );   z1 r( V2 z( N8 A0 @
6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
. k9 ]" ^' X0 Y- K1 t- Q. F 7    }3 ?: n$ p% o! r* U1 h5 a  @1 x
8相关宏定义:
6 [5 v+ T2 d( F. N8 I& r9 W4 }8 ^ 9#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )" Y! P  c) J8 [; G) P* c
10#ifndef portPRIVILEGE_BIT
! {* V+ o3 r/ F. v( ^; `: P11    #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
: a0 w# N! R0 v6 J6 U12#endif
: O" [8 y( L* c7 r8 P13#define configUSE_TIMERS                        1                              # ?+ o: R! |* }$ I! j$ T* a9 w
14 //为1时启用软件定时器
* ~" ?: H8 Y5 _$ s, h  M& Y+ R
从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。
如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是:
1#if ( configUSE_TIMERS == 1 )3 r: r* f9 v8 Z2 X% D% X' V
2    BaseType_t xTimerCreateTimerTask( void )
3 c# e. |2 J7 j1 k/ L+ `+ [- [! }3

7 I. W5 B3 R  g' T, M* c' S& u& c
然后还要把中断关一下
1portDISABLE_INTERRUPTS();
: g' T  _" W! V6 ^
至于为什么关中断,也有说明:
1/* Interrupts are turned off here, toensure a tick does not occur$ d  d5 @) v( y6 j' @5 c, V
2before or during the call toxPortStartScheduler().  The stacks of
4 g9 `  l( r! R. H4 X 3the created tasks contain a status wordwith interrupts switched on
. M" T3 D9 F( |( v% g5 q. t 4so interrupts will automatically getre-enabled when the first task/ C* T2 Z( U" B7 J3 Q% g4 v) S& @
5starts to run. */

- G6 O" s0 w7 D, w6 g- V 6/ *中断在这里被关闭,以确保不会发生滴答/ `. w% c6 A6 u4 z
7在调用xPortStartScheduler()之前或期间。堆栈( \; M1 x; [/ U6 g
8创建的任务包含一个打开中断的状态字
) r2 o- z9 U/ ?  S% s 9因此中断将在第一个任务时自动重新启用
$ S; ?& H0 L& j: b9 h! U10开始运行。*/+ [8 v  y5 G3 {& _

" W& c$ |6 d( ~
& H9 H5 V$ U8 A
那么如何打开中断呢????这是个很重要的问题
别担心,我们在SVC中断服务函数里面就会打开中断的
看代码:
1__asm void vPortSVCHandler( void )0 S2 Q/ K- S" g* y5 v  F' B  p; T
2
{6 L' Y+ x8 L' H1 H' I3 {: n
3         PRESERVE86 P( H* A' R* k' p% C0 c
4         ldr    r3, =pxCurrentTCB  /* Restore the context. */
3 w& z. `, R7 A. |& M  N 5         ldrr1, [r3]                            /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
) \) _& k) u/ \* ^ 6         ldrr0, [r1]                            /* Thefirst item in pxCurrentTCB is the task top of stack. */
* q$ c) I( |/ m$ Z* f8 H5 A% r& Y% A 7         ldmiar0!, {r4-r11}             /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
4 C# L& o, z4 A5 i$ ^& \ 8         msrpsp, r0                                   /*Restore the task stack pointer. */5 j: a4 {" r9 o# e) V
9         isb  e! g, ~' P/ T! u3 m0 x( \5 Q! I
10         movr0, #0
& X% E# P. Y2 n0 b/ L# J11         msr  basepri, r0
% H: V2 Y. s- F  p3 z( ?12         orrr14, #0xd
. V. j' q% ]5 _" ?* L13         bxr143 m' K8 x; c; R, ^6 h9 b. O
14}
, e: U9 C" h* r( s
7 B/ g4 }" i5 M; I/ A9 u$ ]& b1msr  basepri, r08 {5 t3 Q5 q" g9 D. U/ b
  就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。
1 A4 K* F, z: Y' [& c, ]
1xSchedulerRunning = pdTRUE;
, {- r* A( u9 j8 {1 p
任务调度开始运行
9 }: w7 Y: I# {$ {5 g' _: f: x
1/* If configGENERATE_RUN_TIME_STATS isdefined then the following7 ~# h, |* P$ q# t) ^7 c* i
2macro must be defined to configure thetimer/counter used to generate; `3 P. E7 A! D1 B* O
3the run time counter time base. */
; W4 M# B% O: q# y: |/ D% `- S& I5 x
4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();4 V2 v5 l# _6 `0 b1 ]
' [& H5 l9 O2 U7 W7 y
如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。
5 _% t. M7 J; C5 {
来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。
1if( xPortStartScheduler() != pdFALSE )
" i0 R2 K# C5 W1 @/ O2                   {
' l  o/ y! H. ~7 ]3                            /*Should not reach here as if the scheduler is running the/ B, w( h7 G3 n) O+ a
4                            functionwill not return. */
/ `# H3 X$ Y2 l( X5                   }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。

' W% M1 Y. c+ ?# M4 f& W% [
开始任务就按照套路模板添加自己的代码就好啦,很简单的。
创建任务
1 xTaskCreate((TaskFunction_t )led0_task,    , e: a. L9 q/ ]8 k
2                (const char*    )"led0_task",  
) f  e. N: Q: D! P0 M 3                (uint16_t       )LED0_STK_SIZE,# W' }/ ^- n6 O) F
4                (void*          )NULL,                                    
% C% O" s4 N) t% R  B' p 5                (UBaseType_t    )LED0_TASK_PRIO,   
1 N* W# R' W) r3 T! [; Q 6                (TaskHandle_t*  )&LED0Task_Handler);  
7 Y( y0 x7 y; g' a% W' | 7   //创建LED1任务
4 V- j/ Q) G, I  e3 u* ~ 8   xTaskCreate((TaskFunction_t )led1_task,    , Q8 q  |, a: b  p
9                (const char*    )"led1_task",  
/ G& p* {3 n4 T1 F6 L& E. n% b0 S10                (uint16_t       )LED1_STK_SIZE,( k3 }2 u% ~* D; v4 }& S0 M
11                (void*          )NULL,
: i) D7 m) z5 T12                (UBaseType_t    )LED1_TASK_PRIO,) M4 I6 b  R3 \: K$ F2 T
13                (TaskHandle_t*  )&LED1Task_Handler);      
" E, C! b. ]" m6 v: ]9 H+ o0 h1 D: b  X8 v
创建完任务就开启任务调度
1vTaskStartScheduler();          //开启任务调度

5 y% T+ \( G+ |  ~! e5 G9 L+ T0 ]
然后具体实现任务函数

. g& N4 \: v9 r4 |3 `. \ 1//LED0任务函数
" Q1 A% q$ t. n8 L+ ?- P 2void led0_task(void *pvParameters)
7 X' a6 v6 W* Z* |) ] 3
{" W" X7 F% A( u  v
4   while(1)/ S( @( S  y3 [5 U% f" N3 x( S
5    {: R  N0 g/ b. |9 x
6       LED0=~LED0;
" N. T& }: u9 D2 }6 k6 B' y9 u 7       vTaskDelay(500);# J. l% b! v" _  j% y, g
8    }) C2 J" e* R: a6 A" y, ]; R
9}  4 x* r9 ?' I: I
10//LED1任务函数
3 _& ?4 i7 s0 |. T1 C11void led1_task(void *pvParameters)
: j5 a5 f9 y* I+ j% z12
{
" c( q1 I# ]) l3 J" \" y3 d9 F13   while(1)! t. Q5 R! |$ G- w
14    {5 e7 T, [2 h  a2 X
15       LED1=0;7 @: I( W& ~0 b
16       vTaskDelay(200);
) r+ b, r) Z8 n3 B9 c1 p17       LED1=1;, I' F9 Y9 H  K0 V
18       vTaskDelay(800);
* b" |4 N. W& }; t; q" w19    }
3 m6 H% f- f3 }- n' ]3 |, o: i2 m20}1 p) q4 Y. f- n

2 \/ P- h! c1 W) ~/ S" P$ Q
好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~
, z* _* k$ G) m) ~! h2 `4 @# B
+ e6 Y8 {+ B  A; K6 h) i
欢迎大家一起来讨论操作系统的知识
我们的群号是:783234154
$ s- X0 h0 }6 W( u
* M( j/ Y# q8 n- `! \5 M

  n6 w; ?, k% D
【连载】从单片机到操作系统①' D; R7 ?% w  @. S7 C* n+ t

1 ?1 Z5 N$ b9 F# R
创客飞梦空间是开源公众号
欢迎大家分享出去
也欢迎大家投稿
: x) u9 ?) w- t% t6 w+ q- y
/ c2 i4 U1 h- t0 O' \5 ]2 w

9 o3 D' i& j/ F1 m% ?
收藏 评论16 发布时间:2018-5-26 21:52

举报

16个回答
lilei900512 回答时间:2018-6-1 07:14:03
xiaojie0513 发表于 2018-5-31 18:05
- R; A6 |0 q7 @; J6 z怎么样啊,杰杰水平有限,还要多多指教吖

$ X6 p1 Z6 j# [' H写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
moviexm 回答时间:2018-6-14 08:56:04
         
& Q' _/ |9 E: Q5 s参与/回复主题
, ~5 _- c, _: I- G关闭
: E5 w- O5 V3 X2 Y3 L* JRE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
xiaojie0513 回答时间:2018-5-31 18:05:34
lilei900512 发表于 2018-5-31 12:42
! }! J; W& P7 u7 V/ _" q看看怎么样!
$ U: @$ c) B0 }% f- k7 X& z1 S6 @9 a
怎么样啊,杰杰水平有限,还要多多指教吖
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# J  ^6 T5 m5 A3 {
感谢楼主分享

* Z0 n; v; w- q5 ?$ B
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
& N0 N9 ^* ]0 _5 f写的好

4 c5 H" U! J2 G1 V$ V" y0 y7 _谢谢支持
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:201 t' p  X. L/ ?9 W7 J2 q  Z( X& g
支持楼主,好东西
+ D. I! R8 S/ }
谢谢支持
9 H5 |3 k% u: }- ^谢谢支持
12下一页

所属标签

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