第7章 任务管理
第7章 任务管理
对于初学者,特别是对于没有RTOS基础的同学来说,了解RTX的任务管理非常重要,了解任务管理的目的就是让初学者从裸机的,单任务编程过渡到带OS的,多任务编程上来。搞清楚了这点,那么RTX学习就算入门了。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
7.1 单任务系统
7.2 多任务系统
7.3 任务设置
7.4 任务栈设置
7.5 系统栈设置
7.6 栈溢出检测
7.7 RTX初始化和启动
7.9 任务删除
7.10 空闲任务
7.11实验例程说明
7.12 总结
7.1 单任务系统
学习多任务系统之前,我们先来回顾下单任务系统的编程框架,即裸机时的编程框架。裸机编程主要是采用超级循环(super-loops)系统,又称前后台系统。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看做后台行为,中断服务程序处理异步事件,这部分可以看做是前台行为。后台也可以叫做任务级,前台也叫作中断级。
图7.1 单任务系统
对于前后台系统的编程思路主要有以下两种方式:
7.1.1 查询方式
对于一些简单的应用,处理器可以查询数据或者消息是否就绪,就绪后进行处理,然后再等待,如此循环下去。对于简单的任务,这种方式简单易处理。但大多数情况下,需要处理多个接口数据或者消息,那就需要多次处理,如下面的流程图所示:
用查询方式处理简单的应用,效果比较好,但是随着工程的复杂,采用查询方式实现的工程就变的很难维护,同时,由于无法定义查询任务的优先级,这种查询方式会使得重要的接口消息得不到及时响应。比如程序一直在等待一个非紧急消息就绪,如果这个消息后面还有一个紧急的消息需要处理,那么就会使得紧急消息长时间得不到执行。
7.1.2 中断方式
对于查询方式无法有效执行紧急任务的情况,采用中断方式就有效的解决了这个问题,下面是中断方式简单的流程图:
采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了
1. 必须在中断(ISR)内处理时间关键运算:
(1)ISR 函数变得非常复杂,并且需要很长执行时间。
(2)ISR 嵌套可能产生不可预测的执行时间和堆栈需求。
2. 超级循环和ISR之间的数据交换是通过全局共享变量进行的:
(1)应用程序的程序员必须确保数据一致性。
3. 超级循环可以与系统计时器轻松同步,但:
(1)如果系统需要多种不同的周期时间,则会很难实现。
(2) 超过超级循环周期的耗时函数需要做拆分。
(3)增加软件开销,应用程序难以理解。
4. 超级循环使得应用程序变得非常复杂,因此难以扩展:
(1)一个简单的更改就可能产生不可预测的副作用,对这种副作用进行分析非常耗时。
(2)超级循环 概念的这些缺点可以通过使用实时操作系统 (RTOS) 来解决。
7.2 多任务系统
针对这些情况,使用多任务系统就可以解决这些问题了。下面是一个多任务系统的流程图:
多任务系统或者说RTOS的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。如上图所画的那样,创建了任务并完成OS初始化后,就可以通过调度器来决定任务A,任务B和任务C的运行,从而实现多任务系统。另外需要初学者注意的是,这里所说的多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更好的说明这个问题,再举一个详细的运行例子,运行条件如下:
1. 使用抢占式调度器。
2. 1个空闲任务,优先级最低。
3. 2个应用任务,一个高优先级和一个低优先级,优先级都比空闲任务优先级高。
4. 中断服务程序,含USB中断,串口中断和系统滴答定时器中断。
下图7.2所示是任务的运行过程,其中横坐标是任务优先级由低到高排列,纵坐标是运行时间,时间刻度有小到大。
图7.2 多任务系统运行过程
(1) 启动RTOS,首先执行高优先级任务。
(2) 高优先级任务等待事件标志(os_evt_wait_and)被挂起,低优先级任务得到执行。
(3) 低优先级任务执行的过程中产生USB中断,进入USB中断服务程序。
(4) 退出USB中断复位程序,回到低优先级任务继续执行。
(5) 低优先级任务执行过程中产生串口接收中断,进入串口接收中断服务程序。
(6) 退出串口接收中断复位程序,并发送事件标志设置消息(isr_evt_set),被挂起的高优先级任务就会重新进入就绪状态,这个时候高优先级任务和低优先级任务都在就绪态,基于优先级的调度器就会让高优先级的任务先执行,所有此时就会进入高优先级任务。
(7) 高优先级任务由于等待事件标志(os_evt_wait_and)会再次被挂起,低优先级任务开始继续执行。
(8) 低优先级任务调用函数os_dly_wait,低优先级任务被挂起,从而空闲任务得到执行。
(9) 空闲任务执行期间发生滴答定时器中断,进入滴答定时器中断服务程序。
(10) 退出滴答定时器中断,由于低优先级任务延时时间到,低优先级任务继续执行。
(11) 低优先级任务再次调用延迟函数os_dly_wait,低优先级任务被挂起,从而切换到空闲任务。空闲任务得到执行。
通过上面实例的讲解,大家应该对多任务系统完整的运行过程有了一个全面的认识。随着教程后面对调度器,任务切换等知识点的讲解,大家会对这个运行过程有更深刻的理解。
RTX就是一款支持多任务运行的实时操作系统,具有时间片,抢占式和合作式三种调度方法。通过RTX实时操作系统可以将程序函数分成独立的任务,并为其提供合理的调度方式。同时RTX实时操作系统为多任务的执行提供了以下重要优势:
任务调度 - 任务在需要时进行调用,从而确保了更好的程序执行和事件响应。
多任务- 任务调度会产生同时执行多个任务的效果。
确定性的行为 - 在定义的时间内处理事件和中断。
更短的 ISR - 实现更加确定的中断行为。
任务间通信 - 管理多个任务之间的数据、内存和硬件资源共享。
定义的堆栈使用 - 每个任务分配一个堆栈空间,从而实现可预测的内存使用。
系统管理 - 可以专注于应用程序开发而不是资源管理。
图7.3RTX中任务通信
7.3 任务设置
RTX操作系统的配置工作是通过配置文件RTX_Conf_CM.c实现。在MDK工程中打开文件RTX_Conf_CM.c,可以看到如下图7.4所示的工程配置向导:
图7.4RTX配置向导
用于任务配置的主要是如下两个参数:
Number of concurrent running tasks
参数范围0 – 250
表示同时运行的最大任务数,这个数值一定要大于等于用户实际创建的任务数,空闲任务不包含在这个里面。比如当前的数值是6,就表示用户最多可以创建6个任务。
Number of tasks with user-providedstack
参数范围0 – 250
表示自定义任务堆栈的任务数,如果这个参数定义为0的话,表示所有的任务都是使用的配置向导里面第三个参数Task statck size大小。比如:
Numberof concurrent running tasks = 6
Numberof tasks with user-provided stack = 0
表示允许用户创建6个任务,所有的6个任务都是分配第三个参数Task statck size大小的任务堆栈空间。
Numberof concurrent running tasks = 6
Numberof tasks with user-provided stack = 3
表示允许用户创建6个任务,其中3个任务是用户自定义任务堆栈大小,另外3个任务是用的第三个参数Task statck size大小的任务堆栈空间。
7.4 任务栈设置
不管是裸机编程还是RTOS编程,栈的分配大小都非常重要。局部变量,函数调时现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃。
裸机的情况下,用户可以在这里配置栈大小:
STM32F103工程中栈大小的配置文件
STM32F407工程中栈大小的配置文件
不同于裸机编程,在RTOS下,每个任务都有自己的栈空间。任务的栈大小可以在配置向导中通过如下参数进行配置:
需要大家注意的是,默认情况下用户创建的任务栈大小是由参数Task stack size决定的。如果觉得每个任务都分配同样大小的栈空间不方便的话,可以采用自定义任务栈的方式创建任务。采用自定义方式更灵活些。
实际应用中给任务开辟多大的堆栈空间合适呢,这时可以事先给任务开辟一个稍大些的堆栈空间,然后通过第三章3.4小节中介绍的RTX调试方法可以显示任务栈的使用情况,从而调试实际给任务开辟多大的栈空间比较合适。
RTX的任务切换和中断嵌套对栈空间的影响,待我们讲解RTX的任务切换和双堆栈指针章节(此章节在后期RTX教程升级版本时再配套)时再细说。这部分知识点也非常重要,对于初学者,先搞懂这里讲解的知识点即可。
7.5 系统栈设置
上面跟大家讲解了什么是任务栈,这里的系统栈又是什么呢?裸机的情况下,凡是用到栈空间的地方都是用的这里配置的栈空间:
STM32F103工程中栈大小的配置文件
STM32F407工程中栈大小的配置文件
在RTOS下,上面两个截图中设置的栈大小有了一个新的名字叫系统栈空间,而任务栈是不使用这里的空间的。任务栈不使用这里的栈空间,哪里使用这里的栈空间呢?答案就在中断函数和中断嵌套。
对于这个问题,简单的描述如下,更详细的内容待我们讲解RTX任务切换和双堆栈指针时再细说(此章节在后期RTX教程升级版本时再配套)。
1. 由于Cortex-M3和M4内核具有双堆栈指针,MSP主堆栈指针和PSP进程堆栈指针,或者叫PSP任务堆栈指针也是可以的。在RTX操作系统中,主堆栈指针MSP是给系统栈空间使用的,进程堆栈指针PSP是给任务栈使用的。也就是说,在RTX任务中,所有栈空间的使用都是通过PSP指针进行指向的。一旦进入了中断函数已经可能发生的中断嵌套都是用的MSP指针。这个知识点要记住他,当前可以不知道这是为什么,但是一定要记住。
2. 实际应用中系统栈空间分配多大,主要是看可能发生的中断嵌套层数,下面我们就按照最坏执行情况进行考虑,所有的寄存器都需要入栈,此时分为两种情况:
64字节
对于Cortex-M3内核和未使用FPU(浮点运算单元)功能的Cortex-M4内核在发生中断时需要将16个通用寄存器全部入栈,每个寄存器占用4个字节,也就是16*4 = 64字节的空间。
可能发生几次中断嵌套就是要64乘以几即可。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发生中断的话,有8个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈)。
200字节
对于具有FPU(浮点运算单元)功能的Cortex-M4内核,如果在任务中进行了浮点运算,那么在发生中断的时候除了16个通用寄存器需要入栈,还有34个浮点寄存器也是要入栈的,也就是(16+34)*4 = 200字节的空间。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。
(注:任务执行的过程中发送中断的话,有8个通用寄存器和18个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。)。
7.6 栈溢出检测
如果怕任务栈溢出,那么此功能就非常的有用了,用户只需在RTX的配置向导里面使能使用任务栈检测即可:
如果调试过程中某任务的任务栈溢出的话,会在这里有提示:
(注:实际测试发现,不使能此功能,在调试状态下也能够正确的显示任务栈溢出,待进一步测试)。
7.7 RTX初始化和启动
使用如下3个函数可以实现RTX的初始化:
os_sys_init()
os_sys_init_prio()
os_sys_init_user()
关于这3个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里我们重点的说一下函数os_sys_init_user函数,因为从本章节开始所有的例子都是用的这个函数作为RTX的初始化。
函数原型:
- void os_sys_init_user (
- void (*task)(void), /* 任务函数 */
- U8 priority, /* 任务优先级 (1-254) */
- void* stack, /* 任务栈 */
- U16 size); /* 任务栈大小,单位字节*/
函数描述:
函数os_sys_init_user用于实现RTX操作系统的初始化和启动任务的创建,并且使用这个函数做初始化还可以自定义任务栈的大小。
1. 第1个参数填启动任务的函数名。
2. 第2个参数是任务的优先级设置,用户可以设置的任务优先级范围是1-254。优先级0用于空闲任务,如果用户将这个参数设置为0的话,RTX系统会将其更改为1。优先级255被保留用于最重要的任务。
3. 第3个参数是任务栈地址。
4. 第4个参数是任务栈大小。
使用这个函数要注意以下几个问题
1. 必须在main函数中调用os_sys_init_user。
2. 任务栈空间必须8字节对齐,可以将任务栈数组定义成uint64_t类型即可。
3. 优先级255代表更重要的任务。
4. 对于RTX操作系统来说,优先级参数中数值越小优先级越低,也就是说空闲任务的优先级是最低的,因为它的优先级数值是0
使用举例:
- int main (void)
- {
-
- /* RTX初始化并创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
7.8 任务创建
使用如下4个函数可以实现RTX的任务创建:
os_tsk_create
os_tsk_create_ex
os_tsk_create_user
os_tsk_create_user_ex
关于这4个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里我们重点的说一下函数os_tsk_create_user函数,因为从本章节开始所有的例子都是用的这个函数作为RTX的任务创建。
函数原型:
- OS_TID os_tsk_create_user(
- void (*task)(void), /* 任务函数 */
- U8 priority, /* 任务优先级 (1-254) */
- void* stk, /* 任务栈*/
- U16 size ); /* 任务栈大小*/
函数描述:
函数os_tsk_create_use用于实现RTX操作系统的任务创建,并且还可以自定义任务栈的大小。
1. 第1个参数填创建任务的函数名。
2. 第2个参数是任务的优先级设置,用户可以设置的任务优先级范围是1-254。优先级0用于空闲任务,如果用户将这个参数设置为0的话,RTX系统会将其更改为1。优先级255被保留用于更重要的任务。如果新创建的任务优先级比当前正在执行任务的优先级高,那么就会立即切换到高优先级任务去执行。
3. 第3个参数是任务栈地址。
4. 第4个参数是任务栈大小。
5. 函数的返回值是任务的ID,使用ID号可以区分不同的任务。
使用这个函数要注意以下问题
1. 任务栈空间必须8字节对齐,可以将任务栈数组定义成uint64_t类型即可。
使用举例:
- /*
- **********************************************************************************************************
- 变量
- **********************************************************************************************************
- */
- static uint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
-
- /* 任务句柄 */
- OS_TID HandleTaskUserIF = NULL;
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- }
7.9 任务删除
使用如下2个函数可以实现RTX的任务删除:
os_tsk_delete
os_tsk_delete_self
关于这2个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里重点的说一下函数os_tsk_delete,因为从本章节开始所有的例子都是用的这个函数作为RTX的任务删除。
函数原型:
- OS_RESULT os_tsk_delete (
- OS_TID task_id ); /* 任务ID */
函数描述:
函数os_tsk_create_use用于实现RTX操作系统的任务删除
1. 第1个参数填要删除任务的ID。
2. 如果任务删除成功,函数返回OS_R_OK,其余所有情况返回OS_R_NOK,比如所写的任务ID不存在。
使用这个函数要注意以下问题:
1. 如果用往此函数里面填的任务ID是0的话,那么删除的就是当前正在执行的任务,此任务被删除后,RTX会切换到任务就绪列表里面下一个要执行的高优先级任务。
使用举例:
- /*
- **********************************************************************************************************
- 变量
- **********************************************************************************************************
- */
- static uint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
-
- /* 任务句柄 */
- OS_TID HandleTaskUserIF = NULL;
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskDelete
- * 功能说明: 任务删除
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskDelete (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- if(HandleTaskUserIF!= NULL)
- {
- if(os_tsk_delete(HandleTaskUserIF) == OS_R_OK)
- {
- HandleTaskLED = NULL;
- printf("任务AppTaskUser删除成功\r\n");
- }
- else
- {
- printf("任务AppTaskUser删除失败\r\n");
- }
- }
-
- }
7.10 空闲任务
几乎所有的小型 RTOS 中都会有一个空闲任务,空闲任务应该属于系统任务,是必须要执行的,用户程序不能将其关闭。不光小型系统中有空闲任务,大型的系统里面也有的,比如WIN7,下面的截图就是 WIN7中的空闲进程。
空闲任务主要有以下几个作用:
1. 用户不能让系统一直在执行各个应用任务,这样的话系统利用率就是 100%,系统就会一直的超负荷运行,所以空闲任务很有必要。
2. 为了更好的实现低功耗,空闲任务也很有必要,用户可以在空闲任务中实现睡眠,停机等低功耗措施。
RTX操作系统的空闲任务在文件RTX_Conf_CM.c文件里面,源代码如下:
- /*--------------------------- os_idle_demon ---------------------------------*/
-
- __task void os_idle_demon (void) {
- /* The idle demon is a system task, running when no other task is ready */
- /* to run. The 'os_xxx' function calls are not allowed from this task. */
-
- for (;;) {
- /* HERE: include optional user code to be executed when no task runs.*/
- }
- }
7.11 实验例程说明
7.11.1 STM32F103开发板实验
配套例子:
V4-402_RTX实验_任务创建和删除
实验目的:
1. 学习RTX的任务创建和删除。
实验内容:
1. K1按键按下,串口打印。
2. K2按键按下,删除任务AppTaskLED。
3. K3按键按下,重新创建任务AppTaskLED。
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
1. Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
2. Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,删除任务AppTaskLED */
- case KEY_DOWN_K2:
- printf("K2键按下,删除任务HandleTaskLED\r\n");
- if(HandleTaskLED != NULL)
- {
- if(os_tsk_delete(HandleTaskLED) == OS_R_OK)
- {
- HandleTaskLED = NULL;
- printf("任务AppTaskLED删除成功\r\n");
- }
- else
- {
- printf("任务AppTaskLED删除失败\r\n");
- }
- }
- break;
-
- /* K3键按下,重新创建任务AppTaskLED */
- case KEY_DOWN_K3:
- printf("K3键按下,重新创建任务AppTaskLED\r\n");
- if(HandleTaskLED == NULL)
- {
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk));/* 任务栈大小,单位字节数 */
- }
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
- os_dly_wait(200);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里是用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
- os_dly_wait(100);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
7.11.2 STM32F407开发板实验
配套例子:
V5-402_RTX实验_任务创建和删除
实验目的:
1. 学习RTX的任务创建和删除。
实验内容:
1. K1按键按下,串口打印。
2. K2按键按下,删除任务AppTaskLED。
3. K3按键按下,重新创建任务AppTaskLED。
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
1. Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
2. Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,删除任务AppTaskLED */
- case KEY_DOWN_K2:
- printf("K2键按下,删除任务HandleTaskLED\r\n");
- if(HandleTaskLED != NULL)
- {
- if(os_tsk_delete(HandleTaskLED) == OS_R_OK)
- {
- HandleTaskLED = NULL;
- printf("任务AppTaskLED删除成功\r\n");
- }
- else
- {
- printf("任务AppTaskLED删除失败\r\n");
- }
- }
- break;
-
- /* K3键按下,重新创建任务AppTaskLED */
- case KEY_DOWN_K3:
- printf("K3键按下,重新创建任务AppTaskLED\r\n");
- if(HandleTaskLED == NULL)
- {
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk));/* 任务栈大小,单位字节数 */
-
- }
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
- os_dly_wait(200);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里是用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- while(1)
- {
- bsp_LedToggle(1)