第10章 任务调度-抢占式,时间片和合作式
本章教程为大家将介绍RTX操作系统支持的任务调度方式,抢占式,时间片和合作式,这部分算是RTX操作系统的核心了。对于初学者来说,要一下子就能够理解这些比较困难些,需要多花些时间把这些基本概念搞清楚,然后阅读下源码,深入理解实现方法。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
10.1 RTX支持的调度方式
10.2 什么是调度器
10.3 抢占式调度器
10.4 时间片调度器
10.5 合作式调度器
10.6 时间片调度器实验例程说明
10.7 合作式调度器实验例程说明
10.8 总结
10.1 RTX支持的调度方式
RTX操作系统支持三种调度方式:
抢占式调度
每个任务都有不同的优先级,任务会一直运行直到被高优先级任务抢占或者遇到阻塞式的API函数,比如os_dly_wait。
时间片调度
每个任务都有相同的优先级,任务会运行固定的时间片个数直到遇到系统阻塞式的API函数,比如os_dly_wait。
合作式调度
每个任务都有相同的优先级,而且时间片调度要被禁止。任务会一直的运行直到遇到阻塞式的API函数,比如os_dly_wait或者用户调用函数os_tsk_pass。
对于RTX操作系统而言,实际应用主要是抢占式调度和时间片调度,合作式调度用到的很少。
10.2 什么是调度器
简单的说,调度器就是使用相关的调度算法来决定当前需要执行的任务。所有的调度器有一个共同的特性:调度器可以区分就绪态任务和挂起任务(由于延迟,信号量等待,邮箱等待,事件组等待等原因而使得任务被挂起)。调度器可以选择就绪态中的一个任务,然后激活它(通过执行这个任务)。当前正在执行的任务是运行态的任务。不同调度器之间最大的区别就是如何分配就绪态任务间的完成时间。
嵌入式实时操作系统的核心就是调度器和任务切换,调度器的核心就是调度算法。任务切换的实现在各个RTOS中区别不大,基本相同的硬件内核架构,任务切换也是相似的。调度算法就有些区别了。下面我们主要了解一下合作式调度器,抢占式调度器和时间片调度器。
10.3 抢占式调度器
10.3.1 抢占式调度器基本概念
在实际的应用中,不同的任务需要不同的响应时间。例如,我们在一个应用中需要使用电机,键盘和LCD显示。电机比键盘和LCD需要更快速的响应,如果我们使用合作式调度器或者时间片调度,那么电机将无法得到及时的响应,这时抢占式调度是必须的。
如果使用了抢占式调度,最高优先级的任务一旦就绪,总能得到 CPU 的控制权。当一个运行着的任务被其它高优先级的任务抢占,当前任务的 CPU 使用权就被剥夺了,或者说被挂起了,那个高优先级的任务立刻得到了 CPU 的控制权。如果是中断服务程序使一个高优先级的任务进入就绪态,中断完成时,被中断的任务被挂起,优先级高的那个任务开始运行。
使用抢占式调度器,使得最高优先级的任务什么时候可以执行,可以得到 CPU 的控制权是可知的,同时使得任务级响应时间得以最优化。
总的来说,学习抢占式调度掌握最关键的一点是:抢占式调度器会为每个任务都分配一个优先级,调度器会激活就绪任务中优先级最高的任务,并运行任务就绪列表里面优先级最高的那个任务。
10.3.2 RTX抢占式调度器的实现
如果用户在RTX的配置向导中禁止使用时间片调度,那么每个任务必须配置不同的优先级。当RTX多任务启动执行后:
1. 首先执行的最高优先级的任务Task1,Task1会一直运行直到遇到系统阻塞式的API函数,比如延迟,事件标志等待,信号量等待,Task1任务会被挂起,也就是释放CPU的执行权,让低优先级的任务得到执行。
2. RTX操作系统继续执行任务就绪列表中下一个最高优先级的任务Task2,Task2执行过程中有两种情况:
(1)Task1延迟时间到,接收到信号量消息等方面的原因,在抢占式调度器的作用下,Task2的执行会被Task1抢占。
(2)Task2会一直运行直到遇到系统阻塞式的API函数,比如延迟,事件标志等待,信号量等待,Task2任务会被挂起,继而执行就绪列表中下一个最高优先级的任务。
3. 如果用户创建了多个任务并且采用抢占式调度器的话,基本都是按照上面两条来执行。根据抢占式调度器,当前的任务要么被高优先级任务抢占,要么通过调用阻塞式API来释放CPU使用权让低优先级任务执行,没有用户任务执行时就执行空闲任务。
下面我们通过如下的框图来说明一下抢占式调度在RTX中的运行过程,让大家有一个形象的认识。

