【STM32F407】第7章 RTX5任务管理


论坛原始地址(持续更新):http://www.armbbs.cn/forum.php?mod=viewthread&tid=93149

第7章   RTX5任务管理

对于初学者,特别是对于没有RTOS基础的同学来说,了解RTX5的任务管理非常重要,了解任务管理的目的就是让初学者从裸机的,单任务编程过渡到带OS的,多任务编程上来。搞清楚了这点,那么RTX5学习就算入门了。

7.1 初学者重要提示

7.2 单任务系统

7.3 多任务系统

7.4 RTX5任务设置

7.5 RTX5任务栈设置

7.6 RTX5系统栈设置

7.7 RTX5栈溢出检测

7.8 RTX5初始化和启动函数osKernelInitialize

7.9 RTX5启动函数osKernelStart

7.10 RTX5任务创建函数osThreadNew

7.11 RTX5任务删除函数osThreadTerminate

7.12 RTX5空闲任务

7.13实验例程说明

7.14 总结

7.1   初学者重要提示

1、学习本章节前有必要对各种对齐方式有个全面认识:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=109400 。

7.2   单任务系统

学习多任务系统之前,我们先来回顾下单任务系统的编程框架,即裸机时的编程框架。裸机编程主要是采用超级循环(super-loops)系统,又称前后台系统。应用程序是一个无限的循环,循环中调用相应的函数完成相应的操作,这部分可以看做后台行为,中断服务程序处理异步事件,这部分可以看做是前台行为。后台也可以叫做任务级,前台也叫作中断级。

图7.1 单任务系统

对于前后台系统的编程思路主要有以下两种方式:

7.2.1  查询方式

对于一些简单的应用,处理器可以查询数据或者消息是否就绪,就绪后进行处理,然后再等待,如此循环下去。对于简单的任务,这种方式简单易处理。但大多数情况下,需要处理多个接口数据或者消息,那就需要多次处理,如下面的流程图所示:

用查询方式处理简单的应用,效果比较好,但是随着工程的复杂,采用查询方式实现的工程就变的很难维护,同时,由于无法定义查询任务的优先级,这种查询方式会使得重要的接口消息得不到及时响应。比如程序一直在等待一个非紧急消息就绪,如果这个消息后面还有一个紧急的消息需要处理,那么就会使得紧急消息长时间得不到执行。

7.2.2  中断方式

对于查询方式无法有效执行紧急任务的情况,采用中断方式就有效的解决了这个问题,下面是中断方式简单的流程图:

 采用中断和查询结合的方式可以解决大部分裸机应用,但随着工程的复杂,裸机方式的缺点就暴露出来了

1、  必须在中断(ISR)内处理时间关键运算:

  •  ISR 函数变得非常复杂,并且需要很长执行时间。
  •  ISR 嵌套可能产生不可预测的执行时间和堆栈需求。

2、  超级循环和ISR之间的数据交换是通过全局共享变量进行的:

  •  应用程序的程序员必须确保数据一致性。

3、  超级循环可以与系统计时器轻松同步,但:

  •  如果系统需要多种不同的周期时间,则会很难实现。
  •  超过超级循环周期的耗时函数需要做拆分。
  •  增加软件开销,应用程序难以理解。

4、  超级循环使得应用程序变得非常复杂,因此难以扩展:

  •  一个简单的更改就可能产生不可预测的副作用,对这种副作用进行分析非常耗时。
  •  超级循环 概念的这些缺点可以通过使用实时操作系统 (RTOS) 来解决。

7.3   多任务系统

针对这些情况,使用多任务系统就可以解决这些问题了。下面是一个多任务系统的流程图:

 

多任务系统或者说RTOS的实现,重点就在这个调度器上,而调度器的作用就是使用相关的调度算法来决定当前需要执行的任务。如上图所画的那样,创建了任务并完成OS初始化后,就可以通过调度器来决定任务A,任务B和任务C的运行,从而实现多任务系统。另外需要初学者注意的是,这里所说的多任务系统同一时刻只能有一个任务可以运行,只是通过调度器的决策,看起来像所有任务同时运行一样。为了更好的说明这个问题,再举一个详细的运行例子,运行条件如下:

  •   使用抢占式调度器。
  •   1个空闲任务,优先级最低。
  •   2个应用任务,一个高优先级和一个低优先级,优先级都比空闲任务优先级高。
  •   中断服务程序,含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.3 RTX中任务通信

