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 硬件设计
- 例程功能
在UCOSII里面创建6个任务(不包含统计任务和空闲任务):开始任务、LED0任务、LED1任务,触摸屏任务、按键扫描任务和主任务。开始任务用于创建信号量、创建邮箱、初始化统计任务以及其他任务的创建,之后挂起;LED0任务用于DS0控制,提示程序运行状况;LED1任务用于测试信号量,通过请求信号量函数,每得到一个信号量,DS1就亮一下;触摸屏任务用于在屏幕上画图,可以用于测试 CPU使用率;按键扫描任务用于按键扫描,优先级最高,将得到的键值通过消息邮箱发送出去;主任务则通过查询消息邮箱获得键值,并根据键值执行信号量发送(DS1控制)、触摸区域清屏和触摸屏校准等控制。 - 硬件资源
1)LED灯
LED0 – PB5
LED1 – PE5
2)独立按键
KEY0 – PE4
KEY1 – PE3
WK_UP – PA0
- 正点原子 2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
- 原理图
本章用到的硬件资源只有LED灯和按键。电路在开发板上已经连接好了,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图64.2.1所示:
图64.2.1 LED与开发板连接原理图
64.3 程序设计
64.3.1 信号量函数
在这里对本实验用到的UCOSII信号量函数进行介绍,相关代码存放在os_sem.c中。
- OSSemCreate函数
创建信号量函数,其声明如下:
OS_EVENT *OSSemCreate (INT16U cnt)
函数描述:
用于创建一个信号量
函数形参:
cnt是信号量计数器(OSEventCnt)的初始值
函数返回值:
已创建的信号量的指针 - 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,则表明任务的等待时间为无限长。 - 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中 - OSMboxCreate函数
创建邮箱函数,其声明如下:
OS_EVENT *OSMboxCreate (void *pmsg)
函数描述:
用于创建邮箱函数
函数形参:
pmsg:消息的指针
函数返回值:
消息邮箱的指针
注意事项:
调用OSMboxCreate前,需先定义msg的初始值。在一般情况下,这个初始值为NULL。但也可以事先定义一个邮箱,然后把这个邮箱的指针作为参数传递到函数OSMboxCreate中,使得一开始就指向一个邮箱。 - 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:消息缓冲区不能为空 - OSMboxPend函数
请求邮箱函数,其声明如下:
void *OSMboxPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr)
函数描述:
请求消息邮箱,就是等待一个消息传送到消息邮箱或取得一个消息数据。
函数形参:
pevent:消息邮箱的指针
timeout:等待时限
perr:错误信息
函数返回值:
NULL:未得到消息或者pevent指针 !NULL:预期消息的指针 - 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 程序解析
- 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,就相当于清屏一次的效果,不会进行校准)。