本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑 % \; |/ Q7 [' r3 \5 F 创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。 听首歌缓解一下心情。 开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,; ~, x1 i1 r8 m) G$ w. H0 ]. j2 const char * const pcName,# Q. o, L2 B ^" X' v 3 uint16_t usStackDepth, 4 void *pvParameters,- }% L" C. v% v+ S- ]; N, w# y" v 5 UBaseType_t uxPriority, 6 TaskHandle_t *pvCreatedTask1 j2 Q0 M) J' M 7 ); 8创建任务中的堆栈大小问题,在task.h中有这样子的描述:* T: D) L4 K1 s( ^' ~* k _1 L7 \' m 9/**" r% J" v5 E. p0 B' O+ Z3 M& ?* ~, F 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 , f$ Z5 b$ B7 J1 O6 Y! T 11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage. 12*/ 代码可左右滑动 1 T8 L) _: P6 V; Z 当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。 这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。 文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。 当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。 好啦,补充完毕。下面正式开始我们今天的主题。 , C* J, T( [4 k p2 n! j$ B 我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。 ?9 {# C7 i6 w; U3 r 按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。 1int main(void)" h7 w# f4 F! J7 I8 w2{) g. D8 U2 A2 A- }- E3 @( f1 s/ { 3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 4 Delay_Init(); //延时函数初始化 5 Uart_Init(115200); //初始化串口 6 LED_Init(); //初始化LED9 U4 {# _# h, g) ?0 p' A# d 7 KEY_Init(); 8 //创建开始任务 9 xTaskCreate((TaskFunction_t )start_task, //任务函数 10 (const char* )"start_task", //任务名称 11 (uint16_t )START_STK_SIZE, //任务堆栈大小 12 (void* )NULL, //传递给任务函数的参数( \* L% a- K9 b. t 13 (UBaseType_t )START_TASK_PRIO, //任务优先级 14 (TaskHandle_t* )&StartTask_Handler); //任务句柄 15 vTaskStartScheduler(); //开启任务调度" _. B s% F" r o& { 16} 来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。 1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */ s. ~7 J) g- E8 q! q B 3 if( pxStack != NULL ) 4 { 5 /* Allocate space for the TCB. */1 x8 _1 P# P: ^ 6 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );5 I ~7 b4 |) Q# B9 ~ 7 /*lint !e961 MISRA exception as the casts are only redundant for some paths. */5 L' a/ s& y0 I1 h r; c. t 8 if( pxNewTCB != NULL ) 9 {) n5 s. R2 b+ w4 D M6 | 10 /* Store the stack location in the TCB. *// V4 S) [. s2 n 11 pxNewTCB->pxStack = pxStack; 12 }% |7 c5 l2 c" m 13 else 14 {# G' L+ Z6 P7 g4 b 15 /* The stack cannot be used as the TCB was not created. Free: q! i+ N' I! g n; y 16 it again. */: B# {( H9 ]* q' c2 O3 d 17 vPortFree( pxStack );) F- K- p& H8 P/ z2 l7 X x 18 } 19 }# j7 z2 o$ t$ [2 k5 V6 t# I3 M 20 else 21 {! _' x+ k+ Z& y, a) t3 A* z) Z# U 22 pxNewTCB = NULL; 23 }: r5 Z. F! ~. ~( S 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, 2 pcName, : G& e; O! i; W 3 ( uint32_t ) usStackDepth, 4 pvParameters, 5 uxPriority, 6 pxCreatedTask,4 Y8 r* L7 m' s) j; W 7 pxNewTCB, 8 NULL );, E# B% O6 L E( q* d' @ 9 prvAddNewTaskToReadyList( pxNewTCB );9 L$ |* L4 f7 {9 ^+ o 10 xReturn = pdPASS;# Q3 n6 J$ H* c4 v 11 }( O& s, d7 x: }: o. ]: k4 ]1 Q5 ? 12 else 13 { 14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; 15 } 16 return xReturn;$ b7 ?: v9 C- t+ i5 ~ 17 } 18// 相关宏定义4 |, X8 k0 v+ T 19#define pdPASS ( pdTRUE )6 s* A2 D. \$ M0 u4 ~1 x$ x 20#define pdTRUE ( ( BaseType_t ) 1 )5 {; `4 [' h: v# I6 o* s% ] 21/* FreeRTOS error definitions. */, G& c$ s; @( i1 \) `7 ^+ V. v 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创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。 ' e) ]* B1 g5 D5 y: B8 Y创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度 s" r2 N# ~/ }# ?* X& q在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask, S; J& h- Q$ G6 a# y2 "IDLE", configMINIMAL_STACK_SIZE," K: i0 z+ r$ v4 h" l2 J2 g 3 ( void * ) NULL,* G9 V4 z/ |5 e. D# t% Z 4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),4 K' {- y5 I2 W) v n4 i+ F, D 5 &xIdleTaskHandle ); 6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ 7 }& ]+ }" F; q$ {6 a' v* k+ R9 j4 P 8相关宏定义:5 {3 u: {" u" \; x8 V* T' }/ c 9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U ) 10#ifndef portPRIVILEGE_BIT( W+ ?) a* B! ?8 e5 l( I! _ 11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 ) 12#endif 13#define configUSE_TIMERS 1 14 //为1时启用软件定时器 w( ^2 n/ k1 c3 ] 从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )2 BaseType_t xTimerCreateTimerTask( void )/ t; r. O+ M4 f7 ?5 H, y+ } 3% A! S* a/ G, r& x" a l 然后还要把中断关一下 1portDISABLE_INTERRUPTS();至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur$ B9 M" F% R) n- C- I8 b2before or during the call toxPortStartScheduler(). The stacks of& T) _+ X' R, ~4 v# A 3the created tasks contain a status wordwith interrupts switched on7 S% a7 c. J& F% d6 \- V 4so interrupts will automatically getre-enabled when the first task3 k" A: G: x! r7 [ 5starts to run. */" O* _# [6 t5 x5 _1 Z7 h" a 6/ *中断在这里被关闭,以确保不会发生滴答 7在调用xPortStartScheduler()之前或期间。堆栈5 ~+ s/ {- g: m9 |6 D& u 8创建的任务包含一个打开中断的状态字 9因此中断将在第一个任务时自动重新启用$ K# }* r! _2 W1 I* X) Q4 [ 10开始运行。*/ i. ^) \1 j4 Q" \% W! B 9 _5 q0 }$ R0 e( C; N5 } ( Y" R/ E* n6 g, v3 K; J( Z 那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )2{( ?9 F" `) v" _0 c! @# y 3 PRESERVE8 4 ldr r3, =pxCurrentTCB /* Restore the context. */$ h! Y; |1 b8 c% Z+ I 5 ldrr1, [r3] /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */4 j; K) t) z+ u, _) W 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. */' B' F0 v7 R2 P% E2 e& b 8 msrpsp, r0 /*Restore the task stack pointer. */, s( ^5 E* v# t; j# R( x" R 9 isb 10 movr0, #0 11 msr basepri, r08 K# M, ^6 @' n1 U! h5 ^$ B! K' w 12 orrr14, #0xd9 Y/ _% C3 ^$ p 13 bxr14' }: [4 t8 _* v8 X7 b- v: U8 z 14}0 F) t1 s b5 r( |/ T 1msr basepri, r0 就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。 1xSchedulerRunning = pdTRUE;1 r/ v; m" h8 U5 i/ L/ C9 T 任务调度开始运行 # ?3 F' r5 K3 Z7 e" b8 X. R3 U" J1/* If configGENERATE_RUN_TIME_STATS isdefined then the following% @# X. G- G+ D6 g 2macro must be defined to configure thetimer/counter used to generate 3the run time counter time base. */ 4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();& e7 e, C2 ~; i2 w& j 如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。 来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。 1if( xPortStartScheduler() != pdFALSE )5 w0 f# t! F- q4 V2 {5 A2 R; E4 N% b, L 3 /*Should not reach here as if the scheduler is running the 4 functionwill not return. */1 S' E& n" a) ~' Y) R4 i 5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。 开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task, 2 (const char* )"led0_task", ) P3 ^+ n- E; S6 y 3 (uint16_t )LED0_STK_SIZE,1 N4 e* K& [5 H5 k8 P 4 (void* )NULL, 8 w. {7 Q. _ H, h 5 (UBaseType_t )LED0_TASK_PRIO, 6 (TaskHandle_t* )&LED0Task_Handler); O' V# T% Z. p$ p/ } 7 //创建LED1任务 8 xTaskCreate((TaskFunction_t )led1_task, # R5 O: E# v* e0 w, w; t5 M L 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE,* P8 P" X/ D$ q+ e# m& p 11 (void* )NULL, 12 (UBaseType_t )LED1_TASK_PRIO,: I$ j3 D; _7 d6 O$ R. u% S 13 (TaskHandle_t* )&LED1Task_Handler); 创建完任务就开启任务调度: 1vTaskStartScheduler(); //开启任务调度; j V; @$ [! j* E0 Y 然后具体实现任务函数: 1//LED0任务函数 2void led0_task(void *pvParameters)4 d$ i) q6 O( f8 x- i! } 3{: v5 F; q" p( t, w7 [9 a k# h 4 while(1) 5 { 6 LED0=~LED0;' q1 F8 ]6 S3 r/ A 7 vTaskDelay(500);8 k' ~8 R+ D' K9 M+ ~ 8 } 9} , F( p; D: j% ~7 }: a 10//LED1任务函数 11void led1_task(void *pvParameters)+ \6 ^, F O+ \- T( Y4 e3 s 12{, u, u7 L2 \0 x4 D 13 while(1) 14 {4 O' t4 S, u) g$ W# d 15 LED1=0; 16 vTaskDelay(200); 17 LED1=1;8 D5 b) I/ M7 |" K 18 vTaskDelay(800);2 c( I8 Y& Q8 x" `! {' A 19 }9 x1 v) u' ~7 q9 I! F 20} 6 ^6 z# h3 i, w4 \ 好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~ ; }0 i6 U) |5 W欢迎大家一起来讨论操作系统的知识 我们的群号是:783234154 . ]% x7 }& ^ n+ Q; b3 i 【连载】从单片机到操作系统③——走进FreeRTOS" D! N) d% Y2 o + d* w: \$ ]8 H创客飞梦空间是开源公众号 欢迎大家分享出去 也欢迎大家投稿 / E+ a- Y# W8 \; W, T0 V |
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
参与/回复主题
关闭
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
怎么样啊,杰杰水平有限,还要多多指教吖
评分
查看全部评分
谢谢支持
谢谢支持- Q- u$ K, A3 O+ B3 ~& m6 H/ t# R
谢谢支持