【正点原子STM32连载】第六十五章 UCOSII实验3-消息队列、信号量集和软件定时器摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/11/28 4:40:11

1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#

第六十五章 UCOSII实验3-消息队列、信号量集和软件定时器

上一章,我们学习了如何使用UCOSII的信号量和邮箱的使用,本章,我们将学习消息队列、信号量集和软件定时器的使用。本章分为如下几个小节:
65.1 UCOSII消息队列、信号量集和软件定时器简介
65.2 硬件设计
65.3 程序设计
65.4 下载验证

65.1 UCOSII消息队列、信号量集和软件定时器简介
65.1.1 消息队列
消息队列可以视为消息邮箱的数组形式,消息邮箱一次传递一则消息,而消息队列可以在任务之间传递多条消息。消息队列的工作情况如图65.1.1.1所示:
在这里插入图片描述

图65.1.1.1 消息队列的工作情况图
从上图可知,任务可以向消息队列中释放消息,只有任务才能从消息队列中请求消息,任务可以始终请求消息,也可以周期性地请求消息。消息队列具有一定的长度,其长度可包含的消息个数,如果向队列中释放消息的速度大于从队列中请求消息的速度,那么消息队列将会溢出。消息队列的数据结构如图65.1.1.2所示:
在这里插入图片描述

图 65.1.1.2 消息队列的数据结构图
消息队列由三个部分组成:事件控制块、消息队列和消息。当把事件控制块成员OSEventType的值设置为OS_EVENT_TYPE_Q时,该事件控制块描述的就是一个消息队列。
从上图可以知道,消息队列相当于一个任务等待列表的消息邮箱数组,事件控制块成员OSEventPtr指向了一个叫做队列控制块(OS_Q)的结构,该结构管理了一个数组MsgTbl[],该数组中的元素都是一些指向消息的指针。
队列控制块(OS_Q)的结构定义:

typedef struct os_q {                		/* 队列控制块 */
    struct os_q   *OSQPtr;                 /* 指向下一个空的队列控制块 */
    void         **OSQStart;              	/* 指向消息指针数组的起始地址 */
    void         **OSQEnd;            		/* 指向消息指针数组结束单元的下一个单元 */
    void         **OSQIn;              		/* 指向插入一条消息的位置 */
    void         **OSQOut;              	/* 指向被取出消息的位置 */
    INT16U       OSQSize;            		/* 数组的长度 */
    INT16U       OSQEntries;             	/* 已存放消息指针的元素数目 */
} OS_Q;

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

图65.1.1.3 消息指针数组构成的环形数据缓冲区
在UCOSII初始化时,系统将按os_cfg.h文件中的OS_MAX_QS的数值定义OS_MAX_QS个队列控制块,并用队列控制块中的指针OSQptr将所有队列控制块链接为链表。由于这时候还没有使用它们,所以这个链表叫做空队列控制块链表。
消息队列相关的主要操作有:创建消息队列函数OSQCreate、请求消息队列函数OSQPend和向消息队列发送消息函数OSQPost。后面再对这几个函数进行讲解。
消息到这里就介绍完成了,想了解更多的朋友可以参考《嵌入式实时操作系统UCOSII原理及应用》第五章。
65.1.2 信号量集
在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。UCOSII为了实现多个信号量组合的功能定义了一种特殊的数据结构——信号量集。
信号量集所能管理的信号量都是一些二值信号,所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑,其示意图如图65.1.2.1所示:
在这里插入图片描述

图65.1.2.1 信号量集示意图
不同于信号量、消息邮箱、消息队列等事件,UCOSII不使用事件控制块来描述信号量集,而使用了一个叫做标志组的结构OS_FLAG_GRP来描述。OS_FLAG_GRP结构如下:

typedef struct os_flag_grp {                 /* 标志组 */
    INT8U         OSFlagType;                  /* 信号量集的标志 */
    void         *OSFlagWaitList;             /* 指向等待任务链表的指针 */
    OS_FLAGS      OSFlagFlags;                /* 所有信号列表 */
} OS_FLAG_GRP;

成员OSFlagFlags是一个指针,当一个信号量集被创建后,这个指针指向了这个信号量集的等待任务链表。
与其他前面介绍过的事件不同,信号量集用一个双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(node)。标志组OS_FLAG_GRP的成员OSFlagWaitList就指向了信号量集的这个等待任务链表。等待任务链表节点OS_FLAG_NODE的结构如下:

typedef struct os_flag_node {               /*  等待任务链表节点  */
    void         *OSFlagNodeNext;             /*  指向下一个节点的指针 */
    void         *OSFlagNodePrev;             /*  指向前一个节点的指针 */
    void         *OSFlagNodeTCB;              /*  指向对应任务控制块的指针 */
    void         *OSFlagNodeFlagGrp;         /*  反向指向信号量集的指针 */
    OS_FLAGS      OSFlagNodeFlags;           /*  信号过滤器 */
    INT8U         OSFlagNodeWaitType;        /*   定义逻辑运算关系的数据 */
} OS_FLAG_NODE;

其中OSFlagNodeWaitType是定义逻辑运算关系的一个常数(根据需要设置),其可选值和对应的逻辑关系如表65.1.2.1所示:
常数 信号
有效状态 等待任务的就绪条件
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)
表65.1.2.1 OSFlagNodeWaitType可选值及其意义
OSFlagFlags、OSFlagNodeFlags、OSFlagNodeWaitType三者的关系如图65.1.2.2所示:
在这里插入图片描述

图65.1.2.2 标志组与等待任务共同完成信号量集的逻辑运算及控制
为了方便说明,我们将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,那么请求信号量集的任务将得到无效的请求。
信号量集相关的主要操作有:创建一个信号量集函数OSFlagCreate,请求一个信号量集函数OSFlagPend,向信号量集发送信号函数OSFlagPost。后面再对这几个函数进行讲解。
信号量集就介绍到这里,更详细的介绍,请参考《嵌入式实时操作系统UCOSII原理及应用》第六章。
65.1.3 软件定时器
UCOSII从V2.83版本以后,加入了软件定时器,这使得UCOSII的功能更加完善,在其上的应用程序开发与移植也更加方便。在实时操作系统中一个好的软件定时器实现要求有较高的精度、较小的处理器开销,且占用较少的储存器资源。
通过前面的学习,我们知道UCOSII通过OSTimTick函数对时钟节拍进行加1操作,同时遍历任务控制块,以判断任务延时是否到时。软件定时器同样由OSTimTick提供时钟,但是软件定时器的时钟还受OS_TMR_CFG_TICKS_PER_SEC设置的控制,也就是在UCOSII的时钟节拍上面在做了一次“分频”,软件定时器的最快时钟节拍就等于UCOSII的系统时钟节拍。这也决定了软件定时器的精度。
软件定时器定义了一个单独的计数器OSTmrTime,用于软件定时器的计时,UCOSII并不在OSTimTick中进行软件定时器的到时判断与处理,而是创建了一个高于应用程序中所有其他任务优先级的定时器管理任务OSTmr_Task,在这个任务中进行定时器的到时判断和处理。时钟节拍函数通过信号量给这个高优先级任务发信号。这种方法缩短了中断服务程序的执行时间,但也使得定时器到时处理函数的响应收到中断推出时恢复现场和任务切换的影响。
UCOSII中软件定时器的实现方法是,将定时器按定时事件分组,使得每次时钟节拍到来时只对部分定时器及逆行比较操作,缩短了每次处理的时间。但这就需要动态地维护一个定时器组。定时器组的维护只是在每次定时器到时的时候才发生,而且定时器从组中移除和再插入操作不需要排序。这是一种比较高效的算法,减少了维护所需的操作时间。
UCOSII软件定时器实现了3类链表的维护:
OS_EXT OS_TMR OSTmrTbl[OS_TMR_CFG_MAX]; /* 定时器控制块数组OS_EXT */
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和OSTmrPre两个指针同样也组织了所在分组中定时器控制块的双向链表。软件定时器管理所需的数据结构示意图如图65.1.3.1所示:
在这里插入图片描述

