【正点原子STM32连载】第六十四章 UCOSII实验2-信号量和邮箱摘自【正点原子】STM32F103 战舰开发指南V1.2

news2024/10/7 14:30:36

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

第六十四章 UCOSII实验2-信号量和邮箱

上一章,我们学习了如何使用UCOSII,学习了UCOSII的任务调度,但是并没有用到任务间的同步与通信,本章我们将学习两个最基本的任务间通讯方式:信号量和邮箱。本章分为如下几个小节:
64.1 UCOSII信号量和邮箱简介
64.2 硬件设计
64.3 程序设计
64.4 下载验证

64.1 UCOSII信号量和邮箱简介
系统中的多个任务在运行时,经常需要互相无冲突地访问同一个共享资源,或者需要互相支持和依赖,甚至有时还要互相加以必要的限制和制约,才保证任务的顺利运行。因此,操作系统必须具有对任务的运行进行协调的能力,从而使任务之间可以无冲突、流畅地同步运行,而不导致灾难性的后果。
例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态,只有当任务A把打印机释放后,系统才能唤醒任务B,使其获得打印机的使用权。如果这两个任务不这样做,那么会造成极大的混乱。
任务间的同步依赖于任务间的通信。在UCOSII中,是使用信号量、邮箱(消息邮箱)和消息队列,这些被称作事件的中间环节来实现任务之间的通信的。这里我们仅介绍信号量和邮箱,消息队列将会在下一章介绍。
事件
两个任务通过事件进行通讯的示意图如图64.1.1所示:
在这里插入图片描述

图64.1.1 两个任务使用事件进行通信的示意图
在上图中任务1是发信方,任务2是收信方。任务1负责把信息发送到事件上,这项操作叫做发送事件。任务2通过读取事件操作对事件进行查询:如果有信息则读取,否则等待。读事件操作叫做请求事件。
为了把描述事件的数据结构统一起来,UCOSII 使用叫做事件控制块(ECB)的数据结构来描述诸如信号量、邮箱(消息邮箱)和消息队列这些事件。事件控制块中包含包括等待任务表在内的所有有关事件的数据,事件控制块结构体定义如下:

typedef struct os_event {
    INT8U    OSEventType;                   	/* 事件的类型 */
    void    *OSEventPtr;                    	/* 消息或消息队列的指针 */
    INT16U   OSEventCnt;                    	/* 信号量计数器 */
    OS_PRIO  OSEventGrp;                    	/* 等待事件的任务组 */
    OS_PRIO  OSEventTbl[OS_EVENT_TBL_SIZE]; /* 任务等待表 */
#if OS_EVENT_NAME_EN > 0u
    INT8U   *OSEventName;				     /* 事件名 */
#endif
} OS_EVENT;

信号量
使用信号量的最初目的,是为了给共享资源设立一个标志,该标志表示该共享资源的占用情况。这样,当一个任务在访问共享资源之前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
信号量的实质是一个全局计数器的实现机制,释放信号量的任务使得该计数器的值加1,请求到信号量的任务使得该计数器的值减1。如果计数器的值为0,则请求该信号量得任务将挂起等待,直到别的任务释放该信号量。通过这种方式,使得释放信号量的任务可以控制请求信号量的任务的运行。
信号量的工作原理如图64.1.2所示:
在这里插入图片描述

图64.1.2信号量工作原理图
信号量可以分为两种:一种是二值型信号量,另外一种是N 值信号量。
UCOSII 将二值型信号量称之为也叫互斥型信号量,将 N 值信号量称之为计数型信号量, 也就是普通的信号量。
信号量相关的主要操作有:创建信号量OSSemCreate、请求信号量OSSemPend、释放信号量OSSemPost和删除信号量OSSemDel。后面再对这几个函数进行讲解。
邮箱
在多任务操作系统中,常常需要在任务与任务之间通过传递一个数据(这种数据叫做“消息”)的方式来进行通信。为了达到这个目的,可以在内存中创建一个存储空间作为该数据的缓冲区。如果把这个缓冲区称之为消息缓冲区,这样在任务间传递数据(消息)的最简单办法就是传递消息缓冲区的指针。我们把用来传递消息缓冲区指针的数据结构叫做邮箱(消息邮箱)。消息邮箱的工作情况如图64.1.3所示:
在这里插入图片描述

