μC/OSIII学习day9
介绍
事件,软件定时器
# 事件
# 事件的基本概念
- 事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输.与信号量不同的是,它可以实现一对多,多对多的同步.即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理.同样,也可以是多个任务同步多个事件.
- 每一个事件组只需要很少的RAM空间来保存事件组的状态.事件组存储在一个
OS_FLAGS
类型的Flags
变量中,该变量在事件结构体中定义.而变量的宽度由我们自己定义,可以是8位,16位,32位的变量,取决于os_type.h中的 OS_FLAGS的位数.在STM32中,我们一般将其定义为32位的变量,有32个位用来实现事件标志组.每一位代表一个事件,任务通过"逻辑与"或"逻辑或"与一个或多个事件建立关联,形成一个事件组.事件的"逻辑或"也被称作是独立型同步,指的是任务感兴趣的所有事件任一件发生即可被唤醒;事件"逻辑与"则被称为是关联型同步,指的是任务感兴趣的若干事件都发生时才被唤醒,并且事件发生的时间可以不同步. - 多任务环境下,任务,中断之间往往需要同步操作,一个事件发生会告知等待中的任务,即形成一个任务与任务,中断与任务间的同步.事件可以提供一对多,多对多的同步操作.一对多同步模型:一个任务等待多个事件的触发,这种情况是比较常见的;多对多同步模型:多个任务等待多个事件的触发.
- 任务可以通过设置事件位来实现事件的触发和等待操作.μC/OS的事件仅用于同步,不提供数据传输功能.
- μC/OS提供的事件具有如下特点
- 事件只与任务相关联,事件相互独立,一个32位(数据宽度由用户定义)的事件集合用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生,1表示该事件类型已经发生),一共 32 种事件类型.
- 事件仅用于同步,不提供数据传输功能.
- 事件无排队性,即多次向任务设置同一事件(如果任务还未来得及读走),等效于只设置一次.
- 允许多个任务对同一事件进行读写操作.
- 支持事件等待超时机制.
- 支持显式清除事件.
- 在μC/OS的等待事件中,用户可以选择感兴趣的事件,并且选择等待事件的选项,它有4个属性,分别是逻辑与,逻辑或,等待所有事件清除或者等待任意事件清除.当任务等待事件同步时,可以通过任务感兴趣的事件位和事件选项来判断当前获取的事件是否满足要求,如果满足则说明任务等待到对应的事件,系统将唤醒等待的任务;否则,任务会根据用户指定的阻塞超时时间继续等待下去.
# 事件的应用场景
μC/OS的事件用于事件类型的通讯,无数据传输,也就是说,我们可以用事件来做标志位,判断某些事件是否发生了,然后根据结果做处理,那很多人又会问了,为什么我不直接用变量做标志呢,岂不是更好更有效率?非也非也,若是在裸机编程中,用全局变量是最为有效的方法,这点我不否认,但是在操作系统中,使用全局变量就要考虑以下问题了:
- 如何对全局变量进行保护呢,如何处理多任务同时对它进行访问?
- 如何让内核对事件进行有效管理呢?使用全局变量的话,就需要在任务中轮询查看事件是否发送,这简直就是在浪费 CPU 资源啊,还有等待超时机制,使用全局变量的话需要用户自己去实现.
在操作系统中,使用操作系统给我们提供的通信机制就可,简单方便还实用.
在某些场合,可能需要多个时间发生了才能进行下一步操作,比如一些危险机器的启动,需要检查各项指标,当指标不达标的时候,无法启动,但是检查各个指标的时候,不能一下子检测完毕啊,所以,需要事件来做统一的等待,当所有的事件都完成了,那么机器才允许启动,这只是事件的其中一个应用.
事件可使用于多种场合,它能够在一定程度上替代信号量,用于任务与任务间,中断与任务间的同步.一个任务或中断服务例程发送一个事件给事件对象,而后等待的任务被唤醒并对相应的事件进行处理.但是它与信号量不同的是,事件的发送操作是不可累计的,而信号量的释放动作是可累计的.事件另外一个特性是,接收任务可等待多种事件,即多个事件对应一个任务或多个任务.同时按照任务等待的参数,可选择是"逻辑或"触发还是"逻辑与"触发.这个特性也是信号量等所不具备的,信号量只能识别单一同步动作,而不能同时等待多个事件的同步.
各个事件可分别发送或一起发送给事件对象,而任务可以等待多个事件,任务仅对感兴趣的事件进行关注.当有它们感兴趣的事件发生时并且符合感兴趣的条件,任务将被唤醒并进行后续的处理动作.
# 事件运作机制
等待(接收)事件时,可以根据感兴趣的参事件类型等待事件的单个或者多个事件类型.事件等待成功后,必须使用
OS_OPT_PEND_FLAG_CONSUME
选项来清除已接收到的事件类型,否则不会清除已接收到的事件,这样就需要用户显式清除事件位.用户可以自定义通过传入opt选项来选择读取模式,是等待所有感兴趣的事件还是等待感兴趣的任意一个事件.设置事件时,对指定事件写入指定的事件类型,设置事件集合的对应事件位为1,可以一次同时写多个事件类型,设置事件成功可能会触发任务调度.
清除事件时,根据入参数事件句柄和待清除的事件类型,对事件对应位进行清零操作.
事件不与任务相关联,事件相互独立,一个32位的变量就是事件的集合,用于标识该任务发生的事件类型,其中每一位表示一种事件类型(0表示该事件类型未发生,1表示该事件类型已经发生),一共32种事件类型.
事件唤醒机制,当任务因为等待某个或者多个事件发生而进入阻塞态,当事件发生的时候会被唤醒.
任务1对事件3或事件5感兴趣(逻辑或),当发生其中的某一个事件都会被唤醒,并且执行相应操作.而任务2对事件3与事件5感兴趣(逻辑与),当且仅当事件3与事件5都发生的时候,任务2才会被唤醒,如果只有一个其中一个事件发生,那么任务还是会继续等待事件发生.如果在接收事件函数中设置了清除事件位选项
OS_OPT_PEND_FLAG_CONSUME
,那么当任务唤醒后将把事件3和事件5的事件标志清零,否则事件标志将依然存在.
# 事件控制块
理论上用户可以创建任意个事件(仅限制于处理器的 RAM 大小).通过设置os_cfg.h中的宏定义
OS_CFG_FLAG_EN
为1即可开启事件功能.事件是一个内核对象,由数据类型OS_FLAG_GRP
定义,该数据类型由os_flflag_grp
定义(在 os.h 文件).μC/OS的事件由多个元素组成,在事件被创建时,需要由我们自己定义事件(也可以称之为事件句柄),因为它是用于保存事件的一些信息的,其数据结构
OS_FLAG_GRP
除了事件必须的一些基本信息外,还有PendList
链表与一个32位的事件组变量Flags
等,为的是方便系统来管理事件.事件控制块数据结构
struct os_flag_grp { /* ------------------ GENERIC MEMBERS ------------------ */ OS_OBJ_TYPE Type; //事件的类型 CPU_CHAR *NamePtr; //事件的名称 OS_PEND_LIST PendList; //因为可以有多个任务同时等待系统中的事件,所以事件中包含了一个用于控制挂起任务列表的结构体,用于记录阻塞在此事件上的任务 #if OS_CFG_DBG_EN > 0u OS_FLAG_GRP *DbgPrevPtr; OS_FLAG_GRP *DbgNextPtr; CPU_CHAR *DbgNamePtr; #endif /* ------------------ SPECIFIC MEMBERS ------------------ */ OS_FLAGS Flags; //事件中包含了很多标志位,Flags这个变量中保存了当前这些标志位的状态.这个变量可以为8位,16位或32位. CPU_TS TS; //事件中的变量TS用于保存该事件最后一次被释放的时间戳.当事件被释放时,读取时基计数值并存放到该变量中. };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17PS:用户代码不能直接访问这个结构体,必须通过μC/OS提供的API访问.
# 事件函数接口
函数名称 | 函数作用 |
---|---|
OSFlagCreate() | 创建事件 |
OSFlagDel() | 删除事件 |
OSFlagPost() | 设置事件 |
OSFlagPend() | 等待事件 |
OS_FlagPost() | 如果没有启用中断延迟发布,则直接将该事件对应的标志位置位 |
事件创建函数OSFlagCreate()
事件创建函数,顾名思义,就是创建一个事件,与其他内核对象一样,都是需要先创建才能使用的资源,μC/OS给我们提供了一个创建事件的函数
OSFlagCreate()
,当创建一个事件时,系统会对我们定义的事件控制块进行基本的初始化.在使用创建函数之前,需要先定义一个事件控制块(句柄).参数 含义 *p_grp 指向事件标志组变量指针 *p_name 指向事件标志组名字字符串的指针 flags 标志初始值 *p_err 指向返回错误类型的指针 void OSFlagCreate (OS_FLAG_GRP *p_grp, //事件指针 CPU_CHAR *p_name, //命名事件 OS_FLAGS flags, //标志初始值 OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL //如果启用了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 //如果启用了安全关键 if (OSSafetyCriticalStartFlag == DEF_TRUE) //如果afetyCriticalStart()后创建 { *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为"非法创建内核对象" return; //返回,停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_CREATE_ISR; //错误类型为"在中断中创建对象" return; //返回,停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_grp == (OS_FLAG_GRP *)0) //如果 p_grp 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"创建对象为空" return; //返回,停止执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_grp->Type = OS_OBJ_TYPE_FLAG; //标记创建对象数据结构为事件 p_grp->NamePtr = p_name; //标记事件的名称 p_grp->Flags = flags; //设置标志初始值 p_grp->TS = (CPU_TS)0; //清零事件的时间戳 OS_PendListInit(&p_grp->PendList); //初始化该事件的等待列表 #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_FlagDbgListAdd(p_grp); //将该事件添加到事件双向调试链表 #endif OSFlagQty++; //事件个数加1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54事件创建函数的使用实例
OS_FLAG_GRP flag_grp; //声明事件 OS_ERR err; /* 创建事件 flag_grp */ OSFlagCreate ((OS_FLAG_GRP *)&flag_grp, //指向事件的指针 (CPU_CHAR *)"FLAG For Test", //事件的名字 (OS_FLAGS )0, //事件的初始值 (OS_ERR *)&err);
1
2
3
4
5
6
7
8
9事件删除函数OSFlagDel()
参数 含义 *p_grp 指向事件变量的指针 opt 删除事件量时候的选项 *p_err 指向返回错误类型的指针 选项 功能 OS_OPT_DEL_NO_PEND 当事件的等待列表上面没有相应的任务的时候才删除事件 OS_OPT_DEL_ALWAYS 不管事件的等待列表是否有相应的任务都删除事件,删除之前,系统会把所有阻塞在该事件上的任务恢复 错误类型 含义 OS_ERR_DEL_ISR 企图在中断中删除事件 OS_ERR_OBJ_PTR_NULL 参数p_grp是空指针 OS_ERR_OBJ_TYPE 参数p_grp指向的内核变量类型不是事件 OS_ERR_OPT_INVALID opt在给出的选项之外 OS_ERR_TASK_WAITING 在选项opt是OS_OPT_DEL_NO_PEND的时候,并且事件的等待列表上有等待的任务 OS_ERR_NONE 无错误 #if OS_CFG_FLAG_DEL_EN > 0u//如果启用了 OSFlagDel() 函数 OS_OBJ_QTY OSFlagDel(OS_FLAG_GRP *p_grp, //事件指针 OS_OPT opt, //选项 OS_ERR *p_err){ OS_OBJ_QTY cnt; OS_OBJ_QTY nbr_tasks; OS_PEND_DATA *p_pend_data; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_TS ts; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL //如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_DEL_ISR; //错误类型为"在中断中删除对象" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用了参数检测 if (p_grp == (OS_FLAG_GRP *)0) //如果 p_grp 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"对象为空" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果选项在预期内 case OS_OPT_DEL_ALWAYS: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u //如果启用了对象类型检测 if (p_grp->Type != OS_OBJ_TYPE_FLAG) //如果 p_grp 不是事件类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型有误" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif OS_CRITICAL_ENTER(); //进入临界段 p_pend_list = &p_grp->PendList; //获取消息队列的等待列表 cnt = p_pend_list->NbrEntries; //获取等待该队列的任务数 nbr_tasks = cnt; //按照任务数目逐个处理 switch (opt) //根据选项分类处理 { case OS_OPT_DEL_NO_PEND: //如果只在没任务等待时进行删除 if (nbr_tasks == (OS_OBJ_QTY)0) //如果没有任务在等待该事件 { #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_FlagDbgListRemove(p_grp); //将该事件从事件调试列表移除 #endif OSFlagQty--; //事件数目减1 OS_FlagClr(p_grp); //清除该事件的内容 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_NONE; //错误类型为"无错误" } else { OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_TASK_WAITING; //错误类型为"有任务在等待事件" } break; //跳出 case OS_OPT_DEL_ALWAYS: //如果必须删除事件 ts = OS_TS_GET(); //获取时间戳 while (cnt > 0u) //逐个移除该事件等待列表中的任务 { p_pend_data = p_pend_list->HeadPtr; p_tcb = p_pend_data->TCBPtr; OS_PendObjDel((OS_PEND_OBJ *)((void *)p_grp), p_tcb, ts); cnt--; } #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_FlagDbgListRemove(p_grp); //将该事件从事件调试列表移除 #endif OSFlagQty--; //事件数目减 1 OS_FlagClr(p_grp); //清除该事件的内容 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) OSSched(); //调度任务 *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 default: //如果选项超出预期 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" break; //跳出 } return (nbr_tasks); //返回删除事件前等待其的任务数 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103删除事件函数OSFlagDel()的使用实例
OS_FLAG_GRPflag_grp;; //声明事件句柄 OS_ERR err; /* 删除事件 */ OSFlagDel((OS_FLAG_GRP*)&flag_grp, //指向事件的指针 OS_OPT_DEL_NO_PEND, (OS_ERR *)&err);
1
2
3
4
5
6
7
8事件删除函数
OSFlagDel()
使用时传入要删除的事件的句柄与选项并保存返回的错误类型即可,调用函数时,系统将删除这个事件.需要注意的是在调用删除事件函数前,系统应存在已创建的事件.如果删除事件时,系统中有任务正在等待该事件,则不应该进行删除操作.事件设置函数OSFlagPost()
OSFlagPost()
用于设置事件组中指定的位,当位被置位之后,并且满足任务的等待事件,那么等待在事件该标志位上的任务将会被恢复.使用该函数接口时,通过参数指定的事件标志来设置事件的标志位,然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务.简单来说,就是设置自己定义的事件标志位为1,并且看看有没有任务在等待这个事件,有的话就唤醒它.参数 含义 *p_grp 指向要提交的事件的指针 flags 想要设置的标志位 opt 置位选项 *p_err 指向返回错误类型的指针 选项 功能 OS_OPT_POST_FLAG_SET 参数flags选定的标志位全部置1 OS_OPT_POST_FLAG_CLR 参数flags选定的标志位全部置0 错误类型 含义 OS_ERR_OBJ_PTR_NULL 参数p_grp是空指针 OS_ERR_OBJ_TYPE 参数p_grp指向的变量类型不是事件标志组 OS_ERR_OPT_INVALID 参数opt不是指定的选项之一 OS_ERR_NONE 无错误 OS_FLAGS OSFlagPost (OS_FLAG_GRP *p_grp, //事件指针 OS_FLAGS flags, //选定要操作的标志位 OS_OPT opt, //选项 OS_ERR *p_err){ OS_FLAGS flags_cur; CPU_TS ts; #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_FLAGS)0); //返回 0,停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测 if (p_grp == (OS_FLAG_GRP *)0) //如果参数 p_grp 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"事件对象为空" return ((OS_FLAGS)0); //返回 0,停止执行 } switch (opt) //根据选项分类处理 { case OS_OPT_POST_FLAG_SET: //如果选项在预期之内 case OS_OPT_POST_FLAG_CLR: case OS_OPT_POST_FLAG_SET | OS_OPT_POST_NO_SCHED: case OS_OPT_POST_FLAG_CLR | OS_OPT_POST_NO_SCHED: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_FLAGS)0); //返回 0,停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测 if (p_grp->Type != OS_OBJ_TYPE_FLAG) //如果 p_grp 不是事件类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型"对象类型有误" return ((OS_FLAGS)0); //返回 0,停止执行 } #endif ts = OS_TS_GET(); //获取时间戳 #if OS_CFG_ISR_POST_DEFERRED_EN > 0u //如果启用了中断延迟发布 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_FLAG,//将该事件发布到中断消息队列 (void *)p_grp, (void *)0, (OS_MSG_SIZE)0, (OS_FLAGS )flags, (OS_OPT )opt, (CPU_TS )ts, (OS_ERR *)p_err); return ((OS_FLAGS)0); //返回 0,停止执行 } #endif /* 如果没有启用中断延迟发布 */ flags_cur = OS_FlagPost(p_grp, //将事件直接发布 flags, opt, ts, p_err); return (flags_cur); //返回当前标志位的值 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66事件设置函数OSFlagPost()使用实例
#define EVENT1 (0x01<<0) //设置事件掩码的位0 #define EVENT2 (0x01<<1) //设置事件掩码的位1 OS_FLAG_GRP flag_grp; //声明事件标志组 void AppTaskPost(void * p_arg){ OS_ERR err; (void)p_arg; while(DEF_TRUE){ if("EVENT1 has occurred"){ OSFlagPost ((OS_FLAG_GRP *)&flag_grp, //将标志组的 BIT0 置 1 (OS_FLAGS )EVENT1, (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); } else if("EVENT1 has occurred"){ OSFlagPost ((OS_FLAG_GRP *)&flag_grp, //将标志组的 BIT0 置 1 (OS_FLAGS )EVENT2, (OS_OPT )OS_OPT_POST_FLAG_SET, (OS_ERR *)&err); } else {} OSTimeDlyHMSM(0,0,0,20,OS_OPT_TIME_DLY,&err); //每20ms检测一次 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24标志位置位函数OS_FlagPost()
OS_FlagPost()函数可直接将该事件对应的标志位置位.
参数 含义 *p_grp 指向要提交的事件的指针 flags 想要设置的标志位 opt 置位选项 ts 时间戳 *p_err 指向返回错误类型的指针 OS_FLAGS OS_FlagPost (OS_FLAG_GRP *p_grp, //事件指针 OS_FLAGS flags, //选定要操作的标志位 OS_OPT opt, //选项 CPU_TS ts, //时间戳 OS_ERR *p_err){ OS_FLAGS flags_cur; OS_FLAGS flags_rdy; OS_OPT mode; OS_PEND_DATA *p_pend_data; OS_PEND_DATA *p_pend_data_next; OS_PEND_LIST *p_pend_list; OS_TCB *p_tcb; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 CPU_CRITICAL_ENTER(); //关中断 switch (opt) //根据选项分类处理 { case OS_OPT_POST_FLAG_SET: //如果要求将选定位置 1 case OS_OPT_POST_FLAG_SET | OS_OPT_POST_NO_SCHED: p_grp->Flags |= flags; //将选定位置 1 break; //跳出 case OS_OPT_POST_FLAG_CLR: //如果要求将选定位请 0 case OS_OPT_POST_FLAG_CLR | OS_OPT_POST_NO_SCHED: p_grp->Flags &= ~flags; //将选定位请 0 break; //跳出 default: //如果选项超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_FLAGS)0); //返回 0,停止执行 } p_grp->TS = ts; //将时间戳存入事件 p_pend_list = &p_grp->PendList; //获取事件的等待列表 if (p_pend_list->NbrEntries == 0u) /如果没有任务在等待事件 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (p_grp->Flags); //返回事件的标志值 } /* 如果有任务在等待事件 */ OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 p_pend_data = p_pend_list->HeadPtr; //获取等待列表头个等待任务 p_tcb = p_pend_data->TCBPtr; while (p_tcb != (OS_TCB *)0)//从头至尾遍历等待列表的所有任务 { p_pend_data_next = p_pend_data->NextPtr; mode = p_tcb->FlagsOpt & OS_OPT_PEND_FLAG_MASK; //获取任务的标志选项 switch (mode) //根据任务的标志选项分类处理 { case OS_OPT_PEND_FLAG_SET_ALL: //如果要求任务等待的标志位都得置 1 flags_rdy = (OS_FLAGS)(p_grp->Flags & p_tcb->FlagsPend); if (flags_rdy == p_tcb->FlagsPend) //如果任务等待的标志位都置 1 了 { OS_FlagTaskRdy(p_tcb, //让该任务准备运行 flags_rdy, ts); } break; //跳出 case OS_OPT_PEND_FLAG_SET_ANY://如果要求任务等待的标志位有 1 位置 1 即可 flags_rdy = (OS_FLAGS)(p_grp->Flags & p_tcb->FlagsPend); if (flags_rdy != (OS_FLAGS)0) //如果任务等待的标志位有置 1 的 { OS_FlagTaskRdy(p_tcb, //让该任务准备运行 flags_rdy, ts); } break; //跳出 #if OS_CFG_FLAG_MODE_CLR_EN > 0u //如果启用了标志位清零触发模式 case OS_OPT_PEND_FLAG_CLR_ALL: //如果要求任务等待的标志位都得请 0 flags_rdy = (OS_FLAGS)(~p_grp->Flags & p_tcb->FlagsPend); if (flags_rdy == p_tcb->FlagsPend) //如果任务等待的标志位都请 0 了 { OS_FlagTaskRdy(p_tcb, //让该任务准备运行 flags_rdy, ts); } break; //跳出 case OS_OPT_PEND_FLAG_CLR_ANY://如果要求任务等待的标志位有 1 位请 0 即可 flags_rdy = (OS_FLAGS)(~p_grp->Flags & p_tcb->FlagsPend); if (flags_rdy != (OS_FLAGS)0) //如果任务等待的标志位有请 0 的 { OS_FlagTaskRdy(p_tcb, //让该任务准备运行 flags_rdy, ts); } break; //跳出 #endif default: //如果标志选项超出预期 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_FLAG_PEND_OPT; //错误类型为"标志选项非法" return ((OS_FLAGS)0); //返回 0,停止运行 } p_pend_data = p_pend_data_next; //准备处理下一个等待任务 if (p_pend_data != (OS_PEND_DATA *)0) //如果该任务存在 { p_tcb = p_pend_data->TCBPtr; //获取该任务的任务控制块 } else {//如果该任务不存在 p_tcb = (OS_TCB *)0; (30)//清空 p_tcb,退出 while 循环 } } OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) //如果 opt 没选择"发布时不调度任务" { OSSched(); //任务调度 } CPU_CRITICAL_ENTER(); //关中断 flags_cur = p_grp->Flags; //获取事件的标志值 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_cur);//返回事件的当前标志值 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114事件等待函数OSFlagPend()
既然标记了事件的发生,那么我们怎么知道他到底有没有发生,这也是需要一个函数来获取事件是否已经发生,μC/OS提供了一个等待指定事件的函数
OSFlagPend()
,通过这个函数,任务可以知道事件标志组中的哪些位,有什么事件发生了,然后通过"逻辑与","逻辑或"等操作对感兴趣的事件进行获取,并且这个函数实现了等待超时机制,当且仅当任务等待的事件发生时,任务才能获取到事件信息.在这段时间中,如果事件一直没发生,该任务将保持阻塞状态以等待事件发生.当其他任务或中断服务程序往其等待的事件设置对应的标志位,该任务将自动由阻塞态转为就绪态.当任务等待的时间超过了指定的阻塞时间,即使事件还未发生,任务也会自动从阻塞态转移为就绪态.这样子很有效的体现了操作系统的实时性,如果事件正确获取(等待到)则返回对应的事件标志位,由用户判断再做处理,因为在事件超时的时候也可能会返回一个不能确定的事件值,所以最好判断一下任务所等待的事件是否真的发生.参数 含义 *p_grp 指向要提交的事件的指针 flags 想要设置的标志位 timeout 这个参数是设置的是获取不到等待事件时候等待的时间.如果这个值为0,表示一直等待下去,如果这个值不为0,则最多等待timeout个时钟节拍 opt 置位选项 *p_ts 指向等待的事件被删除,等待被强制停止,等待超时等情况时的时间戳的指针 *p_err 指向返回错误类型的指针 选项 功能 OS_OPT_PEND_FLAG_CLR_ALL 等待上面flags选定的位都被清0 OS_OPT_PEND_FLAG_CLR_ANY 等待上面flags选定的位任意一位被清0 OS_OPT_PEND_FLAG_SET_ALL 等待上面flags选定的位都被置1 OS_OPT_PEND_FLAG_SET_ANY 等待上面flags选定的位任意一位被置1 OS_OPT_PEND_FLAG_CONSUME 这个选项主要是获取到任务指定的位都被设置好后对这些位进行置反操作,注意不是清零.这个选项可以起到在满足条件后自动复位的作用.上面4个任意选项之一可以与上这个选项 OS_OPT_PEND_NON_BLOCKING 如果一开始判断指定位的设置情况不满足不继续等待,直接退出函数继续运行任务 OS_OPT_PEND_BLOCKING 如果一开始判断指定位的设置情况不满足不继续等待,直接退出函数继续运行任务 错误类型 含义 OS_ERR_OBJ_DEL 事件已经被删除了 OS_ERR_OBJ_PTR_NULL 输入的事件变量指针是空类型 OS_ERR_OBJ_TYPE p_grp指向的变量内核对象类型不是事件 OS_ERR_OPT_INVALID 参数opt不符合要求 OS_ERR_PEND_ABORT 等待过程,其他的任务调用了函数OSFlagPendAbort强制取消等待 OS_ERR_PEND_ISR 企图在中断中等待事件 OS_ERR_PEND_WOULD_BLOCK 开始获取不到事件,且没有要求等待 OS_ERR_SCHED_LOCKED 调度器被锁住 OS_ERR_STATUS_INVALID 系统出错,导致任务控制块的元素PendStatus不在可能的范围内 OS_ERR_TIMEOUT 等待超时 OS_ERR_NONE 无错误 OS_FLAGS OSFlagPend (OS_FLAG_GRP *p_grp, //事件指针 OS_FLAGS flags, //选定要操作的标志位 OS_TICK timeout, //等待期限(单位:时钟节拍) OS_OPT opt, //选项 CPU_TS *p_ts, //返回等到事件标志时的时间戳 OS_ERR *p_err){ CPU_BOOLEAN consume; OS_FLAGS flags_rdy; OS_OPT mode; OS_PEND_DATA pend_data; CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数在中断中被调用 { *p_err = OS_ERR_PEND_ISR; //错误类型为"在中断中中止等待" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测 if (p_grp == (OS_FLAG_GRP *)0) //如果 p_grp 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"对象为空" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } switch (opt) //根据选项分类处理 { case OS_OPT_PEND_FLAG_CLR_ALL: //如果选项在预期内 case OS_OPT_PEND_FLAG_CLR_ANY: case OS_OPT_PEND_FLAG_SET_ALL: case OS_OPT_PEND_FLAG_SET_ANY: case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_FLAG_CONSUME: case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_FLAG_CONSUME: case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_FLAG_CONSUME: case OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_FLAG_CONSUME: case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_CLR_ALL | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_CLR_ANY | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_SET_ALL | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING: case OS_OPT_PEND_FLAG_SET_ANY | OS_OPT_PEND_FLAG_CONSUME | OS_OPT_PEND_NON_BLOCKING: break; //直接跳出 default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID;//错误类型为"选项非法" return ((OS_OBJ_QTY)0); //返回 0(有错误),停止执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用了对象类型检测 if (p_grp->Type != OS_OBJ_TYPE_FLAG) //如果 p_grp 不是事件类型 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型有误" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } #endif if ((opt & OS_OPT_PEND_FLAG_CONSUME) != (OS_OPT)0) //选择了标志位匹配后自动取反 { consume = DEF_TRUE; } else {//未选择标志位匹配后自动取反 consume = DEF_FALSE; } if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS)0; //初始化(清零)p_ts,待用于返回时间戳 } mode = opt & OS_OPT_PEND_FLAG_MASK; //从选项中提取对标志位的要求 CPU_CRITICAL_ENTER(); //关中断 switch (mode) //根据事件触发模式分类处理 { case OS_OPT_PEND_FLAG_SET_ALL: //如果要求所有标志位均要置 1 flags_rdy = (OS_FLAGS)(p_grp->Flags & flags); //提取想要的标志位的值 if (flags_rdy == flags) //如果该值与期望值匹配 { if (consume == DEF_TRUE)(15)//如果要求将标志位匹配后取反 { p_grp->Flags &= ~flags_rdy; //清零事件的相关标志位 } OSTCBCurPtr->FlagsRdy = flags_rdy; //保存让任务脱离等待的标志值 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_grp->TS; //获取任务等到事件时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_rdy); //返回让任务脱离等待的标志值 } else {//如果想要标志位的值与期望值不匹配 if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"渴求阻塞" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } else {//如果选择了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } } /* 如果调度器未被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 OS_FlagBlock(&pend_data, //阻塞当前运行任务,等待事件 p_grp, flags, opt, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) } break; //跳出 case OS_OPT_PEND_FLAG_SET_ANY: //如果要求有标志位被置 1 即可 flags_rdy = (OS_FLAGS)(p_grp->Flags & flags); //提取想要的标志位的值 if (flags_rdy != (OS_FLAGS)0) //如果有位被置 1 { if (consume == DEF_TRUE) //如果要求将标志位匹配后取反 { p_grp->Flags &= ~flags_rdy; //清零事件的相关标志位 } OSTCBCurPtr->FlagsRdy = flags_rdy; //保存让任务脱离等待的标志值 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_grp->TS; //获取任务等到事件时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_rdy); //返回让任务脱离等待的标志值 } else {//如果没有位被置 1 if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果没设置阻塞任务 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"渴求阻塞" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } else {//如果设置了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } } /* 如果调度器没被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 OS_FlagBlock(&pend_data, //阻塞当前运行任务,等待事件 p_grp, flags, opt, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //退出中断(无调度) } break; //跳出 #if OS_CFG_FLAG_MODE_CLR_EN > 0u //如果启用了标志位清零触发模式 case OS_OPT_PEND_FLAG_CLR_ALL: //如果要求所有标志位均要清零 flags_rdy = (OS_FLAGS)(~p_grp->Flags & flags);//提取想要的标志位的值 if (flags_rdy == flags)//如果该值与期望值匹配 { if(consume == DEF_TRUE) //如果要求将标志位匹配后取反 { p_grp->Flags |= flags_rdy; //置 1 事件的相关标志位 } OSTCBCurPtr->FlagsRdy = flags_rdy; //保存让任务脱离等待的标志值 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_grp->TS; //获取任务等到事件时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_rdy); //返回 0(有错误),停止执行 } else {//如果想要标志位的值与期望值不匹配 if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果选择了不阻塞任务 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"渴求阻塞" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } else {//如果选择了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //关中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } } /* 如果调度器未被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 OS_FlagBlock(&pend_data, //阻塞当前运行任务,等待事件 p_grp, flags, opt, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) } break; //跳出 case OS_OPT_PEND_FLAG_CLR_ANY: //如果要求有标志位被清零即可 flags_rdy = (OS_FLAGS)(~p_grp->Flags & flags);//提取想要的标志位的值 if (flags_rdy != (OS_FLAGS)0) //如果有位被清零 { if (consume == DEF_TRUE) //如果要求将标志位匹配后取反 { p_grp->Flags |= flags_rdy; //置 1 事件的相关标志位 } OSTCBCurPtr->FlagsRdy = flags_rdy; //保存让任务脱离等待的标志值 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = p_grp->TS; //获取任务等到事件时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_rdy); //返回 0(有错误),停止执行 } else {//如果没有位被清零 if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) //如果没设置阻塞任务 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_WOULD_BLOCK; //错误类型为"渴求阻塞" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } else {//如果设置了阻塞任务 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) //如果调度器被锁 { CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } } /* 如果调度器没被锁 */ OS_CRITICAL_ENTER_CPU_EXIT(); //进入临界段,重开中断 OS_FlagBlock(&pend_data, //阻塞当前运行任务,等待事件 p_grp, flags, opt, timeout); OS_CRITICAL_EXIT_NO_SCHED(); //退出中断(无调度) } break; //跳出 #endif default: //如果要求超出预期 CPU_CRITICAL_EXIT(); *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } OSSched(); //任务调度 /* 任务等到了事件后得以继续运行 */ CPU_CRITICAL_ENTER(); //关中断 switch (OSTCBCurPtr->PendStatus) //根据运行任务的等待状态分类处理 { case OS_STATUS_PEND_OK: //如果等到了事件 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回等到事件时的时间戳 } *p_err = OS_ERR_NONE; //错误类型为"无错误" break; //跳出 case OS_STATUS_PEND_ABORT: //如果等待被中止 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回等待被中止时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_PEND_ABORT; //错误类型为"等待被中止" break; //跳出 case OS_STATUS_PEND_TIMEOUT: //如果等待超时 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = (CPU_TS )0; //清零 p_ts } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TIMEOUT; //错误类型为"超时" break; //跳出 case OS_STATUS_PEND_DEL: //如果等待对象被删除 if (p_ts != (CPU_TS *)0) //如果 p_ts 非空 { *p_ts = OSTCBCurPtr->TS; //返回对象被删时的时间戳 } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OBJ_DEL; //错误类型为"对象被删" break; //跳出 default: //如果等待状态超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_STATUS_INVALID; //错误类型为"状态非法" break; //跳出 } if (*p_err != OS_ERR_NONE) //如果有错误存在 { return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } /* 如果没有错误存在 */ flags_rdy = OSTCBCurPtr->FlagsRdy; //读取让任务脱离等待的标志值 if (consume == DEF_TRUE) //如果需要取反触发事件的标志位 { switch (mode)//根据事件触发模式分类处理 { case OS_OPT_PEND_FLAG_SET_ALL: //如果是通过置 1 来标志事件的发生 case OS_OPT_PEND_FLAG_SET_ANY: p_grp->Flags &= ~flags_rdy; //清零事件里触发事件的标志位 break; //跳出 #if OS_CFG_FLAG_MODE_CLR_EN > 0u//如果启用了标志位清零触发模式 case OS_OPT_PEND_FLAG_CLR_ALL: //如果是通过清零来标志事件的发生 case OS_OPT_PEND_FLAG_CLR_ANY: p_grp->Flags |= flags_rdy; //置 1 事件里触发事件的标志位 break; //跳出 #endif default: //如果触发模式超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return ((OS_FLAGS)0); //返回 0(有错误),停止执行 } } CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" return (flags_rdy); //返回让任务脱离等待的标志值 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326事件等待函数OSFlagPend()使用实例
#define EVENT1 (0x01<<0) //设置事件掩码的位0 #define EVENT2 (0x01<<1) //设置事件掩码的位1 OS_FLAG_GRP flag_grp; void AppTaskPend(void * p_arg){ OS_ERR err; (void)p_arg; while(DEF_TRUE){ //等待标志组的BIT0和BIT1位均被置1 OSFlagPend((OS_FLAG_GRP *)&flag_grp, (OS_FLAGS )(EVENT1 | EVENT2), (OS_TICK )0, (OS_OPT )OS_OPT_PEND_FLAG_SET_ALL |OS_OPT_PEND_BLOCKING, (CPU_TS *)0, (OS_ERR *)&err); //等待标志组的BIT0和BIT1有一个被清零 OSFlagPend((OS_FLAG_GRP *)&flag_grp, (OS_FLAGS )(EVENT1 | EVENT2), (OS_TICK )0, (OS_OPT )OS_OPT_PEND_FLAG_CLR_ANY |OS_OPT_PEND_BLOCKING, (CPU_TS *)0, (OS_ERR *)&err); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28当用户调用这个函数接口时,系统首先根据用户指定参数和接收选项来判断它要等待的事件是否发生,如果已经发生,则根据等待选项来决定是否清除事件的相应标志位,并且返回事件标志位的值,但是这个值可能不是一个稳定的值,所以在等待到对应事件的时候,我们最好要判断事件是否与任务需要的一致;如果事件没有发生,则把任务添加到事件等待列表中,将当前任务阻塞,直到事件发生或等待时间超时.
# 事件实验
事件标志组实验是在μC/OS中创建了两个任务,一个是设置事件任务,一个是等待事件任务,两个任务独立运行,设置事件任务通过检测按键的按下情况设置不同的事件标志位,等待事件任务则获取这两个事件标志位,并且判断两个事件是否都发生,如果是则输出相应信息.等待事件任务一直在等待事件的发生,等待到事件之后清除对应的事件标记位.
声明事件标志组,并定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象
每次按下WKUP都会进行任务同步.
# 软件定时器
# 软件定时器的基本概念
定时器,是指从指定的时刻开始,经过一个指定时间,然后触发一个超时事件,用户可以自定义定时器的周期与频率.类似生活中的闹钟,我们可以设置闹钟每天什么时候响,还能设置响的次数,是响一次还是每天都响.
定时器有硬件定时器和软件定时器之分:
硬件定时器是芯片本身提供的定时功能.一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断.硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式.
软件定时器,软件定时器是由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的.
使用硬件定时器时,每次在定时时间到达之后就会自动触发一个中断,用户在中断中处理信息;而使用软件定时器时,需要我们在创建软件定时器时指定时间到达后要调用的函数(也称超时函数/回调函数,为了统一,下文均用回调函数描述),在回调函数中处理信息.
PS:软件定时器回调函数的上下文是任务,下文所说的定时器均为软件定时器.
软件定时器在被创建之后,当经过设定的时钟计数值后会触发用户定义的回调函数.定时精度与系统时钟的周期有关.一般系统利用
SysTick
作为软件定时器的基础时钟,软件定时器的回调函数类似硬件的中断服务函数,所以,回调函数也要快进快出,而且回调函数中不能有任何阻塞任务运行的情况(软件定时器回调函数的上下文环境是任务),比如OSTimeDly()
以及其他能阻塞任务运行的函数,两次触发回调函数的时间间隔period叫定时器的定时周期.
μC/OS操作系统提供软件定时器功能,软件定时器的使用相当于扩展了定时器的数量,允许创建更多的定时业务.μC/OS软件定时器功能上支持:
- 裁剪:能通过宏关闭软件定时器功能.
- 软件定时器创建.
- 软件定时器启动.
- 软件定时器停止.
- 软件定时器删除.
μC/OS 提供的软件定时器支持单次模式和周期模式,单次模式和周期模式的定时时间到之后都
会调用软件定时器的回调函数,用户可以在回调函数中加入要执行的工程代码.
单次模式:当用户创建了定时器并启动了定时器后,定时时间到了,只执行一次回调函数之后就
将不再重复执行,当然用户还是可以调用软件定时器启动函数OSTmrStart()来启动一次软件定时
器.
周期模式:这个定时器会按照设置的定时时间循环执行回调函数,直到用户将定时器删除.
μC/OS中软件定时器的周期模式也分为两种,一种是有初始化延迟的周期模式,另一种是
无初始化延迟的周期模式,由OSTmrCreate()中的"dly"参数设置,这两种周期模式基本是一致
的,但是有个细微的差别.
有初始化延迟的周期模式:在软件定时器创建的时候,其第一个定时周期是由定时器中的dly参
数决定,然后在运行完第一个周期后,其以后的定时周期均由period参数决定.
无初始化延迟的周期模式:该定时器从始至终都按照周期运行.
比如创建两个周期定时器,定时器1是无初始化延迟的定时器,周期为100个tick(时钟节拍),定时器 2是有初始化延迟的定时器,其初始化延迟的dly参数为150个tick,周期为100个tick,从tick为0的时刻就启动了两个软件定时器.定时器1从始至终都按照正常的周期运行,但是定时器2则在第一个周期中的运行周期为dly,从第二个运行周期开始按照正常的100个tick来运行.
μC/OS通过一个
OS_TmrTask
任务(也叫软件定时器任务)来管理软定时器,它是在系统初始化时(OSInit()
函数中)自动创建的,为了满足用户定时需求.TmrTask
任务会在定时器节拍到来的时候检查定时器列表,看看是否有定时器时间到了,如果到了就调用其回调函数.只有设置os_cfg.h 中的宏定义OS_CFG_DBG_EN
设置为1 ,才会将软件定时器相关代码编译进来,才能正常使用软件定时器相关功能.
# 软件定时器应用场景
- 在很多应用中,我们需要一些定时器任务,硬件定时器受硬件的限制,数量上不足以满足用户的实际需求,无法提供更多的定时器,那么可以采用软件定时器来完成,由软件定时器代替硬件定时器任务.但需要注意的是软件定时器的精度是无法和硬件定时器相比的,因为在软件定时器的定时过程中是极有可能被其他中断所打断,因为软件定时器的执行上下文环境是任务.所以,软件定时器更适用于对时间精度要求不高的任务,一些辅助型的任务.
# 软件定时器的精度
在操作系统中,通常软件定时器以系统节拍为计时的时基单位.系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s能跳动多少下,系统节拍配置为
OS_CFG_TICK_RATE_HZ
,该宏在os_app_cfg.h中有定义,默认是1000.那么系统的时钟节拍周期就为1ms(1s跳动1000下,每一下就为ms).μC/OS软件定时器的精度(分辨率) 决定于系统时基频率,也就是变量
OS_CFG_TMR_TASK_RATE_HZ
的值,它是以 Hz 为单位的.如果软件定时器任务的频率OS_CFG_TMR_TASK_RATE_HZ
设置为10Hz,系统中所有软件定时器的精度为十分之一秒.事实上,这是用于软件定时器的推荐值,因为软件定时器常用于不精确时间尺度的任务.而且定时器所定时的数值必须是这个定时器任务精度的整数倍,例如,定时器任务的频率为10HZ,那么上层软件定时器定时数值只能是100ms,200ms,1000ms等,而不能取值为150ms.由于系统节拍与软件定时器频率决定了系统中定时器能够分辨的精确度,用户可以根据实际CPU的处理能力和实时性需求设置合适的数值,软件定时器频率的值越大,精度越高,但是系统开销也将越大,因为这代表在1秒中系统进入定时器任务的次数也就越多.
PS:定时器任务的频率
OS_CFG_TMR_TASK_RATE_HZ
的值不能大于系统时基频率OS_CFG_TMR_TASK_RATE_HZ
的值.
# 软件定时器控制块
μC/OS的软件定时器也属于内核对象,是一个可以裁剪的功能模块,同样在系统中由一个控制块管理其相关信息,软件定时器的控制块中包含创建的软件定时器基本信息,在使用定时器前我们需要通过
OSTmrCreate()
函数创建一个软件定时器,但是在创建前需要我们定义一个定时器的句柄(控制块).struct os_tmr { OS_OBJ_TYPE Type; CPU_CHAR *NamePtr; OS_TMR_CALLBACK_PTR CallbackPtr; void *CallbackPtrArg; OS_TMR *NextPtr; OS_TMR *PrevPtr; OS_TICK Match; OS_TICK Remain; OS_TICK Dly; OS_TICK Period; OS_OPT Opt; OS_STATE State; #if OS_CFG_DBG_EN > 0u OS_TMR *DbgPrevPtr; OS_TMR *DbgNextPtr; #endif };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20- Type:结构体开始于一个"Type"域,μC/OS可以通过这个域辨认它是个定时器(其他内核对象的结构体首部也有"Type").如果函数需传递一种内核对象,μC/OS会检测"Type"域是否为参数所需的类型.
- *NamePtr:每个内核对象都可以被命名,以便于用户调试,这是一个指向内核对象名的指针.
- CallbackPtr:指向函数的指针,被指向的函数称作回调函数,当定时器定时时间到达后,其指向的回调函数将被调用.如果定时器创建时该指针值为NULL,回调函数将不会被调用.
- *CallbackPtrArg:当回调函数需要接受一个参数时(CallbackPtr不为NULL),这个参数通过该指针传递给回调函数,简单来说就是指向回调函数中的形参.
- *NextPtr:指针指向下一个定时器.
- *PrevPtr:指针指向上一个定时器,与NextPtr指针联合工作将定时器链接成一个双向链表.
- Match:当定时器管理器中的变量OSTmrTickCtr的值等于定时器中的Match值时,表示定时器时间到了,Match也被称为匹配时间(唤醒时间).
- Remain:中保存了距定时器定时时间到达还有多少个时基.
- Dly:包含了定时器的初次定时值(可以看作是第一次延迟的 值),这个值以定时器时基为最小单位.
- Period:是定时器的定时周期(当被设置为周期模式时).这个值以 定时器时基为最小单位.
- Opt:定时器的选项,可选参数.
- State:记录定时器的状态.
# 软件定时器函数接口
函数名称 | 函数作用 |
---|---|
OSTmrCreate() | 创建软件定时器 |
OSTmrStart() | 启动软件定时器 |
OSTmrStop() | 停止定时器 |
SOTmrDel() | 删除软件定时器 |
OS_TmrLink() | 将软件定时器插入定时器列表 |
OS_TmrUnlink() | 将运行中的定时器从原本的定时器列表中移除 |
创建软件定时器函数OSTmrCreate()
软件定时器也是内核对象,与消息队列,信号量等内核对象一样,都是需要创建之后才能使用的资源,我们在创建的时候需要指定定时器延时初始值dly,定时器周期,定时器工作模式,回调函数等.每个软件定时器只需少许的RAM 空间,理论上μC/OS支持无限多个软件定时器,只要RAM足够即可.
参数 作用 *p_tmr 用户自定义的定时器变量 *p_name 用户给定时器起的名字,方便调试 opt 功能选项 dly 如果上面的选项是OS_OPT_TMR_ONE_SHOT,那么dly就是这个定时器定时的时间.如果是OS_OPT_TMR_PERIODIC,而且dly不为0,则第一次延时的时间就是dly(为0为0延时初始化定时器,为其他则为延时对应时间启动) period 如果选项是OS_OPT_TMR_PERIODIC,那么第一次过后每次定时的时间都是period 这么长.而且如果选项是OS_OPT_TMR_PERIODIC且dly是0的话,那么定时器第一次延时的时间也是period p_callback 回调函数指针.定时器每次到期的时候都可以执行指定的回调函数,可以在创建的时候设置,回调函数跟钩子函数有很大的相似.回调函数一个很好的定义如下."回调函数是由用户撰写,而由操作系统调用的一类函数,回调函数可以把调用者和被调用者分开,调用者(例如操作系统)不需要关心被调用者到底是哪个函数,它所知道的就是有这么一类函数,这类满足相同的函数签名(函数原型,参数,返回值等),由用户书写完毕后在被调用就可以了.一般来讲:回调函数都是基于某种消息驱动,在获取相应消息时调用该函数你只要把你的函数写好并把地址传过去就行. *p_callback_arg 回调函数传递的参数 *p_err 指向返回错误类型的指针,主要有以下几种类型 选项 功能 OS_OPT_TMR_ONE_SHOT 只调用一次回调函数就停止 OS_OPT_TMR_PERIODIC 定时器到定时时间后重复调用回调函数 错误类型 含义 OS_ERR_ILLEGAL_CREATE_RUN_TIME 在定义OSSafetyCriticalStartFlag为DEF_TRUE后就不运行创建任何内核对象,包括软件定时器 OS_ERR_OBJ_CREATED 该软件定时器已经被创建过了,但是函数中没有这 个错误相关代码 OS_ERR_OBJ_PTR_NULL 参数p_tmr是个空指针 OS_ERR_OPT_INVALID 参数opt没有在规定的范围内 OS_ERR_TMR_INVALID_DLY 创建的是一次性定时器,但是参数dly为0 OS_ERR_TMR_INVALID_PERIOD 创建的是周期性定时器,但是参数peroid为0 OS_ERR_TMR_ISR 在中断中创建定时器 OS_ERR_NONE 无错误 void OSTmrCreate(OS_TMR *p_tmr, //定时器控制块指针 CPU_CHAR *p_name, //命名定时器,有助于调试 OS_TICK dly, //初始定时节拍数 OS_TICK period, //周期定时重载节拍数 OS_OPT opt, //选项 OS_TMR_CALLBACK_PTR p_callback, //定时到期时的回调函数 void*p_callback_arg, //传给回调函数的参数 OS_ERR *p_err){ CPU_SR_ALLOC();//使用到临界段(在关/开中断时)时必须用到该宏 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不执行定时操作 } #endif #ifdef OS_SAFETY_CRITICAL_IEC61508//如果启用(默认禁用)了安全关键 //如果是在调用 OSSafetyCriticalStart() 后创建该定时器 if (OSSafetyCriticalStartFlag == DEF_TRUE) { *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME; //错误类型为"非法创建内核对象" return; //返回,不执行定时操作 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_TMR_ISR; //错误类型为"在中断函数中定时" return; //返回,不执行定时操作 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测 if (p_tmr == (OS_TMR *)0) //如果参数 p_tmr 为空 { *p_err = OS_ERR_OBJ_PTR_NULL; //错误类型为"定时器对象为空" return; //返回,不执行定时操作 } switch (opt) //根据延时选项参数 opt 分类操作 { case OS_OPT_TMR_PERIODIC: //如果选择周期性定时 if (period == (OS_TICK)0) //如果周期重载实参为 0 { *p_err = OS_ERR_TMR_INVALID_PERIOD; //错误类型为"周期重载实参无效" return; //返回,不执行定时操作 } break; case OS_OPT_TMR_ONE_SHOT: //如果选择一次性定时 if (dly == (OS_TICK)0) //如果定时初始实参为 0 { *p_err = OS_ERR_TMR_INVALID_DLY; //错误类型为"定时初始实参无效" return; //返回,不执行定时操作 } break; default: //如果选项超出预期 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不执行定时操作 } #endif OS_CRITICAL_ENTER(); //进入临界段, 初始化定时器指标 p_tmr->State = (OS_STATE )OS_TMR_STATE_STOPPED; p_tmr->Type = (OS_OBJ_TYPE )OS_OBJ_TYPE_TMR; p_tmr->NamePtr = (CPU_CHAR *)p_name; p_tmr->Dly = (OS_TICK )dly; p_tmr->Match = (OS_TICK )0; p_tmr->Remain = (OS_TICK )0; p_tmr->Period = (OS_TICK )period; p_tmr->Opt = (OS_OPT )opt; p_tmr->CallbackPtr = (OS_TMR_CALLBACK_PTR)p_callback; p_tmr->CallbackPtrArg = (void *)p_callback_arg; p_tmr->NextPtr = (OS_TMR *)0; p_tmr->PrevPtr = (OS_TMR *)0; #if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量 OS_TmrDbgListAdd(p_tmr); //将该定时添加到定时器双向调试链表 #endif OSTmrQty++; //定时器个数加 1 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88软件定时器创建实例
OS_ERR err; OS_TMR my_tmr; //声明软件定时器对象 /* 创建软件定时器 */ OSTmrCreate ((OS_TMR *)&my_tmr, //软件定时器对象 (CPU_CHAR *)"MySoftTimer", //命名软件定时器 (OS_TICK )10,//定时器初始值,依 10Hz 时基计算,即为 1s (OS_TICK )10,//定时器周期重载值,依 10Hz 时基计算,即为 1s (OS_OPT )OS_OPT_TMR_PERIODIC, //周期性定时 (OS_TMR_CALLBACK_PTR )TmrCallback, //回调函数 (void *)"Timer Over!", //传递实参给回调函数 (OS_ERR *)err);
1
2
3
4
5
6
7
8
9
10
11
12
13启动软件定时器函数OSTmrStart()
在系统初始化的时候,系统会帮我们自动创建一个软件定时器任务,在这个任务中,如果暂时没有运行中的定时器,任务会进入阻塞态等待定时器任务节拍的信号量.我们在创建一个软件定时器之后,如果没有启动它,该定时器就不会被添加到软件定时器列表中,那么在定时器任务就不会运行该定时器,而
OSTmrStart()
函数就是将已经创建的软件定时器添加到定时器列表中,这样子被创建的定时器就会被系统运行.参数 含义 *p_tmr 指向定时器对象的指针 *p_err 指向返回错误类型的指针 错误类型 含义 OS_ERR_OBJ_TYPE 参数p_tmr没有指向一个定时器类型的变量 OS_ERR_TMR_INVALID 参数p_tmr是空指针 OS_ERR_TMR_INACTIVE 使用定时器没有先初始化,即没有先创建定时器,也可能是定时器已经被删除 OS_ERR_TMR_INVALID_STATE 定时器的状态不可用 OS_ERR_TMR_ISR 企图在中断中启动定时器. 创建完必须要再使用这个函数才可以运行定时器.如果是正在运行中的定时器再调用 启动函数,就会达到重启的效果.下面首先介绍下定时器的状态 OS_TMR_STATE_UNUSED 定时器已经被删除 OS_TMR_STATE_STOPPED 停止一个定时器,刚刚创建完也是这个状态 OS_TMR_STATE_RUNNING 正常运行 OS_TMR_STATE_COMPLETED 定时完成,一次性地定时器才会有这种状态,因为重复性的 定时器不可能自然地停止,用启动函数可以重新启动 OS_ERR_NONE 无错误 CPU_BOOLEAN OSTmrStart (OS_TMR *p_tmr, //定时器控制块指针 OS_ERR *p_err){ OS_ERR err; CPU_BOOLEAN success; //暂存函数执行结果 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_TMR_ISR; //错误类型为"在中断函数中定时" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u //如果启用(默认启用)了参数检测 if(p_tmr == (OS_TMR *)0) //如果启用 p_tmr 的实参为空 { *p_err = OS_ERR_TMR_INVALID; //错误类型为"无效的定时器" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测 if (p_tmr->Type != OS_OBJ_TYPE_TMR) //如果该定时器的对象类型有误 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型错误" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif OSSchedLock(&err); //锁住调度器 switch (p_tmr->State) //根据定时器的状态分类处理 { case OS_TMR_STATE_RUNNING: //如果定时器正在运行,则重启 OS_TmrUnlink(p_tmr); //从定时器列表里移除该定时器 OS_TmrLink(p_tmr, OS_OPT_LINK_DLY); //将该定时器重新插入定时器列表 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_NONE; //错误类型为"无错误" success = DEF_TRUE; //执行结果暂为 DEF_TRUE break; case OS_TMR_STATE_STOPPED: //如果定时器已被停止,则开启 case OS_TMR_STATE_COMPLETED: //如果定时器已完成了,则开启 OS_TmrLink(p_tmr, OS_OPT_LINK_DLY); //将该定时器重新插入定时器列表 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_NONE; //错误类型为"无错误" success = DEF_TRUE; //执行结果暂为 DEF_TRUE break; case OS_TMR_STATE_UNUSED: //如果定时器未被创建 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INACTIVE; //错误类型为"定时器未激活" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; default: //如果定时器的状态超出预期 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INVALID_STATE; //错误类型为"定时器无效" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; } return (success); //返回执行结果 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68启动软件定时器函数使用实例
OS_TMR my_tmr; //声明软件定时器对象 /* 启动软件定时器 */ OSTmrStart ((OS_TMR *)&my_tmr, //软件定时器对象 (OS_ERR *)err);
1
2
3
4
5
6定时器列表插入函数OS_TmrLink()
从定时器列表里移除该定时器之后需要将软件定时器重新按照周期插入定时器列表中,调用OS_TmrLink()函数即可将软件定时器插入定时器列表.
void OS_TmrLink (OS_TMR *p_tmr, //定时器控制块指针 OS_OPT opt){ OS_TMR_SPOKE *p_spoke; OS_TMR *p_tmr0; OS_TMR *p_tmr1; OS_TMR_SPOKE_IX spoke; //重置定时器为运行状态 p_tmr->State = OS_TMR_STATE_RUNNING; if (opt == OS_OPT_LINK_PERIODIC) { //如果定时器是再次插入,匹配时间加个周期重载值 p_tmr->Match = p_tmr->Period + OSTmrTickCtr; } else { //如果定时器是首次插入 if (p_tmr->Dly == (OS_TICK)0) //如果定时器的 Dly = 0,匹配时间加个周期重载值 { p_tmr->Match = p_tmr->Period + OSTmrTickCtr; } else { //如果定时器的 Dly != 0,匹配时间加个 Dly p_tmr->Match = p_tmr->Dly + OSTmrTickCtr; } } //通过哈希算法决定将该定时器插入定时器轮的哪个列表. spoke = (OS_TMR_SPOKE_IX)(p_tmr->Match % OSCfg_TmrWheelSize); p_spoke = &OSCfg_TmrWheel[spoke]; if (p_spoke->FirstPtr == (OS_TMR *)0) { //如果列表为空,直接将该定时器作为列表的第一个元素. p_tmr->NextPtr = (OS_TMR *)0; p_tmr->PrevPtr = (OS_TMR *)0; p_spoke->FirstPtr = p_tmr; p_spoke->NbrEntries = 1u; } else { //如果列表非空,算出定时器 p_tmr 的剩余时间 p_tmr->Remain = p_tmr->Match - OSTmrTickCtr; //取列表的首个元素到 p_tmr1 p_tmr1 = p_spoke->FirstPtr; while (p_tmr1 != (OS_TMR *)0) { //如果 p_tmr1 非空,算出 p_tmr1 的剩余时间 p_tmr1->Remain = p_tmr1->Match - OSTmrTickCtr; if (p_tmr->Remain > p_tmr1->Remain) { //如果 p_tmr 的剩余时间大于 p_tmr1 的 if (p_tmr1->NextPtr != (OS_TMR *)0) { //如果 p_tmr1 后面非空,取 p_tmr1 后一个定时器为新的 p_tmr1 进行下一次循环 p_tmr1 = p_tmr1->NextPtr;(11) } else { //如果 p_tmr1 后面为空,将 p_tmr 插到 p_tmr1 的后面,结束循环 p_tmr->NextPtr = (OS_TMR *)0; p_tmr->PrevPtr = p_tmr1; p_tmr1->NextPtr = p_tmr; p_tmr1 = (OS_TMR *)0; } } else { //如果 p_tmr 的剩余时间不大于 p_tmr1 的, if (p_tmr1->PrevPtr == (OS_TMR *)0) { //将 p_tmr 插入 p_tmr1 的前一个,结束循环. p_tmr->PrevPtr = (OS_TMR *)0; p_tmr->NextPtr = p_tmr1; p_tmr1->PrevPtr = p_tmr; p_spoke->FirstPtr = p_tmr; } else { p_tmr0 = p_tmr1->PrevPtr; p_tmr->PrevPtr = p_tmr0; p_tmr->NextPtr = p_tmr1; p_tmr0->NextPtr = p_tmr; p_tmr1->PrevPtr = p_tmr; } p_tmr1 = (OS_TMR *)0; } } //列表元素成员数加 1 p_spoke->NbrEntries++; } if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) { //更新列表成员数最大值历史记录 p_spoke->NbrEntriesMax = p_spoke->NbrEntries; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82定时器列表删除函数OS_TmrUnlink()
void OS_TmrUnlink (OS_TMR *p_tmr //定时器控制块指针 ){ OS_TMR_SPOKE *p_spoke; OS_TMR *p_tmr1; OS_TMR *p_tmr2; OS_TMR_SPOKE_IX spoke; spoke = (OS_TMR_SPOKE_IX)(p_tmr->Match % OSCfg_TmrWheelSize); //与插入时一样,通过哈希算法找出 p_spoke = &OSCfg_TmrWheel[spoke]; //该定时器在定时器的哪个列表. if (p_spoke->FirstPtr == p_tmr) //如果 p_tmr 是列表的首个元素 { //取 p_tmr 后一个元素为 p_tmr1(可能为空) p_tmr1 = (OS_TMR *)p_tmr->NextPtr; p_spoke->FirstPtr = (OS_TMR *)p_tmr1; //表首改为 p_tmr1 if (p_tmr1 != (OS_TMR *)0) //如果 p_tmr1 确定非空 { p_tmr1->PrevPtr = (OS_TMR *)0; //p_tmr1 的前面清空 } } else {//如果 p_tmr 不是列表的首个元素 //将 p_tmr 从列表移除,并将 p_tmr 前后的两个元素连接在一起. p_tmr1 = (OS_TMR *)p_tmr->PrevPtr; p_tmr2 = (OS_TMR *)p_tmr->NextPtr; p_tmr1->NextPtr = p_tmr2; if (p_tmr2 != (OS_TMR *)0) { p_tmr2->PrevPtr = (OS_TMR *)p_tmr1; } } p_tmr->State = OS_TMR_STATE_STOPPED; //复位 p_tmr 的指标 p_tmr->NextPtr = (OS_TMR *)0; p_tmr->PrevPtr = (OS_TMR *)0; p_spoke->NbrEntries--; //列表元素成员减 1 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37软件定时器列表管理
有些情况下,当系统中有多个软件定时器的时候,μC/OS可能要维护上百个定时器.使用定时器列表会大大降低更新定时器列表所占用的CPU时间,一个一个检测是否到期效率很低,有没有什么办法让系统快速查找到到期的软件定时器?μC/OS 对软件定时器列表的管理就跟时间节拍一样,采用哈希算法.
OS_TmrLink
将不同的定时器变量根据其对OSCfg_TmrWheelSize
余数的不同插入数组OSCfg_TmrWheel[OS_CFG_TMR_WHEEL_SIZE]
中去.定时器列表中包含了
OS_CFG_TMR_WHEEL_SIZE
条记录,该值是一个宏定义,由用户指定,在os_cfg_app.h文件中.能记录定时器的多少仅限于处理器的RAM空间,推荐的设置值为定时器
数/4.定时器列表的每个记录都由3部分组成:
NbrEntriesMax
表明该记录中有多少个定时器;NbrEntriesMax
表明该记录中最大时存放了多少个定时器;FirstPtr
指向当前记录的定时器链表.停止定时器函数OSTmrStop()
停止一个软件定时器.软件定时器被停掉之后可以调用 OSTmrStart() 函数重启,但是重启之后定时器是从头计时,而不是接着上次停止的时刻继续计时.
CPU_BOOLEAN OSTmrStop(OS_TMR *p_tmr, //定时器控制块指针 OS_OPT opt, //选项 void *p_callback_arg, //传给回调函数的新参数 OS_ERR *p_err){ OS_TMR_CALLBACK_PTR p_fnct; OS_ERR err; CPU_BOOLEAN success; //暂存函数执行结果 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_TMR_ISR; //错误类型为"在中断函数中定时" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测 if (p_tmr == (OS_TMR *)0) //如果启用 p_tmr 的实参为空 { *p_err = OS_ERR_TMR_INVALID; //错误类型为"无效的定时器" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测 if (p_tmr->Type != OS_OBJ_TYPE_TMR) //如果该定时器的对象类型有误 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型错误" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif OSSchedLock(&err); //锁住调度器 switch (p_tmr->State) (5) { //根据定时器的状态分类处理 case OS_TMR_STATE_RUNNING: //如果定时器正在运行 OS_TmrUnlink(p_tmr); //从定时器轮列表里移除该定时器 *p_err = OS_ERR_NONE; //错误类型为"无错误" switch (opt) { //根据选项分类处理 case OS_OPT_TMR_CALLBACK: //执行回调函数,使用创建定时器时的实参 p_fnct = p_tmr->CallbackPtr; //取定时器的回调函数 if (p_fnct != (OS_TMR_CALLBACK_PTR)0) //如果回调函数存在 { (*p_fnct)((void *)p_tmr, p_tmr->CallbackPtrArg); //使用创建定时器时的实参执行回调函数 } else { //如果回调函数不存在 *p_err = OS_ERR_TMR_NO_CALLBACK; //错误类型为"定时器没有回调函数" } break; case OS_OPT_TMR_CALLBACK_ARG: //执行回调函数,使用 p_callback_arg 作为实参 p_fnct = p_tmr->CallbackPtr; //取定时器的回调函数 if (p_fnct != (OS_TMR_CALLBACK_PTR)0) //如果回调函数存在 { (*p_fnct)((void *)p_tmr, p_callback_arg); //使用 p_callback_arg 作为实参执行回调函数 } else { //如果回调函数不存在 *p_err = OS_ERR_TMR_NO_CALLBACK; //错误类型为"定时器没有回调函数" } break; case OS_OPT_TMR_NONE: //只需停掉定时器 break; default: (10)//情况超出预期 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项无效" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } OSSchedUnlock(&err); success = DEF_TRUE; break; case OS_TMR_STATE_COMPLETED: //如果定时器已完成第一次定时 case OS_TMR_STATE_STOPPED: //如果定时器已被停止 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_STOPPED; //错误类型为"定时器已被停止" success = DEF_TRUE; //执行结果暂为 DEF_TRUE break; case OS_TMR_STATE_UNUSED: //如果该定时器未被创建过 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INACTIVE; //错误类型为"定时器未激活" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; default: //如果定时器状态超出预期 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INVALID_STATE;//错误类型为"定时器状态非法" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; } return (success); //返回执行结果 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105定时器停止函数的使用实例
OS_ERR err; OS_TMR my_tmr; //声明软件定时器对象 OSTmrStop ((OS_TMR *)&my_tmr, //定时器控制块指针 (OS_OPT )OS_OPT_TMR_NONE, //选项 (void *)"Timer Over!", //传给回调函数的新参数 (OS_ERR *)err);
1
2
3
4
5
6
7删除软件定时器函数OSTmrDel()
删除一个已经被创建成功的软件定时器,删除之后就无法使用该定时器,并且定时器相应的信息也会被系清空.要想使用
OSTmrDel()
函数必须在头文件os_cfg.h中把宏OS_CFG_TMR_DEL_EN
定义为1.#if OS_CFG_TMR_DEL_EN > 0u//如果启用了 OSTmrDel() 函数 CPU_BOOLEAN OSTmrDel(OS_TMR *p_tmr, //定时器控制块指针 OS_ERR *p_err){ OS_ERR err; CPU_BOOLEAN success; //暂存函数执行结果 #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) //如果错误类型实参为空 { OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u //如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) //如果该函数是在中断中被调用 { *p_err = OS_ERR_TMR_ISR; //错误类型为"在中断函数中定时" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测 if (p_tmr == (OS_TMR *)0) //如果启用 p_tmr 的实参为空 { *p_err = OS_ERR_TMR_INVALID; //错误类型为"无效的定时器" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif #if OS_CFG_OBJ_TYPE_CHK_EN > 0u//如果启用(默认启用)了对象类型检测 if (p_tmr->Type != OS_OBJ_TYPE_TMR) //如果该定时器的对象类型有误 { *p_err = OS_ERR_OBJ_TYPE; //错误类型为"对象类型错误" return (DEF_FALSE); //返回 DEF_FALSE,不继续执行 } #endif OSSchedLock(&err); //锁住调度器 #if OS_CFG_DBG_EN > 0u//如果启用(默认启用)了调试代码和变量 OS_TmrDbgListRemove(p_tmr); //将该定时从定时器双向调试链表移除 #endif OSTmrQty--; //定时器个数减 1 switch (p_tmr->State) //根据定时器的状态分类处理 { case OS_TMR_STATE_RUNNING: //如果定时器正在运行 OS_TmrUnlink(p_tmr); //从当前定时器列表列表移除定时器 OS_TmrClr(p_tmr); //复位定时器的指标 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_NONE; //错误类型为"无错误" success = DEF_TRUE; //执行结果暂为 DEF_TRUE break; case OS_TMR_STATE_STOPPED: //如果定时器已被停止 case OS_TMR_STATE_COMPLETED: //如果定时器已完成第一次定时 OS_TmrClr(p_tmr); //复位定时器的指标 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_NONE; //错误类型为"无错误" success = DEF_TRUE; //执行结果暂为 DEF_TRUE break; case OS_TMR_STATE_UNUSED: //如果定时器已被删除 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INACTIVE; //错误类型为"定时器未激活" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; default: //如果定时器的状态超出预期 OSSchedUnlock(&err); //解锁调度器 *p_err = OS_ERR_TMR_INVALID_STATE; //错误类型为"定时器无效" success = DEF_FALSE; //执行结果暂为 DEF_FALSE break; } return (success); //返回执行结果 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76软件定时器删除函数使用实例
OS_ERR err; OS_TMR my_tmr; //声明软件定时器对象 OSTmrDel ((OS_TMR *)&my_tmr, //软件定时器对象 (OS_ERR *)err);
1
2
3
4
5
6
# 软件定时器任务
软件定时器的回调函数的上下文是在任务中,所有,系统中必须要一个任务来管理所有的软件定时器,等到定时时间到达后就调用定时器对应的回调函数,那么软件定时器任务又是一个什么东西呢,它是在系统初始化的时候系统就帮我们创建的一个任务.
创建软件定时器任务
void OS_TmrInit(OS_ERR *p_err) { OS_TMR_SPOKE_IX i; OS_TMR_SPOKE *p_spoke; #ifdef OS_SAFETY_CRITICAL if (p_err == (OS_ERR *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); return; } #endif #if OS_CFG_DBG_EN > 0u OSTmrDbgListPtr = (OS_TMR *)0; #endif if (OSCfg_TmrTaskRate_Hz > (OS_RATE_HZ)0) { OSTmrUpdateCnt = OSCfg_TickRate_Hz / OSCfg_TmrTaskRate_Hz; } else { OSTmrUpdateCnt = OSCfg_TickRate_Hz / (OS_RATE_HZ)10; } OSTmrUpdateCtr = OSTmrUpdateCnt; OSTmrTickCtr = (OS_TICK)0; OSTmrTaskTimeMax = (CPU_TS)0; for (i = 0u; i < OSCfg_TmrWheelSize; i++) { p_spoke = &OSCfg_TmrWheel[i]; p_spoke->NbrEntries = (OS_OBJ_QTY)0; p_spoke->NbrEntriesMax = (OS_OBJ_QTY)0; p_spoke->FirstPtr = (OS_TMR *)0; } /* ---------------- CREATE THE TIMER TASK --------------- */ if (OSCfg_TmrTaskStkBasePtr == (CPU_STK*)0) { *p_err = OS_ERR_TMR_STK_INVALID; return; } if (OSCfg_TmrTaskStkSize < OSCfg_StkSizeMin) { *p_err = OS_ERR_TMR_STK_SIZE_INVALID; return; } if (OSCfg_TmrTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) { *p_err = OS_ERR_TMR_PRIO_INVALID; return; } OSTaskCreate((OS_TCB *)&OSTmrTaskTCB, //创建OS_TmrTask任务 (CPU_CHAR *)((void *)"μC/OS-III Timer Task"), (OS_TASK_PTR )OS_TmrTask, (void *)0, (OS_PRIO )OSCfg_TmrTaskPrio, (CPU_STK *)OSCfg_TmrTaskStkBasePtr, (CPU_STK_SIZE)OSCfg_TmrTaskStkLimit, (CPU_STK_SIZE)OSCfg_TmrTaskStkSize, (OS_MSG_QTY )0, (OS_TICK )0, (void *)0, (OS_OPT)(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR|OS_OPT_TASK_NO_TLS), (OS_ERR *)p_err); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71PS:正常来说定时器任务的执行频率
OSCfg_TmrTaskRate_Hz
是大于0 的,并且能被OSCfg_TickRate_Hz
整除,才能比较准确得到定时器任务运行的频率.如果OSCfg_TmrTaskRate_Hz
设置为大于0,就配置定时器任务的频率.否则就配置为系统时钟频率的十分之一(1/10).不过当设定的定时器的频率大于时钟节拍的执行频率的时候,定时器运行就会出错,但是这里没有进行判断,在写代码的时候需注意.OS_TmrTask()函数
void OS_TmrTask (void *p_arg) { CPU_BOOLEAN done; OS_ERR err; OS_TMR_CALLBACK_PTR p_fnct; OS_TMR_SPOKE *p_spoke; OS_TMR *p_tmr; OS_TMR *p_tmr_next; OS_TMR_SPOKE_IX spoke; CPU_TS ts; CPU_TS ts_start; CPU_TS ts_end; p_arg = p_arg;/* Not using 'p_arg', prevent compiler warning */ while (DEF_ON) { /* 等待信号指示更新定时器的时间 */ (void)OSTaskSemPend((OS_TICK )0, (OS_OPT )OS_OPT_PEND_BLOCKING, (CPU_TS *)&ts, (OS_ERR *)&err); OSSchedLock(&err); ts_start = OS_TS_GET(); /* 增加当前定时器时间 */ OSTmrTickCtr++; /* 通过哈希算法找到对应时间唤醒的列表 */ spoke = (OS_TMR_SPOKE_IX)(OSTmrTickCtr % OSCfg_TmrWheelSize); p_spoke = &OSCfg_TmrWheel[spoke]; /* 获取列表头部的定时器 */ p_tmr = p_spoke->FirstPtr; done = DEF_FALSE; while (done == DEF_FALSE) { if (p_tmr != (OS_TMR *)0) { /* 指向下一个定时器以进行更新, 因为可能当前定时器到时了会从列表中移除 */ p_tmr_next = (OS_TMR *)p_tmr->NextPtr; /* 确认是定时时间到达 */ if (OSTmrTickCtr == p_tmr->Match) { /* 先移除定时器 */ OS_TmrUnlink(p_tmr); /* 如果是周期定时器 */ if (p_tmr->Opt == OS_OPT_TMR_PERIODIC) { /* 重新按照唤醒时间插入定时器列表 */ OS_TmrLink(p_tmr, OS_OPT_LINK_PERIODIC); } else { /* 定时器状态设置为已完成 */ p_tmr->State = OS_TMR_STATE_COMPLETED; } /* 执行回调函数(如果可用)*/ p_fnct = p_tmr->CallbackPtr; if (p_fnct != (OS_TMR_CALLBACK_PTR)0) { (*p_fnct)((void *)p_tmr, p_tmr->CallbackPtrArg); } /* 看看下一个定时器是否匹配 */ p_tmr = p_tmr_next; } else { done = DEF_TRUE; } } else { done = DEF_TRUE; } } /* 测量定时器任务的执行时间 */ ts_end = OS_TS_GET() - ts_start; OSSchedUnlock(&err); if (OSTmrTaskTimeMax < ts_end) { OSTmrTaskTimeMax = ts_end; } } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87调用
OSTaskSemPend()
函数在一直等待定时器节拍的信号量,等待到了才运行.系统的时钟节拍是基于SysTick定时器上的,μC/OS采用Tick任务(OS_TickTask)管理系统的时间节拍,而我们定时器节拍是由系统节拍分频而来,那么其发送信号量的地方当然也是在 SysTick 中断服务函数中,但是μC/OS支持采用中断延迟,如果使用了中断延迟,那么发送任务信号量的地方就会在中断发布任务中(OS_IntQTask),从代码中,我们可以看到当OSTmrUpdateCtr
减到0的时候才会发送一次信号量.
# 软件定时器实验
软件定时器实验是在μC/OS中创建了一个应用任务AppTaskTmr
,在该任务中创建一个软件定时器,周期性定时1s,每秒输出一行.
定义任务空间栈的大小以及任务栈数组,任务控制块和优先级.
定义任务函数
任务启动函数编写
结果现象
经验证软件定时器每秒输出一行.
# 总结
- 定时器的创建,删除,启动,停止这些操作无非就是在操作定时器列表的双向列表和根据不同的设置进行定时器状态的转化以及相关的处理.至于检测定时器到期,系统将时间节拍进行分频得到定时器任务执行的频率,在定时器任务中,系统采用了哈希算法进行快速检测有没有定时器到期,然后执行其对应的回调函数等操作.软件定时器最核心的一点是底层的一个硬件定时器(SysTick内核定时器)上进行软件分频.
- μC/OS 允许用户建立任意数量的定时器(只限制于处理器的RAM大小).
- 回调函数是在定时器任务中被调用,所以回调函数的上下文环境是在任务中,并且运行回调函数时调度器处于被锁状态.一般我们编写的回调函数越简短越好,并且不能在回调函数中等待消息队列,信号量,事件等操作,否则定时器任务会被挂起,导致定时器任务崩溃,这是绝对不允许的.此外还需要注意几点:
- 回调函数是在定时器任务中被执行的,这意味着定时器任务需要有足够的栈空间供回调函数去执行.
- 回调函数是在根据定时器队列中依次存放的,所以在定时器时间到达后回调函数是依次被执行的.
- 定时器任务的执行时间决定于:有多少个定时器期满,执行定时器中的回调函数需多少时间.因为回调函数是用户提供,它可能很大程度上影响了定时器任务的执行时间.
- 回调函数被执行时会锁调度器,所以我们必须让回调函数尽可能地短,以便其他任务能正常运行.