图65.1.3.1 软件定时器管理所需的数据结构示意图
OS_TMR_CFG_WHEEL_SIZE定义了OSTmrWheelTbl的大小,同时这个值也是定时器分组的依据。按照定时器到时值与OS_TMR_CFG_WHEEL_SIZE相除的余数进行分组:不同余数的定时器放在不同分组中;相同余数的定时器处在同一组中,由双向链表连接。这样,在余数值为0~OS_TMR_CFG_WHEEL_SIZE - 1的不同定时器控制块,正好分别对应了数组元素OSTmrWheelTbl[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)相比较。若相等(即到时),则调用该定时器到时回调函数;若不相等,则判断该组中下一个定时器控制块。如此操作,直到该分组链表的结尾。软件定时器管理任务的流程如图65.1.3.2所示:
在这里插入图片描述

图65.1.3.2 软件定时器管理任务流程
当运行完软件定时器的到时处理函数之后,需要进行该定时器控制块在链表中的移除和再插入操作。插入前需要重新计算定时器下次到时时所处的分组。计算公式如下:
定时器下次到时的OSTmrTime值(OSTmrMatch) = 定时器定时值 + 当前OSTmrTime值
新分组 = 定时器下次到时的OSTmrTime值(OSTmrMatch) % OS_TMR_CFG_WHEEL_SIZE
软件定时器相关的主要操作有:创建软件定时器函数OSTmrCreate,开启软件定时器函数OSTmrStart,停止软件定时器函数OSTmrStop。
65.2 硬件设计

  1. 例程功能
    在UCOSII里面创建7个任务:开始任务、LED任务、触摸屏任务、队列消息显示任务、信号量集任务、按键扫描任务和主任务。开始任务用于创建邮箱、消息队列、信号量集以及其他任务,之后任务挂起;触摸屏任务用于在屏幕上画图,可以用于测试 CPU使用率;队列消息显示任务请求消息队列,在得到消息后显示收到的消息数据;信号量集任务用于测试信号量集,采用OS_FLAG_WAIT_SET_ANY的方法,任何按键按下,该任务都会控制DS1闪以下;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务创建3个软件定时器(定时器1,100ms溢出一次,显示CPU和内存使用率;定时器2,200ms溢出一次,在固定区域不停的显示不同颜色;定时器3,100ms溢出一次,用于自动发送消息到消息队列)。KEY0控制软件定时器3的开关,从而控制消息队列的发送;KEY1控制软件定时器2的开关,同时清除LCD触摸屏区域数据;WK_UP按键用于触摸屏校准。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)独立按键
    KEY0 – PE4
    KEY1 – PE3
    WK_UP – PA0
    3)正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  3. 原理图
    用到的硬件用到LED灯和按键。电路在开发板上已经连接好了,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图65.2.1所示:
    在这里插入图片描述

