野火电子论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 27769|回复: 49

[freertos] 【连载】从单片机到操作系统④——FreeRTOS任务详解

  [复制链接]
发表于 2018-5-27 10:53:22 | 显示全部楼层 |阅读模式
本帖最后由 杰杰 于 2018-5-27 10:55 编辑

创客的兄弟姐妹们大家好,我是杰杰。又到了更新的时候了。

开始今天的内容之前,先补充一下上篇文章【连载】从单片机到操作系统③——走进FreeRTOS的一点点遗漏的知识点。
1BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,
2                              const char * const pcName,
3                              uint16_t usStackDepth,
4                              void *pvParameters,
5                              UBaseType_t uxPriority,
6                              TaskHandle_t *pvCreatedTask
7                          )
;

创建任务中的堆栈大小问题,在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)。

  好啦,补充完毕。下面正式开始我们今天的主题。


  我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。
  其实我自己写文章的时候也去跟着火哥的书看着底层的东西啦,但是本身自己也是不懂,不敢乱写。所以,这个《从单片机到操作系统》系列的文章,我会讲一点底层,更多的是应用层,主要是用的方面。

按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。
1int main(void)
2
{
3    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4   
4    Delay_Init();                       //延时函数初始化     
5    Uart_Init(115200);                  //初始化串口
6    LED_Init();                     //初始化LED
7    KEY_Init();
8    //创建开始任务
9    xTaskCreate((TaskFunction_t )start_task,            //任务函数
10                (const char*    )"start_task",          //任务名称
11                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
12                (void*          )NULL,                  //传递给任务函数的参数
13                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
14                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
15    vTaskStartScheduler();          //开启任务调度
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. */
3            if( pxStack != NULL )
4            {
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                {
10                    /* Store the stack location in the TCB. */
11                    pxNewTCB->pxStack = pxStack;
12                }
13                else
14                {
15                    /* The stack cannot be used as the TCB was not created.  Free
16                    it again. */

17                    vPortFree( pxStack );
18                }
19            }
20            else
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;

1prvInitialiseNewTask(     pxTaskCode,
2                          pcName,
3                         ( uint32_t ) usStackDepth,
4                          pvParameters,
5                          uxPriority,
6                         pxCreatedTask,
7                          pxNewTCB,
8                         NULL );
9            prvAddNewTaskToReadyList( pxNewTCB );
10            xReturn = pdPASS;
11        }
12        else
13        {
14            xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
15        }
16        return xReturn;
17    }
18// 相关宏定义
19#define pdPASS            ( pdTRUE )
20#define pdTRUE            ( ( BaseType_t ) 1 )
21/* FreeRTOS error definitions. */
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,
2                          "IDLE", configMINIMAL_STACK_SIZE,
3                          ( void * ) NULL,
4                          ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
5                          &xIdleTaskHandle );
6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
7    }
8相关宏定义:
9#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )
10#ifndef portPRIVILEGE_BIT
11    #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
12#endif
13#define configUSE_TIMERS                        1                              
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 occur
2before 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
5starts to run. */

6/ *中断在这里被关闭,以确保不会发生滴答
7在调用xPortStartScheduler()之前或期间。堆栈
8创建的任务包含一个打开中断的状态字
9因此中断将在第一个任务时自动重新启用
10开始运行。*/

那么如何打开中断呢????这是个很重要的问题
别担心,我们在SVC中断服务函数里面就会打开中断的
看代码:
1__asm void vPortSVCHandler( void )
2
{
3         PRESERVE8
4         ldr    r3, =pxCurrentTCB  /* Restore the context. */
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         isb
10         movr0, #0
11         msr  basepri, r0
12         orrr14, #0xd
13         bxr14
14}

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. */

4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

如果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
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,                                    
5                (UBaseType_t    )LED0_TASK_PRIO,   
6                (TaskHandle_t*  )&LED0Task_Handler);  
7   //创建LED1任务
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,
13                (TaskHandle_t*  )&LED1Task_Handler);      

创建完任务就开启任务调度
1vTaskStartScheduler();          //开启任务调度

然后具体实现任务函数

