UCOSIII 钩子函数(介入函数)
UCOSIII的钩子函数有8个,有些书籍会翻译为介入函数,其实是同一个意思。什么是钩子函数呢,也许是因为函数名字直译过来就是钩子吧。具体作用还真的不好定义,还不如直接看这几个函数的具体功能作用来得快。
在介绍具体的钩子函数前,先介绍下钩子函数的初始化函数,这个初始化函数可以一次性对所有钩子函数进行初始化,函数原型如下;
void App_OS_SetAllHooks (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
CPU_SR_ALLOC();
CPU_CRITICAL_ENTER();
OS_AppTaskCreateHookPtr = App_OS_TaskCreateHook;
OS_AppTaskDelHookPtr = App_OS_TaskDelHook;
OS_AppTaskReturnHookPtr = App_OS_TaskReturnHook;
OS_AppIdleTaskHookPtr = App_OS_IdleTaskHook;
OS_AppStatTaskHookPtr = App_OS_StatTaskHook;
OS_AppTaskSwHookPtr = App_OS_TaskSwHook;
OS_AppTimeTickHookPtr = App_OS_TimeTickHook;
CPU_CRITICAL_EXIT();
#endif
}
可以看到,只有使能了钩子函数后,这个初始化函数才会对钩子函数进行初始化。同一功能的钩子函数其实是有两个钩子函数组成,其中一个是给UCOS移植者使用,另外一个是给UCOS应用开发者使用,而这个初始化函数就是把UCOS移植者的函数和应用开发者的函数绑定在一起。同一功能的两个钩子函数其实很有规律的,这两者的名字是很相近的,只是应用开发者的钩子函数多了个APP开头,具体看下表;
| 移植者使用的钩子函数 | 应用开发者使用的钩子函数 |
空闲任务钩子函数 | void OSIdleTaskHook (void) | void App_OS_IdleTaskHook (void) |
系统初始化钩子函数 | void OSInitHook (void) | 无 |
任务创建钩子函数 | void OSTaskCreateHook (OS_TCB *p_tcb) | void App_OS_TaskCreateHook (OS_TCB *p_tcb) |
任务删除钩子函数 | void OSTaskDelHook (OS_TCB *p_tcb) | void App_OS_TaskDelHook (OS_TCB *p_tcb) |
任务意外返回钩子函数 | void OSTaskReturnHook (OS_TCB *p_tcb) | void App_OS_TaskReturnHook (OS_TCB *p_tcb) |
任务开始运行钩子函数 | void OSStatTaskHook (void) | void App_OS_StatTaskHook (void) |
任务切换钩子函数 | void OSTaskSwHook (void) | void App_OS_TaskSwHook (void) |
软件定时器节拍钩子函数 | void OSTimeTickHook (void) | void App_OS_TimeTickHook (void) |
- 空闲任务钩子函数
在系统不执行其他任务的时候,也就是开发者所建立的任务全都被挂起来的时候,CPU总不能停止吧,所以这个时候系统就来执行空闲任务了,而空闲任务里也可以做一些无关紧要的事情,这些无关紧要的事情的代码就写在空闲任务钩子函数里面。当CPU很忙的时候就很少执行空闲任务钩子函数的代码,当CPU不忙的时候空闲任务钩子函数执行的次数就多一些。当然,如果为了省电也可以在空闲任务钩子函数里让CPU进入休眠态,不过进入休眠后得有个唤醒的方式,可以是周期性的唤醒,也可以是外部中断唤醒,这个的看具体的芯片。
函数原型;
void OSIdleTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
(*OS_AppIdleTaskHookPtr)();
}
#endif
}
很明显有个使能开关OS_CFG_APP_HOOKS_EN,要使用空闲任务钩子函数的话就得打开这个使能开关。从代码可以看到,这个函数里面有一个OS_AppIdleTaskHookPtr,这玩意经过几个传递后最终指向void App_OS_IdleTaskHook (void)这个函数,找到这个void App_OS_IdleTaskHook (void)函数的定义可以看到,这个函数是空的,我们的空闲任务代码就写在这个函数里面。至于说为什么不直接在void OSIdleTaskHook (void)里面写空闲任务的代码,而是在void App_OS_IdleTaskHook (void)这个函数里写呢,也许是为了让空闲任务的代码看起来更清晰吧。
这个章节带了一个软件例程,看了例程应该就知道空闲任务可以干嘛了,例程在文章的最后面。
- 系统初始化钩子函数 void OSInitHook (void)
在系统初始化的时候除了要对系统进行初始化外,我们也可以在系统初始化的过程中做一些我们想要做的事情,我们在这个时候要做的事情就写在系统初始化钩子函数里,其实一般只有在移植UCOS的时候才会用到这个钩子函数,毕竟不同的芯片有些寄存器是不一样的。但是在系统移植完成后,在写应用任务的时候一般不会在用到这个钩子函数了,其实这个钩子函数是给UCOS的移植人员用的,并不是给UCOS的应用开发者用的。使用这个系统初始化钩子函数的话,我们的代码直接写在这个函数里就好。
- 任务创建钩子函数 void OSTaskCreateHook (OS_TCB *p_tcb)
这个钩子函数是在我们创建任务的时候调用的,可以把创建任务时要做的其他事情写在任务创建的钩子函数里。其实这个钩子函数是给UCOS的移植人员用的,并不是给UCOS的应用开发者用的。void OSTaskCreateHook (OS_TCB *p_tcb)是给移植者用的,应用开发者要使用这个函数的话代码应该写在void App_OS_TaskCreateHook (OS_TCB *p_tcb)这个函数里的。
- 任务删除钩子函数void OSTaskDelHook (OS_TCB *p_tcb)
既然有创建任务的钩子函数,那么当然也有任务删除的钩子函数了。在任务被删除后系统会调用这个钩子函数,估计是做一些释放内存之类的事情吧,这个钩子函数也是给UCOS的移植人员用的。应用开发者要使用这个函数的话代码应该写在void App_OS_TaskDelHook (OS_TCB *p_tcb)这个函数里。
- 任务意外返回钩子函数 void OSTaskReturnHook (OS_TCB *p_tcb)
任务按理说是一个无限循环,永远不会返回,但是当发生意外的时候还是有可能会返回 ,当意外返回的时候就执行这个钩子函数,具体这个函数里面需要干写什么事情呢,据说这个函数是给UCOS移植者用的,不是给应用开发者用的,所以暂时不纠结这个该怎么用。说白了就是我也不知道怎么用。应用开发者要使用这个函数的话代码应该写在void App_OS_TaskReturnHook (OS_TCB *p_tcb)里面。
- 任务开始运行钩子函数 void OSStatTaskHook (void)
这玩意也是给UCOS移植者用的,暂时不纠结功能和用法。应用开发者要使用这个函数的话代码应该写在在void App_OS_StatTaskHook (void)里面。
- 任务切换钩子函数 void OSTaskSwHook (void)
每次任务切换的时候都会调用这个钩子函数,但是上面这个钩子函数是给移植者用的,应用开发者的钩子函数代码应该写在void App_OS_TaskSwHook (void)里面。
- 软件定时器节拍钩子函数 void OSTimeTickHook (void)
从函数名可以很容易的看出来,软件定时器每来一个节拍就调用一次这个函数,可以在这个钩子函数里执行周期性的事情,周期还特别稳定。同理,这个函数也是给移植者用的,应用开发者应该用下面的这个钩子函数void App_OS_TimeTickHook (void)。
来个栗子吧,这个栗子是针对空闲任务钩子函数的,另外几个钩子函数就不写工程了。要使用空闲任务钩子函数很简单,可以分为以下几个步骤;
- 打开钩子函数使能开关 OS_CFG_APP_HOOKS_EN
- 对钩子函数进行初始化 App_OS_SetAllHooks();
- 在App_OS_IdleTaskHook()函数里写想让空闲任务干的事情
这个工程实现的功能很简单,一共两个任务,一个任务控制LED闪烁提示系统运行,另外一个是串口任务。定义一个全局变量testCount,这个变量在空闲任务钩子函数里面累加,每隔一秒钟就在串口任务中输出这个变量的值,然后清零,就看看每秒钟这个值能累加到多少。我用的STM32运行在72m主频,系统滴答频率为200hz,testCount每秒钟能累加到974194左右,这个一个浮动的值,但是波动不大,只有正负10这样子。很明显这个累加值比系统节拍大很多,所以运行一次空闲任务的话空闲任务钩子函数会运行多次。代码如下
Main.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "includes.h"
#include "task.h"
/************************************************
ALIENTEK战舰STM32开发板UCOS实验
技术支持:www.openedv.com
淘宝店铺:http://eboard.taobao.com
关注微信公众平台微信号:"正点原子",免费获取STM32资料。
广州市星翼电子科技有限公司
作者:正点原子 @ALIENTEK
************************************************/
//UCOSIII中以下优先级用户程序不能使用,ALIENTEK
//将这些优先级分配给了UCOSIII的5个系统内部任务
//优先级0:中断服务服务管理任务 OS_IntQTask()
//优先级1:时钟节拍任务 OS_TickTask()
//优先级2:定时任务 OS_TmrTask()
//优先级OS_CFG_PRIO_MAX-2:统计任务 OS_StatTask()
//优先级OS_CFG_PRIO_MAX-1:空闲任务 OS_IdleTask()
//技术支持:www.openedv.com
//淘宝店铺:http://eboard.taobao.com
//广州市星翼电子科技有限公司
//作者:正点原子 @ALIENTEK
//*****************************************************************
//开始任务
OS_TCB starTaskTCB; //任务控制块
#define STAR_TASK_PRIO 3 //任务优先级
#define STAR_TASK_STK_SIZE 128 //任务堆栈总大小
CPU_STK STAR_TASK_STK[ STAR_TASK_STK_SIZE ]; //任务堆栈数组
int main()
{
OS_ERR myErr; //os的错误码
CPU_SR_ALLOC(); //使用临街保护就得添加这玩意,不然会报错
delay_init(); //时钟初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断分组配置
uart_init(115200); //串口初始化
LED_Init(); //LED初始化
OSInit(&myErr); //os初始化
//创建任务的时候进入临街保护比较好,但是这个函数非用户的API函数,用户使用是否合适以后再讨论
OS_CRITICAL_ENTER();
//创建开始任务,在开始任务里创建其他任务,为每个函数入口参数都强制转换类型吧,以防万一
OSTaskCreate ( (OS_TCB *)&starTaskTCB, //任务控制块,
(CPU_CHAR *)"star task", //任务名字
(OS_TASK_PTR )starTaskFunc, //任务函数
(void *)0, //任务函数入口参数
(OS_PRIO )STAR_TASK_PRIO, //任务优先级
(CPU_STK *)&STAR_TASK_STK[0], //堆栈数组基地址
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE/10, //堆栈溢出限位
(CPU_STK_SIZE ) STAR_TASK_STK_SIZE, //堆栈大小
(OS_MSG_QTY ) 0, //本任务消息长度
(OS_TICK ) 0, //时间片个数
(void *) 0, //任务块补充参数
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR, //选项
(OS_ERR *)&myErr); //错误码
OS_CRITICAL_EXIT(); //任务创建完了就该退出临界保护了
OSStart(&myErr); //任务创建完成后就该运行了
while(1); //运行系统后就一直在系统里面跑了,应该运行不到这里
}
Task.c文件
#include "task.h"
#include "sys.h"
#include "led.h"
#include "includes.h" //想要使用UCOS系统,得包含这个头文件
#include "os_app_hooks.h"
u32 testCount=0;
//LED秒闪任务,提示系统正在运行
OS_TCB ledTaskTCB;
#define LED_TASK_PRIO 4
#define LED_TASK_STK_SIZE 128
CPU_STK LED_TASK_STK[LED_TASK_STK_SIZE];
//串口输出任务1
OS_TCB uartTask_1_TCB;
#define UART_TASK_1_PRIO 5
#define UART_TASK_1_STK_SIZE 128
CPU_STK UART_TASK_1_STK[ UART_TASK_1_STK_SIZE ];
void starTaskFunc()
{
OS_ERR myErr;
App_OS_SetAllHooks();//钩子函数初始化
OSTaskCreate ( (OS_TCB *)&ledTaskTCB,
(CPU_CHAR *)"led task",
(OS_TASK_PTR )ledTaskFunc,
(void *)0,
(OS_PRIO )LED_TASK_PRIO,
(CPU_STK *)&LED_TASK_STK[0],
(CPU_STK_SIZE ) LED_TASK_STK_SIZE/10,
(CPU_STK_SIZE ) LED_TASK_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
OSTaskCreate ( (OS_TCB *)&uartTask_1_TCB,
(CPU_CHAR *)"uart task 1 ",
(OS_TASK_PTR )uartTask_1_Func,
(void *)0,
(OS_PRIO )UART_TASK_1_PRIO,
(CPU_STK *)&UART_TASK_1_STK[0],
(CPU_STK_SIZE ) UART_TASK_1_STK_SIZE/10,
(CPU_STK_SIZE ) UART_TASK_1_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT ) OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR *)&myErr);
//这个任务只是用来创建其他任务的,其他任务创建完成了这个任务也就没用了,删除掉
OSTaskDel((OS_TCB*)0,&myErr);
}
//LED任务的任务函数
//LED每隔500ms翻转一次,提示系统正在运行
void ledTaskFunc()
{
OS_ERR myErr;
while(1) //注意,每个任务都是一个死循环
{
OSTimeDlyHMSM ( (CPU_INT16U ) 0, //时
(CPU_INT16U ) 0, //分
(CPU_INT16U ) 0, //秒
(CPU_INT32U ) 100, //毫秒
(OS_OPT ) OS_OPT_TIME_HMSM_STRICT, //选项
(OS_ERR *)&myErr); //错误码
LED = !LED; //LED状态翻转
}
}
//串口任务1的任务函数
//功能;每隔1秒向电脑串口助手发送一次数据
void uartTask_1_Func()
{
OS_ERR myErr;
while(1)
{
OSTimeDlyHMSM(0,0,1,0,OS_OPT_TIME_HMSM_STRICT,&myErr);//延时1秒
printf("testCount=%d\r\n",testCount);
testCount=0;
}
}
Task.h文件
#ifndef __TASK_H
#define __TASK_H
#include "sys.h"
extern u32 testCount;//定义为全局变量
void starTaskFunc();
void ledTaskFunc();
void uartTask_1_Func();
#endif
os_app_hooks.c文件
这个文件里面有很多函数,但是我值用到一个函数,这里就只把用到的代码粘贴出来,没用到的就不粘贴了。
void App_OS_IdleTaskHook (void)
{
testCount++;
}