专家
公告
财富商城
电子网
旗下网站
首页
问题库
专栏
标签库
话题
专家
NEW
门户
发布
提问题
发文章
STM32
【ALIENTEK 战舰STM32开发板例程系列连载+教学】第六十章 UCOSII实验3-消息队列、信号量集和软件定时器
2019-10-16 05:47
发布
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
站内问答
/
STM32/STM8
7656
15
1314
第六十章
UCOSII
实验
3-
消息队列、信号量集和软件定时器
上一章,我们学习了
UCOSII
的信号量和邮箱的使用,本章,我们将学习消息队列、信号量集和软件定时器的使用。本章分为如下几个部分:
60.1 UCOSII
消息队列、信号量集和软件定时器简介
60.2
硬件设计
60.3
软件设计
60.4
下载验证
60.1
UCOSII
消息队列、信号量集和软件定时器简介
上一章,我们介绍了信号量和邮箱的使用,本章我们介绍比较复杂消息队列、信号量集以及软件定时器的使用。
消息队列
使用消息队列可以在任务之间传递多条消息。消息队列由三个部分组成:事件控制块、消息队列和消息。当把事件控制块成员
OSEventType
的值置为
OS_EVENT_TYPE_Q
时,该事件控制块描述的就是一个消息队列。
消息队列的数据结构如图
60.1.1
所示。从图中可以看到,消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员
OSEventPtr
指向了一个叫做队列控制块(
OS_Q
)的结构,该结构管理了一个数组
MsgTbl[]
,该数组中的元素都是一些指向消息的指针。
图
60.1.1
消息队列的数据结构
队列控制块(
OS_Q
)的结构定义如下:
typedef struct os_q
{
struct os_q *OSQPtr;
void **OSQStart;
void **OSQEnd;
void **OSQIn;
void **OSQOut;
INT16U OSQSize;
INT16U OSQEntries;
} OS_Q;
该结构体中各参数的含义如表
60.1.1
所示:
参数
说明
OSQPtr
指向下一个空的队列控制块
OSQSize
数组的长度
OSQEntres
已存放消息指针的元素数目
OSQStart
指向消息指针数组的起始地址
OSQEnd
指向消息指针数组结束单元的下一个单元。它使得数组构
成了一个循环的缓冲区
OSQIn
指向插入一条消息的位置。当它移动到与
OSQEnd
相等时,
被调整到指向数组的起始单元
OSQOut
指向被取出消息的位置。当它移动到与
OSQEnd
相等时,被
调整到指向数组的起始单元
表
60.1.1
队列控制块各参数含义
其中,可以移动的指针为
OSQIn
和
OSQOut
,而指针
OSQStart
和
OSQEnd
只是一个标志(常指针)。当可移动的指针
OSQIn
或
OSQOut
移动到数组末尾,也就是与
OSQEnd
相等时,可移动的指针将会被调整到数组的起始位置
OSQStart
。也就是说,从效果上来看,指针
OSQEnd
与
OSQStart
等值。于是,这个由消息指针构成的数组就头尾衔接起来形成了一个如图
60.1.2
所示的循环的队列。
图
60.1.2
消息指针数组构成的环形数据缓冲区
在
UCOSII
初始化时,系统将按文件
os_cfg.h
中的配置常数
OS_MAX_QS
定义
OS_MAX_QS
个队列控制块,并用队列控制块中的指针
OSQPtr
将所有队列控制块链接为链表。由于这时还没有使用它们,故这个链表叫做空队列控制块链表。
接下来我们看看在
UCOSII
中,与消息队列相关的几个函数(未全部列出,下同)。
1)
创建消息队列函数
创建一个消息队列首先需要定义一指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,然后再调用函数
OSQCreate
来创建消息队列。创建消息队列函数
OSQCreate
的原型为:
OS_EVENT *OSQCreate(void**start,INT16U size)
。其中,
start
为存放消息缓冲区指针数组的地址,
size
为该数组大小。该函数的返回值为消息队列指针。
2)
请求消息队列函数
请求消息队列的目的是为了从消息队列中获取消息。任务请求消息队列需要调用函数
OSQPend
,该函数原型为:
void*OSQPend(OS_EVENT*pevent,INT16U timeout,INT8U *err)
。
其中,
pevent
为所请求的消息队列的指针,
timeout
为任务等待时限,
err
为错误信息。
3)
向消息队列发送消息函数
任务可以通过调用函数
OSQPost
或
OSQPostFront
两个函数来向消息队列发送消息。函数
OSQPost
以
FIFO
(先进先出)的方式组织消息队列,函数
OSQPostFront
以
LIFO
(后进先出)的方式组织消息队列。这两个函数的原型分别为:
INT8U OSQPost(OS_EVENT
*pevent,void *msg)
和
INT8U OSQPost(OS_EVENT*pevent,void*msg)
。
其中,
pevent
为消息队列的指针,
msg
为待发消息的指针。
消息队列还有其他一些函数,这里我们就不介绍了,感兴趣的朋友可以参考《嵌入式实时操作系统
UCOSII
原理及应用》第五章,关于队列更详细的介绍,也请参考该书。
信号量集
在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。
UCOSII
为了实现多个信号量组合的功能定义了一种特殊的数据结构——信号量集。
信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑,其示意图如图
60.1.3
所示
图
60.1.3
信号量集示意图
不同于信号量、消息邮箱、消息队列等事件,
UCOSII
不使用事件控制块来描述信号量集,而使用了一个叫做标志组的结构
OS_FLAG_GRP
来描述。
OS_FLAG_GRP
结构如下:
typedef struct
{
INT8U OSFlagType; //
识别是否为信号量集的标志
void *OSFlagWaitList; //
指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //
所有信号列表
}OS_FLAG_GRP;
成员
OSFlagWaitList
是一个指针,当一个信号量集被创建后,这个指针指向了这个信号量集的等待任务链表。
与其他前面介绍过的事件不同,信号量集用一个双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(
Node
)。标志组
OS_FLAG_GRP
的成员
OSFlagWaitList
就指向了信号量集的这个等待任务链表。等待任务链表节点
OS_FLAG_NODE
的结构如下:
typedef struct
{
void *OSFlagNodeNext; //
指向下一个节点的指针
void *OSFlagNodePrev; //
指向前一个节点的指针
void *OSFlagNodeTCB; //
指向对应任务控制块的指针
void *OSFlagNodeFlagGrp; //
反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags; //
信号过滤器
INT8U OSFlagNodeWaitType; //
定义逻辑运算关系的数据
} OS_FLAG_NODE;
其中
OSFlagNodeWaitType
是定义逻辑运算关系的一个常数(根据需要设置),其可选值和对应的逻辑关系如表
60.1.2
所示:
常数
信号有效状态
等待任务的就绪条件
WAIT_CLR_ALL
或
WAIT_CLR_AND
0
信号全部有效(全
0
)
WAIT_CLR_ANY
或
WAIT_CLR_OR
0
信号有一个或一个以上有效(有
0
)
WAIT_SET_ALL
或
WAIT_SET_AND
1
信号全部有效(全
1
)
WAIT_SET_ANY
或
WAIT_SET_OR
1
信号有一个或一个以上有效(有
1
)
表
60.1.2 OSFlagNodeWaitType
可选值及其意义
OSFlagFlags
、
OSFlagNodeFlags
、
OSFlagNodeWaitType
三者的关系如图
60.1.4
所示:
图
60.1.4
标志组与等待任务共同完成信号量集的逻辑运算及控制
图中为了方便说明,我们将
OSFlagFlags
定义为
8
位,但是
UCOSII
支持
8
位
/16
位
/32
位定义,这个通过修改
OS_FLAGS
的类型来确定(
UCOSII
默认设置
OS_FLAGS
为
16
位)。
上图清楚的表达了信号量集各成员的关系:
OSFlagFlags
为信号量表,通过发送信号量集的任务设置;
OSFlagNodeFlags
为信号滤波器,由请求信号量集的任务设置,用于选择性的挑选
OSFlagFlags
中的部分(或全部)位作为有效信号;
OSFlagNodeWaitType
定义有效信号的逻辑运算关系,也是由请求信号量集的任务设置,用于选择有效信号的组合方式(
0/1?
与
/
或?)。
举个简单的例子,假设请求信号量集的任务设置
OSFlagNodeFlags
的值为
0X0F
,设置
OSFlagNodeWaitType
的值为
WAIT_SET_ANY
,那么只要
OSFlagFlags
的低四位的任何一位为
1
,请求信号量集的任务将得到有效的请求,从而执行相关操作,如果低四位都为
0
,那么请求信号量集的任务将得到无效的请求。
接下来我们看看在
UCOSII
中,与信号量集相关的几个函数。
1)
创建信号量集函数
任务可以通过调用函数
OSFlagCreate
来创建一个信号量集。函数
OSFlagCreate
的原型为:
OS_FLAG_GRP *OSFlagCreate (OS_FLAGS flags,INT8U *err )
。其中,
flags
为信号量的初始值(即
OSFlagFlags
的值),
err
为错误信息,返回值为该信号量集的标志组的指针,应用程序根据这个指针对信号量集进行相应的操作。
2)
请求信号量集函数
任务可以通过调用函数
OSFlagPend
请求一个信号量集,函数
OSFlagPend
的原型为:
OS_FLAGS OSFlagPend(OS_FLAG_GRP*pgrp, OS_FLAGS flags, INT8U wait_type, INT16U timeout, INT8U *err)
。其中,
pgrp
为所请求的信号量集指针,
flags
为滤波器(即
OSFlagNodeFlags
的值),
wait_type
为逻辑运算类型(即
OSFlagNodeWaitType
的值),
timeout
为等待时限,
err
为错误信息。
3)
向信号量集发送信号函数
任务可以通过调用函数
OSFlagPost
向信号量集发信号,函数
OSFlagPost
的原型为:
OS_FLAGS OSFlagPost (OS_FLAG_GRP *pgrp, OS_FLAGS flags, INT8U opt, INT8U *err)
。其中,
pgrp
为所请求的信号量集指针,
flags
为选择所要发送的信号,
opt
为信号有效选项,
err
为错误信息。
所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“
1
”(置位)或置“
0
”(复位)的操作。至于对信号量集中的哪些信号进行操作,用函数中的参数
flags
来指定;对指定的信号是置“
1
”还是置“
0
”,用函数中的参数
opt
来指定(
opt = OS_FLAG_SET
为置“
1
”操作;
opt = OS_FLAG_CLR
为置“
0
”操作)。
信号量集就介绍到这,更详细的介绍,请参考《嵌入式实时操作系统
UCOSII
原理及应用》第六章。
软件定时器
UCOSII
从
V2.83
版本以后,加入了软件定时器,这使得
UCOSII
的功能更加完善,在其上的应用程序开发与移植也更加方便。在实时操作系统中一个好的软件定时器实现要求有较高的精度、较小的处理器开销,且占用较少的存储器资源。
通过前面的学习,我们知道
UCOSII
通过
OSTimTick
函数对时钟节拍进行加
1
操作,同时遍历任务控制块,以判断任务延时是否到时。软件定时器同样由
OSTimTick
提供时钟,但是软件定时器的时钟还受
OS_TMR_CFG_TICKS_PER_SEC
设置的控制,也就是在
UCOSII
的时钟节拍上面再做了一次“分频”,软件定时器的最快时钟节拍就等于
UCOSII
的系统时钟节拍。这也决定了软件定时器的精度。
软件定时器定义了一个单独的计数器
OSTmrTime
,用于软件定时器的计时,
UCOSII
并不在
OSTimTick
中进行软件定时器的到时判断与处理,而是创建了一个高于应用程序中所有其他任务优先级的定时器管理任务
OSTmr_Task
,在这个任务中进行定时器的到时判断和处理。时钟节拍函数通过信号量给这个高优先级任务发信号。这种方法缩短了中断服务程序的执行时间,但也使得定时器到时处理函数的响应受到中断退出时恢复现场和任务切换的影响。软件定时器功能实现代码存放在
tmr
.
c
文件中,移植时需只需在
os_cfg
.
h
文件中使能定时器和设定定时器的相关参数。
UCOSII
中软件定时器的实现方法是,将定时器按定时时间分组,使得每次时钟节拍到来时只对部分定时器进行比较操作,缩短了每次处理的时间。但这就需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时时才发生,而且定时器从组中移除和再插入操作不需要排序。这是一种比较高效的算法,减少了维护所需的操作时间。
UCOSII
软件定时器实现了
3
类链表的维护:
OS_EXT OS_TMR OSTmrTbl[OS_TMR_CFG_MAX]; //
定时器控制块数组
OS_EXT OS_TMR *OSTmrFreeList; //
空闲定时器控制块链表指针
OS_EXT OS_TMR_WHEEL OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE];//
定时器轮
其中
OS_TMR
为定时器控制块,定时器控制块是软件定时器管理的基本单元,包含软件定时器的名称、定时时间、在链表中的位置、使用状态、使用方式,以及到时回调函数及其参数等基本信息。
OSTmrTbl
[OS_TMR_CFG_MAX];
:以数组的形式静态分配定时器控制块所需的
RAM
空间,并存储所有已建立的定时器控制块,
OS_TMR_CFG_MAX
为最大软件定时器的个数。
OSTmrFreeLiSt
:为空闲定时器控制块链表头指针。空闲态的定时器控制块
(OS_TMR)
中,
OSTmrnext
和
OSTmrPrev
两个指针分别指向空闲控制块的前一个和后一个,组织了空闲控制块双向链表。建立定时器时,从这个链表中搜索空闲定时器控制块。
OSTmrWheelTbl[OS_TMR_CFG_WHEEL_SIZE]
:该数组的每个元素都是已开启定时器的一个分组,元素中记录了指向该分组中第一个定时器控制块的指针,以及定时器控制块的个数。运行态的定时器控制块
(OS_TMR)
中,
OSTmrnext
和
OSTmrPrev
两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器管理所需的数据结构示意图如图
60.1.5
所示:
图
60.1.5
软件
定时器管理所需的数据结构示意图
OS_TMR_CFG_WHEEL_SIZE
定义了
OSTmrWheelTbl
的大小,同时这个值也是定时器分组的依据。按照定时器到时值与
OS_TMR_CFG_WHEEL_SIZE
相除的余数进行分组:不同余数的定时器放在不同分组中;相同余数的定时器处在同一组中,由双向链表连接。这样,余数值为
0
~
OS_TMR_CFG_WHEEL_SIZE-1
的不同定时器控制块,正好分别对应了数组元素
OSTmr-WheelTbl[0]
~
OSTmrWheelTbl[OS_TMR_CFGWHEEL_SIZE-1]
的不同分组。每次时钟节拍到来时,时钟数
OSTmrTime
值加
1
,然后也进行求余操作,只有余数相同的那组定时器才有可能到时,所以只对该组定时器进行判断。这种方法比循环判断所有定时器更高效。随着时钟数的累加,处理的分组也由
0
~
OS_TMR_CFG_WHE EL_SIZE-1
循环。这里,我们推荐
OS_TMR_CFG_WHEEL_SIZE
的取值为
2
的
N
次方,以便采用移位操作计算余数,缩短处理时间。
信号量唤醒定时器管理任务,计算出当前所要处理的分组后,程序遍历该分组中的所有控制块,将当前
OSTmrTime
值与定时器控制块中的到时值(
OSTmrMatch
)相比较。若相等
(
即到时
)
,则调用该定时器到时回调函数;若不相等,则判断该组中下一个定时器控制块。如此操作,直到该分组链表的结尾。软件定时器管理任务的流程如图
60.1.6
所示。
图
60.1.6
软件定时器管理任务流程
当运行完软件定时器的到时处理函数之后,需要进行该定时器控制块在链表中的移除和再插入操作。插入前需要重新计算定时器下次到时时所处的分组。计算公式如下:
定时器下次到时的
OSTmrTime
值
(OSTmrMatch)=
定时器定时值
+
当前
OSTmrTime
值
新分组
=
定时器下次到时的
OSTmrTime
值
(OSTmrMatch)%OS_TMR_CFG_WHEEL_SIZE
接下来我们看看在
UCOSII
中,与软件定时器相关的几个函数。
1)
创建软件定时器函数
创建软件定时器通过函数
OSTmrCreate
实现,该函数原型为:
OS_TMR *OSTmrCreate (INT32U dly, INT32U period, INT8U opt, OS_TMR_CALLBACK callback,void *callback_arg, INT8U *pname, INT8U *perr)
。
dly
,用于初始化定时时间,对单次定时(
ONE-SHOT
模式)的软件定时器来说,这就是该定时器的定时时间,而对于周期定时(
PERIODIC
模式)的软件定时器来说,这是该定时器第一次定时的时间,从第二次开始定时时间变为
period
。
period
,在周期定时(
PERIODIC
模式),该值为软件定时器的周期溢出时间。
opt
,用于设置软件定时器工作模式。可以设置的值为:
OS_TMR_OPT_ONE_SHOT
或
OS_TMR_OPT_PERIODIC
,如果设置为前者,说明是一个单次定时器;设置为后者则表示是周期定时器。
callback
,为软件定时器的回调函数,当软件定时器的定时时间到达时,会调用该函数。
callback_arg
,回调函数的参数。
pname
,为软件定时器的名字。
perr
,为错误信息。
软件定时器的回调函数有固定的格式,我们必须按照这个格式编写,软件定时器的回调函数格式为:
void (*OS_TMR_CALLBACK)(void *ptmr, void *parg)
。其中,函数名我们可以自己随意设置,而
ptmr
这个参数,软件定时器用来传递当前定时器的控制块指针,所以我们一般设置其类型为
OS_TMR*
类型,第二个参数(
parg
)为回调函数的参数,这个就可以根据自己需要设置了,你也可以不用,但是必须有这个参数。
2)
开启软件定时器函数
任务可以通过调用函数
OSTmrStart
开启某个软件定时器,该函数的原型为:
BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr)
。其中
ptmr
为要开启的软件定时器指针,
perr
为错误信息。
3)
停止软件定时器函数
任务可以通过调用函数
OSTmrStop
停止某个软件定时器,该函数的原型为:
BOOLEAN OSTmrStop (OS_TMR *ptmr,INT8U opt,void *callback_arg,INT8U *perr)
。
其中
ptmr
为要停止的软件定时器指针。
opt
为停止选项,可以设置的值及其对应的意义为:
OS_TMR_OPT_NONE
,直接停止,不做任何其他处理
OS_TMR_OPT_CALLBACK
,停止,用初始化的参数执行一次回调函数
OS_TMR_OPT_CALLBACK_ARG
,停止,用新的参数执行一次回调函数
callback_arg
,新的回调函数参数。
perr
,错误信息。
软件定时器我们就介绍到这。
60.2
硬件设计
本节实验功能简介:本章我们在
UCOSII
里面创建
7
个任务:开始任务、
LED
任务、触摸屏任务、队列消息显示任务、信号量集任务、按键扫描任务和主任务,开始任务用于创建邮箱、消息队列、信号量集以及其他任务,之后挂起;触摸屏任务用于在屏幕上画图,测试
CPU
使用率;队列消息显示任务请求消息队列,在得到消息后显示收到的消息数据;信号量集任务用于测试信号量集,采用
OS_FLAG_WAIT_SET_ANY
的方法,任何按键按下(包括
TPAD
),该任务都会控制蜂鸣器发出“滴”的一声;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务创建
3
个软件定时器(定时器
1
,
100ms
溢出一次,显示
CPU
和内存使用率;定时
2
,
200ms
溢出一次,在固定区域不停的显示不同颜 {MOD};定时
3
,
,100ms
溢出一次,用于自动发送消息到消息队列),并通过查询消息邮箱获得键值,根据键值执行
DS1
控制、控制软件定时器
3
的开关、触摸区域清屏、触摸屏校和软件定时器
2
的开关控制等。
所要用到的硬件资源如下:
1)
指示灯
DS0
、
DS1
2)
4
个机械按键(
KEY0/KEY1/KEY2/WK_UP
)
3)
TPAD
触摸按键
4)
蜂鸣器
5)
TFTLCD
模块
这些,我们在前面的学习中都已经介绍过了。
60.3
软件设计
本章,我们在第四十三章实验
(实验
38
)的基础上修改,首先,是
UCOSII
代码的添加,具体方法同第五十九章一模一样,本章就不再详细介绍了。本章
OS_TICKS_PER_SEC
的设置还是为
500
,即
UCOSII
的时钟节拍为
2ms
。另外由于我们创建了
7
个任务,加上统计任务、空闲任务和软件定时器任务,总共
10
个任务,如果你还想添加其他任务,请把
OS_MAX_TASKS
的值适当改大。
另外,我们还需要在
os_cfg.h
里面修改软件定时器管理部分的宏定义,修改如下:
#define OS_TMR_EN 1u //
使能软件定时器功能
#define OS_TMR_CFG_MAX 16u //
最大软件定时器个数
#define OS_TMR_CFG_NAME_EN 1u //
使能软件定时器命名
#define OS_TMR_CFG_WHEEL_SIZE 8u //
软件定时器轮大小
#define OS_TMR_CFG_TICKS_PER_SEC 100u //
软件定时器的时钟节拍(
10ms
)
#define OS_TASK_TMR_PRIO 0u //
软件定时器的优先级
,
设置为最高
这样我们就使能
UCOSII
的软件定时器功能了,并且设置最大软件定时器个数为
16
,定时器轮大小为
8
,软件定时器时钟节拍为
10ms
(即定时器的最少溢出时间为
10ms
)。
最后,我们只需要修改
test.c
函数了,打开
test.c
,输入如下代码:
/////////////////////////UCOSII
任务设置
///////////////////////////////////
//START
任务
#define START_TASK_PRIO 10 //
设置任务优先级
#define START_STK_SIZE 64 //
设置任务堆栈大小
OS_STK START_TASK_STK[START_STK_SIZE]; //
任务堆栈
void start_task(void *pdata); //
任务函数
//LED
任务
#define LED_TASK_PRIO 7 //
设置任务优先级
#define LED_STK_SIZE 64 //
设置任务堆栈大小
OS_STK LED_TASK_STK[LED_STK_SIZE]; //
任务堆栈
void led_task(void *pdata); //
任务函数
//
触摸屏任务
#define TOUCH_TASK_PRIO 6 //
设置任务优先级
#define TOUCH_STK_SIZE 64 //
设置任务堆栈大小
OS_STK TOUCH_TASK_STK[TOUCH_STK_SIZE];//
任务堆栈
void touch_task(void *pdata); //
任务函数
//
队列消息显示任务
#define QMSGSHOW_TASK_PRIO 5 //
设置任务优先级
#define QMSGSHOW_STK_SIZE 64 //
设置任务堆栈大小
OS_STK QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE]; //
任务堆栈
void qmsgshow_task(void *pdata); //
任务函数
//
主任务
#define MAIN_TASK_PRIO 4 //
设置任务优先级
#define MAIN_STK_SIZE 128 //
设置任务堆栈大小
OS_STK MAIN_TASK_STK[MAIN_STK_SIZE]; //
任务堆栈
void main_task(void *pdata); //
任务函数
//////////////////////////////////////////////////////////////////////////////
//
信号量集任务
#define FLAGS_TASK_PRIO 3 //
设置任务优先级
#define FLAGS_STK_SIZE 64 //
设置任务堆栈大小
OS_STK FLAGS_TASK_STK[FLAGS_STK_SIZE]; //
任务堆栈
void flags_task(void *pdata); //
任务函数
//
按键扫描任务
#define KEY_TASK_PRIO 2 //
设置任务优先级
#define KEY_STK_SIZE 64 //
设置任务堆栈大小
OS_STK KEY_TASK_STK[KEY_STK_SIZE]; //
任务堆栈
void key_task(void *pdata); //
任务函数
OS_EVENT * msg_key; //
按键邮箱事件块
OS_EVENT * q_msg; //
消息队列
OS_TMR * tmr1; //
软件定时器
1
OS_TMR * tmr2; //
软件定时器
2
OS_TMR * tmr3; //
软件定时器
3
OS_FLAG_GRP * flags_key; //
按键信号量集
void * MsgGrp[256]; //
消息队列存储地址
,
最大支持
256
个消息
//
软件定时器
1
的回调函数
//
每
100ms
执行一次
,
用于显示
CPU
使用率和内存使用率
void tmr1_callback(OS_TMR *ptmr,void *p_arg)
{
static u16 cpuusage=0; static u8 tcnt=0;
POINT_COLOR=BLUE;
if(tcnt==5)
{
LCD_ShowxNum(182,10,cpuusage/5,3,16,0); //
显示
CPU
使用率
cpuusage=0; tcnt=0;
}
cpuusage+=OSCPUUsage; tcnt++;
LCD_ShowxNum(182,30,mem_perused(SRAMIN),3,16,0); //
显示内存使用率
LCD_ShowxNum(182,50,((OS_Q*)(q_msg->OSEventPtr))->OSQEntries,3,16,0X80);
//
显示队列当前的大小
}
//
软件定时器
2
的回调函数
void tmr2_callback(OS_TMR *ptmr,void *p_arg)
{
static u8 sta=0;
switch(sta)
{
case 0: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,RED); break;
case 1: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,GREEN); break;
case 2: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,BLUE); break;
case 3: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,MAGENTA); break;
case 4: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,GBLUE); break;
case 5
CD_Fill(121,221,lcddev.width-1,lcddev.height-1,YELLOW); break;
case 6: LCD_Fill(121,221,lcddev.width-1,lcddev.height-1,BRRED); break;
}
sta++; if(sta>6)sta=0;
}
//
软件定时器
3
的回调函数
void tmr3_callback(OS_TMR *ptmr,void *p_arg)
友情提示:
此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
15条回答
wwjdwy
2019-10-16 10:32
while(1)
{
key=(u32)OSMboxPend(msg_key,10,&err);
if(key)
{
flagsclrt=51;//500ms后清除
OSFlagPost(flags_key,1<<(key-1),OS_FLAG_SET,&err);//设置信号量为1
}
if(flagsclrt)//倒计时
{
flagsclrt--;
if(flagsclrt==1)LCD_Fill(140,162,239,162+16,WHITE);//清除显示
}
switch(key)
{
请教一下,这里如果没有按键按下,key=(u32)OSMboxPend(msg_key,10,&err);会超时,任务进入就绪状态,这时候是不是任务就不会往下执行了?下一次的时候到达它的优先级又变成运行状态再次去查询邮箱?相当于只有key=(u32)OSMboxPend(msg_key,10,&err)OK才会运行后面代码?
加载中...
查看其它15个回答
一周热门
更多
>
相关问题
STM32F4上I2C(在PROTEUS中模拟)调试不通的问题
6 个回答
芯片供应紧张,准备换个MCU,MM32L系列替换STM32L系列的怎么样?
7 个回答
STM32同时使用两个串口进行数据收发时数据丢包的问题
5 个回答
STM32F103串口通信死机问题
4 个回答
STM32WLE5CC连接SX1268在LoRa模式下能与 SX1278互通吗?
2 个回答
相关文章
ST公司第一款无线低功耗单片机模块有效提高物联网设计生产效率
0个评论
如何实现对单片机寄存器的访问
0个评论
通过USB用STM32片内自带Bootloader下载程序及注意事项
0个评论
欲练此功必先自宫之STM32汇编启动,放慢是为了更好的前行
0个评论
×
关闭
采纳回答
向帮助了您的知道网友说句感谢的话吧!
非常感谢!
确 认
×
关闭
编辑标签
最多设置5个标签!
STM32
保存
关闭
×
关闭
举报内容
检举类型
检举内容
检举用户
检举原因
广告推广
恶意灌水
回答内容与提问无关
抄袭答案
其他
检举说明(必填)
提交
关闭
×
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
×
付费偷看金额在0.1-10元之间
确定
×
关闭
您已邀请
0
人回答
查看邀请
擅长该话题的人
回答过该话题的人
我关注的人
{
key=(u32)OSMboxPend(msg_key,10,&err);
if(key)
{
flagsclrt=51;//500ms后清除
OSFlagPost(flags_key,1<<(key-1),OS_FLAG_SET,&err);//设置信号量为1
}
if(flagsclrt)//倒计时
{
flagsclrt--;
if(flagsclrt==1)LCD_Fill(140,162,239,162+16,WHITE);//清除显示
}
switch(key)
{
请教一下,这里如果没有按键按下,key=(u32)OSMboxPend(msg_key,10,&err);会超时,任务进入就绪状态,这时候是不是任务就不会往下执行了?下一次的时候到达它的优先级又变成运行状态再次去查询邮箱?相当于只有key=(u32)OSMboxPend(msg_key,10,&err)OK才会运行后面代码?
一周热门 更多>