1//LED0任务函数
2void led0_task(void *pvParameters)
3
{
4   while(1)
5    {
6       LED0=~LED0;
7       vTaskDelay(500);
8    }
9}  
10//LED1任务函数
11void led1_task(void *pvParameters)
12
{
13   while(1)
14    {
15       LED1=0;
16       vTaskDelay(200);
17       LED1=1;
18       vTaskDelay(800);
19    }
20}

好啦,今天的介绍到这了为止,后面还会持续更新,敬请期待哦~

想更深入了解底层的可以去看看野火的书籍
写的都是很不错的
当然还要多多支持我们国家自主研发的操作系统
RT-Thread
因为杰杰自己最近也要学了
板子用的是火哥的 i.MX RT1052
欢迎大家一起来讨论操作系统的知识
我们的群号是:783234154
2.jpg




创客:
创客飞梦空间是开源公众号
欢迎大家分享出去
也欢迎大家投稿




回复

使用道具 举报

发表于 2018-5-27 21:50:41 | 显示全部楼层
感谢大神分享  顶一个
回复 支持 反对

使用道具 举报

发表于 2018-6-6 10:00:14 | 显示全部楼层
贴主,发一个freertos上的项目代码让大家学习学习呗
回复 支持 反对

使用道具 举报

发表于 2018-6-6 10:20:36 | 显示全部楼层
学习中。。。。。。楼主辛苦了!!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-6-6 13:16:41 | 显示全部楼层
xuetudou 发表于 2018-6-6 10:00
贴主,发一个freertos上的项目代码让大家学习学习呗

火哥代码不是有一堆吗
回复 支持 反对

使用道具 举报

发表于 2018-6-6 14:03:05 | 显示全部楼层
感谢楼主,我刚刚开始准备学习freertos,我会一直关注你的帖子
回复 支持 反对

使用道具 举报

发表于 2018-6-21 16:11:08 | 显示全部楼层
kankanxuexile
回复 支持 反对

使用道具 举报

发表于 2018-6-28 13:17:11 | 显示全部楼层
支持杰哥分享。
回复 支持 反对

使用道具 举报

发表于 2018-8-15 21:01:27 | 显示全部楼层
讲的真是通俗易懂,真好
回复 支持 反对

使用道具 举报

发表于 2018-8-21 15:37:41 | 显示全部楼层
刚接触freertos,学习了
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-8-23 17:44:37 | 显示全部楼层
qiyuwang 发表于 2018-6-6 14:03
感谢楼主,我刚刚开始准备学习freertos,我会一直关注你的帖子

谢谢支持
回复 支持 反对

使用道具 举报

 楼主| 发表于 2018-8-23 17:45:05 | 显示全部楼层
Lunatic 发表于 2018-8-15 21:01
讲的真是通俗易懂,真好

你的支持是我最大的动力,还会更新的
回复 支持 反对

使用道具 举报

发表于 2018-8-24 23:38:57 | 显示全部楼层
写的很好,不错啊
回复 支持 反对

使用道具 举报

发表于 2018-8-29 14:17:50 | 显示全部楼层
:lol:lol:lol:lol:lol:lol:lol:lol:lol:lol:lol
回复 支持 反对

使用道具 举报

发表于 2018-9-20 08:37:47 | 显示全部楼层
看帖还得先回复
回复 支持 反对

使用道具 举报

发表于 2018-11-24 16:07:20 | 显示全部楼层
前来学习,谢谢楼主!
回复 支持 反对

使用道具 举报

发表于 2018-12-25 21:33:34 | 显示全部楼层
我来学习学习!
回复 支持 反对

使用道具 举报

发表于 2018-12-26 18:59:36 | 显示全部楼层
看看哈哈哈哈哈哈哈哈哈哈哈哈
回复 支持 反对

使用道具 举报

发表于 2019-2-6 14:56:06 | 显示全部楼层
一定回复看看!!!
回复 支持 反对

使用道具 举报

发表于 2019-2-18 13:15:34 | 显示全部楼层
学习学习,谢谢楼主!
回复 支持 反对

使用道具 举报

发表于 2019-2-21 10:09:51 | 显示全部楼层
学习了,多谢分享!
回复 支持 反对

使用道具 举报