运行条件:
1. 这里仅对抢占式调度进行说明。
2. 创建3个任务Task1,Task2和Task3。
3. Task1的优先级为1,Task2的优先级为2,Task3的优先级为3。RTX操作系统是设置的数值越小任务优先级越低,故Task3的优先级最高,Task1的优先级最低。
4. 此框图是RTX操作系统运行过程中的一部分。
运行过程描述如下:
1. 此时任务Task1在运行中,运行过程中由于Task2就绪,在抢占式调度器的作用下任务Task2抢占Task1的执行。Task2进入到运行态,Task1由运行态进入到就绪态。
2. 任务Task2在运行中,运行过程中由于Task3就绪,在抢占式调度器的作用下任务Task3抢占Task2的执行。Task3进入到运行态,Task2由运行态进入到就绪态。
3. 任务Task3运行过程中调用了阻塞式API函数,比如os_dly_wait,任务Task3被挂起,在抢占式调度器的作用下查找到下一个要执行的最高优先级任务是Task2,任务Task2由就绪态进入到运行态。
4. 任务Task2在运行中,运行过程中由于Task3再次就绪,在抢占式调度器的作用下任务Task3抢占Task2的执行。Task3进入到运行态,Task2由运行态进入到就绪态。
上面就是一个简单的不同优先级任务通过抢占式调度进行任务调度和任务切换的过程。
10.4 时间片调度器
10.4.1 抢占式调度器基本概念
在小型的嵌入式RTOS中,最常用的的时间片调度算法就是Round-robin调度算法。这种调度算法可以用于抢占式或者合作式的多任务中,时间片调度适合用于不要求任务实时响应的情况下。
实现Round-robin调度算法需要给同优先级的任务分配一个专门的列表,用于记录当前就绪的任务,并为每个任务分配一个时间片(也就是需要运行的时间长度,时间片用完了就进行任务切换)。
10.4.2 RTX时间片调度器的实现
在RTX操作系统中只有同优先级任务才会使用时间片调度,另外还需要用户在配置向导中使能时间片调度。

Round-Robin Task switching
选择是否使能时间片调度,选上单选框表示使能时间片调度,取消单选框表示不使用时间片调度。
Round-Robin Timeout [ticks]
范围1 – 1000。
表示时间片的大小,单位是系统时钟节拍个数。
下面我们通过如下的框图来说明一下时间片调度在RTX中的运行过程,让大家有一个形象的认识。

运行条件:
1. 这里仅对时间片调度进行说明。
2. 创建4个同优先级任务Task1,Task2,Task3和Task4。
3. 每个任务分配的时间片大小是5个系统时钟节拍。
运行过程描述如下:
1. 先运行任务Task1,运行够5个系统时钟节拍后,通过时间片调度切换到任务Task2。
2. 任务Task2运行够5个系统时钟节拍后,通过时间片调度切换到任务Task3。
3. 任务Task3在运行期间调用了阻塞式API函数,调用函数时,5个系统时钟节拍的时间片大小还没有用完,此时会通过时间片调度切换到下一个任务Task4。
4. 任务Task4运行够5个系统时钟节拍后,通过时间片调度切换到任务Task1。
上面就是一个简单的同优先级任务通过时间片调度进行任务调度和任务切换的过程。
10.5 合作式调度器
10.5.1 合作式调度器基本概念
RTX中设计的合作式调度器比较简单,实战意义不大,用户作为了解即可,项目中没有必要采用这种调度方式的任务设计,这个也是官方的意思。原话是这么说的:For most applications, this(Round Robin Pre-emptive Scheduling) is the most useful option and you should use this scheduling schemeunless there is a strong reason to do otherwise。
对于同优先级的任务,如果用户将RTX系统配置向导中时间片调度关闭后,这些同优先级的任务就是在合作式调度器的作用下运行。其表现出来的效果就是这些同优先级的任务会依次执行,每个任务会一直执行直到遇到阻塞式API函数或者函数os_tsk_pass ()就会切换到下个任务,这就是RTX中所说的合作式调度器。
10.5.2 RTX合作式调度器的实现
如果用户打算使用合作式调度器必须在RTX的配置向导中将时间片调度关闭并且这些任务必须是同优先级的。
下面我们通过如下的框图来说明一下合作式调度在RTX中的运行过程,让大家有一个形象的认识。

