【STM32F429】第7章RTX5任务管理
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
【STM32F429】第7章RTX5任务管理
第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 初学者重要提⽰
学习本章节前有必要对各种对齐⽅式有个全⾯认识:
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字节对齐的:
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 实验例程说明
配套例⼦:
V6-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的基本操作函数,初学者要熟练的掌握,另外任务栈和系统栈也要随着后⾯的学习搞清楚。