图65.2.1 LED与STM32F103连接原理图
65.3 程序设计
65.3.1 UCOSII程序流程图
消息队列函数
在这里对本实验用到的UCOSII消息队列函数进行介绍,相关代码存放在os_q.c中。

  1. OSQCreate函数
    创建消息队列函数,其声明如下:
    OS_EVENT *OSQCreate (void **start, INT16U size)
    函数描述:
    用于创建一个消息队列
    函数形参:
    start:存放消息缓冲区指针数组的地址
    size:该数组的大小
    函数返回值:
    返回一个指向消息队列控制块的指针。如果没有空闲的控制块,返回空指针。
  2. OSQPend函数
    请求消息队列函数,其声明如下:
    void *OSQPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
    函数描述:
    用于向消息队列请求消息。
    函数形参:
    pevent:要访问的消息队列事件控制块的指针 timeout:等待时限
    timeout:等待时限
    perr:错误信息
    OS_ERR_NONE:调用成功,消息被正确地接受
    OS_ERR_TIMEOUT :消息没有在指定数目的时钟周期内被接收
    OS_ERR_PEND_ABOUT:取消队列等待
    OS_ERR_EVENT_TYPE:没有传递一个指向队列的指针
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    OS_ERR_PEND_ISR :从中断调用该函数,导致任务挂起
    OS_ERR_PEND_LOCKED:调度器上锁了
    函数返回值:

    注意事项:
    如果队列中有消息,该消息传递给任务,并从队列中清除该消息;如果队列中没有消息,则调用该函数的任务被挂起等待,直到有消息或者等待超时。当有多个任务请求到同一消息队列时,UCOSII进行任务调度,当前任务和所有请求该消息队列的任务中最高优先级的任务得到运行。
  3. OSQPost函数
    向消息队列发送消息函数,其声明如下:
    INT8U OSQPost (OS_EVENT *pevent, void *pmsg)
    函数描述:
    用于向消息队列发送消息
    函数形参:
    pevent:消息队列指针 pmsg:消息指针
    函数返回值:
    OS_ERR_NONE:函数调用成功,成功发送消息到消息队列
    OS_ERR_Q_FULL:队列已满
    OS_ERR_EVENT_TYPE:pevent不是指向消息队列的指针
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    信号量集函数
    在这里对本实验用到的UCOSII信号量集函数进行介绍,相关代码存放在os_flag.c中。
  4. OSFlagCreate函数
    创建信号量集函数,其声明如下:
    OS_FLAG_GRP *OSFlagCreate (OS_FLAGS flags, INT8U *perr)
    函数描述:
    用于创建一个信号量集
    函数形参:
    flags:信号的初始值
    perr:错误信息
    OS_ERR_NONE:函数调用成功
    OS_ERR_CREATE_ISR:在中断中创建信号量集
    OS_ERR_FLAG_GRP_DEPLETED:没有更多的事件标志组
    函数返回值:
    这个信号量集的标志组的指针,应用程序可以用这个指针对信号量集进行相对应的操作。
  5. OSFlagPend函数
    请求信号量集函数,其声明如下:
    OS_FLAGS OSFlagPend (OS_FLAG_GRP *pgrp,
    OS_FLAGS flags,
    INT8U wait_type,
    INT32U timeout,
    INT8U *perr)
    函数描述:
    用于请求一个信号量集
    函数形参:
    pgrp:消息邮箱的指针
    flags :消息指针
    wait_type:逻辑运算类型
    OS_FLAG_WAIT_CLR_ALL:等待“mask”中的所有位被清除
    OS_FLAG_WAIT_SET_ALL:等待“mask”中的所有位被设置
    OS_FLAG_WAIT_CLR_ANY:等待“mask”中的任何位被清除
    OS_FLAG_WAIT_SET_ANY:等待“mask”中的任何位被设置
    timeout:等待时延
    perr:错误信息
    OS_ERR_NONE:指定比特数已经被设置“超时”
    OS_ERR_PEND_ISR:从中断挂起
    OS_ERR_FLAG_INVALID_PGRP:pgrp是一个空指针
    OS_ERR_TIMEOUT:指定比特数未设置“超时”
    OS_ERR_PEND_ABORT:取消标志等待
    OS_ERR_FLAG_WAIT_TYPE:没有指定正确的“wait_type”参数
    函数返回值:
    返回标志组成员OSFlagFlags的值
  6. OSFlagPost函数
    向信号量集发送信号函数,其声明如下:
    OS_FLAGS OSFlagPost (OS_FLAG_GRP *pgrp,
    OS_FLAGS flags,
    INT8U opt,
    INT8U *perr)
    函数描述:
    用于向信号量集发送信号
    函数形参:
    pgrp:信号量集指针
    flags:选择所要发送的信号
    opt:信号有效的选项
    perr:错误信息
    OS_ERR_NONE:函数调用成功
    OS_ERR_FLAG_INVALID_PGRP:pgrp是一个空指针
    OS_ERR_EVENT_TYPE:没有指向时间标志组
    OS_ERR_FLAG_INVALID_OPT:使用无效选项
    函数返回值:
    返回标志组成员OSFlagFlags的值
    注意事项:
    所谓任务向信号量集发信号,就是对信号量集标志组中的信号进行置“1”(置位)或者置“0”(复位)的操作。至于对信号量几种的哪些信号进行操作,用函数中的参数flags来指定;对指定的信号是置“1”还是置“0”,用函数中的参数opt来指定(opt = OS_FLAG_SET为置“1”操作;opt = OS_FLAG_CLR为置“0”操作)。
    软件定时器函数
    在这里对本实验用到的UCOSII软件定时器函数进行介绍,相关代码存放在os_tmr.c中。
  7. OSTmrCreate函数
    创建软件定时器函数,其声明如下:
    OS_TMR *OSTmrCreate (INT32U dly,
    INT32U period,
    INT8U opt,
    OS_TMR_CALLBACK callback,
    void *callback_arg,
    INT8U *pname,
    INT8U *perr)
    函数描述:
    用于创建软件定时器
    函数形参:
    dly:用于初始化定时时间
    period:软件定时器的周期溢出时间
    opt:设置软件定时器工作模式
    OS_TMR_OPT_ONE_SHOT:计时器只倒计时一次
    OS_TMR_OPT_PERIODIC:定时器倒计时,然后重新加载
    callback_arg:回调函数的参数
    pname:软件定时器的名字
    perr:错误信息
    OS_ERR_NONE:函数调用成功
    OS_ERR_TMR_INVALID_DLY:执行无效延时操作
    OS_ERR_TMR_INVALID_PERIOD:执行操作时输入了不合法的时间
    OS_ERR_TMR_INVALID_OPT:使用无效选项
    OS_ERR_TMR_ISR:在中断中创建
    OS_ERR_TMR_NON_AVAIL:定时器池中没有空闲的定时器
    函数返回值:
    返回定时器控制块
    注意事项:
    软件定时器的回调函数有固定格式,我们必须按照这个格式编写,软件定时器的回调函数格式为:void (*OS_TMR_CALLBACK)(void *ptmr, void parg)。其中,函数名我们可以自己随意设置,而ptmr这个参数,软件定时器用来传递当前定时器的控制块指针,所以我们一般设置其类型为OS_TMR类型,第二个参数parg为回调函数的参数,这个就可以根据自己需要设置了,你也可以不用,但是必须有这个参数。
  8. OSTmrStart函数
    开启软件定时器函数,其声明如下:
    BOOLEAN OSTmrStart (OS_TMR *ptmr, INT8U *perr)
    函数描述:
    用于开启某个软件定时器
    函数形参:
    ptmr:要开启的软件定时器指针
    perr:错误信息
    OS_ERR_NONE:函数调用成功
    OS_ERR_TMR_INVALID:定时器无效
    OS_ERR_TMR_INVALID_TYPE:ptmr不是指向OS_TMR
    OS_ERR_TMR_ISR:从中断中创建
    OS_ERR_TMR_INACTIVE:定时器没有被创建
    OS_ERR_TMR_INVALID_STATE:定时器处于无效状态
    函数返回值:
    返回布尔值 OS_TRUE:软件定时器开启 OS_FALSE:错误发生
  9. 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:错误信息
    OS_ERR_NONE:函数调用成功
    OS_ERR_TMR_INVALID:定时器无效
    OS_ERR_TMR_INVALID_TYPE:ptmr不是指向OS_TMR
    OS_ERR_TMR_ISR:从中断中创建
    OS_ERR_TMR_INACTIVE:定时器没有被创建
    OS_ERR_TMR_INVALID_OPT:操作时使用无效选项
    OS_ERR_TMR_STOPPED:定时器已经停止
    OS_ERR_TMR_INVALID_STATE:定时器处于无效状态
    OS_ERR_TMR_NO_CALLBACK:定时器没有定义回调函数
    函数返回值:
    返回布尔值 OS_TRUE:软件定时器停止 OS_FALSE:软件定时器停止失败
    65.3.2 程序流程图
    消息队列我们在多任务间进行,所以我们需要定义多个任务,在不同任务中传递消息。
    图65.3.2.1 UCOSII消息队列、信号量集和软件定时器实验
    程序我们按流程图的设计来实现本节的功能代码。我们通过start_task创建其它任务,包括:
    LED任务,用于定时闪烁LED灯;
    触摸任务,用于实现触摸画线功能;
    按键扫描函数,根据按键发送不同的消息;
    qmsgshow_task,请求并显示收到的队列消息;
    flag_task用于请求和显示按键信号量集,获取完的信号于一段时间后从队列中清除;
    main_task用于创建定时器任务,并根据获取的队列消息得到按键值并投递给flag_task进行显示,根据按键值设置软件定时器1,2,3的状态,控制部分任务运行或挂起,重绘界面。
    65.3.3 程序解析
  10. main.c代码
    在main.c文件下,只初始化一些外设,然后调用ucosii的例程入口函数uc_os2_demo(),如下代码所示:
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/BEEP/beep.h"
#include "./BSP/TOUCH/touch.h"
#include "./BSP/SRAM/sram.h"
#include "./MALLOC/malloc.h"
#include "uc-os2_demo.h"

