本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑 % a6 l3 M2 S4 l% W5 H L3 @ " q$ }+ t# N2 X- A 创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。 听首歌缓解一下心情。 开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,; a; n# |( ~+ ^1 G N' q3 T2 const char * const pcName, 3 uint16_t usStackDepth,1 J/ x0 j( D( h5 D. b" u: [; W0 b 4 void *pvParameters,+ Q* w% t; [6 m [- Y5 L 5 UBaseType_t uxPriority, 6 TaskHandle_t *pvCreatedTask9 G/ h7 q, p8 x+ |. _# ` 7 ); 8创建任务中的堆栈大小问题,在task.h中有这样子的描述:% a( B+ p8 B6 k* \, j% u 9/**" u6 q' d- h5 @; A Z; e7 S( I 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 8 q; N* x& Z9 r) F5 [ 11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.0 l$ h# t9 K6 K* e. ~ 12*/ 代码可左右滑动 ! y* w/ Y# Z7 J+ y3 U 当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。 这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。 文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。 当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。 / ], N$ r' \) u 好啦,补充完毕。下面正式开始我们今天的主题。 我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。 按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。 1int main(void)4 M& K v& |( O: B# s, n' o2{! u) J7 T, Q6 N, Z! r 3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 7 r: H7 g3 n3 g0 [8 s: Q 4 Delay_Init(); //延时函数初始化 5 Uart_Init(115200); //初始化串口 6 LED_Init(); //初始化LED 7 KEY_Init(); ! f5 r5 f S, L z 8 //创建开始任务 9 xTaskCreate((TaskFunction_t )start_task, //任务函数: ], k H u6 K" ~3 p 10 (const char* )"start_task", //任务名称 11 (uint16_t )START_STK_SIZE, //任务堆栈大小* E7 G: |' {% L5 T( F+ Z) v 12 (void* )NULL, //传递给任务函数的参数% M; X& L0 a! p/ ] 13 (UBaseType_t )START_TASK_PRIO, //任务优先级( U, z& o/ n9 `2 Q" v5 i: @8 a 14 (TaskHandle_t* )&StartTask_Handler); //任务句柄 15 vTaskStartScheduler(); //开启任务调度0 X' c! K2 ?7 U! H! n 16} 来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。 1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 8 j% m3 J7 R! L2 m2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */) D& {9 J0 g k- v% _( `& Q 3 if( pxStack != NULL ); t7 i/ V$ z* I# ^) o$ o 4 {5 N' S* v! D$ @+ f- F, A! x 5 /* Allocate space for the TCB. */ 6 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 7 /*lint !e961 MISRA exception as the casts are only redundant for some paths. */ 8 if( pxNewTCB != NULL ) 9 {! D; n8 m) X: R6 c 10 /* Store the stack location in the TCB. */ 11 pxNewTCB->pxStack = pxStack;! H: V, A# @. j9 l5 F$ n* U) \ 12 }$ q- O& Y% [& r+ K- X; ?: y 13 else! J- k ~5 z7 ^$ a: Z2 u/ M8 F 14 { 15 /* The stack cannot be used as the TCB was not created. Free8 |% ~$ I2 o; W! ]/ ~) G2 @3 ]: } 16 it again. */ 17 vPortFree( pxStack );* O& S' |1 k2 w+ F. e 18 }6 A0 i! P. L0 X1 S' q& C 19 } 20 else$ \: R+ C+ B' b1 V 21 { 22 pxNewTCB = NULL; 23 } 24 } 首先是利用pvPortMalloc给任务的堆栈分配空间,if( pxStack != NULL )如果内存申请成功,就接着给任务控制块申请内存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同样是使用pvPortMalloc();如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈的内存vPortFree( pxStack ); 然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中prvAddNewTaskToReadyList( pxNewTCB ); 最后返回任务的状态,如果是成功了就是pdPASS,假如失败了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; # N# S) p/ R. C! h/ ~1prvInitialiseNewTask( pxTaskCode, ) ]/ ~- M0 R4 q8 c 2 pcName, 3 ( uint32_t ) usStackDepth, 4 pvParameters,2 x$ H$ O2 z0 p+ G 5 uxPriority, 6 pxCreatedTask, 7 pxNewTCB, 8 NULL ); 9 prvAddNewTaskToReadyList( pxNewTCB );8 m, n7 a$ W5 g( t7 x' ] 10 xReturn = pdPASS; 11 }4 H3 Z! f+ I3 |$ A( g/ O 12 else( K+ w' N- [% O+ v& _" `3 f1 J 13 { 14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; 15 }4 f1 j8 a# B. m 16 return xReturn;5 Z6 e# b0 U# h5 z, Y 17 } 18// 相关宏定义 19#define pdPASS ( pdTRUE )6 y: F8 ?4 X! W 20#define pdTRUE ( ( BaseType_t ) 1 ) 21/* FreeRTOS error definitions. */& `8 l& G M6 H9 | 22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 ) " Q) j! } s. S1 |9 U4 @" T 具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。 因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。 + T: `. e: o+ T2 m8 o, N创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度% @6 |7 B& \8 j$ T 在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask, c4 B( q+ H5 ?$ p8 g5 c* q5 p! n2 "IDLE", configMINIMAL_STACK_SIZE, 3 ( void * ) NULL,1 P5 Z! m8 F& J. P3 l/ p; ?: j7 y4 \ 4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),0 T6 g" ?- |6 z0 T" d, K, [7 Z 5 &xIdleTaskHandle ); ( R3 `& F* V: A& t 6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */- F+ z1 x3 F4 u$ d& `1 H0 G 7 }% u; h6 G( j* F) b) k 8相关宏定义:: d" O# G1 b" h 9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U ) 10#ifndef portPRIVILEGE_BIT0 ~# t; X2 M0 t2 n: F 11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 ) 12#endif 13#define configUSE_TIMERS 1 % u# Y2 }8 r! G8 b& U- n 14 //为1时启用软件定时器 从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )2 BaseType_t xTimerCreateTimerTask( void ): _, Z0 D' E: j( r( y! ~ C 3/ O. t8 C+ M; l0 ?% O% M- n- | 然后还要把中断关一下 1portDISABLE_INTERRUPTS();至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur; z6 ^/ W* g+ S+ ]% ? l2before or during the call toxPortStartScheduler(). The stacks of 3the created tasks contain a status wordwith interrupts switched on 4so interrupts will automatically getre-enabled when the first task. L3 b6 h1 s" D/ b! e0 A& w# B 5starts to run. */: _; l8 `3 K4 T" G 6/ *中断在这里被关闭,以确保不会发生滴答 7在调用xPortStartScheduler()之前或期间。堆栈9 s, \( Z; ]) E& i( o 8创建的任务包含一个打开中断的状态字 9因此中断将在第一个任务时自动重新启用 10开始运行。*/ - r% h$ I6 G, e8 \# k6 E @ 那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )2{- K6 `( l; O* ]- e 3 PRESERVE8$ E W" N7 f% H# ^* l, J8 { 4 ldr r3, =pxCurrentTCB /* Restore the context. */; ~2 L8 d6 h' i3 J+ T* f 5 ldrr1, [r3] /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */ 6 ldrr0, [r1] /* Thefirst item in pxCurrentTCB is the task top of stack. */) @9 i K5 @! I$ f 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. */. t6 W* b9 V4 h 9 isb# \% x( s4 _* L( R% I 10 movr0, #05 D. Z- T( U, ]. G 11 msr basepri, r0; C( M- [9 q/ m6 r& N5 u 12 orrr14, #0xd 13 bxr14 14}' l- N# W- _4 y* ~8 C ! f* d! R% ?0 B5 w: r. }3 M: M3 T 1msr basepri, r09 m& E# F3 X! t 就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。 ) X4 x3 H" U4 [ M8 d+ k- m1xSchedulerRunning = pdTRUE; 任务调度开始运行 . ?1 C$ b4 q! p8 b1/* If configGENERATE_RUN_TIME_STATS isdefined then the following# u1 ~ x/ y7 B/ V: j* \9 z 2macro must be defined to configure thetimer/counter used to generate; y6 d- y7 R; v4 b5 X; N8 g- n* l 3the run time counter time base. */% H: Q1 ^2 Q Q( ]+ O- v 4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();+ N4 j0 Q3 I P# P9 @+ e 如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。 来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。 1if( xPortStartScheduler() != pdFALSE )2 {. N$ c( V. h! e, s# m2 b 3 /*Should not reach here as if the scheduler is running the 4 functionwill not return. */ 5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。 开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task, / P; S3 x5 q. [2 (const char* )"led0_task", 3 (uint16_t )LED0_STK_SIZE,: [7 ^' E% F2 ?# T; Z 4 (void* )NULL, 5 (UBaseType_t )LED0_TASK_PRIO, 6 (TaskHandle_t* )&LED0Task_Handler); 1 \7 {& W( J) l1 _2 V" t0 F 7 //创建LED1任务% a: [0 K+ p7 g$ U# O3 ^0 ~' n 8 xTaskCreate((TaskFunction_t )led1_task, 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE, 11 (void* )NULL,, a) X/ ?% a8 ^9 R- q. N 12 (UBaseType_t )LED1_TASK_PRIO, 13 (TaskHandle_t* )&LED1Task_Handler); 8 H4 b; q1 ?' p) X q 创建完任务就开启任务调度: 1vTaskStartScheduler(); //开启任务调度" @) W3 [( w0 D* ]( M 然后具体实现任务函数: 4 f9 y4 M1 M2 s# F" m& s1//LED0任务函数 2void led0_task(void *pvParameters)( V: n ^& Q$ H8 ^: H 3{8 f7 \, j. q6 L7 E5 |. O1 ` 4 while(1)$ _5 h& w' z$ p 5 {7 e* M2 z7 i4 } a9 d! U 6 LED0=~LED0;' l3 `+ }5 O, F 7 vTaskDelay(500);; q2 V: v% c$ O p. ~ 8 } 9} 10//LED1任务函数 11void led1_task(void *pvParameters) g# x( q; ^* Y2 r# W6 k 12{3 ]. Z9 V' Y8 k 13 while(1) 14 {5 F D9 p& J3 U2 u5 n6 A4 K 15 LED1=0; 16 vTaskDelay(200);/ c* c! [2 y! ^: b 17 LED1=1; 18 vTaskDelay(800);' \9 D7 z6 o1 b 19 }8 J3 u5 v L& b0 D1 s! F 20} 好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~ % p B5 Q+ ~/ s% G" q( r3 z1 i) ?5 u8 u+ t 欢迎大家一起来讨论操作系统的知识 我们的群号是:783234154 6 h& w* q b3 `2 Y. V( ~4 D W4 [! ^# f2 b+ X0 V( Z2 n 【连载】从单片机到操作系统②$ d* `6 M$ p$ W* t8 i4 R1 L2 c+ k# l9 j 5 B0 v7 \! ?( J# I创客飞梦空间是开源公众号 欢迎大家分享出去 也欢迎大家投稿 5 H& R' F+ S. |8 x- e8 W/ X; z5 }2 C6 a! L1 X+ |2 ~" ~ |
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
参与/回复主题8 P* L2 J3 e7 y; j8 P; J4 X
关闭
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
怎么样啊,杰杰水平有限,还要多多指教吖
评分
查看全部评分
谢谢支持
谢谢支持% [- R1 ` ]9 O v) H- Z8 H
谢谢支持