7.4   RTX5任务设置

RTX5操作系统的配置工作是通过配置文件RTX_Config.h实现。在MDK工程中打开文件RTX_Config.h,可以看到如下图7.4所示的工程配置向导:

 

图7.4 RTX配置向导

我们使用全局动态内存,即Global Dynamic Memory size,所以我们这里无需做特别修改。

7.5   RTX5任务栈设置

不管是裸机编程还是RTOS编程,栈的分配大小都非常重要。局部变量,函数调时现场保护和返回地址,函数的形参,进入中断函数前和中断嵌套等都需要栈空间,栈空间定义小了会造成系统崩溃。

裸机的情况下,用户可以在这里配置栈大小:

 不同于裸机编程,在RTOS下,每个任务都有自己的栈空间。任务的栈大小可以在配置向导中通过如下参数进行配置:

需要大家注意的是,默认情况下用户创建的任务栈大小是由参数Default Thread Task stack size决定的。如果觉得每个任务都分配同样大小的栈空间不方便的话,可以采用自定义任务栈的方式创建任务。采用自定义方式更灵活些。

实际应用中给任务开辟多大的堆栈空间合适呢,这时可以事先给任务开辟一个稍大些的堆栈空间,然后通过第6章6.3小节中介绍的RTX5调试方法可以显示任务栈的使用情况,从而调试实际给任务开辟多大的栈空间比较合适。

RTX5的任务切换和中断嵌套对栈空间的影响,待我们讲解RTX5的任务切换和双堆栈指针章节(此章节在后期RTX5教程升级版本时再配套)时再细说。这部分知识点也非常重要,对于初学者,先搞懂这里讲解的知识点即可。

7.6   RTX5系统栈设置

上面跟大家讲解了什么是任务栈,这里的系统栈又是什么呢?裸机的情况下,凡是用到栈空间的地方都是用的这里配置的栈空间:

 在RTOS下,上面两个截图中设置的栈大小有了一个新的名字叫系统栈空间,而任务栈是不使用这里的空间的。任务栈不使用这里的栈空间,哪里使用这里的栈空间呢?答案就在中断函数和中断嵌套。

对于这个问题,简单的描述如下,更详细的内容待我们讲解RTX5任务切换和双堆栈指针时再细说(此章节在后期RTX5教程升级版本时再配套)。

1、  由于Cortex-M3,M4,M7内核具有双堆栈指针,MSP主堆栈指针和PSP进程堆栈指针,或者叫PSP任务堆栈指针也是可以的。在RTX5操作系统中,主堆栈指针MSP是给系统栈空间使用的,进程堆栈指针PSP是给任务栈使用的。也就是说,在RTX5任务中,所有栈空间的使用都是通过PSP指针进行指向的。一旦进入了中断函数已经可能发生的中断嵌套都是用的MSP指针。这个知识点要记住他,当前可以不知道这是为什么,但是一定要记住。

2、  实际应用中系统栈空间分配多大,主要是看可能发生的中断嵌套层数,下面我们就按照最坏执行情况进行考虑,所有的寄存器都需要入栈,此时分为两种情况:

  •   64字节

对于Cortex-M3内核和未使用FPU(浮点运算单元)功能的Cortex-M4,M6内核在发生中断时需要将16个通用寄存器全部入栈,每个寄存器占用4个字节,也就是16*4 = 64字节的空间。

可能发生几次中断嵌套就是要64乘以几即可。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。

(注:任务执行的过程中发生中断的话,有8个寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余寄存器入栈以及发生中断嵌套都是用的系统栈)。

  •   200字节

对于具有FPU(浮点运算单元)功能的Cortex-M4,M7内核,如果在任务中进行了浮点运算,那么在发生中断的时候除了16个通用寄存器需要入栈,还有34个浮点寄存器也是要入栈的,也就是(16+34)*4 = 200字节的空间。当然,这种是最坏执行情况,也就是所有的寄存器都入栈。

(注:任务执行的过程中发送中断的话,有8个通用寄存器和18个浮点寄存器是自动入栈的,这个栈是任务栈,进入中断以后其余通用寄存器和浮点寄存器入栈以及发生中断嵌套都是用的系统栈。)。

7.7   RTX5栈溢出检测