int main(void)
{
    HAL_Init();							/* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);	/* 设置时钟,72M */
    delay_init(72);						/* 初始化延时函数 */
    usart_init(115200);					/* 串口初始化为115200 */
    led_init();							/* 初始化LED */
    lcd_init();							/* 初始化LCD */
    key_init();							/* 初始化按键 */
    beep_init();							/* 初始化蜂鸣器 */
    sram_init();							/* SRAM初始化 */
    tp_dev.init();						/* 触摸屏初始化 */
    my_mem_init(SRAMIN);				/* 初始化内部SRAM内存池 */
    my_mem_init(SRAMEX);				/* 初始化外部SRAM内存池 */
    uc_os2_demo();						/* 运行uC/OS-II例程 */
}
可以看到,在main.c文件中只包含了一个main()函数,main()函数主要就是完成了一些外设的初始化,如串口、LED、LCD等,并在最后调用了函数uc_os2_demo()。
下面看一下uc-os2_demo.c的代码:
/* UCOSII任务设置 */

/* START 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define START_TASK_PRIO                 10		/* 开始任务的优先级设置为最低 */
#define START_STK_SIZE                  128		/* 堆栈大小 */

OS_STK START_TASK_STK[START_STK_SIZE];			/* 任务堆栈 */
void start_task(void *pdata);					/* 任务函数 */

/* 触摸屏任务 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define TOUCH_TASK_PRIO                 7			/* 优先级设置(越小优先级越高) */
#define TOUCH_STK_SIZE                  128		/* 堆栈大小 */

OS_STK TOUCH_TASK_STK[TOUCH_STK_SIZE];			/* 任务堆栈 */
void touch_task(void *pdata);					/* 任务函数 */

/* LED 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define LED_TASK_PRIO                   6			/* 优先级设置(越小优先级越高) */
#define LED_STK_SIZE                    128		/* 堆栈大小 */

OS_STK LED_TASK_STK[LED_STK_SIZE];				/* 任务堆栈 */
void led_task(void *pdata);						/* 任务函数 */

/* 队列消息显示 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define QMSGSHOW_TASK_PRIO              5			/* 优先级设置(越小优先级越高) */
#define QMSGSHOW_STK_SIZE               128		/* 堆栈大小 */

OS_STK QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE];	/* 任务堆栈 */
void qmsgshow_task(void *pdata);                	/* 任务函数 */

/* 主 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define MAIN_TASK_PRIO                  4       	/* 优先级设置(越小优先级越高) */
#define MAIN_STK_SIZE                   512     	/* 堆栈大小 */

OS_STK MAIN_TASK_STK[MAIN_STK_SIZE];			/* 任务堆栈 */
void main_task(void *pdata);					/* 任务函数 */

/* 信号量集 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define FLAGS_TASK_PRIO                 3			/* 优先级设置(越小优先级越高) */
#define FLAGS_STK_SIZE                  512		/* 堆栈大小 */

OS_STK FLAGS_TASK_STK[FLAGS_STK_SIZE];			/* 任务堆栈 */
void flags_task(void *pdata);					/* 任务函数 */

/* 按键扫描 任务 配置
 * 包括: 任务优先级 堆栈大小 等
 */
#define KEY_TASK_PRIO                   2			/* 优先级设置(越小优先级越高) */
#define KEY_STK_SIZE                    512		/* 堆栈大小 */

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;							/* 按键信号量集 */

/* 消息队列存储地址,最大支持256个消息 */
void *MsgGrp[256];

/* 这些函数在main函数后面实现 */
void tmr1_callback(OS_TMR *ptmr, void *p_arg);
void tmr2_callback(OS_TMR *ptmr, void *p_arg);
void tmr3_callback(OS_TMR *ptmr, void *p_arg);

void ucos_load_main_ui(void);
void lcd_draw_bline(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t size, uint16_t color);

/**
 * @brief       uC/OS-II例程入口函数
 * @param       无
 * @retval      无
 */