图64.1.3 消息邮箱工作情况图
从上图可知道,只有任务才能请求消息,邮箱里仅能存放一条消息,如果释放消息的速度比请求消息的速度快,则释放的消息将会丢失。可以通过广播的方式,使得释放的消息传递给所有请求该消息邮箱的任务。如果当前邮箱为空,且有某一任务2正在请求邮箱,则当另一任务1向邮箱中释放消息时,释放的消息将直接发送给任务2,而不用经过邮箱中转。
在UCOSII中,我们通过事件控制块的OSEventPrt来传递消息缓冲区指针,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。
与消息邮箱相关的主要操作有:创建邮箱函数OSMboxCreate、向邮箱发送消息函数OSMboxPost、请求邮箱函数OSMboxPend、查询邮箱状态函数OSMboxQuery和删除邮箱函数OSMboxDel。后面再对这几个进行函数进行讲解。
64.2 硬件设计

  1. 例程功能
    在UCOSII里面创建6个任务(不包含统计任务和空闲任务):开始任务、LED0任务、LED1任务,触摸屏任务、按键扫描任务和主任务。开始任务用于创建信号量、创建邮箱、初始化统计任务以及其他任务的创建,之后挂起;LED0任务用于DS0控制,提示程序运行状况;LED1任务用于测试信号量,通过请求信号量函数,每得到一个信号量,DS1就亮一下;触摸屏任务用于在屏幕上画图,可以用于测试 CPU使用率;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务则通过查询消息邮箱获得键值,并根据键值执行信号量发送(DS1控制)、触摸区域清屏和触摸屏校准等控制。
  2. 硬件资源
    1)LED灯
    LED0 – PB5
    LED1 – PE5
    2)独立按键
    KEY0 – PE4
    KEY1 – PE3
    WK_UP – PA0
  1. 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
  1. 原理图
    本章用到的硬件资源只有LED灯和按键。电路在开发板上已经连接好了,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图64.2.1所示:
    在这里插入图片描述

