# 补充
开始今天的内容之前,先补充一下上篇文章[从单片机到操作系统-1](https://jiejietop.gitee.io/freertos-1/)的一点点遗漏的知识点。

```js
BaseType_t xTaskCreate(       TaskFunction_t pvTaskCode,
                             const char * const pcName,
                             uint16_t usStackDepth,
                             void *pvParameters,
                             UBaseType_t uxPriority,
                             TaskHandle_t *pvCreatedTask
                         );
```
创建任务中的堆栈大小问题,在task.h中有这样子的描述:

```js
/**
* @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  
* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.
*/
```

当任务创建时,内核会分为每个任务分配属于任务自己的唯一堆栈。usStackDepth 值用于告诉内核为它应该分配多大的栈空间。

这个值指定的是栈空间可以保存多少个字(word) ,而不是多少个字节(byte)。

文档也有说明,如果是16位宽度的话,假如usStackDepth = 100;那么就是200个字节(byte)。

当然,我用的是stm32,32位宽度的, usStackDepth=100;那么就是400个字节(byte)。

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

---

我自己学的是应用层的东西,很多底层的东西我也不懂,水平有限,出错了还请多多包涵。

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

按照一般的写代码的习惯,在main函数里面各类初始化完毕了,并且创建任务成功了,那么,可以开启任务调度了。

```js
int main(void)
{
   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4    
   Delay_Init();                       //延时函数初始化    
   Uart_Init(115200);                  //初始化串口
   LED_Init();                     //初始化LED
   KEY_Init();
   //创建开始任务
   xTaskCreate((TaskFunction_t )start_task,            //任务函数
               (const char*    )"start_task",          //任务名称
               (uint16_t       )START_STK_SIZE,        //任务堆栈大小
               (void*          )NULL,                  //传递给任务函数的参数
               (UBaseType_t    )START_TASK_PRIO,       //任务优先级
               (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
   vTaskStartScheduler();          //开启任务调度
}

```

来大概看看分析一下创建任务的过程,虽然说会用就行,但是也是要知道了解一下的。

注意:下面说的创建任务均为xTaskCreate(动态创建)而非静态创建。

```js
pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) );
/*lint !e961 MISRA exception as the casts are only redundant for some ports. */
           if( pxStack != NULL )
           {
               /* Allocate space for the TCB. */
               pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
               /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
               if( pxNewTCB != NULL )
               {
                   /* Store the stack location in the TCB. */
                   pxNewTCB->pxStack = pxStack;
               }
               else
               {
                   /* The stack cannot be used as the TCB was not created.  Free
                   it again. */
                   vPortFree( pxStack );
               }
           }
           else
           {
               pxNewTCB = NULL;
           }
       }
```
首先是利用`pvPortMalloc`给任务的堆栈分配空间,`if( pxStack != NULL )`如果内存申请成功,就接着给任务控制块申请内存。`pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );`同样是使用`pvPortMalloc();`如果任务控制块内存申请失败则释放 之前已经申请成功的任务堆栈`的内存vPortFree( pxStack );`

然后就初始化任务相关的东西,并且将新初始化的任务控制块添加到列表中`prvAddNewTaskToReadyList( pxNewTCB );`

  最后返回任务的状态,如果是成功了就是`pdPASS`,假如失败了就是返回`errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;`

```js
prvInitialiseNewTask(    pxTaskCode,
                      pcName,
                        ( uint32_t ) usStackDepth,
                      pvParameters,
                      uxPriority,
                        pxCreatedTask,
                      pxNewTCB,
                        NULL );
           prvAddNewTaskToReadyList( pxNewTCB );
           xReturn = pdPASS;
       }
       else
       {
           xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
       }
       return xReturn;
   }
// 相关宏定义
#define pdPASS            ( pdTRUE )
#define pdTRUE            ( ( BaseType_t ) 1 )
/* FreeRTOS error definitions. */
#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创建任务内部调用的,我们无需理会这些函数的实现过程,当然如果需要请自行了解。

创建完任务就开启任务调度了:
```js
vTaskStartScheduler();          //开启任务调度
```

在任务调度里面,会创建一个空闲任务(我们将的都是动态创建任务,静态创建其实一样的)
```js
xReturn = xTaskCreate(    prvIdleTask,
                         "IDLE", configMINIMAL_STACK_SIZE,
                         ( void * ) NULL,
                         ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                         &xIdleTaskHandle );
/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
   }
相关宏定义:
#define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )
#ifndef portPRIVILEGE_BIT
   #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )
#endif
#define configUSE_TIMERS                        1                              
//为1时启用软件定时器
```

从上面的代码我们可以看出,空闲任务的优先级是tskIDLE_PRIORITY为0,也就是说空闲任务的优先级最低。当CPU没事干的时候才执行空闲任务,以待随时切换优先级更高的任务。

如果使用了软件定时器的话,我们还需要创建定时器任务,创建的函数是:
```js
#if ( configUSE_TIMERS == 1 )
   BaseType_t xTimerCreateTimerTask( void )
```
然后还要把中断关一下

```js
portDISABLE_INTERRUPTS();
```

至于为什么关中断,也有说明:

```js
/* Interrupts are turned off here, toensure a tick does not occur
before or during the call toxPortStartScheduler().  The stacks of
the created tasks contain a status wordwith interrupts switched on
so interrupts will automatically getre-enabled when the first task
starts to run. */
```
中断在这里被关闭,以确保不会发生滴答在调用xPortStartScheduler()之前或期间。堆栈创建的任务包含一个打开中断的状态字因此中断将在第一个任务时自动重新启用开始运行。

那么如何打开中断呢????这是个很重要的问题

别担心,我们在SVC中断服务函数里面就会打开中断的

看代码:
```js
__asm void vPortSVCHandler( void )
{
        PRESERVE8
        ldr    r3, =pxCurrentTCB  /* Restore the context. */
        ldrr1, [r3]                            /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */
        ldrr0, [r1]                            /* Thefirst item in pxCurrentTCB is the task top of stack. */
        ldmiar0!, {r4-r11}             /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */
        msrpsp, r0                                   /*Restore the task stack pointer. */
        isb
        movr0, #0
        msr  basepri, r0
        orrr14, #0xd
        bxr14
}
```

```js
msr  basepri, r0
```

就是它把中断打开的。看不懂没所谓,我也不懂汇编,看得懂知道就好啦。

```js
xSchedulerRunning = pdTRUE;
```
任务调度开始运行

```js
/* If configGENERATE_RUN_TIME_STATS isdefined then the following
macro must be defined to configure thetimer/counter used to generate
the run time counter time base. */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
```

如果`configGENERATE_RUN_TIME_STATS`使用时间统计功能,这个宏为`1`,那么用户必须实现一个宏`portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();`用来配置一个定时器或者计数器。

来到我们的重点了,开启任务调度,那么任务到这了就不会返回了。
```js
if( xPortStartScheduler() != pdFALSE )
{
   /*Should not reach here as if the scheduler is running the
   functionwill not return. */                  
}
```

然后就能开启第一个任务了,感觉好难是吧,我一开始也是觉得的,但是写了这篇文章,觉得还行吧,也不算太难,可能也是在查看代码跟别人的书籍吧,写东西其实还是蛮好的,能加深理解,写过文章的人就知道,懂了不一定能写出来,所以,我还是很希望朋友们能投稿的。杰杰随时欢迎。。。

开始任务就按照套路模板添加自己的代码就好啦,很简单的。

先创建任务:
```js
xTaskCreate((TaskFunction_t )led0_task,    
             (const char*    )"led0_task",  
             (uint16_t       )LED0_STK_SIZE,
             (void*          )NULL,                                    
              (UBaseType_t    )LED0_TASK_PRIO,  
             (TaskHandle_t*  )&LED0Task_Handler);  
  //创建LED1任务
  xTaskCreate((TaskFunction_t )led1_task,    
              (const char*    )"led1_task",  
              (uint16_t       )LED1_STK_SIZE,
            (void*          )NULL,
              (UBaseType_t    )LED1_TASK_PRIO,
             (TaskHandle_t*  )&LED1Task_Handler);      
```

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

然后具体实现任务函数:

```js
//LED0任务函数
void led0_task(void *pvParameters)
{
  while(1)
   {
      LED0=~LED0;
      vTaskDelay(500);
   }
}  

