咨询下QP状态机下LCD12864菜单绘制和按键扫描的用法
QP的概念基本也知道一些了,但是怎么投入使用,还没转过弯来,对状态图(机)认识还不够。下面2点是我的理解,不知道对不对。
1.QP应该是最适合处理菜单之类的操作了,比如这是LCD12864,4行中文菜单。
一个屏幕当作一个状态,在进入这个状态时候就绘制4行菜单字符串,并对第一行反白。
向上或者向下导航键被按下后,重绘4行菜单字符串,并对相应行反白。
确定键或者取消键就直接转换到另一个状态。
2.QP例子设置的TICK一般都是10MS,用QP的定时器事件虽然也能实现按键扫描去抖,但是这状态函数不断循环频繁执行,还不如传统的做法方便(放进硬件定时器中断扫描)。
//假如是12864,4行汉字的菜单
QState Menu_one(Menu * const me, QEvt const * const e) {
QState status;
switch (e->sig) {
case Q_ENTRY_SIG: {
清屏
显示4行菜单字符串
第一行反色
status = Q_HANDLED();
break;
}
case KEY_MOVE_UP_SIG: {
清屏
显示4行菜单字符串
上一行反色
status = Q_HANDLED();
break;
}
case KEY_MOVE_DOWN_SIG: {
清屏
显示4行菜单字符串
下一行反色
status = Q_HANDLED();
break;
}
case KEY_MOVE_ENTER_SIG: {
status = Q_TRAN(&Menu_son);
break;
}
case KEY_MOVE_EXIT_SIG: {
status = Q_TRAN(&Menu_parent);
break;
}
default: {
status = Q_SUPER(&QHsm_top);
break;
}
}
return status;
} 好好看这个帖子里我的发言,必定收获大大的。
http://www.amobbs.com/thread-5582451-1-1.html
几点提示:
1:设定一个刷新的状态,该状态的处理流程:
检测不同的刷新标志,来进行屏幕的绘制。具体到菜单项就是,有时候只需要反显,有时候需要全屏更新。所以采用专门的一个状态刷新,可以使得操作与显示隔离,同时
提高刷新效率(不需要每次全屏刷,根据需要在其它状态中,设立标志,然后跳转到此状态即可)
2: 设定一个输入状态,用于接受输入,如按键,或者串口等等,此状态处理输入,并设定相应的状态。
3: 抽象出共用的部分,此部分为parent 状态,可以简化很多地方的处理。 mcu_lover 发表于 2014-6-9 23:05
好好看这个帖子里我的发言,必定收获大大的。
http://www.amobbs.com/thread-5582451-1-1.html
你的帖子我都看过,而且很认真看过几次。
但是总感觉不好理解,你的代码太过抽象化了。
外人一时半会只能学到皮毛,但是内涵不好理解。 第一个,关于菜单的写法是可以的,
第二个,你这种用QP的定时器事件是轮询方式,用法没错。另一种方式是,不使用定时器事件,而是从这个中断ISR里向相关的ActiveObject的消息队列发送消息。 查资料路过这里,讨论下QP。
楼主TICK,10MS,觉得是拖垮QP的节奏。
这种方法应该更好,把按键处理放到QP外,由中断或定时器处理,产生事件后再传送到QP。 bbglx 发表于 2014-10-27 10:48
查资料路过这里,讨论下QP。
楼主TICK,10MS,觉得是拖垮QP的节奏。
这种方法应该更好,把按键处理放到QP外 ...
{:lol:} 改用FreeRTOS了,用起来太省心了,QP的那种思维还是适应不了。 68336016 发表于 2014-10-27 10:51
改用FreeRTOS了,用起来太省心了,QP的那种思维还是适应不了。
确实,少人用是有道理的,按照正常思维处理QP会进入死胡同。
头疼{:dizzy:} 傻孩子的多级菜单用起来很方便. 楼主觉得抽象,那是因为你写的东西还没达到那个水平,抽象的东西绝对是趋势。 建议楼主先看傻孩子的菜单,再看红金龙的帖子。主要是思维,要面向事件而不是过程,还有就是抽象。
/********************************数据类型定义*********************************/
//图标数据类型
typedef struct {
INT16U w;
INT16U h;
const INT8U *p;
} ICON;
//窗口数据结构
typedef struct {
INT8U x; // 窗口位置(左上角的x坐标)
INT8U y; // 窗口位置(左上角的y坐标)
INT8U w; // 窗口宽度
INT8U h; // 窗口高度
INT8U *title; // 定义标题栏指针
INT32Ustyle;
} WINDOW;
//条目元素类型定义,只用来继承,不用来定义数据
typedef struct tagITEM {
const INT8U *Name;
const ICON *Icon;
} ITEM_ELEMENT;
//条目控制类型定义,只用来继承,不用来定义数据
typedef struct {
INT16U Beginning;
INT16U Selected;
} ITEM_CTRL;
//条目对象类型定义,只用来继承,不用来定义数据
typedef struct {
INT16U Style;
INT16U ItemNum;
const ITEM_ELEMENT **ItemTbl;
} ITEM_OBJ;
//条目类型定义,只用来继承,不用来定义数据
typedef struct {
const ITEM_OBJ *item;
ITEM_CTRL ctrl;
} ITEM;
//菜单数据类型
typedef struct tagMENU_ITEM {
const INT8U * Name;
const ICON* Icon;
INT16U Style;
INT16U ItemNum;
const struct tagMENU_ITEM ** ItemTbl;
struct tagMENU_ITEM *Parent;
void * Fun;
INT32U Handle; //用户自定义类型,可以是指针等
}MENU_ITEM;
//菜单控制块
typedef struct {
INT16U Beginning;
INT16U Selected;
MENU_ITEM * Menu;
} MENU_CTRL;
//菜单控制块栈
typedef struct {
INT32U top;
MENU_CTRL tbl;
} MENU_STACK; //空递增
//菜单数据类型定义
typedef struct {
MENU_CTRL ctrl;
MENU_STACKstack;
INT8U act_flag;
} MENU;
//单选框数据类型
typedef struct {
ITEM item;
}RADIO;
我的菜单结构是基于链表和栈的。
/****************************************************************************
* Description:菜单栈进栈
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuStackPush(MENU *menu)
{
menu->stack.tbl.Menu = menu->ctrl.Menu;
menu->stack.tbl.Beginning = menu->ctrl.Beginning;
menu->stack.tbl.Selected = menu->ctrl.Selected;
menu->stack.top++;
menu->act_flag = 1;
}
/****************************************************************************
* Description:菜单栈出栈
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuStackPop(MENU *menu)
{
menu->stack.top--;
menu->ctrl.Menu = menu->stack.tbl.Menu;
menu->ctrl.Beginning = menu->stack.tbl.Beginning;
menu->ctrl.Selected = menu->stack.tbl.Selected;
menu->act_flag = 1;
}
/****************************************************************************
* Description:菜单向前滚动
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuRollForward(MENU *menu)
{
const MENU_ITEM *menu_item = menu->ctrl.Menu;
//如果menu的条目数小于等于一屏可显示的条目数
if (HMI_ITEM_DISPLAY_NUM >= menu_item->ItemNum)
{
//如果光标在第一行
if (menu->ctrl.Selected == 0)
{
menu->ctrl.Selected = menu_item->ItemNum - 1;
}
else
{
menu->ctrl.Selected--;
}
}
else
{
//如果光标在第一行
if (menu->ctrl.Selected == 0)
{
//如果菜单还没到头
if (menu->ctrl.Beginning > 0)
{
//菜单向下滚动一行,光标位置不变
menu->ctrl.Beginning--;
}
else
{
//光标到末行,菜单到末行
menu->ctrl.Selected = HMI_ITEM_DISPLAY_NUM - 1;
menu->ctrl.Beginning = menu_item->ItemNum - HMI_ITEM_DISPLAY_NUM;
}
}
else
{
menu->ctrl.Selected--;
}
}
}
/****************************************************************************
* Description:菜单向后滚动
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuRollBack(MENU *menu)
{
const MENU_ITEM *menu_item = menu->ctrl.Menu;
//如果menu的条目数小于等于一屏可显示的条目数
if (HMI_ITEM_DISPLAY_NUM >= menu_item->ItemNum)
{
//如果当前光标在最后一个条目
if (menu->ctrl.Selected == (menu_item->ItemNum - 1))
{
menu->ctrl.Selected = 0;
}
else
{
menu->ctrl.Selected++;
}
}
else
{
//如果光标在最后一行
if (menu->ctrl.Selected == (HMI_ITEM_DISPLAY_NUM - 1))
{
//如果菜单还没到头
if (menu->ctrl.Beginning < (menu_item->ItemNum - HMI_ITEM_DISPLAY_NUM))
{
//菜单向上滚动一行,光标位置不变
menu->ctrl.Beginning++;
}
else
{
//光标回到第一行,菜单回到第一行
menu->ctrl.Selected = 0;
menu->ctrl.Beginning = 0;
}
}
else
{
menu->ctrl.Selected++;
}
}
}
/****************************************************************************
* Description:进入下层菜单
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuEnter(MENU *menu)
{
if (menu->ctrl.Menu->ItemTbl && menu->ctrl.Menu->ItemNum)
{
/*入栈MENU_MCBCur*/
HMI_MenuStackPush(menu);
/*从新的MENU_MCB加载到MENU_MCBCur*/
menu->ctrl.Menu = (MENU_ITEM *)menu->ctrl.Menu->ItemTbl;
menu->ctrl.Beginning = 0;
menu->ctrl.Selected = 0;
}
}
/****************************************************************************
* Description:返回上层菜单
*
* Arguments: 无
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_MenuReturn(MENU *menu)
{
/*出栈到MENU_MCBCur*/
HMI_MenuStackPop(menu);
}
void HMI_MenuInit(MENU *menu, const MENU_ITEM *start)
{
menu->stack.top = 0; //如果菜单栈空,初始化菜单控制块
menu->ctrl.Beginning = 0;
menu->ctrl.Selected= 0;
menu->ctrl.Menu = (MENU_ITEM *)start;
menu->act_flag = 1;
}
/****************************************************************************
* Description:绘制菜单
*
* Arguments: win 要显示菜单的窗口
*
* Returns: 无
*
* Notes: 无
****************************************************************************/
void HMI_DrawMenu(const WINDOW *win, MENU *menu)
{
INT32U dsp_line_cnt, i;
MENU_ITEM *menu_item = menu->ctrl.Menu;
if ((menu->act_flag) && (win->style & HMI_WS_TITLEBAR)) //如果发生菜单进入或退出,并且显示标题
{
menu->act_flag = 0;
HMI_SetWindowTitle(win, (INT8U *)menu_item->Name);
}
HMI_ClientErease(win);
//显示的数目:当前菜单要显示的条目数和HMI_MENU_DISPLAY_NUM中的小者
dsp_line_cnt = menu_item->ItemNum - menu->ctrl.Beginning;
dsp_line_cnt = (dsp_line_cnt < HMI_ITEM_DISPLAY_NUM)? dsp_line_cnt : HMI_ITEM_DISPLAY_NUM;
for (i = 0; i < dsp_line_cnt; i++)
{
HMI_TextOut(win, 0, (HMI_FONT_HIGH + 1) * i,
(INT8U *)menu_item->ItemTbl->Name);
if (menu->ctrl.Selected == i)
{
if (!(win->style & HMI_WS_TITLEBAR))
{
GUI_RectangleFill(win->x + 2, win->y + (HMI_FONT_HIGH + 1) * i,
win->w - 4 - HMI_WIN_SCROLLBAR_WIDTH, HMI_FONT_HIGH, LCD_COLOR_FLIP);
}
else
{
GUI_RectangleFill(win->x + 2, win->y + HMI_WIN_TITLEBAR_HIGH + (HMI_FONT_HIGH + 1) * i,
win->w - 4 - HMI_WIN_SCROLLBAR_WIDTH, HMI_FONT_HIGH, LCD_COLOR_FLIP);
}
}
}
}
待机界面的处理函数:
/************************************待机页面***************************************/
QState HMI_smStandbyWin(HMI * const me, QEvt const * const e)
{
QState status_;
static RTC_TIME_Type current_time;
const WINDOW standby_win = {
0,
0,
240,
128,
{NULL, NULL},
0
};
switch (e->sig)
{
case Q_ENTRY_SIG:
{
HMI_CreateWindow((WINDOW *)&standby_win);
RTC_GetFullTime(¤t_time);
sprintf(HMI_TmpString, "%02d:", current_time.HOUR);
sprintf(HMI_TmpString + 3, "%02d:", current_time.MIN);
sprintf(HMI_TmpString + 6, "%02d", current_time.SEC);
HMI_TextOut((WINDOW *)&standby_win, 0, 0, (INT8U *)HMI_TmpString);
QTimeEvt_postEvery(&me->timeEvt, (QActive *)me, 30); //每300ms检测一次IC卡
status_ = Q_HANDLED();
}
break;
case Q_EXIT_SIG:
{
QTimeEvt_disarm(&me->timeEvt);
HMI_DestroyWindow((WINDOW *)&standby_win);
status_ = Q_HANDLED();
}
break;
case HMI_TIMEOUT_SIG:
{
INT8U *pID;
INT32U i;
if (!RFIC_PollingCard()) //有卡插入,转到提币窗口
{
//保存该卡ID
pID = RFIC_GetCardID();
if (NULL == pID)
{
status_ = Q_HANDLED();
break;
}
HMI_CardID = pID;
for (i = 1; i < pID + 1; i++)
{
HMI_CardID = pID;
}
//使卡进入休眠状态
RFIC_CardSleep();
return Q_TRAN(&HMI_smCoinExchangeWin);
}
status_ = Q_HANDLED();
}
break;
case KEYBOARD_SIG: //键盘事件
{
INT8U key;
key = HMI_KeyDecoder(((KEYBOARD_Evt *)e)->value); //解码
switch (key)
{
case HMI_KEY_FUN:
{
//如果管理员钥匙插入,按下功能键,转到菜单页面
HMI_CoinSalerMenuInit();
return Q_TRAN(&HMI_smMenuWin);
}
break;
// case HMI_KEY_LOCK: //测试用,模拟售币
// {
// return Q_TRAN(&HMI_smCoinSaleWin);
// }
// break;
default:
break;
}
}
break;
case RTC_SEC_INC_SIG: //实时时钟事件
{
//显示时间
RTC_GetFullTime(¤t_time);
sprintf(HMI_TmpString, "%02d:", current_time.HOUR);
sprintf(HMI_TmpString + 3, "%02d:", current_time.MIN);
sprintf(HMI_TmpString + 6, "%02d", current_time.SEC);
HMI_TextOut((WINDOW *)&standby_win, 0, 0, (INT8U *)HMI_TmpString);
status_ = Q_HANDLED();
}
break;
case NOTE_SIG: //纸币事件
{
NOTE_Evt *msg = (NOTE_Evt *)e;
//查看信息类型
switch (HWORD(msg->param))
{
case NOTE_INSERT: //如果是纸币信息
{
//检测币值
if (LWORD(msg->param) < NOTE_UNDEFINED)
{
NOTE_Evt *note_evt = Q_NEW(NOTE_Evt, NOTE_SIG);
note_evt->param = msg->param;
QActive_postFIFO((QActive *)me, (QEvt *)note_evt);
return Q_TRAN(&HMI_smCoinSaleWin);
}
}
break;
default:
break;
}
}
break;
//管理员钥匙事件,显示提示窗口
default:
{
status_ = Q_IGNORED();
}
break;
}
return status_;
}
半年前做的,忘差不多了,讲不清 对于菜单而言,还是面向屏幕好些,比面向事件的结构更清晰 后面我搞了个群,欢迎大家加一下:129063491
页:
[1]