图64.2.1 LED与开发板连接原理图
64.3 程序设计
64.3.1 信号量函数
在这里对本实验用到的UCOSII信号量函数进行介绍,相关代码存放在os_sem.c中。

  1. OSSemCreate函数
    创建信号量函数,其声明如下:
    OS_EVENT *OSSemCreate (INT16U cnt)
    函数描述:
    用于创建一个信号量
    函数形参:
    cnt是信号量计数器(OSEventCnt)的初始值
    函数返回值:
    已创建的信号量的指针
  2. OSSemPend函数
    请求信号量函数,其声明如下:
    void OSSemPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
    函数描述:
    请求信号量
    函数形参:
    perr:错误信息
    OS_ERR_NONE:调用成功,信号量不为零
    OS_ERR_TIMEOUT :信号量没有在指定数目的时钟周期内被设置
    OS_ERR_PEND_ABOUT:取消对信号量的等待
    OS_ERR_EVENT_TYPE:没有传递信号量的指针
    OS_ERR_PEND_ISR:从中断调用该函数时错误
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    OS_ERR_PEND_LOCKED:调度器上锁了
    函数返回值:

    注意事项:
    为了防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
  3. OSSemPost函数
    发送信号量函数,其声明如下:
    INT8U OSSemPost (OS_EVENT *pevent)
    函数描述:
    用于发送信号量或者称为释放信号量
    函数形参:
    pevent:被请求信号量的指针
    函数返回值:
    OS_ERR_NONE:函数调用成功,信号量被成功地设置
    OS_ERR_SEM_OVF:信号量的值溢出
    OS_ERR_EVENT_TYPE:pevent不是指向信号量的指针
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    4.OSSemDel函数
    删除信号量函数,其声明如下:
    OS_EVENT *OSSemDel (OS_EVENT *pevent, INT8U opt, INT8U *perr)
    函数描述:
    用于删除信号量并准备挂起所有任务
    函数形参:
    pevent:要删除的信号量指针
    opt:删除条件选项
    OS_DEL_NO_PEND:在没有任务挂起时删除信号量
    OS_DEL_ALWAYS:删除信号量,即使任务正在等待。
    perr:错误信息
    OS_ERR_NONE:函数调用成功,成功删除信号量
    OS_ERR_DEL_ISR:尝试在中断中删除信号量
    OS_ERR_INVALID_OPT:指向一个无效的选项
    OS_ERR_TASK_WAITING:一个或多个任务在等待这个信号量
    OS_ERR_EVENT_TYPE:没有传递一个指向信号量的指针
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    函数返回值:
    pevent :存在错误 (OS_EVENT *)0 : 该信号量被成功删除。
    消息邮箱函数
    在这里对本实验用到的UCOSII消息邮箱函数进行介绍,相关代码存放在os_mbox.c中
  4. OSMboxCreate函数
    创建邮箱函数,其声明如下:
    OS_EVENT *OSMboxCreate (void *pmsg)
    函数描述:
    用于创建邮箱函数
    函数形参:
    pmsg:消息的指针
    函数返回值:
    消息邮箱的指针
    注意事项:
    调用OSMboxCreate前,需先定义msg的初始值。在一般情况下,这个初始值为NULL。但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate中,使得一开始就指向一个邮箱。
  5. OSMboxPost函数
    向邮箱发送消息函数,其声明如下:
    INT8U OSMboxPost (OS_EVENT *pevent, void *pmsg)
    函数描述:
    用于向消息邮箱发送消息
    函数形参:
    pevent:消息邮箱的指针
    pmsg :消息指针
    函数返回值:
    OS_NO_ERR:消息发送成功
    OS_MBOX_FULL:不能向满邮箱再发送消息
    OS_ERR_EVENT_TYPE:指定的事件不是消息邮箱类型
    OS_ERR_PEVENT_NULL:不能向不存在的消息邮箱发送消息
    OS_ERR_POST_NULL_PTR:消息缓冲区不能为空
  6. OSMboxPend函数
    请求邮箱函数,其声明如下:
    void *OSMboxPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
    函数描述:
    请求消息邮箱,就是等待一个消息传送到消息邮箱或取得一个消息数据。
    函数形参:
    pevent:消息邮箱的指针
    timeout:等待时限
    perr:错误信息
    函数返回值:
    NULL:未得到消息或者pevent指针 !NULL:预期消息的指针
  7. OSMboxQuery函数
    查询邮箱状态函数,其声明如下:
    UINT8U *OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *p_mbox_data)
    函数描述:
    获取消息邮箱的相关信息
    函数形参:
    pevent:消息邮箱的指针
    p_mbox_data:存放邮箱信息的结构
    函数返回值:
    OS_ ERR_NONE:调用成功
    OS_ERR_EVENT_TYPE:pevent不是指向消息邮箱的指针
    OS_ERR_PEVENT_NULL:不能向不存在的消息邮箱发送消息
    OS_ERR_PDATA_NULL:p_mbox_data是一个空指针
    注意事项:
    必须先建立消息邮箱,然后使用
    5.OSMboxDel函数
    删除邮箱函数,其声明如下:
    OS_EVENT *OSMboxDel (OS_EVENT *pevent, INT8U opt, INT8U *perr)
    函数描述:
    对一个不再使用的消息邮箱及时删除以释放资源
    函数形参:
    pevent:要删除的邮箱指针
    opt:删除条件选项
    OS_DEL_NO_PEND:在没有任务挂起时删除邮箱
    OS_DEL_ALWAYS:无条件删除邮箱,所有等待该事件的任务转到就绪态。
    perr:错误信息
    OS_ERR_NONE:函数调用成功,成功删除邮箱
    OS_ERR_DEL_ISR:不支持在中断中删除邮箱
    OS_ERR_INVALID_OPT:指向一个无效的选项
    OS_ERR_TASK_WAITING:一个或多个任务在等待这个信号量
    OS_ERR_EVENT_TYPE:没有传递一个指向邮箱的指针
    OS_ERR_PEVENT_NULL:pevent是一个空指针
    函数返回值:
    pevent :存在错误 (OS_EVENT *)0 : 该邮箱被成功删除。
    64.3.2 程序流程图
    在这里插入图片描述

图64.3.2.1 UCOSII信号量和邮箱实验
64.3.3 程序解析

  1. 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 "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();							/* 初始化蜂鸣器 */
    tp_dev.init();						/* 触摸屏初始化 */
    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 BEEP_TASK_PRIO                  5		/* 优先级设置(越小优先级越高) */
#define BEEP_STK_SIZE                   128	/* 堆栈大小 */

OS_STK BEEP_TASK_STK[BEEP_STK_SIZE];		/* 任务堆栈 */
void beep_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 KEY_TASK_PRIO                   3		/* 优先级设置(越小优先级越高) */
#define KEY_STK_SIZE                    128	/* 堆栈大小 */

OS_STK KEY_TASK_STK[KEY_STK_SIZE];			/* 任务堆栈 */
void key_task(void *pdata);					/* 任务函数 */

