|
本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑 创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。 听首歌缓解一下心情。 开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,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, 6 TaskHandle_t *pvCreatedTask 7 ); 8创建任务中的堆栈大小问题,在task.h中有这样子的描述: 9/** 10* @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. 12*/ 代码可左右滑动 当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。 这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。 文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。 当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。 6 u. {) `9 X0 F$ n( Q( k 好啦,补充完毕。下面正式开始我们今天的主题。 我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。 % 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(); 8 //创建开始任务 9 xTaskCreate((TaskFunction_t )start_task, //任务函数 10 (const char* )"start_task", //任务名称 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, //任务优先级 14 (TaskHandle_t* )&StartTask_Handler); //任务句柄 15 vTaskStartScheduler(); //开启任务调度5 ~7 y. \6 j4 h0 S+ p8 l$ i: o' B, A 16} 2 _& X1 G* A3 a5 h 来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。 1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); . e% M( x7 v0 p! D2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */ 3 if( pxStack != NULL ) 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 ) ); 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; 12 }' c8 j. \. {. W4 L$ v; } 13 else 14 {4 \# C* @, D. Q. |) v 15 /* The stack cannot be used as the TCB was not created. Free 16 it again. */; l# o# q2 Y4 s0 c; h8 l 17 vPortFree( pxStack ); 18 }6 w+ E, R; A: }8 z 19 } 20 else 21 {# 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; 1prvInitialiseNewTask( pxTaskCode, / s# E, }0 `, a% }4 f$ Q4 ^9 n 2 pcName, 3 ( uint32_t ) usStackDepth, 4 pvParameters, 5 uxPriority, 6 pxCreatedTask,( g1 y1 k7 C. p, k 7 pxNewTCB, ) i; Q$ Z/ s0 Z 8 NULL ); 9 prvAddNewTaskToReadyList( pxNewTCB );, l' ~4 l. }; R# P/ E 10 xReturn = pdPASS;! y5 D6 [. O( M: e 11 } 12 else9 s0 \; W* M2 [& N 13 {! x* k9 L# {$ Z0 J) x 14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; 15 }* P. x% ^% ] `" \9 _ 16 return xReturn;9 @6 V/ ~! M2 I2 M7 j* h 17 } 18// 相关宏定义6 K# `# J2 ?( S. E 19#define pdPASS ( pdTRUE )7 q {9 S& \, f1 K: g2 m7 z 20#define pdTRUE ( ( BaseType_t ) 1 ) 21/* FreeRTOS error definitions. */8 q! D! {6 [/ E 22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 ) 具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。 因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。 创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask,# \8 `8 J0 V! w, ?' s- m* q2 "IDLE", configMINIMAL_STACK_SIZE,2 I; q% @* P: y! s* @2 ]. c1 B2 I 3 ( void * ) NULL, 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 11 #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 14 //为1时启用软件定时器, h* ]- h. z8 b3 d" M9 U' O; x 从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )6 [$ F4 o# m) D/ u2 BaseType_t xTimerCreateTimerTask( void )& N5 F }$ K9 u* Y& X$ ?6 I9 Q$ ^ 3 然后还要把中断关一下 1portDISABLE_INTERRUPTS();: o. W9 B" k/ z5 {& s# ]" c至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur2before 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 5starts to run. */ 6/ *中断在这里被关闭,以确保不会发生滴答. y0 R0 M z% R, b 7在调用xPortStartScheduler()之前或期间。堆栈 8创建的任务包含一个打开中断的状态字 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 那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )- @5 W# `5 g! ]3 A2 u2{3 s7 I, v1 r( @" c, O$ L8 @ 3 PRESERVE8 4 ldr r3, =pxCurrentTCB /* Restore the context. */4 d! U/ K( s$ k8 @ B" T 5 ldrr1, [r3] /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */ 6 ldrr0, [r1] /* Thefirst item in pxCurrentTCB is the task top of stack. */ 7 ldmiar0!, {r4-r11} /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */ 8 msrpsp, r0 /*Restore the task stack pointer. */ 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 13 bxr14 14}9 T& G+ D# Q# ^& A: F# M/ K ! l }5 r7 e) [- ` 1msr basepri, r0 就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。 1xSchedulerRunning = pdTRUE; 任务调度开始运行 1/* If configGENERATE_RUN_TIME_STATS isdefined then the following 2macro must be defined to configure thetimer/counter used to generate 3the run time counter time base. */- x2 V' ~, K7 s' } 4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); $ 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 S2 {2 r6 r/ u' g o# e( ]6 B2 e; y @ 3 /*Should not reach here as if the scheduler is running the 4 functionwill not return. */ 5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。 开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task, 2 (const char* )"led0_task", 3 (uint16_t )LED0_STK_SIZE, 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任务 8 xTaskCreate((TaskFunction_t )led1_task, & v5 a: }; D1 [ 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE," V2 | W a! V: n, R 11 (void* )NULL, 12 (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 P1//LED0任务函数 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 { 6 LED0=~LED0; 7 vTaskDelay(500); 8 } 9} 6 [0 v3 B9 @1 K# H 10//LED1任务函数 11void led1_task(void *pvParameters)" p# }! x6 F z1 Q1 Z# U 12{% Q l- S. Y: |% @ 13 while(1) 14 {# b H7 i8 i9 a" n: u9 U 15 LED1=0; 16 vTaskDelay(200); 17 LED1=1;- C2 F" V! T p, a 18 vTaskDelay(800);9 c* K8 U3 ~' l" O/ c+ y& j 19 } 20} 9 n3 ^! f, ~5 P" X% R# B" W 好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~ 欢迎大家一起来讨论操作系统的知识 我们的群号是:783234154 % r1 S- G# r; z* E! f& ~& m/ v$ f; m' G# m# `. N 【连载】从单片机到操作系统③——走进FreeRTOS; \/ R2 Y- i7 @% a" B( G% i 创客飞梦空间是开源公众号 欢迎大家分享出去 也欢迎大家投稿 ( c0 t2 V9 ^; R6 ~0 [; M6 k% V! v4 r3 I% E& r: ~) F/ Z |
微信公众号
手机版
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
参与/回复主题: R% {, s" t2 [8 k2 W5 g- O0 A/ S$ \
关闭
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
怎么样啊,杰杰水平有限,还要多多指教吖
评分
查看全部评分
谢谢支持
谢谢支持
谢谢支持