stm32 ucosii消息队列 串口_UC/OSII消息队列、信号量集和软件定时器

2023-05-16

UCOSII消息队列、信号量集和软件定时器简介

上一章,我们介绍了信号量和邮箱的使用,本章我们介绍比较复杂消息队列、信号量集以及软件定时器的使用。

消息队列

使用消息队列可以在任务之间传递多条消息。消息队列由三个部分组成:事件控制块、消息队列和消息。当把事件控制块成员OSEventType的值置为OS_EVENT_TYPE_Q时,该事件控制块描述的就是一个消息队列。

消息队列的数据结构如图63.1.1所示。从图中可以看到,消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员OSEventPtr指向了一个叫做队列控制块(OS_Q)的结构,该结构管理了一个数组MsgTbl[],该数组中的元素都是一些指向消息的指针。

10ba7a3a79442f56ddc68c9892672616.png

队列控制块(OS_Q)的结构定义如下:

typedef struct os_q

{

  struct os_q *OSQPtr;

  void **OSQStart;

  void **OSQEnd;

  void **OSQIn;

  void **OSQOut;

  INT16U OSQSize;

  INT16U OSQEntries;

} OS_Q;

该结构体中各参数的含义如表63.1.1所示:

2c57308bb09f092e6217cdc4125b4ed7.png

其中,可以移动的指针为OSQIn和OSQOut,而指针OSQStart和OSQEnd只是一个标志(常指针)。当可移动的指针OSQIn或OSQOut移动到数组末尾,也就是与OSQEnd相等时,可移动的指针将会被调整到数组的起始位置OSQStart。也就是说,从效果上来看,指针OSQEnd与OSQStart等值。于是,这个由消息指针构成的数组就头尾衔接起来形成了一个如图63.1.2所示的循环的队列。

2cb396c8f846b61abe29116c7bece05c.png

在UCOSII初始化时,系统将按文件os_cfg.h中的配置常数OS_MAX_QS定义OS_MAX_QS个队列控制块,并用队列控制块中的指针OSQPtr将所有队列控制块链接为链表。由于这时还没有使用它们,故这个链表叫做空队列控制块链表。

接下来我们看看在UCOSII中,与消息队列相关的几个函数(未全部列出,下同)。

1) 创建消息队列函数

创建一个消息队列首先需要定义一指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,然后再调用函数OSQCreate来创建消息队列。创建消息队列函数OSQCreate的原型为:

OS_EVENT *OSQCreate(void**start,INT16Usize);

其中,start为存放消息缓冲区指针数组的地址,size为该数组大小。该函数的返回值为消息队列指针。

2) 请求消息队列函数

请求消息队列的目的是为了从消息队列中获取消息。任务请求消息队列需要调用函数OSQPend,该函数原型为:

void*OSQPend(OS_EVENT*pevent,INT16Utimeout,INT8U *err);

其中,pevent为所请求的消息队列的指针,timeout为任务等待时限,err为错误信息。

3) 向消息队列发送消息函数

任务可以通过调用函数OSQPost或OSQPostFront两个函数来向消息队列发送消息。函数OSQPost以FIFO(先进先出)的方式组织消息队列,函数OSQPostFront以LIFO(后进先出)的方式组织消息队列。这两个函数的原型分别为:

INT8U OSQPost(OS_EVENT*pevent,void*msg);

INT8U OSQPostFront (OS_EVENT*pevent,void*msg);

其中,pevent为消息队列的指针,msg为待发消息的指针。消息队列还有其他一些函数,这里我们就不介绍了,感兴趣的朋友可以参考《嵌入式实时操作系统UCOSII原理及应用》第五章,关于队列更详细的介绍,也请参考该书。

信号量集

在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。UCOSII为了实现多个信号量组合的功能定义了一种特殊的数据结构——信号量集。

信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑,其示意图如图63.1.3所示

f54d6acb1ef72b287864e535c00f2aaa.png

不同于信号量、消息邮箱、消息队列等事件,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_FLAGSOSFlagNodeFlags; //信号过滤器

  INT8UOSFlagNodeWaitType; //定义逻辑运算关系的数据

} OS_FLAG_NODE;

