公司新闻
「正点原子NANO STM32开发板资料连载」第三十八章 UCOSII 实验 3
1)实验平台:alientek NANO STM32F411 V1开发板2)摘自《正点原子STM32F4 开发指南(HAL 库版》关注官方微信号民众号,获取更多资料:正点原子第三十八章 UCOSII 实验 3-消息行列、信号量集和软件定时器上一章,我们学习了 UCOSII 的信号量和邮箱的使用,本章,我们将学习消息行列、信号量集和软件定时器的使用。本章分为如下几个部门:38.1 UCOSII 消息行列、信号量集和软件定时器简介38.2 硬件设计38.3 软件设计38.4 下载验证38.1 UCOSII 消息行列、信号量集和软件定时器简介上一章,我们先容了信号量和邮箱的使用,本章我们先容比力庞大消息行列、信号量集以及软件定时器的使用。消息行列使用消息行列可以在任务之间通报多条消息。消息行列由三个部门组成:事件控制块、消息行列和消息。
当把事件控制块成员 OSEventType 的值置为 OS_EVENT_TYPE_Q 时,该事件控制块形貌的就是一个消息行列。消息行列的数据结构如图 38.1.1 所示。
从图中可以看到,消息行列相当于一个共用一个任务等候列表的消息邮箱数组,事件控制块成员 OSEventPtr 指向了一个叫做行列控制块(OS_Q)的结构,该结构治理了一个数组 MsgTbl[],该数组中的元素都是一些指向消息的指针。图 38.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;该结构体中各参数的寄义如表 38.1.1 所示:表 38.1.1 行列控制块各参数寄义其中,可以移动的指针为 OSQIn 和 OSQOut,而指针 OSQStart 和 OSQEnd 只是一个标志(常指针)。当可移动的指针 OSQIn 或 OSQOut 移动到数组末尾,也就是与 OSQEnd 相等时,可移动的指针将会被调整到数组的起始位置 OSQStart。也就是说,从效果上来看,指针 OSQEnd与 OSQStart 等值。
于是,这个由消息指针组成的数组就头尾衔接起来形成了一个如图 38.1.2所示的循环的行列。图 38.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 为了实现多个信号量组合的功效界说了一种特殊的数据结构——信号量集。
信号量集所能治理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号举行基本逻辑运算的组合逻辑,其示意图如图 38.1.3 所示图 38.1.3 信号量集示意图差别于信号量、消息邮箱、消息行列等事件,UCOSII 不使用事件控制块来形貌信号量集,而使用了一个叫做标志组的结构 OS_FLAG_GRP 来形貌。OS_FLAG_GRP 结构如下:typedef struct{INT8UOSFlagType;//识别是否为信号量集的标志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 是界说逻辑运算关系的一个常数(凭据需要设置),其可选值和对应的逻辑关系如表 38.1.2 所示:表 38.1.2 OSFlagNodeWaitType 可选值及其意义OSFlagFlags、OSFlagNodeFlags、OSFlagNodeWaitType 三者的关系如图 38.1.4 所示:图 38.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 两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器治理所需的数据结构示意图如图 38.1.5 所示:图 38.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)相比力。若相等(即到时),则挪用该定时器到时回调函数;若不相等,则判断该组中下一个定时器控制块。
如此操作,直到该分组链表的末端。软件定时器治理任务的流程如图 38.1.6 所示。图 38.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,错误信息。软件定时器我们就先容到这。
38.2 硬件设计本节实验功效简介:本章我们在 UCOSII 内里建立 7 个任务:开始任务、LED 任务、数码管显示任务、行列消息显示任务、信号量集任务、按键扫描任务和主任务,开始任务用于建立邮箱、消息行列、信号量集以及其他任务,之后挂起;数码管显示任务用于更新数码管的显示;行列消息显示任务请求消息行列,在获得消息后显示收到的消息数据;信号量集任务用于测试信号量集,接纳 OS_FLAG_WAIT_SET_ANY 的方法,KEY0、KEY1、KEY2 按键按下,该任务都市控制蜂鸣器发出“滴”的一声;按键扫描任务用于按键扫描,优先级最高,将获得的键值通过消息邮箱发送出去;主任务建立 3 个软件定时器(定时器 1,500ms 溢出一次,显示CPU 和内存使用率;定时器 2,1000ms 溢出一次,数码管显示更新;定时 3,,100ms 溢出一次,用于自动发送消息到消息行列),并通过查询消息邮箱获得键值,凭据键值执行 DS1 控制、控制软件定时器 3 的开关、和软件定时器 2 的开关控制等。通过按下 KEY0,可以控制软件定时器 3 的开关,从而控制消息行列的发送,可以在串口打印上看到 Q 和 MEM 的值逐步变大(说明行列消息在增多,占用内存也随着消息增多而增大),在 QUEUE MSG 区,开始显示行列消息,再按一次 KEY0 停止 tmr3,此时可以看到 Q 和 MEM逐渐减小。当 Q 值变为 0 的时候,QUEUE MSG 也停止显示(行列为空)。
通过按 KEY1 按键,可以控制软件定时器 2 的开关,停止数码管变换显示。通过按 KEY2 按键,可以控制 DS1 的亮灭。
所要用到的硬件资源如下:1) 指示灯 DS0 、DS12) 3 个机械按键(KEY0/KEY1/KEY2)3) 蜂鸣器4) 数码管5) 串口这些,我们在前面的学习中都已经先容过了。38.3 软件设计本章,我们在第九章实验 (实验 5)的基础上修改,首先,是 UCOSII 代码的添加,详细方法同第 34 章一模一样,本章就不再详细先容了。
由于我们建立了 7 个任务,加上统计任务、空闲任务和软件定时器任务,总共 10 个任务,如果你还想添加其他任务,请把 OS_MAX_TASKS的值适当改大。另外,我们还需要在 os_cfg.h 内里修改软件定时器治理部门的宏界说,修改如下:#define OS_TMR_EN1u//使能软件定时器功效#define OS_TMR_CFG_MAX16u//最大软件定时器个数#define OS_TMR_CFG_NAME_EN1u//使能软件定时器命名#define OS_TMR_CFG_WHEEL_SIZE8u//软件定时器轮巨细#define OS_TMR_CFG_TICKS_PER_SEC100u//软件定时器的时钟节奏(10ms)#define OS_TASK_TMR_PRIO0u//软件定时器的优先级,设置为最高这样我们就使能 UCOSII 的软件定时器功效了,而且设置最大软件定时器个数为 16,定时器轮巨细为 8,软件定时器时钟节奏为 10ms(即定时器的最少溢出时间为 10ms)。最后,我们只需要修改 main.c 函数了,打开 main.c,输入如下代码:////////////////////////UCOSII 任务设置/////////////////////////////////////START 任务//设置任务优先级#define START_TASK_PRIO10 //开始任务的优先级设置为最低//设置任务客栈巨细#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 SMG_TASK_PRIO 6//设置任务客栈巨细#define SMG_STK_SIZE 64//任务客栈OS_STK SMG_TASK_STK[SMG_STK_SIZE];//任务函数void smg_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;//软件定时器 1OS_TMR* tmr2;//软件定时器 2OS_TMR* tmr3;//软件定时器 3OS_FLAG_GRP * flags_key;//按键信号量集void * MsgGrp[256];//消息行列存储地址,最大支持 256 个消息//共阴数字数组//0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F, .,全灭u8 smg_num[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6,0xee,0x3e,0x9c,0x7a,0x9e,0x8e,0x01,0x00};u8 smg_duan=0;//数码管段选//软件定时器 1 的回调函数//每 500ms 执行一次,用于显示 CPU 使用率和内存使用率void tmr1_callback(OS_TMR *ptmr,void *p_arg){static u16 cpuusage=0;static u8 tcnt=0;if(tcnt==5){printf("CPU:%d%%rn",cpuusage/5); //显示 CPU 使用率cpuusage=0;tcnt=0;}cpuusage+=OSCPUUsage;tcnt++;printf("MEM:%d%%rn",mem_perused());//显示内存使用率printf("Q:%drnrn",((OS_Q*)(q_msg->OSEventPtr))->OSQEntries); //显示行列当前的巨细}//软件定时器 2 的回调函数void tmr2_callback(OS_TMR *ptmr,void *p_arg){smg_duan++;if(smg_duan>16) smg_duan=0;}//软件定时器 3 的回调函数void tmr3_callback(OS_TMR *ptmr,void *p_arg){u8* p;u8 err;static u8 msg_cnt=0;//msg 编号p=mymalloc(13); //申请 13 个字节的内存if(p){sprintf((char*)p,"ALIENTEK %03d",msg_cnt);msg_cnt++;err=OSQPost(q_msg,p); //发送行列if(err!=OS_ERR_NONE)//发送失败{myfree(p);//释放内存OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err);//关闭软件定时器 3}}}int main(void){HAL_Init();//初始化 HAL 库Stm32_Clock_Init(96,4,2,4);//设置时钟,96Mhzdelay_init(96);//初始化延时函数uart_init(115200);//串口初始化 115200BEEP_Init();//蜂鸣器初始化LED_Init();//初始化与 LED 毗连的硬件接口KEY_Init();//按键初始化mem_init();//初始化内存池LED_SMG_Init();//数码管初始化printf("NANO STM32rn");printf("UCOSII TEST3rn");printf("KEY0:Q SW KEY1:TMR2 SW KEY2:DS1rnrn");OSInit();OSTaskCreate(start_task,(void *)0,(OS_STK *)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO );//建立起始任务OSStart();}//开始任务void start_task(void *pdata){OS_CPU_SR cpu_sr=0;u8 err;pdata = pdata;msg_key=OSMboxCreate((void*)0);//建立消息邮箱q_msg=OSQCreate(&MsgGrp[0],256); //建立消息行列flags_key=OSFlagCreate(0,&err);//建立信号量集OSStatInit();//初始化统计任务.这里会延时 1 秒钟左右OS_ENTER_CRITICAL();//进入临界区(无法被中断打断)OSTaskCreate(led_task,(void *)0,(OS_STK*)&LED_TASK_STK[LED_STK_SIZE-1],LED_TASK_PRIO);OSTaskCreate(smg_task,(void *)0,(OS_STK*)&SMG_TASK_STK[SMG_STK_SIZE-1],SMG_TASK_PRIO);OSTaskCreate(qmsgshow_task,(void *)0,(OS_STK*)&QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE-1],QMSGSHOW_TASK_PRIO);OSTaskCreate(main_task,(void *)0,(OS_STK*)&MAIN_TASK_STK[MAIN_STK_SIZE-1],MAIN_TASK_PRIO);OSTaskCreate(flags_task,(void *)0,(OS_STK*)&FLAGS_TASK_STK[FLAGS_STK_SIZE-1],FLAGS_TASK_PRIO);OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);OSTaskSuspend(START_TASK_PRIO);//挂起起始任务.OS_EXIT_CRITICAL();//退出临界区(可以被中断打断)}//LED 任务void led_task(void *pdata){u8 t;while(1){t++;delay_ms(10);if(t==8)LED0=1; //LED0 灭if(t==100)//LED0 亮{t=0;LED0=0;}}}//数码管显示任务void smg_task(void *pdata){while(1){LED_Write_Data(smg_num[smg_duan],7);//数码管显示LED_Refresh();//刷新显示delay_ms(10);}}//行列消息显示任务void qmsgshow_task(void *pdata){u8 *p;u8 err;while(1){p=OSQPend(q_msg,0,&err);//请求消息行列printf("%srn",p);//串口打印消息myfree(p);delay_ms(500);}}//主任务void main_task(void *pdata){u32 key=0;u8 err;u8 tmr2sta=1; //软件定时器 2 开关状态u8 tmr3sta=0; //软件定时器 3 开关状态tmr1=OSTmrCreate(10,50,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK)tmr1_callback,0,"tmr1",&err); //500ms 执行一次tmr2=OSTmrCreate(10,100,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK)tmr2_callback,0,"tmr2",&err); //1000ms 执行一次tmr3=OSTmrCreate(10,10,OS_TMR_OPT_PERIODIC,(OS_TMR_CALLBACK)tmr3_callback,0,"tmr3",&err); //100ms 执行一次OSTmrStart(tmr1,&err);//启动软件定时器 1OSTmrStart(tmr2,&err);//启动软件定时器 2while(1){key=(u32)OSMboxPend(msg_key,10,&err);if((key==KEY0_PRES)||(key==KEY1_PRES)||(key==KEY2_PRES))OSFlagPost(flags_key,1<<(key-1),OS_FLAG_SET,&err);//设置对应的信号量为 1switch(key){case KEY0_PRES://软件定时器 3 开关tmr3sta=!tmr3sta;if(tmr3sta){printf("TMR3 STARTrn");//提示定时器 3 打开了OSTmrStart(tmr3,&err);}else{OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err);//关闭软件定时器 3printf("TMR3 STOPrn");//提示定时器 3 关闭了}break;case KEY1_PRES://软件定时器 2 开关tmr2sta=!tmr2sta;if(tmr2sta){printf("TMR2 STARTrn");//提示定时器 2 打开了OSTmrStart(tmr2,&err); //开启软件定时器 2}else{OSTmrStop(tmr2,OS_TMR_OPT_NONE,0,&err);//关闭软件定时器 2printf("TMR2 STOPrn");//提示定时器 2 关闭了}break;case KEY2_PRES://控制 DS1LED1=!LED1;break;}delay_ms(10);}}//信号量集处置惩罚任务void flags_task(void *pdata){u16 flags;u8 err;while(1){flags=OSFlagPend(flags_key,0X001F,OS_FLAG_WAIT_SET_ANY,0,&err);//等候信号量if(flags&0X0001) printf("KEY0 DOWNrn");else if(flags&0X0002) printf("KEY1 DOWNrn");else if(flags&0X0004) printf("KEY2 DOWNrn");BEEP=0;delay_ms(50);BEEP=1;OSFlagPost(flags_key,0X0007,OS_FLAG_CLR,&err);//全部信号量清零}}//按键扫描任务void key_task(void *pdata){u8 key;while(1){key=KEY_Scan(0);if(key)OSMboxPost(msg_key,(void*)key);//发送消息delay_ms(10);}}本章 main.c 的代码有点多,因为我们建立了 7 个任务,3 个软件定时器及其回调函数,所以,整个代码有点多,我们建立的 7 个任务为:start_task、led_task、smg_task、qmsgshow_task 、main_task、flags_task 和 key_task,优先级划分是 10 和 7~2,客栈巨细除了 main 是 128,其他都是 64。我们还建立了 3 个软件定时器 tmr1、tmr2 和 tmr3,tmr1 用于打印 CPU 使用率和内存使用率,每 500ms 执行一次;tmr2 用于更新数码管的显示,每 1000ms 执行一次;tmr3 用于定时向行列发送消息,每 100ms 发送一次。
本章,我们依旧使用消息邮箱 msg_key 在按键任务和主任务之间通报键值数据,我们建立信号量集 flags_key,在主任务内里将按键键值通过信号量集通报给信号量集处置惩罚任务flags_task,实现按键信息的打印以及发出按键提示音。本章,我们还建立了一个巨细为 256 的消息行列 q_msg,通过软件定时器 tmr3 的回调函数向消息行列发送消息,然后在消息行列显示任务 qmsgshow_task 内里请求消息行列,并在串口调试助手打印获得的消息。消息行列还用到了动态内存治理。
在主任务 main_task 内里,我们实现了 35.2 节先容的功效:KEY0 控制软件定时器 tmr3 的开关,间接控制行列信息的发送;KEY1 控制软件定时器 tmr2 开关,控制数码管的更新显示;KEY2 控制 LED1 亮灭;软件设计部门就为大家先容到这里。38.4 下载验证在代码编译乐成之后,我们通过下载代码到 NANO STM32F4 V1 上,打开串口调试助手,显示如图 38.4.1 所示:图 38.4.1 串口调试助手显示从图中可以看出,默认状态下,CPU 使用率为 2%左右。这主要是 key_task 内里增加不停的更新数码管显示(tmr2)操作导致的。通过按 KEY0 则可以启动 tmr3 控制消息行列发送,可以在串口助手上面看到 Q 和 MEM 的值逐步变大(说明行列消息在增多,占用内存也随着消息增多而增大),在 QUEUE MSG 区,开始显示行列消息,再按一次 KEY1 停止 tmr3,此时可以看到 Q 和 MEM 逐渐减小。
当 Q 值变为 0 的时候,QUEUE MSG 也停止显示(行列为空)。通过按 KEY1,可以控制数码管的更新显示。通过按 KEY2,可以控制 DS1 的亮灭;KEY0、KEY1、KEY2 按键按下,蜂鸣器都市发出“滴”的一声,提示按键被按下,同时串口会打印显示按键信息。
本文关键词:「,正点,原子,NANO,STM32,开发,板,资料,连载,」,开云体育全站app下载
本文来源:开云体育app-www.wanlinkkj.com