使用C实现 类QT/Emwin 框架
- 作者:MrWang
- 日期: 2020/6/1
- 目的:帮助理解QT框架 或 类Emwin框架,可用于单片机断码屏LCD等简易页面开发(已验证)
1.设计 通信消息机制
文件名称: message.h
#ifndef _MESSAGE_H_
#define _MESSAGE_H_
/* Includes ----------------------------------------------------------*/
/* Define ------------------------------------------------------------*/
/* 定义通信事件 --------------------------------------------------------*/
/* 系统通信事件 */
#define WM_NULL (0x00U) /* 投递事件结束 */
#define WM_INIT (0x01U) /* 事件初始化 */
#define WM_PAINT (0x02U) /* 事件重绘 */
#define WM_CLEAR (0x03U) /* 事件清屏 */
#define WM_DESTROY (0x04U) /* 事件销毁 */
#define WM_UPDATE (0x05U) /* 事件更新 */
#define WM_TIMER (0x06U) /* 事件定时器 */
/* 此处添加系统通信事件 ... ... */
/* 系统按键事件偏移 */
#define WM_KEY_OFFSET (0x20U)
/* 在用户按键中自定义键值在此基础上偏移 */
/* 用户自定义消息偏移 */
#define WM_USER_OFFSET (0x80U)
/* 用户自定义消息在此基础上偏移 ... ... */
/* Typedef ---------------------------------------------------------*/
typedef unsigned char Msg; /* 自定义事件类型 */
typedef void (*MsgPrce)(void); /* 自定义目标对象执行函数类型 */
/* 自定义类 */
typedef struct{
Msg m_msg; /* 派发的事件 */
MsgPrce m_msgPrce; /* 事件目标函数 */
MsgPrce m_theObject; /* 当前目标对象 */
}CMsgDC;
/* Enume ----------------------------------------------------------*/
/* Extern ---------------------------------------------------------*/
extern CMsgDC msgDC;
/* 宏定义设置当前目标函数 */
#define CMsgPr_SetMsgObject(X) (msgDC.m_theObject = (MsgPrce)(X))
/* Declaration ----------------------------------------------------*/
Msg CMsgPr_GetMessage(void); /* 获取当前事件后并清空 */
void CMsgPr_PollMessage(Msg aMsg); /* 向当前对象派发事件 */
void CMsgPr_SendMessage(MsgPrce pmsgPrce, Msg aMsg); /* 向控件派发事件 */
void CMsgPr_ParentMessage(MsgPrce pmsgPrce, Msg aMsg); /* 向基类投递事件 */
void CMsgPr_EndMessage(void); /* 结束事件投递 */
#endif // _MESSAGE_H_
- 文件名称: message.c
- 作用: 伪框架的通信机制
/* Includes --------------------------------------------------------*/
#include "message.h"
/* Declaration -----------------------------------------------------*/
/* Global variable -----------------------------------=-------------*/
CMsgDC msgDC; /* 保存当前执行信息 */
/* 获取当前事件后并清空
** aMsg:事件
**/
Msg CMsgPr_GetMessage(void){
Msg amsg = msgDC.m_msg; /* 缓存事件 */
msgDC.m_msg = WM_NULL; /* 获取当前事件后清空事件 */
return amsg; /* 返回当前事件 */
}
/* 向当前对象(自己)派发事件
** aMsg:事件
**/
void CMsgPr_PollMessage(Msg aMsg){
msgDC.m_msg = aMsg; /* 保存当前的事件 */
msgDC.m_msgPrce = msgDC.m_theObject; /* 将当前对象放入执行目标中 */
}
/* 向控件派发事件(核心部分)
** pmsgPrce -> 事件目标 aMsg -> 事件
**/
void CMsgPr_SendMessage(MsgPrce pmsgPrce, Msg aMsg){
msgDC.m_msg = aMsg; /* 消息事件保存 */
msgDC.m_msgPrce = pmsgPrce;
msgDC.m_theObject = pmsgPrce;
for (;;){ /* 调用事件目标函数 */
/* 事件目标函数/事件 无效则直接退出 */
if ((WM_NULL == msgDC.m_msgPrce) || (WM_NULL == msgDC.m_msg)){
return;
}
msgDC.m_msgPrce(); /* 执行事件目标函数 */
}
}
/* 向基类投递事件
** pmsgPrce -> 事件目标 aMsg -> 事件
**/
void CMsgPr_ParentMessage(MsgPrce pmsgPrce, Msg aMsg){
msgDC.m_msg = aMsg;
msgDC.m_msgPrce = pmsgPrce;
}
/* 结束事件投递 */
void CMsgPr_EndMessage(void){
msgDC.m_msg = WM_NULL;
msgDC.m_msgPrce = WM_NULL;
}
2.设计 页面框架
2.1 页面框架.h文件
- 数据类型说明:
- typedef unsigned char uint8_t;
- typedef unsigned short int uint16_t;
- typedef unsigned int uint32_t;
#ifndef _MENU_H_
#define _MENU_H_
/* Includes ----------------------------------------------------------*/
/* 系统库 头文件 ... ... */
#include "bsp_key.h" /* 底层按键驱动 */
#include "bsp_lcd.h" /* 底层LCD驱动 */
/* 其它库 头文件 ... ... */
/* Define ------------------------------------------------------------*/
/* 用于页面文件的调试打印 0:close 1:open ---------------------------------*/
#if 0
#define Mprint printf
#else
#define Mprint(...)
#endif
/* 系统参数设置 单位:根据定时器巡检时基(此处为10ms) -------------------------*/
/* 定时时间到后会执行相应的操作 */
#define MENU_EVENT_DURATION (25U) /* 事件定时: 250ms */
#define MENU_BACKLIGH_DURATION (2500U) /* 背光定时: 25S */
#define MENU_EVENT_OFF (0x00U) /* 事件开*/
#define MENU_EVENT_OPEN (0x01U) /* 事件关 */
/* 系列菜单ID定义 */
#define MENU_FUNTION_1 (0x00U)
/* 此处添加菜单ID 每一个page都有一个ID,以作区分 ... ... */
/* Typedef ----------------------------------------------------------*/
typedef void (*EXEFUN)(void); /* 自定义回调函数类型 */
typedef void (*BACKLIGHT_FUN)(void); /* 自定义背光操作(开/关)函数类型 */
/* 菜单结构体 */
typedef struct{
EXEFUN g_pfun; /* 函数指针 */
MsgPrce g_CurrentFocus; /* 当前焦点 */
uint8_t g_menuIndex; /* 菜单索引 */
struct {
uint32_t g_LightTimer; /* 背光定时器 */
uint32_t g_EventTimer; /* 事件定时器 */
}MenuTimer; /* 定时器变量 */
struct {
uint8_t b_LightTM :1; /* 定时背光标志 */
uint8_t b_EventTM :1; /* 定时事件标志 */
}MenuTMFlag; /* 定时器标志位 */
BACKLIGHT_FUN g_pOnBackLight; /* 开启背光灯函数指针 */
BACKLIGHT_FUN g_pOffBackLight; /* 关闭背光灯函数指针 */
}g_GlobalMenu;
/* Enume ----------------------------------------------------------*/
/* Extern ---------------------------------------------------------*/
extern g_GlobalMenu g_MenuVar; /* 外部声明菜单结构变量 */
/* Declaration ----------------------------------------------------*/
/* 初始化菜单 执行之前调用 */
void R_MenuListInit(BACKLIGHT_FUN onBackLight, BACKLIGHT_FUN OffBackLight);
/* 用于定时器扫描计时,在定时器中断里调用 */
void R_MenuTimerScan(void);
/* Menu获取按键事件,在主函数里 循环调用 */
void R_AppMenu_KeyEvent(uint8_t (*GetKeyEvent)(void), uint8_t KeyNoPress);
/* Menu定时器事件巡检,在主函数里循环调用 */
void R_MenuTimerEvent(void);
#endif /* _MENU_H_ */
2.2 页面框架.c文件
2.2.1 定义全局菜单变量
作用: 记录操作
g_GlobalMenu g_MenuVar; /* 菜单结构变量 */
2.2.2 设置当前执行目标函数
- 名称: R_MenuSetFocus
- 作用: 保存当前执行的目标函数,并设置事件对象(只在该文件下有效,对外不提供接口)
- 参数: aFocus -> 目标执行函数
static void R_MenuSetFocus(MsgPrce aFocus){
g_MenuVar.g_CurrentFocus = aFocus; /* 缓存当前输入焦点 */
CMsgPr_SetMsgObject(aFocus); /* 设置消息栈的消息对象 */
}
2.2.3 初始化菜单 (外部接口)
- 名称: R_MenuListInit
- 作用: 初始化菜单,在底层驱动初始化后调用,且必须在 while(1){} 执行之前调用
- 参数: OnBackLight : 开启lcd背光函数 OffBackLight: 关闭lcd背光函数
- 说明: 该函数指示初始化菜单变量,并跳转到第一级执行函数,该函数名称为 void R_MenuObjectProc(void);
void R_MenuListInit(BACKLIGHT_FUN onBackLight, BACKLIGHT_FUN OffBackLight){
/* 关闭背光 及 事件 标志 */
g_MenuVar.MenuTMFlag.b_EventTM = MENU_EVENT_OFF;
g_MenuVar.MenuTMFlag.b_LightTM = MENU_EVENT_OFF;
/* 清空背光计时 及 事件计时 */
g_MenuVar.MenuTimer.g_EventTimer = 0;
g_MenuVar.MenuTimer.g_LightTimer = 0;
/* 指定背光开启/关闭函数 */
g_MenuVar.g_pOnBackLight = onBackLight;
g_MenuVar.g_pOffBackLight = OffBackLight;
/* 设置投递事件:操作完成后自动跳转到 设置目标函数 中执行初始化操作 */
R_MenuSetFocus(R_MenuObjectProc);
/* 向控件派发 初始化事件 */
CMsgPr_SendMessage(g_MenuVar.g_CurrentFocus, WM_INIT);
/* 投递结束事件 就转到执行目标操作函数 进行初始化 */
CMsgPr_EndMessage();
}
2.2.4 定时器扫描事件函数 (外部接口)
- 名称: R_MenuTimerScan
- 作用: 用于定时器扫描计时,在定时器中断里调用
- 说明: 当前定时器中断时间:10ms
void R_MenuTimerScan(void){
/* 事件定时器开启后计时 */
if(g_MenuVar.MenuTMFlag.b_EventTM)
g_MenuVar.MenuTimer.g_EventTimer++;
/* 背光定时器开启后计时 */
if(g_MenuVar.MenuTMFlag.b_LightTM)
g_MenuVar.MenuTimer.g_LightTimer++;
}
2.2.5 获取按键事件函数 (外部接口)
- 名称: R_AppMenu_KeyEvent
- 作用: Menu获取按键事件,在主函数里 循环调用
- 参数: GetKeyEvent -> 获取按键的键值函数指针 KeyNoPress -> 无按键按下的键值
void R_AppMenu_KeyEvent(uint8_t (*GetKeyEvent)(void), uint8_t KeyNoPress){
/* 获取按键键值 */
uint8_t g_key_event = GetKeyEvent();
/* 有按键按下 */
if(KeyNoPress != g_key_event){
/* 这样做的目的为 有按键事件发生时 重新计时背光 */
g_MenuVar.MenuTimer.g_LightTimer = 0;
/* 像当前目标焦点 派发按键事件 */
CMsgPr_SendMessage(g_MenuVar.g_CurrentFocus, g_key_event);
}
}
2.2.6 Menu定时器事件检测 (外部接口)
- 名称: R_MenuTimerEvent
- 作用: Menu定时器事件巡检,在主函数里循环调用
- 说明: 检测当前 定时器事件 是否到来,到了执行相应的操作
void R_MenuTimerEvent(void){
/* 事件定时器打开 定时发送定时消息 */
if(g_MenuVar.MenuTMFlag.b_EventTM){
/* 事件定时到 */
if(g_MenuVar.MenuTimer.g_EventTimer >= MENU_EVENT_DURATION){
/* 向控件派发 定时器事件 */
CMsgPr_SendMessage(g_MenuVar.g_CurrentFocus, WM_TIMER);
/* 投递结束事件 就转到执行目标操作函数 进行定时器事件操作 */
CMsgPr_EndMessage();
}
}
/* 背光定时器打开 开启背光 */
if(g_MenuVar.MenuTMFlag.b_LightTM){
/* 背光定时器 25s时间到 */
if(g_MenuVar.MenuTimer.g_LightTimer >= MENU_BACKLIGH_DURATION){
/* 关闭OLED背光 */
g_MenuVar.g_pOffBackLight();
/* 关闭背光标志位 防止溢出 */
g_MenuVar.MenuTMFlag.b_LightTM = MENU_EVENT_OFF;
/* 设置投递事件:操作完成后自动跳转到 设置目标函数 中执行初始化操作 */
/* 设置目标函数 */
R_MenuSetFocus(R_MenuObjectProc);
/* 向控件派发 初始化事件 */
CMsgPr_SendMessage(g_MenuVar.g_CurrentFocus, WM_INIT);
/* 投递结束事件 就转到执行目标操作函数 进行初始化 */
CMsgPr_EndMessage();
}
}
}
2.2.7 标准用户操作框架
- 名称: R_MenuObjectProc
- 作用: 用户自定义目标函数, 这里只是标准框架,用户需要多级页面的话,需要编辑多个
- 说明: 程序中的 KEY_1_UP 以及 KEY_1_DOWN 是底层提供的事件, 该事件必须在 WM_KEY_OFFSET(message.h中)基础上偏移
void R_MenuObjectProc(void)
{
switch(CMsgPr_GetMessage()){ /* 获取当前事件 */
case WM_INIT: /* 初始化事件 */
/* 初始化事件发生后 执行的操作 ... ... */
/* 开启事件标志 */
g_MenuVar.MenuTMFlag.b_EventTM = MENU_EVENT_OPEN;
/* 当前菜单索引 */
g_MenuVar.g_menuIndex = MENU_FUNTION_1;
/* 默认消息结束 */
CMsgPr_EndMessage();
break;
case KEY_1_UP: /* 按键1抬起时 的事件操作 */
/* 打开LCD背光灯 */
g_MenuVar.g_pOnBackLight();
/* 复位背光计时 重新开始计时 */
g_MenuVar.MenuTimer.g_LightTimer = 0;
/* 开启背光标志 开始在定时器中断里计时 */
g_MenuVar.MenuTMFlag.b_LightTM = MENU_EVENT_OPEN;
break;
case KEY_1_DOWN: /* 按键1按下时 的事件操作 */
/* 指向下一个执行函数 用户自定义 */
g_MenuVar.g_pfun = /* xxxxx */;
/* 设置输入焦点 */
R_MenuSetFocus(g_MenuVar.g_pfun);
/* 派发下一菜单的初始化事件 */
CMsgPr_PollMessage(WM_INIT);
break;
case WM_TIMER: /* 定时器事件 */
/* ... 定时器事件发生后 执行的操作 ... */
g_MenuVar.MenuTimer.g_EventTimer = 0; /* 复位定时器计时 */
break;
default:
CMsgPr_EndMessage(); /* 默认消息结束 */
break;
}
}
3. 主函数中调用框架
int main(){
/* 硬件设备初始化 ....。。。。。.......................*/
/* 菜单初始化 LcdDisplayOn-> 开启LCD背光操作函数
LcdDisplayOFF -> 关闭LCD背光操作函数 */
R_MenuListInit(LcdDisplayOn, LcdDisplayOFF);
for(;;){
R_MenuTimerEvent(); /* 定时器事件触发 */
/* 菜单控制按键事件处理: 参数:获取按键键值函数, 未按下键值 */
R_AppMenu_KeyEvent(bsp_GetKey, KEY_NONE);
/* 其它任务在此处添加 ...........................*/
}
}
4. 说明
- 设计初始目的: 断码屏幕 涉及 自动灭屏和光标闪烁等(光标闪烁已删减, 可用户添加), 本来页面就简便,为方便开发,所以设计此应用
- 原创性说明: 此处不是原创,原帖在阿莫论坛中;吸收学习后改造为以上程序
- 此贴类型: 学习笔记