//LED1任务函数
void led1_task(void *pvParameters)
{
  while(1)
   {
      LED1=0;
      vTaskDelay(200);
      LED1=1;
      vTaskDelay(800);
   }
}
```

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

欢迎大家一起来讨论操作系统的知识

我们的群号是:783234154

![欢迎关注我公众号](https://img2018.cnblogs.com/blog/1834930/201910/1834930-20191015203637034-1803649238.jpg)

更多资料欢迎关注“物联网IoT开发”公众号!

从0开始学FreeRTOS-(创建任务)-2的更多相关文章

  1. 从0开始学Swift笔记整理(三)

    这是跟在上一篇博文后续内容: --Swift中相关的属性 存储属性 Swift中的属性分为存储属性和计算属性,存储属性就是Objective-C中的数据成员,计算属性不存储数据,但可以通过计算其他属性 ...

  2. 从0系统学Android--4.1探究碎片

    从0系统学Android--4.1探究碎片 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 第四章:手机平板要兼顾--探究碎片 平板电脑和手机最 ...

  3. 从0系统学Android--3.7 聊天界面编写

    从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...

  4. 从0系统学Android--3.6 RecyclerView

    从0系统学Android--更强大的滚动控件---RecyclerView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 参考<第一行代码> 首先说明一点昨天发了一篇关于 L ...

  5. 从0系统学Android--3.5 最常用和最难用的控件---ListView

    从0系统学Android-- 3.5 最常用和最难用的控件---ListView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.5 最常用和最难用的控件---ListView Lis ...

  6. 从0系统学Android--3.2四种基本布局

    从0系统学Android--3.2四种基本布局 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.3 系统控件不够用?创建自定义控件 上一节我们学习了 Android 中的一些常用的控件 ...

  7. 从0系统学Android--3.1编写UI界面

    从0系统学Android--3.1编写UI界面 本系列文章目录:更多精品文章分类 本系列持续更新中.... 界面设计和功能开发同样重要,界面美观的应用程序不仅可以大大增加用户粘性,还能帮我们吸引到更多 ...

  8. 从0开始学爬虫8使用requests/pymysql和beautifulsoup4爬取维基百科词条链接并存入数据库

    从0开始学爬虫8使用requests和beautifulsoup4爬取维基百科词条链接并存入数据库 Python使用requests和beautifulsoup4爬取维基百科词条链接并存入数据库 参考 ...

  9. 从0系统学Android--5.2 发送广播

    从0系统学Android--52 发送广播 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 5.3 发送自定义广播 前面已经学习了如何接受广播了 ...

  10. <-0基础学python.第一课->

    初衷:我电脑里面的歌曲很久没换了,我想听一下新的歌曲,把他们下载下来听,比如某个榜单的,但是一首一首的点击下载另存为真的很恶心 所以我想有没有办法通过程序的方式来实现,结果还真的有,而且网上已经有有人 ...

随机推荐

  1. hadoop之数据倾斜

    数据倾斜介绍 在做Shuffle阶段的优化过程中,遇到了数据倾斜的问题,造成了对一些情况下优化效果不明显.主要是因为在Job完成后的所得到的Counters是整个Job的总和,优化是基于这些Count ...

  2. mysql 主主从配置

    配置主服务器:主服务器1 Ip:  192.168.0.1 主服务器2 Ip:  192.168.0.2 主服务器1配置 2.1.修改mysql配置文件 vim /etc/my.conf Server ...

  3. Shiro使用Session缓存

    Shiro的Session缓存主要有两种方案,一种是使用Shiro自己的Session,不使用HttpSession,自己实现Shiro的Cache接口和Session缓存等:另外一种是直接使用spr ...

  4. 微信小程序点击控制元素的显示与隐藏

    微信小程序点击控制元素的显示与隐藏 首先我们先来看一下单个点击效果 我们来看一下wxml中的代码: <view class="conten"> <view cla ...

  5. 「小技巧」使用Git从其他分支merge个别文件

    小明发现在实际项目开发过程中,总会遇到各种各样的情况,比如一个大型的项目或版本迭代可能不是一次上线,可能会分好几次上线,这时候就会涉及创建多个分支,分别开发. 项目背景 产品经理:我们本次开发三个功能 ...

  6. JDK11,JDK12没有JRE的解决方法

    jdk11和jdk12在以前版本基础上,改动有点大,安装后默认是没有jre的. 解决方法: 在JDK目录下使用bin\jlink.exe --module-path jmods --add-modul ...

  7. 实战限流(guava的RateLimiter)

    关于限流 常用的限流算法有漏桶算法和令牌桶算法,guava的RateLimiter使用的是令牌桶算法,也就是以固定的频率向桶中放入令牌,例如一秒钟10枚令牌,实际业务在每次响应请求之前都从桶中获取令牌 ...

  8. DOM之事件(一)

    DOM事件,就是浏览器或用户针对页面可以做出的某种动作,我们称这些动作为DOM事件.它是用户和页面交互的核心.当动作发生(事件触发)时,我们可以为其绑定一个或多个事件处理程序(函数),来完成我们想要实 ...

  9. WordPress对接微信小程序遇到的问题

    1.文章内容中的“<”和“>”字符显示问题 小程序是使用“wxPares工具来实现html转wxml的,如果你的文本包含了代码比如xml会携带<>符号,程序会将其转化,造成解析 ...

  10. elasticsearch深度分页问题

    elasticsearch专栏:https://www.cnblogs.com/hello-shf/category/1550315.html 一.深度分页方式from + size es 默认采用的 ...