|
本帖最后由 xiaojie0513 于 2018-5-29 13:42 编辑 " i% N+ p) _4 y 创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。 听首歌缓解一下心情。 . v8 K8 J, R* j% g) O9 p/ ~ 开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。 1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode,. M0 }+ p f1 @3 }* _. _2 const char * const pcName,' w4 F) ~* I; {( I 3 uint16_t usStackDepth,* {8 g7 {$ I7 G" ^* ~ d$ z' t' [ 4 void *pvParameters, 5 UBaseType_t uxPriority,* X8 Z3 E3 z! R9 ~' w% X 6 TaskHandle_t *pvCreatedTask! `; {# x# e9 R$ O 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 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)。 ! t M$ t% ^3 W$ K, k# S( o, y 好啦,补充完毕。下面正式开始我们今天的主题。 我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。 其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。 + m4 z: d# B7 `, u 按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。 1int main(void)2{ 3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4 1 k7 {7 d; M6 w5 _; R4 R 4 Delay_Init(); //延时函数初始化 ' m2 J6 v2 b! r$ r0 X2 H7 f 5 Uart_Init(115200); //初始化串口 6 LED_Init(); //初始化LED 7 KEY_Init(); 6 u. H8 ~5 G+ v c+ A" ? 8 //创建开始任务5 K j9 r1 }! g3 C9 R 9 xTaskCreate((TaskFunction_t )start_task, //任务函数 10 (const char* )"start_task", //任务名称1 K9 P5 o+ ?, M) B: Z: {* T) ?4 x 11 (uint16_t )START_STK_SIZE, //任务堆栈大小 12 (void* )NULL, //传递给任务函数的参数 13 (UBaseType_t )START_TASK_PRIO, //任务优先级# E* q7 ~6 c @# w! m; ` 14 (TaskHandle_t* )&StartTask_Handler); //任务句柄 # W( X5 v8 D. v 15 vTaskStartScheduler(); //开启任务调度 16} # r) j3 l p O( U5 n1 U 来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。 注意:下面说的创建任务均为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. */& v- o) o7 W; I5 I1 ? 3 if( pxStack != NULL ) 4 {4 p: F, ?# j9 d3 \ 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 )+ }, P& F" I% J- M' M9 t 9 { 10 /* Store the stack location in the TCB. */3 b1 S0 X: S- y; f% g$ v# R% \6 s3 L 11 pxNewTCB->pxStack = pxStack; 12 }1 e$ o' v3 W5 o/ h6 f5 t5 J 13 else 14 {7 T5 s* y ]% E# h. j/ C 15 /* The stack cannot be used as the TCB was not created. Free 16 it again. */ 17 vPortFree( pxStack );* u# I3 u( u% l9 w" o 18 } 19 }! z. G' |! q/ D 20 else 21 {2 R9 ^4 |: K+ Y: G, X; b) T$ m 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; 1prvInitialiseNewTask( pxTaskCode, 2 pcName, 3 ( uint32_t ) usStackDepth,0 @0 \5 ]' Q% I7 b2 X) T0 t 4 pvParameters, 5 uxPriority, ! z- L1 D! T+ X5 W( n5 R/ Y$ h/ v1 k 6 pxCreatedTask, 7 pxNewTCB, 8 NULL ); 9 prvAddNewTaskToReadyList( pxNewTCB ); 10 xReturn = pdPASS; 11 }4 x+ u4 T, x2 X0 y# |$ e) Q% U 12 else 13 { 14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;9 {) k; `7 \/ }2 h9 } 15 }+ f& X# f: w" \$ c( O& l 16 return xReturn; 17 } 18// 相关宏定义( L+ ^& a# x p) t 19#define pdPASS ( pdTRUE ) 20#define pdTRUE ( ( BaseType_t ) 1 )$ ~' c: {) `3 X! r" U/ g( U$ V r7 Y, z 21/* FreeRTOS error definitions. */ 22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 ): d3 t0 y2 T4 _ 3 r1 g% q' ]3 ]" m4 [6 e 具体的static void prvInitialiseNewTask(()实现请参考FreeRTOS的tasks.c文件的767行代码。具体的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )实现请参考FreeRTOS的tasks.c文件的963行代码。 $ F: ]( D* `2 T3 V, @ 因为这些是tasks.c中的静态的函数,仅供xTaskCreate创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。 创建完任务就开启任务调度了: 1vTaskStartScheduler(); //开启任务调度8 c0 t' y8 ~+ P2 G$ T4 N- F( e6 d* o* b6 m0 `6 B) G8 _& f 在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的) 1xReturn = xTaskCreate( prvIdleTask,8 q7 D t6 y+ j- K; U+ p2 "IDLE", configMINIMAL_STACK_SIZE,( F. i% o/ Z# e! `, a3 ]% ^2 S 3 ( void * ) NULL,% N9 v$ l4 c0 @6 k0 j+ s- S 4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),. N$ Z$ h! l) |& O, ?, ^# n 5 &xIdleTaskHandle ); 8 y+ x/ j5 G1 l3 ?- F4 c$ a 6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ 7 }+ L! T4 Q# t; u& L 8相关宏定义:$ k. I6 F1 c: u1 P( t 9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U ), n6 j$ r/ t* T9 p0 X+ A 10#ifndef portPRIVILEGE_BIT 11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 ) 12#endif5 e) A' V+ G! q- w( K1 C/ d- H 13#define configUSE_TIMERS 1 14 //为1时启用软件定时器0 u* a* h4 N( W# B' ^7 S; I 从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。 如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是: 1#if ( configUSE_TIMERS == 1 )2 BaseType_t xTimerCreateTimerTask( void )4 y. U5 A' M( ?% I 3 然后还要把中断关一下 1portDISABLE_INTERRUPTS();至于为什么关中断,也有说明: 1/* Interrupts are turned off here, toensure a tick does not occur2before or during the call toxPortStartScheduler(). The stacks of. F9 _0 @5 r) ? C( W 3the created tasks contain a status wordwith interrupts switched on& A% s/ [. N9 W( ~ 4so interrupts will automatically getre-enabled when the first task 5starts to run. */! `0 w8 R, H5 b5 @4 W% d$ k0 n 6/ *中断在这里被关闭,以确保不会发生滴答 7在调用xPortStartScheduler()之前或期间。堆栈 8创建的任务包含一个打开中断的状态字. o! j) D# R4 z4 d2 v 9因此中断将在第一个任务时自动重新启用 10开始运行。*/ 8 P1 q" M, o# Q! m9 a6 y' f L! H- p) z; {) `" r 那么如何打开中断呢????这是个很重要的问题 别担心,我们在SVC中断服务函数里面就会打开中断的 看代码: 1__asm void vPortSVCHandler( void )2{ 3 PRESERVE8; L& o, t/ I! i2 v6 f/ w' `. r1 c 4 ldr r3, =pxCurrentTCB /* Restore the context. */% x" G& s1 z! g1 D+ n# M$ E 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. */. N7 A! K3 `0 w( J6 d g9 N 9 isb+ O. s+ T# }' F3 `9 c 10 movr0, #08 t4 i$ q6 `; L$ @5 a 11 msr basepri, r0 12 orrr14, #0xd5 M; T( N3 k4 c* k 13 bxr14 14} ) K7 K: R6 V8 i! ^ 1msr basepri, r0 就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。 1xSchedulerRunning = pdTRUE; 任务调度开始运行 1/* If configGENERATE_RUN_TIME_STATS isdefined then the following Q0 ?* A+ J# A 2macro must be defined to configure thetimer/counter used to generate- ? b6 @$ o: \9 p6 t0 Y 3the run time counter time base. */ 4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();7 |5 f3 H5 O: Y) d8 N/ E0 _0 f $ E. D( N; X) S! A0 O 如果configGENERATE_RUN_TIME_STATS使用时间统计功能,这个宏为1,那么用户必须实现一个宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用来配置一个定时器或者计数器。 % Q& k! \2 C/ j# n- [6 l) X来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。 1if( xPortStartScheduler() != pdFALSE )2 {5 A) l1 [$ u* X& [ U, m 3 /*Should not reach here as if the scheduler is running the 4 functionwill not return. */( G9 E: w4 R' X, V 5 }然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。 开始任务就按照套路模板添加自己的代码就好啦,很简单的。 先创建任务: 1 xTaskCreate((TaskFunction_t )led0_task, & h. L1 G: x: \# C3 s2 (const char* )"led0_task", 3 (uint16_t )LED0_STK_SIZE,$ f4 g/ b) M) B5 s4 V. }+ z0 a/ f 4 (void* )NULL, / N& _$ r+ W! F& Z* z 5 (UBaseType_t )LED0_TASK_PRIO, 6 (TaskHandle_t* )&LED0Task_Handler); & {, `* [% B5 l+ J6 m 7 //创建LED1任务9 y; @0 _- J& p+ ^' u4 @ F 8 xTaskCreate((TaskFunction_t )led1_task, 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE, 11 (void* )NULL, 12 (UBaseType_t )LED1_TASK_PRIO,0 \$ }9 z. _* _$ Q6 ?+ { 13 (TaskHandle_t* )&LED1Task_Handler); 9 {, p8 l) v$ s$ _8 c9 A0 S 创建完任务就开启任务调度: 1vTaskStartScheduler(); //开启任务调度5 O/ @( W2 A, a# N- a, `2 \/ { 然后具体实现任务函数: ' S. E4 _) K* S# b* Y3 p1//LED0任务函数4 L7 \, A( ?3 C6 F 2void led0_task(void *pvParameters) 3{ 4 while(1) 5 { 6 LED0=~LED0; 7 vTaskDelay(500);8 F; o, Q$ a$ g5 i! U% n* x4 y3 o% P 8 }6 Q( W0 i* ]# @+ c3 C+ u# v6 J 9} 9 J1 m8 w R6 }" L- ` 10//LED1任务函数" U1 y; r. M) N- Z; c$ I, ^+ P: s 11void led1_task(void *pvParameters) 12{ 13 while(1), J% G' G( w5 t0 B5 A& f) r4 W' t 14 {* B; b8 ~+ G, w4 w# A 15 LED1=0;0 G; Y( h5 G: Q1 ]' V# d/ Z 16 vTaskDelay(200);( O* [' j+ f; z/ ?; h& [ 17 LED1=1;$ ^. Y6 Y ~9 ?- w" |: b4 m 18 vTaskDelay(800);! Z C( t# k1 X: V 19 }! I4 a/ p9 ]0 c/ k 20}, N& G8 @3 M8 }6 J: m. U 好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~ : Y) ^1 L, ^* W% g# S ?9 K- ?) K! E# z/ ~9 G5 ? 欢迎大家一起来讨论操作系统的知识 我们的群号是:783234154 ! C# v, }- c t+ |6 c) ?" F- e【连载】从单片机到操作系统②/ o7 [0 ^) f \) Q/ M ! m/ ?4 b d7 c7 c5 K% h创客飞梦空间是开源公众号 欢迎大家分享出去 也欢迎大家投稿 6 g4 [; C$ |8 R- R0 i+ c1 C |
微信公众号
手机版
写的很好。我对于单片机的操作系统不是很了解,底层的倒是知道,还有就是了解些Linux系统。
参与/回复主题% ~+ k( ?& g* |8 M! x' L% {2 {
关闭4 Q# U6 N6 a% J6 }0 P& v& d5 w
RE: 【连载】从单片机到操作系统④——FreeRTOS创建任务&开启... [修改]
怎么样啊,杰杰水平有限,还要多多指教吖
评分
查看全部评分
谢谢支持
谢谢支持
谢谢支持