void uc_os2_demo(void)
{
ucos_load_main_ui();								/* 加载主界面 */
OSInit();											/* UCOS初始化 */ 
OSTaskCreateExt((void(*)(void *)  )start_task,		/* 任务函数 */ 
/* 传递给任务函数的参数 */
                    (void *           )0,
/* 任务堆栈栈顶 */
                    (OS_STK *         )&START_TASK_STK[START_STK_SIZE - 1],
                    (INT8U            )START_TASK_PRIO,		/* 任务优先级 */
                    /* 任务ID,这里设置为和优先级一样 */
                    (INT16U           )START_TASK_PRIO,
                    (OS_STK *         )&START_TASK_STK[0],	/* 任务堆栈栈底 */
                    (INT32U           )START_STK_SIZE,		/* 任务堆栈大小 */
                    (void *           )0,					/* 用户补充的存储区 */
/* 任务选项,为了保险起见,所有任务都保存浮点寄存器的值 */
                    (INT16U           )	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    OSStart();  											/* 开始任务 */
    
    for (;;)
    {
        /* 不会进入这里 */
    }
}

/**
 * @brief       开始任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void start_task(void *pdata)
{
    uint8_t err;
    OS_CPU_SR cpu_sr = 0;
    CPU_INT32U cnts;    
    /* 根据配置的节拍频率配置SysTick */
    cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OS_TICKS_PER_SEC);
    OS_CPU_SysTickInit(cnts);
    
    msg_key = OSMboxCreate((void *)0);	/* 创建消息邮箱 */
    q_msg = OSQCreate(&MsgGrp[0], 256);	/* 创建消息队列 */
    flags_key = OSFlagCreate(0, &err); 	/* 创建信号量集 */
    OSStatInit();                       	/* 开启统计任务 */
    OS_ENTER_CRITICAL();                	/* 进入临界区(关闭中断) */
    
    /* LED任务 */
    OSTaskCreateExt((void(*)(void *) )led_task,
                    (void *		)0,
                    (OS_STK *		)&LED_TASK_STK[LED_STK_SIZE - 1],
                    (INT8U			)LED_TASK_PRIO,
                    (INT16U		)LED_TASK_PRIO,
                    (OS_STK *		)&LED_TASK_STK[0],
                    (INT32U		)LED_STK_SIZE,
                    (void *		)0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* 触摸任务 */
    OSTaskCreateExt((void(*)(void *) )touch_task,
                    (void *		)0,
                    (OS_STK *		)&TOUCH_TASK_STK[TOUCH_STK_SIZE - 1],
                    (INT8U			)TOUCH_TASK_PRIO,
                    (INT16U		)TOUCH_TASK_PRIO,
                    (OS_STK *		)&TOUCH_TASK_STK[0],
                    (INT32U		)TOUCH_STK_SIZE,
                    (void *		)0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* 消息队列显示任务 */
    OSTaskCreateExt((void(*)(void *) )qmsgshow_task,
                    (void *         )0,
                    (OS_STK *		)&QMSGSHOW_TASK_STK[QMSGSHOW_STK_SIZE - 1],
                    (INT8U			)QMSGSHOW_TASK_PRIO,
                    (INT16U		)QMSGSHOW_TASK_PRIO,
                    (OS_STK *		)&QMSGSHOW_TASK_STK[0],
                    (INT32U		)QMSGSHOW_STK_SIZE,
                    (void *		)0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* 主任务 */
    OSTaskCreateExt((void(*)(void *) )main_task,
                    (void *         )0,
                    (OS_STK *		)&MAIN_TASK_STK[MAIN_STK_SIZE - 1],
                    (INT8U			)MAIN_TASK_PRIO,
                    (INT16U		)MAIN_TASK_PRIO,
                    (OS_STK *		)&MAIN_TASK_STK[0],
                    (INT32U		)MAIN_STK_SIZE,
                    (void *		)0,
                    (INT16U         )	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* 信号量集任务 */
    OSTaskCreateExt((void(*)(void *) )flags_task,
                    (void *         )0,
                    (OS_STK *		)&FLAGS_TASK_STK[FLAGS_STK_SIZE - 1],
                    (INT8U			)FLAGS_TASK_PRIO,
                    (INT16U		)FLAGS_TASK_PRIO,
                    (OS_STK *		)&FLAGS_TASK_STK[0],
                    (INT32U		)FLAGS_STK_SIZE,
                    (void *		)0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    /* 按键任务 */
    OSTaskCreateExt((void(*)(void *) )key_task,
                    (void *         )0,
                    (OS_STK *		)&KEY_TASK_STK[KEY_STK_SIZE - 1],
                    (INT8U			)KEY_TASK_PRIO,
                    (INT16U         )KEY_TASK_PRIO,
                    (OS_STK *		)&KEY_TASK_STK[0],
                    (INT32U		)KEY_STK_SIZE,
                    (void *		)0,
                    (INT16U		)	OS_TASK_OPT_STK_CHK |
OS_TASK_OPT_STK_CLR |
OS_TASK_OPT_SAVE_FP);
    OS_EXIT_CRITICAL();            	 	/* 退出临界区(开中断) */
    OSTaskSuspend(START_TASK_PRIO); 	/* 挂起开始任务 */
}

/**
 * @brief       LED0任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void led_task(void *pdata)
{
    uint8_t t;
    
    while (1)
    {
        t++;
        OSTimeDly(10);
        
        if (t == 8)
        {
            LED0(1);    /* LED0灭 */
        }
        
        if (t == 100)
        {
            t = 0;
            LED0(0);    /* LED0亮 */
        }
    }
}

/**
 * @brief       触摸屏任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void touch_task(void *pdata)
{
    uint32_t cpu_sr;
    uint16_t lastpos[2];					/* 最后一次的数据 */
    
    while (1)
    {
        tp_dev.scan(0);
        
        if (tp_dev.sta & TP_PRES_DOWN)		/* 触摸屏被按下 */
        {
            if (tp_dev.x[0] < (130 - 1) &&
			   tp_dev.y[0] < lcddev.height &&
			   tp_dev.y[0] > (220 + 1))
            {
                if (lastpos[0] == 0XFFFF)
                {
                    lastpos[0] = tp_dev.x[0];
                    lastpos[1] = tp_dev.y[0];
                }
                /* 进入临界段,防止其他任务,打断LCD操作,导致液晶乱序 */
                OS_ENTER_CRITICAL();
                lcd_draw_bline(lastpos[0],
lastpos[1],
tp_dev.x[0],
tp_dev.y[0],
2, 
RED);			/* 画线 */
                OS_EXIT_CRITICAL();
                lastpos[0] = tp_dev.x[0];
                lastpos[1] = tp_dev.y[0];
            }
        }
        else
        {
            lastpos[0] = 0XFFFF;
            OSTimeDly(10);					/* 没有按键按下的时候 */
        }
    }
}

/**
 * @brief       队列消息显示任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void qmsgshow_task(void *pdata)
{
    char *p;
    uint8_t err;
    
    while (1)
    {
        printf("qmsgshow_task\r\n");
        p = OSQPend(q_msg, 0, &err);			/* 请求消息队列 */
/* 显示消息 */
        lcd_show_string(5, 170, 240, 16, 16, p, RED);
        myfree(SRAMIN, p);
        OSTimeDly(500);
    }
}