/******************************************************************************************/
OS_EVENT *msg_key;							/* 按键邮箱事件块指针 */
OS_EVENT *sem_beep;							/* 蜂鸣器信号量指针 */

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-III例程入口函数
 * @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)
{
    OS_CPU_SR cpu_sr = 0;
    CPU_INT32U cnts;
    
    msg_key = OSMboxCreate((void *)0);	/* 创建消息邮箱 */
    sem_beep = OSSemCreate(0);			/* 创建信号量 */
    
    OSStatInit();						/* 开启统计任务 */
    /* 根据配置的节拍频率配置SysTick */
    cnts = (CPU_INT32U)(HAL_RCC_GetSysClockFreq() / OS_TICKS_PER_SEC);
    OS_CPU_SysTickInit(cnts);
    
    OS_ENTER_CRITICAL();				/* 进入临界区(关闭中断) */
    
    /* 触摸任务 */
    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);
    /* 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 *) )beep_task,
                    (void *		)0,
                    (OS_STK *		)&BEEP_TASK_STK[BEEP_STK_SIZE - 1],
                    (INT8U			)BEEP_TASK_PRIO,
                    (INT16U		)BEEP_TASK_PRIO,
                    (OS_STK *		)&BEEP_TASK_STK[0],
                    (INT32U		)BEEP_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 *) )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 beep_task(void *pdata)
{
    uint8_t err;
    
    while (1)
    {
        OSSemPend(sem_beep, 0, &err);	/* 请求信号量 */
        BEEP(1);							/* 打开蜂鸣器 */
        OSTimeDly(60);
        BEEP(0);							/* 关闭蜂鸣器 */
        OSTimeDly(940);
    }
}


/**
 * @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] < lcddev.width &&
				tp_dev.y[0] < lcddev.height && tp_dev.y[0] > 120)
            {
                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 main_task(void *pdata)
{
    uint32_t key = 0;
    uint8_t err;
    uint8_t semmask = 0;
    uint8_t tcnt = 0;
    
    while (1)
    {
        key = (uint32_t)OSMboxPend(msg_key, 10, &err);
        
        switch (key)
        {
            case KEY0_PRES:
            {
                
/* 控制DS1,并清除触摸区域 */
                LED1_TOGGLE();
                lcd_fill(0, 121, lcddev.width - 1, lcddev.height - 1, WHITE);
                break;
            }
            case KEY1_PRES:
            {
                /* 发送信号量 */
                semmask = 1;
                OSSemPost(sem_beep);
                break;
            }
            case WKUP_PRES:
            {
                /* 校准 */
                OSTaskSuspend(TOUCH_TASK_PRIO);   /* 挂起触摸屏任务 */
                
                if ((tp_dev.touchtype & 0X80) == 0)
                {
                    tp_adjust();
                }
                OSTaskResume(TOUCH_TASK_PRIO);	/* 解挂 */
                ucos_load_main_ui();				/* 重新加载主界面 */
                break;
            }
        }
            
            if (semmask || sem_beep->OSEventCnt)	/* 需要显示sem */
            {
			  /* 显示信号量的值 */
                lcd_show_xnum(192, 50, sem_beep->OSEventCnt, 3, 16, 0X80, BLUE);
                
                if (sem_beep->OSEventCnt == 0)
                {
                    semmask = 0;					/* 停止更新 */
                }
            }
            
            if (tcnt == 10)						/* 0.6秒更新一次CPU使用率 */
            {
                tcnt = 0;
		       /* 显示CPU使用率 */
                lcd_show_xnum(192, 30, OSCPUUsage, 3, 16, 0, BLUE);
            }
            
        tcnt++;
        OSTimeDly(10);
    }
}

/**
 * @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       加载主界面
 * @param       无
 * @retval      无
 */
void ucos_load_main_ui(void)
{
    lcd_clear(WHITE);   /* 清屏 */
    lcd_show_string(30, 10, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 30, 200, 16, 16, "UCOSII TEST2", RED);
    lcd_show_string(30, 50, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 75, 200, 16, 16, "KEY0:DS1 AND CLEAR", RED);
    lcd_show_string(30, 95, 200, 16, 16, "KEY1:BEEP  KEY_UP:ADJUST", RED);
    lcd_show_string(80, 210, 200, 16, 16, "Touch Area", RED);
    lcd_draw_line(0, 120, lcddev.width - 1, 120, RED);
    lcd_draw_line(0, 70, lcddev.width - 1, 70, RED);
    lcd_draw_line(150, 0, 150, 70, RED);
    
    lcd_show_string(160, 30, 200, 16, 16, "CPU:   %", BLUE);
    lcd_show_string(160, 50, 200, 16, 16, "SEM:000", BLUE);
}