如果怕任务栈溢出,那么此功能就非常的有用了,用户只需在RTX5的配置向导里面使能使用任务栈检测即可:

 

如果调试过程中某任务的任务栈溢出的话,会在这里有提示:

 (注:实际测试发现,不使能此功能,在调试状态下也能够正确的显示任务栈溢出,待进一步测试)。

7.8   RTX5初始化函数osKernelInitialize

函数原型:

osStatus_t osKernelInitialize    (void)  

函数描述:

此函数用于初始化RTX5内核。这个函数调用前,只有osKernelGetInfo 和 osKernelGetState可以被调用。

函数参数:

  返回值:

  •   osOK 表示返回成功。
  •   osError 表示未指定类型的错误。
  •   osErrorISR 表示不支持在中断服务程序里面调用。
  •   osErrorNoMemory 表示没有内存空间不足。

注意事项:

  这个函数不可以在中断服务程序里面调用。

使用举例:

/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
     /* HAL库,MPU,Cache,时钟等系统初始 */
    System_Init();

    /* 内核开启前关闭HAL的时间基准 */
    HAL_SuspendTick();
    
    /* 进入ThreadX内核 */
    tx_kernel_enter();

    while(1);
}

7.9   RTX5启动函数osKernelStart

函数原型:

osStatus_t osKernelStart (void )

函数描述:

此函数用于启动RTX5内核并开始执行任务切换。正常情况下这个函数是不会返回的,如果返回了,说明启动失败。这个函数调用前,只有osKernelGetInfo ,osKernelGetState和osXxxNew(任务和任务通信组件创建)可以被调用。

函数参数:

  返回值:

  •  osError 表示未指定类型的错误。
  •  osErrorISR 表示不支持在中断服务程序里面调用。

注意事项:

  •   这个函数不可以在中断服务程序里面调用。

使用举例:

/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
int main(void)
{
     /* HAL库,MPU,Cache,时钟等系统初始 */
    System_Init();

    /* 内核开启前关闭HAL的时间基准 */
    HAL_SuspendTick();
    
    /* 进入ThreadX内核 */
    tx_kernel_enter();

    while(1);
}

7.10 RTX5任务创建函数osThreadNew

函数原型:

osThreadId_t osThreadNew(osThreadFunc_t     func,
                         void *     argument,
                         const osThreadAttr_t *     attr )

函数描述:

此函数用于实现RTX5操作系统的任务创建,并且还可以自定义任务栈的大小。

函数参数:

  •   第1个参数填创建任务的函数名。
  •   第2个参数是传递给任务的形参。
  •   第3个参数是任务属性,如果如果写NULL的话,将使用默认属性(含默认任务栈大小),也可以用户定义,可以定义参数如下:
typedef struct {
  const char                   *name;   ///< 任务名
  uint32_t                 attr_bits;   ///< 任务属性,支持独立Detached和联合Joinable两种模式
  void                      *cb_mem;    ///< 任务控制块地址
  uint32_t                   cb_size;   ///< 任务控制块大小
  void                   *stack_mem;    ///< 任务栈地址
  uint32_t                stack_size;   ///< 任务栈大小
  osPriority_t              priority;   ///< 任务优先级 (默认: osPriorityNormal)
  TZ_ModuleId_t            tz_module;   ///< TrustZone标识符
  uint32_t                  reserved;   ///< 默认 (必须是 0)
} osThreadAttr_t;
  •   函数的返回值是任务的ID,使用ID号可以区分不同的任务。

注意事项:

  •   这个函数不可以在中断服务程序里面调用。
  •   此函数可以在osKernelStart前调用,但不可以在osKernelInitialize前调用。

使用举例1:简单创建

__NO_RETURN void thread1 (void *argument) 
{
  // ...
  for (;;) {}
}
 
int main (void) {

  osKernelInitialize();
  ;
  osThreadNew(thread1, NULL, NULL);     // 使用默认属性创建
  ;
  osKernelStart(); 
}

使用举例2:自定义任务栈创建

__NO_RETURN void thread1 (void *argument) {
  // ...
  for (;;) {}
}
 
const osThreadAttr_t thread1_attr = {
  .stack_size = 1024           // 设置任务栈大小是1024字节,其它参数使用默认配置。注意这种写法需要C语言的
C99标准支持。
};
 