/**
 * @brief       主任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void main_task(void *pdata)
{
    uint32_t key = 0;
    uint8_t err;
    uint8_t tmr2sta = 1;						/* 软件定时器2开关状态 */
    uint8_t tmr3sta = 0;						/* 软件定时器3开关状态 */
    uint8_t flagsclrt = 0;						/* 信号量集显示清零倒计时 */
    tmr1 = OSTmrCreate(10, 10, OS_TMR_OPT_PERIODIC,
(OS_TMR_CALLBACK)tmr1_callback,
0, 
(unsigned char*)"tmr1", &err);		/* 100ms执行一次 */
    tmr2 = OSTmrCreate(10, 20, OS_TMR_OPT_PERIODIC,  (OS_TMR_CALLBACK)tmr2_callback, 
            0, 
(unsigned char*)"tmr2", &err);		/* 200ms执行一次 */
    tmr3 = OSTmrCreate(10, 10, OS_TMR_OPT_PERIODIC, (OS_TMR_CALLBACK)tmr3_callback, 
            0, 
(unsigned char*)"tmr3", &err);		/* 100ms执行一次 */
    OSTmrStart(tmr1, &err); 						/* 启动软件定时器1 */
    OSTmrStart(tmr2, &err); 						/* 启动软件定时器2 */
    
    while (1)
    {
        key = (uint32_t)OSMboxPend(msg_key, 10, &err);
        
        if (key)
        {
            flagsclrt = 51;                          /* 500ms后清除 */
/* 设置对应的信号量为1 */
            OSFlagPost(flags_key, 1 << (key - 1), OS_FLAG_SET, &err);
        }
        
        if (flagsclrt)                               /* 倒计时 */
        {
            flagsclrt--;

            /* 清除显示 */
            if (flagsclrt == 1)lcd_fill(140, 162, 239, 162 + 16, WHITE);
        }
        
        switch (key)
        {
            case KEY0_PRES:
            {
                /* 软件定时器2 开关,并清屏 */
                tmr2sta = !tmr2sta;
                
                if (tmr2sta)
                {
                    OSTmrStart(tmr2, &err);        /* 开启软件定时器2 */
                }
                else
                {	 /* 关闭软件定时器2 */
                    OSTmrStop(tmr2, OS_TMR_OPT_NONE, 0, &err);
/* 提示定时器2关闭了 */
                    lcd_show_string(148, 262, 240, 16, 16, "TMR2 STOP", RED); 
                }
                /* 顺便清屏 */
                lcd_fill(0, 221, 129, lcddev.height - 1, WHITE); 
                break;
            }
            case KEY1_PRES:
            {
                /* 控制软件定时器3 */
                tmr3sta = !tmr3sta;
                
                if (tmr3sta)
                {
                    OSTmrStart(tmr3, &err);
                }
                else
                {
/* 关闭软件定时器3 */
                    OSTmrStop(tmr3, OS_TMR_OPT_NONE, 0, &err);
                }
                break;
            }
            case WKUP_PRES:
            {
                /* 校准 */
                OSTaskSuspend(TOUCH_TASK_PRIO);			 /* 挂起触摸屏任务 */
                OSTaskSuspend(QMSGSHOW_TASK_PRIO);		 /* 挂起队列信息显示任务 */
                OSTmrStop(tmr1, OS_TMR_OPT_NONE, 0, &err);/* 关闭软件定时器1 */
                
                if (tmr2sta)
                {
                    /* 关闭软件定时器2 */
                    OSTmrStop(tmr2, OS_TMR_OPT_NONE, 0, &err);
                }
                
                if ((tp_dev.touchtype & 0X80) == 0)
                {
                    tp_adjust();
                }
                /* 重新开启软件定时器1 */
                OSTmrStart(tmr1, &err);
                
                if (tmr2sta)
                {
                    OSTmrStart(tmr2, &err);				 /* 重新开启软件定时器2 */
                }
                
                OSTaskResume(TOUCH_TASK_PRIO);			 /* 解挂 */
                OSTaskResume(QMSGSHOW_TASK_PRIO);			 /* 解挂 */
                ucos_load_main_ui();						 /* 重新加载主界面 */
                break;
            }
        }
        OSTimeDly(10);
    }
}

/**
 * @brief       信号量集处理任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void flags_task(void *pdata)
{
    uint16_t flags;
    uint8_t err;
    
    while (1)
    {   /* 等待信号量 */
        flags = OSFlagPend(flags_key, 0X0007, OS_FLAG_WAIT_SET_ANY, 0, &err);
        
        if (flags & 0X0001)
        {
            lcd_show_string(140, 162, 240, 16, 16, "KEY0 DOWN  ", RED);
        }
        
        if (flags & 0X0002)
        {
            lcd_show_string(140, 162, 240, 16, 16, "KEY1 DOWN  ", RED);
        }
        
        if (flags & 0X0004)
        {
            lcd_show_string(140, 162, 240, 16, 16, "KEY_UP DOWN  ", RED);
        }
        
        BEEP(1);
        OSTimeDly(50);
        BEEP(0);
        OSFlagPost(flags_key, 0X0007, OS_FLAG_CLR, &err);   /* 全部信号量清零 */
    }
}

/**
 * @brief       按键扫描任务
 * @param       pdata : 传入参数(未用到)
 * @retval      无
 */
void key_task(void *pdata)
{
    uint32_t key;
    
    while (1)
    {
        key = key_scan(0);
        
        if (key)
        {
            OSMboxPost(msg_key, (void *)key);   /* 发送消息 */
        }
        OSTimeDly(10);
    }
}

/**
 * @brief       软件定时器1的回调函数
 * @note        每100ms执行一次,用于显示CPU使用率和内存使用率
 * @param       ptmr : 软件定时器指针
 * @param       p_arg: 参数指针(未用到)
 * @retval      无
 */
void tmr1_callback(OS_TMR *ptmr, void *p_arg)
{
    static uint16_t cpuusage = 0;
    static uint8_t tcnt = 0;
    
    if (tcnt == 5)
    {   /* 显示CPU使用率 */
        lcd_show_xnum(202, 10, cpuusage / 5, 3, 16, 0, BLUE);
        cpuusage = 0;
        tcnt = 0;
    }
    
    cpuusage += OSCPUUsage;
tcnt++;
/* 显示内存使用率 */
lcd_show_xnum(202, 30, my_mem_perused(SRAMIN) / 10, 3, 16, 0, BLUE);
/* 显示队列当前的大小 */
lcd_show_xnum(202, 50, ((OS_Q *)
(q_msg->OSEventPtr))->OSQEntries, 3, 16, 0X80, BLUE);
}

/**
 * @brief       软件定时器2的回调函数
 * @note        每200ms执行一次
 * @param       ptmr : 软件定时器指针
 * @param       p_arg: 参数指针(未用到)
 * @retval      无
 */
void tmr2_callback(OS_TMR *ptmr, void *p_arg)
{
    static uint8_t sta = 0;
    
    switch (sta)
    {
        case 0:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, RED);
            break;
        }
        case 1:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, GREEN);
            break;
        }
        case 2:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, BLUE);
            break;
        }
        case 3:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, MAGENTA);
            break;
        }
        case 4:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, CYAN);
            break;
        }
        case 5:
        {
            
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, YELLOW);
            break;
        }
        case 6:
        {
            lcd_fill(131, 221, lcddev.width - 1, lcddev.height - 1, BRRED);
            break;
        }
    }
    
    sta++;
    
    if (sta > 6)
    {
        sta = 0;
    }
}

/**
 * @brief       软件定时器3的回调函数
 * @note        每300ms执行一次
 * @param       ptmr : 软件定时器指针
 * @param       p_arg: 参数指针(未用到)
 * @retval      无
 */
void tmr3_callback(OS_TMR *ptmr, void *p_arg)
{
    uint8_t *p;
    uint8_t err;
    static uint8_t msg_cnt = 0;							/* msg编号 */
    p = mymalloc(SRAMIN, 13);							/* 申请13个字节的内存 */
    
    if (p)
    {
        sprintf((char *)p, "ALIENTEK %03d", msg_cnt);
        msg_cnt++;
        err = OSQPost(q_msg, p);							/* 发送队列 */
        
        if (err != OS_ERR_NONE)							/* 发送失败 */
        {
            myfree(SRAMIN, p);							/* 释放内存 */
            OSTmrStop(tmr3, OS_TMR_OPT_NONE, 0, &err);	/* 关闭软件定时器3 */
        }
    }
}

/**
 * @brief       加载主界面
 * @param       无
 * @retval      无
 */