其中OSFlagNodeWaitType是定义逻辑运算关系的一个常数(根据需要设置),其可选值和对应的逻辑关系如表63.1.2所示:

3b06115a91cda523bca1624daa124e2e.png

OSFlagFlags、OSFlagNodeFlags、OSFlagNodeWaitType三者的关系如图63.1.4所示:

d965a3ea1d247e9b7c2f333a32450588.png

图中为了方便说明,我们将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两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器管理所需的数据结构示意图如图63.1.5所示:

0a4dfdb1b5d0ca8e548be15cb48c771b.png

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_WHEEL_SIZE-1循环。这里,我们推荐OS_TMR_CFG_WHEEL_SIZE的取值为2的N次方,以便采用移位操作计算余数,缩短处理时间。

信号量唤醒定时器管理任务,计算出当前所要处理的分组后,程序遍历该分组中的所有控制块,将当前OSTmrTime值与定时器控制块中的到时值(OSTmrMatch)相比较。若相等(即到时),则调用该定时器到时回调函数;若不相等,则判断该组中下一个定时器控制块。如此操作,直到该分组链表的结尾。软件定时器管理任务的流程如图63.1.6所示。

0810589d13afc0564717ad80afab1ad3.png

当运行完软件定时器的到时处理函数之后,需要进行该定时器控制块在链表中的移除和再插入操作。插入前需要重新计算定时器下次到时时所处的分组。计算公式如下:

定时器下次到时的OSTmrTime值(OSTmrMatch)=定时器定时值+当前OSTmrTime值

新分组=定时器下次到时的OSTmrTime值(OSTmrMatch)%OS_TMR_CFG_WHEEL_SIZE

接下来我们看看在UCOSII中,与软件定时器相关的几个函数。

1) 创建软件定时器函数

创建软件定时器通过函数OSTmrCreate实现,该函数原型为:

OS_TMR *OSTmrCreate (INT32U dly, INT32U period, INT8Uopt,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停止某个软件定时器,该函数的原型为:

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,错误信息。

软件定时器我们就介绍到这。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

stm32 ucosii消息队列 串口_UC/OSII消息队列、信号量集和软件定时器 的相关文章

  • c项目makefile多重定义错误

    这个问题是一个对应于创建的repexthis问题 在我的嵌入式 C 项目中 我有两个独立的板 我想为每个板创建两个 c 文件 master c 和 Slave c 其中包含自己的特定main 功能 我使用 stm32cumbemx 生成带有
  • 如何让printf在STM32F103上工作?

    我是 STM32F103 世界的新手 我有一个STM32F103的演示代码 我正在使用arm none eabi来编译它 我尝试了在谷歌上可以找到的内容 但到目前为止没有任何效果 我已经花了三天时间来解决这个问题 任何人都可以给我一个运行良
  • 134-基于stm32单片机矿井瓦斯天然气浓度温湿度检测自动通风系统Proteus仿真+源程序...

    资料编号 134 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 ds1302时钟 DHT11温湿度 电机 蜂鸣器 制作一个基于stm32单片机矿井瓦斯天然气浓度温湿度检测自动通风系统Proteus仿真 2 通过DH
  • HAL库STM32常用外设教程(二)—— GPIO输入\输出

    HAL库STM32常用外设教程 二 GPIO输入 输出 文章目录 HAL库STM32常用外设教程 二 GPIO输入 输出 前言 一 GPIO功能概述 二 GPIO的HAl库驱动 三 GPIO使用示例 1 示例功能 四 代码讲解 五 总结
  • 匹配 STM32F0 和 zlib 中的 CRC32

    我正在研究运行 Linux 的计算机和 STM32F0 之间的通信链路 我想对我的数据包使用某种错误检测 并且由于 STM32F0 有 CRC32 硬件 并且我在 Linux 上有带有 CRC32 的 zlib 所以我认为在我的项目中使用
  • STM32用一个定时器执行多任务写法

    文章目录 main c include stm32f4xx h uint32 t Power check times 电量检测周期 uint32 t RFID Init Check times RFID检测周期 int main Timer
  • HAL库学习

    CMSIS简介 CMSIS Cortex Microcontroller Software Interface Standard 微控制器软件接口标准 由ARM和其合作的芯片厂商 ST NXP 软件工具厂商 KEIL IAR 共同制定的标准
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • [屏驱相关]【SWM166-SPI-Y1.28C1测评】+ 有点惊艳的开箱

    耳闻华芯微特许久了 看到论坛得评测活动赶紧上了末班车 毕竟对有屏幕得板子也是很喜欢得 京东快递小哥客客气气 微笑着把快递给了我 好评 直接拆了包 在此之前没看过视频号 所以这个圆盘盘得模具还是有点惊喜的 正面照如下 开机有灯光秀 还有动画
  • [MM32硬件]搭建灵动微MM32G0001A6T的简易开发环境

    作为学习单片机的经典 自然是通过GPIO点亮LED 或者是响应按钮的外部中断例程 这我们看看SOP8封装的芯片MM32G0001A6T得引脚 除了VDD和GND固定外 我们可以使用PA14 PA1 PA13 PA15 PA2 PA3这六个G
  • HAL 锁定和解锁函数如何使用以及为什么?

    我试图理解另一位程序员编写的代码 它使用了I C http en wikipedia org wiki I C2 B2C通信以将数据写入 STM32 微控制器的 EEPROM 一般来说 我理解他的代码是如何工作的 但我不明白他为什么使用HA
  • 高可用:如何实现消息队列的 HA?

    管理学上有一个木桶理论 一只水桶能装多少水取决于它最短的那块木板 这个理论推广到分布式系统的可用性上 就是系统整体的可用性取决于系统中最容易出现故障 或者性能最低的组件 系统中的各个组件都要进行高可用设计 防止单点故障 消息队列也不例外 本
  • STM32F207 I2C 测试失败

    我正在使用 STM32F207 微控制器在 STM3220G EVAL 板上学习嵌入式开发 我尝试通过连接同一芯片上的两个 I2C2 和 I2C3 模块并发送 接收字符来测试 I2C 接口 这是我当前编写的代码 使用 mdk arm 5 i
  • 无法使用 OpenOCD 找到脚本文件

    我正在尝试按照本教程将 OpenOCD 与我的 ST 发现板一起使用 https japaric github io discovery README html https japaric github io discovery READM
  • 毕设开题分享 单片机智能教室系统(智能照明+人数统计)

    1 简介 Hi 大家好 今天向大家介绍一个学长做的单片机项目 单片机智能教室系统 智能照明 人数统计 大家可用于 课程设计 或 毕业设计 项目分享 https gitee com feifei1122 simulation project
  • systick定时器

    systick定时器 文章目录 前言 一 前期疑惑 二 解答 1 关于systick是阻塞的吗 2 非阻塞 三 软件编写 总结 前言 这边记录systick相关知识点 一 前期疑惑 在学习systick志气啊 其实对于systick还是一脸
  • 通过JTAG恢复STM32 MCU磨掉的标记

    我有一块可能带有 STM32 MCU 的板 我想为该板制作定制固件 因为库存板有很多问题 不幸的是 电路板制造商很友善地磨掉了所有标记 有没有办法通过 jtag 获取设备 系列 ID 并将其交叉引用到型号 我能找到的一切都是关于获取芯片的唯
  • STM32 上的 ADC 单次转换

    我正在研究 STM32 F103x 上的 ADC 编程 并从最简单的情况 单次转换开始 测量内部温度传感器 连接到 ADC1 的值 并使用 USART 将其发送到 COM 端口 目标似乎很明确 但是当我尝试将源代码下载到闪存时 它不会向 C
  • PWM DMA 到整个 GPIO

    我有一个 STM32F4 我想对一个已与掩码进行 或 运算的 GPIO 端口进行 PWM 处理 所以 也许我们想要 PWM0b00100010一段时间为 200khz 但随后 10khz 后 我们现在想要 PWM0b00010001 然后
  • STM32 传输结束时,循环 DMA 外设到存储器的行为如何?

    我想问一下 在以下情况下 STM32 中的 DMA SPI rx 会如何表现 我有一个指定的 例如 96 字节数组 名为 A 用于存储从 SPI 接收到的数据 我打开循环 SPI DMA 它对每个字节进行操作 配置为 96 字节 是否有可能

随机推荐