int main (void) {
  ;  
  osThreadNew(thread1, NULL, &thread1_attr);   // 创建任务
  ;
}

使用举例2:自定义任务栈创建

const osThreadAttr_t thread1_attr = {
  .stack_size = 1024           // 设置任务栈大小是1024字节,其它参数使用默认配置。注意这种写法需要C语言的
C99标准支持。
};

__NO_RETURN void thread1 (void *argument) {
  // ...
  for (;;) {}
}
 
int main (void) {
  ;  
  osThreadNew(thread1, NULL, &thread1_attr);   // 创建任务
  ;
}

使用举例3:采用静态方式创建任务,即定义一个全局变量数组,注意任务栈要8字节对齐,可以将任务栈数组定义成uint64_t类型即可,这样就可以保证任务栈是8字节对齐的:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=109400

static uint64_t thread1_stk_1[1024/8];

const osThreadAttr_t thread1_attr = {
  .stack_mem  = &thread1_stk_1[0],  //采用静态数组设置任务栈大小和地址,其它参数使用默认配置。
注意这种写法需要C语言的C99标准支持。
  .stack_size = sizeof(thread1_stk_1)
};

__NO_RETURN void thread1 (void *argument) {
  // ...
  for (;;) {}
}

 
int main (void) {
  ;  
  osThreadNew(thread1, NULL, &thread1_attr);   // 创建任务
  ;
}

使用举例4:采用静态方式创建任务控制块

static osRtxThread_t thread1_tcb;
 
const osThreadAttr_t thread1_attr = {
  .cb_mem  = &thread1_tcb,        //采用静态变量设置任务控制块,其它参数使用默认配置。
                                    注意这种写法需要C语言的C99标准支持
  .cb_size = sizeof(thread1_tcb),
};
 
int main (void) {
  ;
  osThreadNew(thread1, NULL, &thread1_attr);    // Create thread with custom tcb memory
  ;
}

使用举例5:创建不同的任务优先级

__NO_RETURN void thread1 (void *argument) {
  // ...
  for (;;) {}
}
 
const osThreadAttr_t thread1_attr = {
  .priority = osPriorityHigh      //设置任务优先级 
  
};
 
int main (void) {
  ;
  osThreadNew(thread1, NULL, &thread1_attr);
  ;
}

使用举例6:创建任务Joinable联合模式

任务设置为Joinable模式后,可以函数osThreadExit 和 osThreadJoin  配合一起用,仅此作用,别无他用。任务里面调用函数osThreadExit(注意仅仅停止任务调用,并没有销毁任务)退出任务。并通过函数osThreadJoin最终返回。

__NO_RETURN void worker (void *argument) {
  ; // work a lot on data[]
  osDelay(1000U);
  osThreadExit();
}

__NO_RETURN void thread1 (void *argument) {
  osThreadAttr_t worker_attr;
  osThreadId_t worker_ids[4];
  uint8_t data[4][10];
  memset(&worker_attr, 0, sizeof(worker_attr));
  worker_attr.attr_bits = osThreadJoinable;

  worker_ids[0] = osThreadNew(worker, &data[0][0], &worker_attr);
  worker_ids[1] = osThreadNew(worker, &data[1][0], &worker_attr);
  worker_ids[2] = osThreadNew(worker, &data[2][0], &worker_attr);
  worker_ids[3] = osThreadNew(worker, &data[3][0], &worker_attr);

  osThreadJoin(worker_ids[0]);
  osThreadJoin(worker_ids[1]);
  osThreadJoin(worker_ids[2]);
  osThreadJoin(worker_ids[3]);

  osThreadExit();
}

7.11 RTX5任务删除函数osThreadTerminate

函数原型:

osStatus_t osThreadTerminate (osThreadId_t thread_id);

函数描述:

此函数用于实现RTX5操作系统的任务删除。

函数参数:

1、  第1个参数填要删除任务的ID。

2、  返回值:

  •   osOK 表示任务删除成功。
  •   osErrorParameter  表示任务ID是NULL或者无效
  •   osErrorResource 表示无效的任务状态。
  •   osErrorISR 表示不支持在中断服务程序里面调用。

注意事项:

  •   这个函数不可以在中断服务程序里面调用。
  •   注意避免去删除ID不存在的任务或者任务已经被删除。
  •   osThreadTerminate删除Detached独立任务,对任务ID的后续访问(比如osThreadGetState))将返回osThreadError。

