从0开始学FreeRTOS-(创建任务)-2
# 补充
开始今天的内容之前,先补充一下上篇文章[从单片机到操作系统-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

更多资料欢迎关注“物联网IoT开发”公众号!
从0开始学FreeRTOS-(创建任务)-2的更多相关文章
- 从0开始学Swift笔记整理(三)
这是跟在上一篇博文后续内容: --Swift中相关的属性 存储属性 Swift中的属性分为存储属性和计算属性,存储属性就是Objective-C中的数据成员,计算属性不存储数据,但可以通过计算其他属性 ...
- 从0系统学Android--4.1探究碎片
从0系统学Android--4.1探究碎片 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 第四章:手机平板要兼顾--探究碎片 平板电脑和手机最 ...
- 从0系统学Android--3.7 聊天界面编写
从0系统学Android--3.7 聊天界面编写 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.7 编写界面的最佳实践 前面学习了那么多 UI 开发的知识,下面来进行实践,做一个美观 ...
- 从0系统学Android--3.6 RecyclerView
从0系统学Android--更强大的滚动控件---RecyclerView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 参考<第一行代码> 首先说明一点昨天发了一篇关于 L ...
- 从0系统学Android--3.5 最常用和最难用的控件---ListView
从0系统学Android-- 3.5 最常用和最难用的控件---ListView 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.5 最常用和最难用的控件---ListView Lis ...
- 从0系统学Android--3.2四种基本布局
从0系统学Android--3.2四种基本布局 本系列文章目录:更多精品文章分类 本系列持续更新中.... 3.3 系统控件不够用?创建自定义控件 上一节我们学习了 Android 中的一些常用的控件 ...
- 从0系统学Android--3.1编写UI界面
从0系统学Android--3.1编写UI界面 本系列文章目录:更多精品文章分类 本系列持续更新中.... 界面设计和功能开发同样重要,界面美观的应用程序不仅可以大大增加用户粘性,还能帮我们吸引到更多 ...
- 从0开始学爬虫8使用requests/pymysql和beautifulsoup4爬取维基百科词条链接并存入数据库
从0开始学爬虫8使用requests和beautifulsoup4爬取维基百科词条链接并存入数据库 Python使用requests和beautifulsoup4爬取维基百科词条链接并存入数据库 参考 ...
- 从0系统学Android--5.2 发送广播
从0系统学Android--52 发送广播 本系列文章目录:更多精品文章分类 本系列持续更新中.... 初级阶段内容参考<第一行代码> 5.3 发送自定义广播 前面已经学习了如何接受广播了 ...
- <-0基础学python.第一课->
初衷:我电脑里面的歌曲很久没换了,我想听一下新的歌曲,把他们下载下来听,比如某个榜单的,但是一首一首的点击下载另存为真的很恶心 所以我想有没有办法通过程序的方式来实现,结果还真的有,而且网上已经有有人 ...
随机推荐
- Educational Codeforces Round 69 (Rated for Div. 2)
A. DIY ...
- Django + Gunicorn + Nginx 部署之路
前言 最近,我已经成功将我的个人网站从 Flask 迁移到 Django 了,最早接触 Django 的时候大概是在 4 年前,我记得那个时候 Django 中的路由配置使用 正则 来进行的,但是我有 ...
- 如何将 JavaScript 代码添加到网页中,以及 <script> 标签的属性
Hello, world! 本教程的这一部分内容是关于 JavaScript 语言本身的. 但是,我们需要一个工作环境来运行我们的脚本,由于本教程是在线的,所以浏览器是一个不错的选择.我们会尽可能少地 ...
- java多线程之创建线程的4种方式及Future
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例.Java可以用四种方式来创建线程: 继承Thread创建线程 实现Runnable接口创建线程 实现callab ...
- Docker Compose基本使用-使用Compose启动Tomcat为例
场景 Docker-Compose简介与Ubuntu Server 上安装Compose: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...
- Java8之熟透Optional
一.使用Optional引言 1.1.代码问题引出 在写程序的时候一般都遇到过 NullPointerException,所以经常会对程序进行非空的判断: User user = getUserByI ...
- Tomcat 应用中并行流带来的类加载问题
本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/f-X3n9cvDyU5f5NYH6mhxQ作者:肖铭轩.王道环 随着 Java8 的不断流行, ...
- App引流增长技术:Deeplink(深度链接)技术
移动互联网时代,信息的分享传播无疑是 App 引流增长的关键,与其花费大量精力和成本找渠道.硬推广,不如从细节下手,用最快最简便的方法实现 Deeplink(深度链接)技术,打破信息孤岛.缩短分享路径 ...
- Nginx 配置Https转发http、 websocket
系统启动Nginx后,报 [emerg] bind() to 0.0.0.0:XXXX failed (13: Permission denied)错误的处理方式,分为两种: 第一种:端口小于1024 ...
- [LeetCode]Unique Binary Search TreesII
题目:Unique Binary Search TreesII 如果要列出所有可能的二叉搜索树,可以在上面的思路上进一步. f(n) = f(0)*f(n-1) + f(1)*f(n-2) + ... ...