void ucos_load_main_ui(void)
{
    lcd_clear(WHITE);   /* 清屏 */
    lcd_show_string(10, 10, 200, 16, 16, "STM32", RED);
    lcd_show_string(10, 30, 200, 16, 16, "UCOSII TEST3", RED);
    lcd_show_string(10, 50, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(10, 75, 240, 16, 16, "KEY0:TMR2 SW & CLR KEY1:Q SW", RED);
    lcd_show_string(10, 95, 240, 16, 16, "KEY_UP:ADJUST", RED);
    lcd_draw_line(0, 70, lcddev.width - 1, 70, RED);
    lcd_draw_line(150, 0, 150, 70, RED);
    
    lcd_draw_line(0, 120, lcddev.width - 1, 120, RED);
    lcd_draw_line(0, 220, lcddev.width - 1, 220, RED);
    lcd_draw_line(130, 120, 130, lcddev.height - 1, RED);
    
    lcd_show_string(5, 125, 240, 16, 16, "QUEUE MSG", RED);	/* 队列消息 */
    lcd_show_string(5, 150, 240, 16, 16, "Message:", RED);
    lcd_show_string(5 + 130, 125, 240, 16, 16, "FLAGS", RED);	/* 信号量集 */
    lcd_show_string(5, 225, 240, 16, 16, "TOUCH", RED);			/* 触摸屏 */
    lcd_show_string(5 + 130, 225, 240, 16, 16, "TMR2", RED);	/* 队列消息 */
    
    lcd_show_string(170, 10, 200, 16, 16, "CPU:   %", BLUE);
    lcd_show_string(170, 30, 200, 16, 16, "MEM:   %", BLUE);
    lcd_show_string(170, 50, 200, 16, 16, " Q :000", BLUE);
    
    delay_ms(300);
}

/**
 * @brief       画粗线
 * @param       x1,y1: 起点坐标
 * @param       x2,y2: 终点坐标
 * @param       size : 线条粗细程度
 * @param       color: 线的颜色
 * @retval      无
 */
void lcd_draw_bline(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t size, uint16_t color)
{
    uint16_t t;
    int xerr = 0, yerr = 0, delta_x, delta_y, distance;
    int incx, incy, row, col;
    
    if (x1 < size || x2 < size || y1 < size || y2 < size)
    {
        return;
    }
    
    delta_x = x2 - x1;							/* 计算坐标增量 */
    delta_y = y2 - y1;
    row = x1;
    col = y1;
    
    if (delta_x > 0)
    {
        incx = 1;								/* 设置单步方向 */
    }
    else if (delta_x == 0)
    {
        incx = 0;								/* 垂直线 */
    }
    else
    {
        incx = -1;
        delta_x = -delta_x;
    }
    
    if (delta_y > 0)
    {
        incy = 1;
    }
    else if (delta_y == 0)
    {
        incy = 0;								/* 水平线 */
    }
    else
    {
        incy = -1;
        delta_y = -delta_y;
    }
    
    if ( delta_x > delta_y)
    {
        distance = delta_x;						/* 选取基本增量坐标轴 */
    }
    else 
    {
        distance = delta_y;
    }
    
    for (t = 0; t <= distance + 1; t++ )        	/* 画线输出 */
    {
        lcd_fill_circle(row, col, size, color);	/* 画点 */
        xerr += delta_x ;
        yerr += delta_y ;
        
        if (xerr > distance)
        {
            xerr -= distance;
            row += incx;
        }
        
        if (yerr > distance)
        {
            yerr -= distance;
            col += incy;
        }
    }
}

上面就是对创建的start_task、led_task、touch_task、qmsgshow_task、main_task、flags_task和key_task等7个任务的参数进行配置,例如优先级、堆栈大小和任务函数的声明及定义。此外还有3个软件定时器及其回调函数。
软件定时器tmr1、tmr2和tmr3,tmr1用于显示CPU使用率和内存使用率,每100ms执行一次;tmr2用于在LCD的右下角区域不停的显示各种颜色,每200ms执行一次;tmr3用于定时器向队列发送消息(用到了对台内存申请),每100ms发送一次。
在本实验中,我们还是使用消息邮箱msg_key在按键任务和主任务之间传递键值数据,我们创建信号量集flags_key,在主任务里面将按键键值通过信号量集传递给信号量集处理任务flags_task,实现按键信息的显示以及LED1的提示性闪灯。
此外我们还创建了一个大小为256的消息队列q_msg,通过软件定时器tmr3的回调函数向消息队列发送消息,然后在消息队列显示任务qmsgshow_task里面请求消息队列,并在LCD上面显示得到的消息。消息队列还用到了动态内存管理。
在主任务main_task里面,我们实现了前面介绍的功能:KEY0控制软件定时器3的开关,间接控制消息队列的发送;KEY1控制软件定时器2的开关,同时清除LCD触摸屏区域的数据;WK_UP用于触摸屏校准,在校准的时候,要先挂起触摸屏任务、队列消息显示任务,并停止软件定时器tmr1和tmr2,否则可能对校准时的LCD显示造成干扰;
其他任务做的事情在前面程序流程图里面已经有说明,这里就不多说了。
65.4 下载验证
将程序下载到开发板后,可以看到LCD显示界面如图65.4.1所示:
在这里插入图片描述

图65.4.1 初始化界面
从上图可以看到,默认情况下,CPU使用率为25%,比上一章节多了一些,主要原因是软件定时器2不停的刷屏导致的。
通过按KEY1,控制软件定时器3的开关,从而控制消息队列的发送,可以在LCD中看到Q和MEM的值慢慢变大,说明队列消息在增多,占用内存也随着消息增多而增大,在QUEUE MSG区,开始显示队列信息,再按一次KEY1停止软件定时器3,此时可以看到Q和MEM逐渐减小。当Q值变为0的时候,QUEUE MSG也停止显示(队列为空)。
通过按KEY0可以控制软件定时器2的开关,同时清除LCD触摸屏区域数据;通过按下WK_UP可以进入校准程序,对触摸屏进行校准。在TOUCH区域,可以输入手写内容。任何按键按下,DS1都会闪一下,提示按键被按下,同时在 FLAGS区域显示按键信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/769498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux系统编程】Linux调试器——gdb 的基本使用

文章目录 1. 准备工作及知识补充1.1 源文件和Makefile1.2 安装gdb并解决没有调式信息的问题debug和release的了解如何解决 2. gdb的基本使用2.1 显示代码2.2 设置、删除和查看断点2.3 禁用和启用断点2.4 逐语句和逐过程调式2.5 查看函数调用堆栈2.6 查看指定变量的值2.7 跳至指…

STM32学习笔记(十二)丨RTC实时时钟

本篇文章包含的内容 一、计算机底层计时系统——时间戳1.1 时间戳简介1.2 GMT/UTC1.3 C语言和time.h库 二、STM32的BKP和RTC时钟2.1 BKP&#xff08;Backup Registers&#xff09;备份寄存器2.2 RTC&#xff08;Real Time Clock&#xff09;实时时钟2.2.1 RTC简介2.2.2 RTC的内…

行业追踪,2023-07-18,减速器,汽车零部件是重点关注板块,随时开启

自动复盘 2023-07-18 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

PB从入坑到放弃(五)窗口使用技巧

PB从入坑到放弃&#xff08;五&#xff09;窗口使用技巧 一、窗口类型二、窗口属性2.1 General 属性页属性2.2 Scroll 属性页属性2.3 ToolBar 属性页属性2.4 Other 属性页中的属性 三、11种常用控件四、窗口事件4.1 常用事件4.2 举个栗子 五、窗口常用函数5.1 open 函数5.2 clo…

【力扣每日一题】2023.7.19 模拟行走机器人

题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 机器人模拟类题目,力扣里有很多这样的模拟题,就是模拟机器人在坐标系上行走. 套路就是记下每个方向行走后,x轴和y轴的变化&#xff08;代码中的direction&#xff09;,例如 direction[0] 就表示向北走一次,y轴1,x轴不变.…

防静电门禁闸机的设计和功能

防静电门禁闸机是一种用于控制人员出入的设备&#xff0c;主要用于对人员进行身份验证和进出控制。它的主要功能是防止静电干扰和未经授权的人员进入特定区域。 防静电门禁闸机一般包括以下几个方面的设计和功能&#xff1a; 1. 门禁系统&#xff1a;防静电门禁闸机通常集成了…

宝塔面板登陆不上去了,一直显示加载

宝塔面板登陆不上去了&#xff0c;一直显示加载 半天了登不上去&#xff0c;不知道这是怎么回事? 解答&#xff1a; 您好&#xff0c;服务器内执行bt 16命令先修复&#xff0c;然后重新访问看下。

安卓APK反编译+修改+重打包+签名

目录 1.下载反编译工具包。2.将APK包&#xff0c;重命名为ZIP&#xff0c;解压。放到反编译根目录下。3.使用apktool反编译修改smail文件&#xff0c;进行重打包4.重新打包5.重签名 1.下载反编译工具包。 反编译工具包地址&#xff1a;百度网盘 提取码&#xff1a;dsu3 解压后…

汽配企业专用的MES管理系统与普通系统相比有哪些特征

随着全球化的加速和市场竞争的日益激烈&#xff0c;汽配企业对于生产管理的要求越来越高。为了满足这种需求&#xff0c;越来越多的汽配企业开始引入MES管理系统解决方案来管理和优化其生产线。与传统的普通系统相比&#xff0c;汽配企业MES管理系统具有以下几个显著的特征&…

Ceph 分布式存储之部署

一.Ceph 存储基础 1、单机存储设备 DAS&#xff08;直接附加存储&#xff0c;是直接接到计算机的主板总线上去的存储&#xff09; IDE、SATA、SCSI、SAS、USB 接口的磁盘 所谓接口就是一种存储设备驱动下的磁盘设备&#xff0c;提供块级别的存储 NAS&#xff08;网络附加存储…

谷歌和加州大学伯克利分校的“改革者”在单个GPU上运行64K序列

转换器模型是自然语言处理&#xff08;NLP&#xff09;研究领域越来越流行的神经网络架构&#xff0c;大型变压器可以在许多任务上实现最先进的性能。代价是转换器过多的计算消耗和成本&#xff0c;尤其是对于长序列上的训练模型。 谷歌和加州大学伯克利分校的研究人员最近发表…

C#被指定窗体的MdiParent的窗体不是MdiContainer

工作的时候遇到一个问题&#xff1a; 被指定窗体的MdiParent的窗体不是MdiContainer 这个问题的原因是父窗体的IsMdiContainer 属性设置为false导致的。将此属性设置为true&#xff0c;即可解决此问题。有两种方式设置窗体的IsMdiContainer 属性。 第一种&#xff0c;在父窗口…

IndexedDB

IndexedDB 操作流程打开数据库新建数据库新增数据读取数据遍历数据更新数据删除数据使用索引案例 indexedDB对象indexedDB.open() 操作流程 打开数据库 使用IndexedDB 的第一步是打开数据库&#xff0c;使用indexedDB.open()方法 // 第一个参数是字符串&#xff0c;表示数据…

MyBatis注解开发

1 Mybatis注解开发单表操作 1.1 MyBatis的常用注解 Mybatis也可以使用注解开发方式&#xff0c;这样可以减少编写Mapper映射文件 Insert&#xff1a;实现新增 Update&#xff1a;实现更新 Delete&#xff1a;实现删除 Select&#xff1a;实现查询 Result&#xff1a;实现…

【MongoDB实战】数据备份与恢复(部分迁移)

场景&#xff1a; 需求&#xff1a; 解决方案&#xff1a; 步骤&#xff1a; Stage 1&#xff1a;【生产环境】修改备份文件映射 Stage 2&#xff1a;【生产环境】重新构建mongodb Stage 3&#xff1a;【客户环境】修改备份文件映射&#xff0c;同 Stage 1 Stage 4&…

FPGA+x86构建高性能国产网络测试仪竞技之道

众所周知&#xff0c;以太网已经深入我们的生活无处不在&#xff0c;企业、校园、大数据中心和家庭等都离不开网络&#xff0c;否则我们的生活将受到严重的影响。 以太网的接口速率也是迅速发展&#xff1a;10M、100M、GE、10GE、40GE、100GE&#xff0c;到目前逐步成熟的2.5G…

数据结构与算法——静态链表及其创建(C语言实现)

《顺序表和链表优缺点》里面&#xff0c;我们了解了两种存储结构各自的特点&#xff0c;那么&#xff0c;是否存在一种存储结构&#xff0c;可以融合顺序表和链表各自的优点&#xff0c;从而既能快速访问元素&#xff0c;又能快速增加或删除数据元素。 静态链表&#xff0c;也…

【雕爷学编程】Arduino动手做(22)——8X8 LED点阵MAX7219屏7

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#xff0c;这…

Java开发中使用sql简化开发

引语&#xff1a; 在Java开发中&#xff0c;我们更希望数据库能直接给我们必要的数据&#xff0c;然后在业务层面直接进行使用&#xff0c;所以写一个简单的sql语句有助于提高Java开发效率&#xff0c;本文由简单到复杂的小白吸收&#xff0c;还请多多指教。 使用MySQL数据库…

GO 语言GC

目录 写屏障 读屏障 GO语言GC准备 堆内存结构: GC内存分配: GC触发&#xff1a; P的作用: 写屏障 实现强弱三色不式,为了避免误删,则实现写屏障. 写屏障是在写操作中插入指令,目的是把数据对象的修改通知到GC GO语言支持两种写屏障 读屏障 非移动垃圾回收(例如 三色)天…