osThreadTerminate不会销毁Joinable任务,调用函数osThreadGetState会返回状态osThreadTerminated,直到调用了函数osThreadJoin才会完全销毁。

使用举例

#include "cmsis_os2.h"
 
void Thread_1 (void *arg);                      // 任务声明
 
void ThreadTerminate_example (void) {
  osStatus_t status;
  osThreadId_t id;
 
  id = osThreadNew(Thread_1, NULL, NULL);       // 创建任务
                                               
  status = osThreadTerminate(id);               // 删除任务
  if (status == osOK) {
                                                // 任务删除成功
  }
  else {
                                               // 任务删除失败
  }
}

7.12 RTX5空闲任务

几乎所有的小型 RTOS 中都会有一个空闲任务,空闲任务应该属于系统任务,是必须要执行的,用户程序不能将其关闭。不光小型系统中有空闲任务,大型的系统里面也有的,比如WIN7,下面的截图就是 WIN7中的空闲进程。

 

 空闲任务主要有以下几个作用:

  •   用户不能让系统一直在执行各个应用任务,这样的话系统利用率就是 100%,系统就会一直的超负荷运行,所以空闲任务很有必要。
  •   为了更好的实现低功耗,空闲任务也很有必要,用户可以在空闲任务中实现睡眠,停机等低功耗措施。

RTX5操作系统的空闲任务在文件RTX_Config.c文件里面,源代码如下:

// OS Idle Thread
__WEAK __NO_RETURN void osRtxIdleThread (void *argument) {
  (void)argument;

  for (;;) {}
}

7.13 实验例程说明

配套例子:

V5-403_RTX5 Task Control

实验目的:

  1. 学习RTX的任务管理。

实验内容:

  1. K2按键按下,删除任务AppTaskLED。
  2. K3按键按下,重新创建任务AppTaskLED。
  3. 各个任务实现的功能如下:

AppTaskUserIF任务   : 按键消息处理。

AppTaskLED任务      : LED闪烁。

AppTaskMsgPro任务   : 消息处理。

AppTaskStart任务      : 启动任务,也是最高优先级任务,这里实现按键扫描。

osRtxTimerThread任务 : 定时器任务,暂未使用。

串口打印信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1。

 

RTX配置:

RTX配置向导详情如下:

  System Configuration

  •   Global Dynamic Memory size

全局动态内存,这里设置为32KB。

  •   Kernel Tick Frequency

系统时钟节拍,这里设置为1KHz。

  Thread Configuration

  •   Default Thread Stack size

默认的任务栈大小,这里设置为1024字节

RTX5任务调试信息:

 

程序设计

  任务栈大小分配:

全部独立配置,没有使用RTX5默认配置:

/*
**********************************************************************************************************
                                             变量
**********************************************************************************************************
*/
/* 任务的属性设置 */
const osThreadAttr_t ThreadStart_Attr = 
{
    /* 未使用 */
//    .cb_mem = &worker_thread_tcb_1,
//    .cb_size = sizeof(worker_thread_tcb_1),
//    .stack_mem = &worker_thread_stk_1[0],
//    .stack_size = sizeof(worker_thread_stk_1),
//    .priority = osPriorityAboveNormal,
//    .tz_module = 0
    
    .name = "osRtxStartThread",
    .attr_bits = osThreadDetached, 
    .priority = osPriorityHigh4,
    .stack_size = 2048,
};

const osThreadAttr_t ThreadMsgPro_Attr = 
{
    .name = "osRtxMsgProThread",
    .attr_bits = osThreadDetached, 
    .priority = osPriorityHigh3,
    .stack_size = 1024,
};

const osThreadAttr_t ThreadLED_Attr = 
{
    .name = "osRtxLEDThread",
    .attr_bits = osThreadDetached, 
    .priority = osPriorityHigh2,
    .stack_size = 512,
};

const osThreadAttr_t ThreadUserIF_Attr = 
{
    .name = "osRtxThreadUserIF",
    .attr_bits = osThreadDetached, 
    .priority = osPriorityHigh1,
    .stack_size = 1024,
};

  系统栈大小分配:

  RTX5初始化:

/*
*********************************************************************************************************
*    函 数 名: main
*    功能说明: 标准c程序入口。
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
int main (void) 
{    
    /* HAL库,MPU,Cache,时钟等系统初始化 */
    System_Init();

    /* 内核开启前关闭HAL的时间基准 */
    HAL_SuspendTick();
    
    /* 内核初始化 */
    osKernelInitialize();                                  

    /* 创建启动任务 */
    ThreadIdStart = osThreadNew(AppTaskStart, NULL, &ThreadStart_Attr);  

    /* 开启多任务 */
    osKernelStart();
    
    while(1);
}

  RTX5任务创建:

/*
*********************************************************************************************************
*    函 数 名: AppTaskCreate
*    功能说明: 创建应用任务
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void AppTaskCreate (void)
{
    ThreadIdTaskMsgPro = osThreadNew(AppTaskMsgPro, NULL, &ThreadMsgPro_Attr);  
    ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
    ThreadIdTaskUserIF = osThreadNew(AppTaskUserIF, NULL, &ThreadUserIF_Attr);  
}

  四个RTX任务的实现:

/*
*********************************************************************************************************
*    函 数 名: AppTaskUserIF
*    功能说明: 按键消息处理        
*    形    参: 无
*    返 回 值: 无
*   优 先 级: osPriorityHigh1 (数值越小优先级越低,这个跟uCOS相反)
*********************************************************************************************************
*/
void AppTaskUserIF(void *argument)
{
    uint8_t ucKeyCode;

    while(1)
    {
        ucKeyCode = bsp_GetKey();
        
        if (ucKeyCode != KEY_NONE)
        {
            switch (ucKeyCode)
            {
                /* K1键按下,删除任务AppTaskLED */
                case KEY_DOWN_K1:
                    printf("K1键按下,删除任务 HandleTaskLED\r\n");
                    if(ThreadIdTaskLED != NULL)
                    {
                        if(osThreadTerminate(ThreadIdTaskLED) == osOK)
                        {
                            ThreadIdTaskLED = NULL;
                            printf("任务 AppTaskLED 删除成功\r\n");
                        }
                        else
                        {
                            printf("任务 AppTaskLED 删除失败\r\n");
                        }
                    }
                    break;
                    
                /* K2键按下,重新创建任务AppTaskLED */
                case KEY_DOWN_K2:
                    printf("K2键按下,重新创建任务 AppTaskLED\r\n");
                    if(ThreadIdTaskLED == NULL)
                    {
                        ThreadIdTaskLED = osThreadNew(AppTaskLED, NULL, &ThreadLED_Attr);  
                    }
                    break;

                /* 其他的键值不处理 */
                default:                     
                    break;
            }
        }
        
        osDelay(20);
    }
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskLED
*    功能说明: LED闪烁。
*    形    参: 无
*    返 回 值: 无
*   优 先 级: osPriorityHigh2 
*********************************************************************************************************
*/
void AppTaskLED(void *argument)
{
    const uint16_t usFrequency = 200; /* 延迟周期 */
    uint32_t tick;

    /* 获取当前时间 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        bsp_LedToggle(2);
        /* 相对延迟 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskMsgPro
*    功能说明: 消息处理,暂时未用到。
*    形    参: 无
*    返 回 值: 无
*   优 先 级: osPriorityHigh3  
*********************************************************************************************************
*/
void AppTaskMsgPro(void *argument)
{
    while(1)
    {
        osDelay(10);
    }    
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskStart
*    功能说明: 启动任务,这里用作BSP驱动包处理。
*    形    参: 无
*    返 回 值: 无
*   优 先 级: osPriorityHigh4  
*********************************************************************************************************
*/
void AppTaskStart(void *argument)
{
    const uint16_t usFrequency = 1; /* 延迟周期 */
    uint32_t tick;
    
    /* 初始化外设 */
    HAL_ResumeTick();
    bsp_Init();

    /* 创建任务 */
    AppTaskCreate();

    /* 获取当前时间 */
    tick = osKernelGetTickCount(); 
    
    while(1)
    {
        /* 需要周期性处理的程序,对应裸机工程调用的SysTick_ISR */
        bsp_ProPer1ms();
        
        /* 相对延迟 */
        tick += usFrequency;                          
        osDelayUntil(tick);
    }
}

7.14 总结

任务管理中涉及到的API是RTX5的基本操作函数,初学者要熟练的掌握,另外任务栈和系统栈也要随着后面的学习搞清楚。

相关