运行条件:
1. 这里仅对合作式调度进行说明。
2. 创建4个同优先级任务Task1,Task2,Task3和Task4。
运行过程描述如下:
1. 先运行任务Task1,然后调用阻塞式API或者os_tsk_pass切换到任务Task2。
2. 任务Task2运行,然后调用阻塞式API或者os_tsk_pass切换到任务Task3。
3. 任务Task3运行,然后调用阻塞式API或者os_tsk_pass切换到任务Task4。
4. 任务Task4运行,然后调用阻塞式API或者os_tsk_pass重新切换回任务Task1。
5. 一直如此循环往复下去。
上面就是一个简单的同优先级任务通过合作式调度进行任务调度和任务切换的过程。
10.6 时间片调度器实验例程说明
10.6.1 STM32F103开发板实验
配套例子:
V4-405_RTX实验_时间片调度
实验目的:
1. 本实验主要学习RTX的时间片调度
实验内容:
1. K1按键按下,串口打印。
2. 本实验将任务AppTaskLED和AppTaskMsgPro的优先级都设置为2,同优先级的任务才会用到时间片调度。
3. 时间片调度的使能和每个任务时间片的大小在文件RTX_Conf_CM.c文件里面
#define OS_ROBIN 1 //使能时间片调度
#define OS_ROBINTOUT 5 //设置每个同优先级任务的时间片大小。
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
Round-Robin Task switching
使能时间片调度
Round-Robin Timeout [ticks]
范围1 – 1000。
设置同优先级任务的时间片是5个系统时间节拍。
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, /* 任务函数 */
- 2, /* 任务优先级 */
- &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键按下,非特权级模式下设置NVIC分组,从而导致系统进入到硬件异常HardFault_Handler */
- case KEY_DOWN_K2:
- printf("K2键按下,非特权级模式下设置NVIC分组,从而导致系统进入到硬件异常
- HardFault_Handler\r\n");
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- const uint16_t usFrequency = 500; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- /* 创建任务 */
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
10.6.2 STM32F407开发板实验
配套例子:
V5-405_RTX实验_时间片调度
实验目的:
1. 本实验主要学习RTX的时间片调度
实验内容:
1. K1按键按下,串口打印。
2. 本实验将任务AppTaskLED和AppTaskMsgPro的优先级都设置为2,同优先级的任务才会用到时间片调度。
3. 时间片调度的使能和每个任务时间片的大小在文件RTX_Conf_CM.c文件里面
#define OS_ROBIN 1 //使能时间片调度
#define OS_ROBINTOUT 5 //设置每个同优先级任务的时间片大小。
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
Round-Robin Task switching
使能时间片调度
Round-Robin Timeout [ticks]
范围1 – 1000。
设置同优先级任务的时间片是5个系统时间节拍。
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任务创建:
- /*
- /*
- *********************************************************************************************************
- * 函 数 名: 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;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- const uint16_t usFrequency = 500; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- /* 创建任务 */
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
10.7 合作式调度器实验例程说明
10.7.1 STM32F103开发板实验
配套例子:
V4-406_RTX实验_合作式调度
实验目的:
1. 本实验主要学习RTX的合作式调度
实验内容:
1. K1按键按下,串口打印。
2. 本实验将任务AppTaskLED和AppTaskMsgPro的优先级都设置为2,同优先级的任务才会用到合作式调度。
3. 使用合作式调度的话,用户必须在RTX操作系统的配置向导文件RTX_Conf_CM.c中禁止时间片调度:
#define OS_ROBIN 0 //禁止时间片调度
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
Round-Robin Task switching
使用合作式调度必须禁止时间片调度,切记。
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, /* 任务函数 */
- 2, /* 任务优先级 */
- &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;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- const uint16_t usFrequency = 500; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- /* 创建任务 */
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
10.7.2 STM32F407开发板实验
配套例子:
V5-406_RTX实验_合作式调度
实验目的:
1. 本实验主要学习RTX的合作式调度
实验内容:
1. K1按键按下,串口打印。
2. 本实验将任务AppTaskLED和AppTaskMsgPro的优先级都设置为2,同优先级的任务才会用到合作式调度。
3. 使用合作式调度的话,用户必须在RTX操作系统的配置向导文件RTX_Conf_CM.c中禁止时间片调度:
#define OS_ROBIN 0 //禁止时间片调度
4. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:

Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,这里是用作LED闪烁。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
Round-Robin Task switching
使用合作式调度必须禁止时间片调度,切记。
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任务创建:
- /*
- /*
- *********************************************************************************************************
- * 函 数 名: 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;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,这里用作LED闪烁
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- const uint16_t usFrequency = 500; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(1);
- bsp_LedToggle(4);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- /* 创建任务 */
- AppTaskCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
10.8 总结
本章节是RTX操作系统的核心,初学者要深入理解的话要多花些时间。当然,如果有其它RTOS的基础的话,这个学起来也是很快的。