发表于 2019-2-21 16:54:28 | 显示全部楼层
6666666666
回复 支持 反对

使用道具 举报

发表于 2019-2-22 13:56:45 | 显示全部楼层
OK
拭目以待
回复 支持 反对

使用道具 举报

发表于 2019-3-23 13:58:39 | 显示全部楼层
感谢楼住分享
回复 支持 反对

使用道具 举报

发表于 2019-3-25 21:24:13 | 显示全部楼层
楼主幸苦,感谢大神分享
回复 支持 反对

使用道具 举报

发表于 2019-3-29 10:31:57 | 显示全部楼层
初学rtos,感谢分享
回复 支持 反对

使用道具 举报

发表于 2019-4-29 14:40:32 | 显示全部楼层
看看~~~
回复

使用道具 举报

发表于 2019-4-29 16:46:55 | 显示全部楼层
从单片机到操作系统 good
回复 支持 反对

使用道具 举报

发表于 2019-5-5 09:53:50 | 显示全部楼层
感谢大神分享  顶一个
回复 支持 反对

使用道具 举报

发表于 2019-5-27 19:54:03 | 显示全部楼层
学习一下...
回复

使用道具 举报

发表于 2019-6-9 12:02:53 | 显示全部楼层
讲的很好,谢谢!!!!!!!!!!!
回复 支持 反对

使用道具 举报

发表于 2019-10-18 09:39:53 | 显示全部楼层
学习一下......................
回复

使用道具 举报

发表于 2019-11-28 10:47:51 | 显示全部楼层
感谢分享,顶一下
回复 支持 反对

使用道具 举报

发表于 2019-12-2 17:09:25 | 显示全部楼层
学习                  
回复 支持 反对

使用道具 举报

发表于 2019-12-4 16:31:04 | 显示全部楼层
非常感谢你的分享。
回复 支持 反对

使用道具 举报

发表于 2019-12-6 15:44:26 | 显示全部楼层
楼主帅气!
回复

使用道具 举报

发表于 2020-1-13 15:57:27 | 显示全部楼层
曾在CSDN也见过楼主的大作。楼主牛逼。
回复 支持 反对

使用道具 举报

发表于 2020-1-15 11:22:46 | 显示全部楼层
如果你只是需要一点自由告诉我
回复 支持 反对

使用道具 举报

发表于 2020-1-17 11:29:48 | 显示全部楼层
谢谢分享,顶一下。
回复 支持 反对

使用道具 举报

发表于 2020-1-19 11:31:26 | 显示全部楼层
跟着学习学习
回复 支持 反对

使用道具 举报

发表于 2020-3-11 21:39:50 | 显示全部楼层
666666666666666666666666
回复 支持 反对

使用道具 举报

发表于 2020-3-14 09:01:45 | 显示全部楼层
学习下新知识
回复 支持 反对

使用道具 举报

发表于 2020-3-17 15:32:17 | 显示全部楼层
谢谢大神分享。学习下
回复 支持 反对

使用道具 举报

发表于 2020-3-23 10:44:36 | 显示全部楼层
感谢分享,操作系统自己学好难
回复 支持 反对

使用道具 举报

发表于 2020-3-28 18:16:59 | 显示全部楼层
加上登记卡撒大声地
回复 支持 反对

使用道具 举报

发表于 2020-4-1 12:26:06 | 显示全部楼层

学习了,多谢分享!
回复 支持 反对

使用道具 举报

发表于 2020-4-20 17:45:56 | 显示全部楼层
学习学习
回复

使用道具 举报

发表于 2020-5-7 15:57:28 | 显示全部楼层
顶一下进来学习学习
回复 支持 反对

使用道具 举报

发表于 2021-1-19 16:11:27 | 显示全部楼层
写得不错啊,正好可以学习一下.
回复 支持 反对

使用道具 举报

发表于 2024-2-19 11:04:34 | 显示全部楼层
学习一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

联系站长|手机版|野火电子官网|野火淘宝店铺|野火电子论坛 ( 粤ICP备14069197号 ) 大学生ARM嵌入式2群

GMT+8, 2024-11-1 07:08 , Processed in 0.089593 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表