本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑 7 X4 ?9 C' H2 c1 {; j 创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。 听首歌缓解一下心情。 开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,9 }& H' T% z$ W" |& H- d2 const char * const pcName,8 J- d- c R; ^" D. t 3 uint16_t usStackDepth, 4 void *pvParameters,5 K. ] L1 X( N; {) H5 s/ N$ v 5 UBaseType_t uxPriority,; X1 a f; C* b( F9 f* B 6 TaskHandle_t *pvCreatedTask8 }' T+ r+ z" Z7 r9 q 7 );4 l( H) M) y! }8 D 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 ; T/ ?! N/ o( y' r; Q! c 11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.* G1 o/ S+ w \ 12*/ 代码可左右滑动 当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。 这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。 文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。 当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。 好啦,补充完毕。下面正式开始我们今天的主题。 我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。 * w7 ]3 H! A: F0 K0 W8 l 按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。 1int main(void)2{ 3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 4 Delay_Init(); //延时函数初始化 5 Uart_Init(115200); //初始化串口( u4 M; T/ V9 p t6 A) j6 Z; n 6 LED_Init(); //初始化LED 7 KEY_Init(); 8 //创建开始任务6 ^- Z! l! I. N! N 9 xTaskCreate((TaskFunction_t )start_task, //任务函数 10 (const char* )"start_task", //任务名称- z2 n. F& [# ?) @3 D 11 (uint16_t )START_STK_SIZE, //任务堆栈大小 12 (void* )NULL, //传递给任务函数的参数 [$ t) H/ X# N$ q4 t 13 (UBaseType_t )START_TASK_PRIO, //任务优先级3 ~/ i; r6 `$ @+ ~+ o# c% {3 f 14 (TaskHandle_t* )&StartTask_Handler); //任务句柄 + K) {& Q* @2 o. s$ U 15 vTaskStartScheduler(); //开启任务调度 16}/ k# C. s" T+ R, r9 E Q; M" Q0 Z* q0 k: I 来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为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. */ 3 if( pxStack != NULL )6 d- S3 m- p& Z 4 { 5 /* Allocate space for the TCB. */- [2 ~, L0 o6 x% W. `2 t% n9 I. T 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 )4 h5 Q+ W7 X5 z 9 {- |6 a+ W* S3 P2 c 10 /* Store the stack location in the TCB. */ 11 pxNewTCB->pxStack = pxStack; 12 }- j5 u+ \6 ]1 q6 i3 D( [, G 13 else 14 {6 V0 n) W( G3 c* l' e; O* r3 o 15 /* The stack cannot be used as the TCB was not created. Free 16 it again. */ 17 vPortFree( pxStack ); 18 }6 a4 F- c: X: k# n 19 } 20 else 21 { 22 pxNewTCB = NULL;+ F( Q6 X3 X. I 23 } 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, / v7 r' `5 n5 B4 [$ E# z) L 3 ( uint32_t ) usStackDepth,* k9 V, Q' ^* f$ B8 u! n 4 pvParameters, 5 uxPriority, 6 pxCreatedTask, 7 pxNewTCB, 8 NULL ); 9 prvAddNewTaskToReadyList( pxNewTCB );- @, @: A9 J3 [7 O 10 xReturn = pdPASS;4 _) B. v8 B% R/ ]. b/ P" I/ e3 ]3 x 11 }6 B. Q0 I0 T5 K/ D- d# \1 P5 b 12 else) ]: u* I; q5 ?; P# C' U$ @ 13 {' b( E5 m) b7 K, i9 _ 14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; 15 } 16 return xReturn; 17 }& O2 o8 D2 }0 ~, }, l, v* @' N 18// 相关宏定义 19#define pdPASS ( pdTRUE ) 20#define pdTRUE ( ( BaseType_t ) 1 )# {) \* ^; ` s3 S: h 21/* FreeRTOS error definitions. */' ^' l o. n3 t+ h ?4 s 22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 ) $ }' P8 c1 q8 l 具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。 因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。 创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask,2 "IDLE", configMINIMAL_STACK_SIZE,5 x0 |0 s/ P- C8 J: u$ O 3 ( void * ) NULL, 4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 5 &xIdleTaskHandle ); 6 [7 y8 U5 ?( u$ i! T 6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */) U) |" T$ p- {9 ^3 n. L+ ^ 7 }* o5 Z5 c K/ W4 z1 I+ i2 X7 c" c2 e 8相关宏定义: 9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U ) 10#ifndef portPRIVILEGE_BIT 11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 ) 12#endif, m. c% t; w4 o& b7 c2 B" k- u 13#define configUSE_TIMERS 1 . A* h6 [) j: X3 w 14 //为1时启用软件定时器 从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )2 BaseType_t xTimerCreateTimerTask( void ) 3 然后还要把中断关一下 1portDISABLE_INTERRUPTS();至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur2before or during the call toxPortStartScheduler(). The stacks of8 ^+ s) C+ z# o6 a0 W9 k 3the created tasks contain a status wordwith interrupts switched on 4so interrupts will automatically getre-enabled when the first task 5starts to run. */ 6/ *中断在这里被关闭,以确保不会发生滴答; K/ w* t, e3 g1 l, I' h 7在调用xPortStartScheduler()之前或期间。堆栈- |# e2 Z! I; L* M( R8 P 8创建的任务包含一个打开中断的状态字 9因此中断将在第一个任务时自动重新启用3 }% H M' J7 w7 V/ b P% V, x 10开始运行。*/$ T" o6 i- g; x) L# J( u8 @ . x" d' _. z! B8 C& u 3 _! A6 a2 D E' X 那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )2{0 x E9 l- D6 t' K2 T2 k$ ]2 d 3 PRESERVE8" @% u# e1 L7 y# j: f9 e 4 ldr r3, =pxCurrentTCB /* Restore the context. */4 c) |) S. g5 z0 R$ O' _( ^: N 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. */$ n L& d6 T* [ i2 D9 @5 G0 C 8 msrpsp, r0 /*Restore the task stack pointer. */ 9 isb 10 movr0, #0" e" _! K; d- X. Q 11 msr basepri, r0 12 orrr14, #0xd6 D' h, X8 v+ Q; F 13 bxr147 F5 s: h* X; Y 14} 4 `# `4 y* P9 {0 h/ \ 1msr basepri, r0. T2 }% ]; x6 H5 H) T 就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。 " p" s* f$ b" b- M1xSchedulerRunning = pdTRUE; 任务调度开始运行 1/* If configGENERATE_RUN_TIME_STATS isdefined then the following 2macro must be defined to configure thetimer/counter used to generate5 w' B' v$ f, w9 m) k/ }/ m0 q 3the run time counter time base. */$ h( N" ]3 A u" E0 [ 4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS(); ' F+ X- [9 } y* e6 \, v7 C 如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。 来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。 1if( xPortStartScheduler() != pdFALSE )2 { 3 /*Should not reach here as if the scheduler is running the: R, V4 |1 n1 h 4 functionwill not return. */' h' ~- O" f4 o& U! o 5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。 开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task, & h8 E Z0 D' l, C" Y) d2 (const char* )"led0_task", + m7 l5 @/ V7 S 3 (uint16_t )LED0_STK_SIZE,3 Q& Q# C+ ?, @6 _7 [, ~ 4 (void* )NULL, 5 (UBaseType_t )LED0_TASK_PRIO, 6 (TaskHandle_t* )&LED0Task_Handler); + m: y( B0 x8 h 7 //创建LED1任务 8 xTaskCreate((TaskFunction_t )led1_task, 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE,- i( o' [+ j0 T; v 11 (void* )NULL, 12 (UBaseType_t )LED1_TASK_PRIO, 13 (TaskHandle_t* )&LED1Task_Handler); 创建完任务就开启任务调度: 1vTaskStartScheduler(); //开启任务调度然后具体实现任务函数: 1//LED0任务函数. Q- M/ _. l$ \& `+ }0 C 2void led0_task(void *pvParameters)5 J0 p* Q! d; V 3{ 4 while(1)- T2 r* u) H! l" x* Q 5 { 6 LED0=~LED0;0 u" K0 a$ |2 K* Y- e 7 vTaskDelay(500);! a( K. O# X; J' i. A! y( R6 u: q 8 } 9} 10//LED1任务函数 11void led1_task(void *pvParameters)& C5 S. s3 K% u9 A; D 12{ 13 while(1) 14 {" L% O' m3 V! b/ D 15 LED1=0; 16 vTaskDelay(200); 17 LED1=1;4 ^/ E0 u3 T; z9 C 18 vTaskDelay(800); 19 }, b+ M" M! X$ t n 20}3 z$ k. e7 c' }3 F3 n2 D/ ] 6 X4 `/ c3 y; U `4 W 好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~ 欢迎大家一起来讨论操作系统的知识 我们的群号是:783234154 $ O) ]/ ~. w F2 U8 a. I6 Q6 c6 n0 ^2 K: c - k" g- v% v% x- H) r) t7 ~ 【连载】从单片机到操作系统②- U8 J, c. [. w 创客飞梦空间是开源公众号 欢迎大家分享出去 也欢迎大家投稿 , I+ W" N O. D# |' b% l |
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
参与/回复主题
关闭
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
怎么样啊,杰杰水平有限,还要多多指教吖
评分
查看全部评分
谢谢支持
谢谢支持
谢谢支持