1)实验平台:正点原子stm32f103战舰开发板V4
2)平台购买地址:https://detail.tmall.com/item.htm?id=609294757420
3)全套实验源码+手册+视频下载地址: http://www.openedv.com/thread-340252-1-1.html#
上一章,我们学习了如何使用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 硬件设计
图65.2.1 LED与STM32F103连接原理图
65.3 程序设计
65.3.1 UCOSII程序流程图
消息队列函数
在这里对本实验用到的UCOSII消息队列函数进行介绍,相关代码存放在os_q.c中。
#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区域显示按键信息。