/**
 * @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、led0_task、touch_task、led1_task、main_task和key_task等6个任务的参数进行配置,例如优先级、堆栈大小和任务函数的声明及定义。这6个任务做的事情在前面程序流程图里面已经有说明,这里就不多说了。
这一章的的运行流程比上一章复杂了一些,我们创建了消息邮箱msg_key,用于按键任务和主任务之间的数据传输(传递键值)。另外创建了信号量sem_led1,用于LED1任务和主任务之间的通信。
在代码中,我们使用了UCOSII提供的CPU统计服务,通过OSStatInit初始化CPU统计任务,然后在主任务中显示CPU使用率。
另外,在主任务中,我们用到了任务的挂起和恢复函数,在执行触摸校准的时候,我们必须先将触摸屏任务挂起,待校准完成之后,再恢复触摸屏任务。这是因为触摸屏校准和触摸屏任务都用到了触摸屏和TFTLCD,而这两个东西是不支持多任务占用的,所有必须采用独占的方式使用,否则可能导致数据错乱。
64.4 下载验证
将程序下载到开发板后,可以看到LCD显示界面如图64.4.1所示:
在这里插入图片描述

图64.4.1 初始化界面
从上图可以看到,默认情况下,CPU使用率仅为1%。此时通过在触摸区域(Touch Area)画图,可以看到CPU使用率飙升,这说明触摸屏任务是一个很占CPU的任务;通过按KEY0,可以控制LED1的亮灭;同时,可以清除触摸区域的笔迹;通过按下KEY1可以控制蜂鸣器响一次,如果多次按下,可以看到剩余信号量请求次数;通过按下WK_UP可以进入校准程序,对触摸屏进行校准(注意,电容触摸屏不需要校准,所以如果是电容屏,按KEY_UP,就相当于清屏一次的效果,不会进行校准)。

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

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

相关文章

【C++】多线程交替打印奇偶数

目录 版本1 双信号量版 版本二 单信号量版 版本三 信号量版 共享资源是100个数字&#xff08;一个计数器的 由两个进程争抢完成&#xff09; 首先访问临界资源&#xff08;对计数器操作&#xff09;是肯定的要加锁的&#xff0c;交替打印肯定要用条件变量来互相唤醒 互相锁死…

计及需求侧响应日前—日内两阶段鲁棒备用优化(matlab代码)

目录 1 主要内容 日前计划模型 日内调整模型 不确定集建模 2 部分代码 3 程序结果 1 主要内容 该程序复现文章《计及需求侧响应日前—日内两阶段鲁棒备用优化》&#xff0c;以6节点系统为例&#xff0c;综合考虑风电出力不确定性与电力设备 N-k强迫停运&#xff0c;增强电…

创建型模式 - 原型模式

概述 用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象。 结构 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了具体原型对象必须实现的的 clone() 方法。 具体原型类&#xff1a;实现抽象原型类的 clone() 方…

如何用3D格式转换工具HOOPS Exchange读取颜色和材料信息?

作为应用程序开发人员&#xff0c;非常希望导入部件的图形表示与它们在创作软件中的外观尽可能接近。外观可以在每个B-Rep面的基础上指定&#xff0c;而且&#xff0c;通过装配层次结构的特定路径可以在视觉外观上赋予父/子覆盖。HOOPS ExchangeHOOPS Exchange可捕获有关来自各…

创建型模式 - 建造者模式

概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于&#xff1a;某个对象的构建过程复杂的情况。 由于实现了构建和装配的解耦。…

虾皮、Lazada爆款打造计划,自养号测评补单技术成重要的运营手段【无标题】

在如今的电商平台中&#xff0c;虾皮和lazada是东南亚地区主要的电商平台&#xff0c;平台广告是它的主要利润来源之一。然而&#xff0c;随着大量卖家涌入东南亚市场&#xff0c;商家之间的竞争也日益激烈&#xff0c;高额的广告投入并没有带来预期的效果。为了提高产品在平台…

【26】SCI易中期刊推荐——计算机人工智能(中科院4区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…

【视频+文字讲解】C++那些事之彻底搞懂STL HashTable

C那些事之彻底搞懂STL HashTable C那些事之彻底搞懂STL HashTable1.常用容器 1.1 接口层2.HashTable原理 2.1 _Hash_node结构2.2 hashtable实现2.3 如何确定桶&#xff1f;2.4 如何确定在桶中的位置&#xff1f;3.Rehash 3.1 计算新桶大小3.2 判断是否需要rehash3.3 rehash 最近…

Github实时数据分析与可视化训练营火热开启!免费领取5000元云上资源

此次训练营内容基于GitHub Archive公开数据集&#xff0c;通过DataWorks将GitHub中的项目、行为等20多种事件类型数据实时采集至Hologres进行分析&#xff0c;同时使用DataV内置模板&#xff0c;快速搭建实时可视化数据大屏&#xff0c;从开发者、项目、编程语言等多个维度了解…

vscode 使用ssh进行远程开发 (remote-ssh)

介绍 visual studio code remote - ssh 可以通过ssh连接远程主机、虚拟机&#xff0c;打开远程文件夹&#xff0c;并利用vscode 的插件优势进行远程开发、调试等。 步骤 一、配置环境 因为remote-ssh 的ssh连接是基于openssh实现的&#xff0c;以及后续我们需要使用生成ss…

手把手教你搭建SpringCloud项目(七)集成Consul服务注册中心

一、了解Consul 这篇文章学习另外一个服务注册中心Consul&#xff0c;那什么是Consul&#xff1f; Consul是一个服务网格&#xff08;微服务间的 TCP/IP&#xff0c;负责服务之间的网络调用、限流、熔断和监控&#xff09;解决方案&#xff0c;它是一个一个分布式的&#xff…

[PCIE体系结构导读]PCIE总结(二)

PMCR&#xff08;Power Management Capabilities Register&#xff09;和PMCSR&#xff08;Power Management Control and Status Register) PMCR寄存器由16位组成&#xff0c;其中所有位和字段都是只读的。该寄存器的主要木得是记录当前PCIe设备的物理属性&#xff0c;系统软件…

vue2的 element 表格单元格合并

<template><div><el-table show-summary :summary-method"getSummaries" :span-method"objectSpanMethod" :data"tableData" row-key"id" ref"tableDom" border><el-table-column label"序号&quo…

10-2. 数组 Array 的实例方法

本文并没有写出数组的全部使用方法&#xff0c;想看全的可以看一下这个 Array - JavaScript | MDN 目录 1 常用内置方法 1.1 合并数组 concat() 1.2 复制元素覆盖到指定位置 copyWithin() 1.3 返回其他变量的数组形式 Array.from() 1.3.1 基本使用 1.3.2 将伪数组…

Hive Metastore、Hive server和Hive thrift服务

Hive Metastore Hive Metastore是Hive的核心元数据管理服务,它提供了元数据的持久化存储和访问控制的能力,使得 Hive 成为一个强大的数据仓库和分析平台,适用于处理大数据和进行复杂的数据查询与分析任务。 Apache Hive是一个建立在 Apache Hadoop 上的数据仓库和分析工具…

java多线程之FutureTask、Future、CompletableFuture

前面已经在多线程创建的时候有提到Future和FutureTask的简单用法&#xff0c;这里详细介绍下FutureTask以及CompletableFuture 一、FutureTask 1、FutureTask简介 FutureTask除了实现Future接口外&#xff0c;还实现了Runnable接口。因此&#xff0c;FutureTask可以交给 Exe…

回溯法实现N皇后问题

题1&#xff1a;&#xff08;需打印矩阵&#xff09; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#…

命名空间,缺省参数与函数重载

目录 一&#xff0c;命名空间 1.何为命名空间 2.命名空间的使用 ​编辑 4.::作用域限定符 3.命名空间的展开 全局展开&#xff1a; 局部展开&#xff1a; 4.嵌套命名空间 二&#xff0c;缺省参数与函数重载 1.什么是缺省参数 2.什么是函数重载 3.两者的冲突 一&…

内部数据泄露:保护数据安全的挑战与解决方案

导语&#xff1a; 在当今数字化时代&#xff0c;数据是企业的核心资产之一。然而&#xff0c;随着科技的快速发展和信息的日益增长&#xff0c;数据安全问题也日益突出。其中&#xff0c;内部数据泄露成为企业所面临的重大挑战之一。本文将探讨内部数据泄露的危害&#xff0c;…

简直太高效了!一篇文章帮你快速了解企业如何实现无纸化办公

随着科技的发展和信息技术的普及&#xff0c;无纸化办公已经成为了现代企业的一个趋势。无纸化办公即指在企业日常工作中&#xff0c;尽量减少或不使用纸张作为工作载体&#xff0c;通过电子邮件、电子文档、电子表格等工具实现信息的传递和共享。无纸化办公不仅有利于环保&…