搜索
bottom↓
回复: 11

咨询下QP状态机下LCD12864菜单绘制和按键扫描的用法

[复制链接]

出130入129汤圆

发表于 2014-6-9 22:53:53 | 显示全部楼层 |阅读模式
QP的概念基本也知道一些了,但是怎么投入使用,还没转过弯来,对状态图(机)认识还不够。
下面2点是我的理解,不知道对不对。

1.QP应该是最适合处理菜单之类的操作了,比如这是LCD12864,4行中文菜单。
一个屏幕当作一个状态,在进入这个状态时候就绘制4行菜单字符串,并对第一行反白。
向上或者向下导航键被按下后,重绘4行菜单字符串,并对相应行反白。
确定键或者取消键就直接转换到另一个状态。


2.QP例子设置的TICK一般都是10MS,用QP的定时器事件虽然也能实现按键扫描去抖,但是这状态函数不断循环频繁执行,还不如传统的做法方便(放进硬件定时器中断扫描)。

//假如是12864,4行汉字的菜单

  1. QState Menu_one(Menu * const me, QEvt const * const e) {
  2.         QState status;
  3.         switch (e->sig) {
  4.                 case Q_ENTRY_SIG: {
  5.                         清屏
  6.                         显示4行菜单字符串
  7.                         第一行反色
  8.                         status = Q_HANDLED();
  9.                         break;
  10.                 }
  11.                 case KEY_MOVE_UP_SIG: {
  12.                         清屏
  13.                         显示4行菜单字符串
  14.                         上一行反色
  15.                         status = Q_HANDLED();
  16.                         break;
  17.                 }
  18.                 case KEY_MOVE_DOWN_SIG: {
  19.                         清屏
  20.                         显示4行菜单字符串
  21.                         下一行反色
  22.                         status = Q_HANDLED();
  23.                         break;
  24.                 }
  25.                 case KEY_MOVE_ENTER_SIG: {
  26.                         status = Q_TRAN(&Menu_son);
  27.                         break;
  28.                 }
  29.                 case KEY_MOVE_EXIT_SIG: {
  30.                         status = Q_TRAN(&Menu_parent);
  31.                         break;
  32.                 }
  33.                 default: {
  34.                         status = Q_SUPER(&QHsm_top);
  35.                         break;
  36.                 }
  37.         }
  38.         return status;
  39. }
复制代码

出0入264汤圆

发表于 2014-6-9 23:05:26 | 显示全部楼层
好好看这个帖子里我的发言,必定收获大大的。
http://www.amobbs.com/thread-5582451-1-1.html

几点提示:
1:设定一个刷新的状态,该状态的处理流程:
     检测不同的刷新标志,来进行屏幕的绘制。具体到菜单项就是,有时候只需要反显,有时候需要全屏更新。所以采用专门的一个状态刷新,可以使得操作与显示隔离,同时
     提高刷新效率(不需要每次全屏刷,根据需要在其它状态中,设立标志,然后跳转到此状态即可)
2: 设定一个输入状态,用于接受输入,如按键,或者串口等等,此状态处理输入,并设定相应的状态。
3: 抽象出共用的部分,此部分为parent 状态,可以简化很多地方的处理。

出130入129汤圆

 楼主| 发表于 2014-6-9 23:08:51 | 显示全部楼层
mcu_lover 发表于 2014-6-9 23:05
好好看这个帖子里我的发言,必定收获大大的。
http://www.amobbs.com/thread-5582451-1-1.html

你的帖子我都看过,而且很认真看过几次。
但是总感觉不好理解,你的代码太过抽象化了。

外人一时半会只能学到皮毛,但是内涵不好理解。

出0入0汤圆

发表于 2014-6-9 23:41:03 | 显示全部楼层
第一个,关于菜单的写法是可以的,
第二个,你这种用QP的定时器事件是轮询方式,用法没错。另一种方式是,不使用定时器事件,而是从这个中断ISR里向相关的ActiveObject的消息队列发送消息。

出0入0汤圆

发表于 2014-10-27 10:48:40 | 显示全部楼层
查资料路过这里,讨论下QP。
楼主TICK,10MS,觉得是拖垮QP的节奏。
这种方法应该更好,把按键处理放到QP外,由中断或定时器处理,产生事件后再传送到QP。

出130入129汤圆

 楼主| 发表于 2014-10-27 10:51:15 | 显示全部楼层
bbglx 发表于 2014-10-27 10:48
查资料路过这里,讨论下QP。
楼主TICK,10MS,觉得是拖垮QP的节奏。
这种方法应该更好,把按键处理放到QP外 ...

改用FreeRTOS了,用起来太省心了,QP的那种思维还是适应不了。

出0入0汤圆

发表于 2014-10-27 10:58:35 | 显示全部楼层
68336016 发表于 2014-10-27 10:51
改用FreeRTOS了,用起来太省心了,QP的那种思维还是适应不了。

确实,少人用是有道理的,按照正常思维处理QP会进入死胡同。
头疼

出0入0汤圆

发表于 2014-11-4 16:46:45 | 显示全部楼层
傻孩子的多级菜单用起来很方便.

出0入0汤圆

发表于 2014-11-5 11:59:51 | 显示全部楼层
楼主觉得抽象,那是因为你写的东西还没达到那个水平,抽象的东西绝对是趋势。

出0入0汤圆

发表于 2014-11-28 21:39:54 | 显示全部楼层
建议楼主先看傻孩子的菜单,再看红金龙的帖子。主要是思维,要面向事件而不是过程,还有就是抽象。

/********************************数据类型定义*********************************/
//图标数据类型
typedef struct {
    INT16U w;
    INT16U h;
    const INT8U *p;
} ICON;

//窗口数据结构
typedef struct {  
        INT8U   x;                        // 窗口位置(左上角的x坐标)
           INT8U   y;                        // 窗口位置(左上角的y坐标)
           INT8U   w;                    // 窗口宽度
           INT8U   h;                    // 窗口高度
   
           INT8U   *title[GUI_LANGUAGE_NUM];                // 定义标题栏指针
           INT32U  style;               
} WINDOW;

//条目元素类型定义,只用来继承,不用来定义数据
typedef struct tagITEM {
    const INT8U    *Name[HMI_LANGUAGE_NUM];
    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[HMI_LANGUAGE_NUM];
    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[HMI_MENU_DEPTH + 1];
} MENU_STACK;   //空递增

//菜单数据类型定义
typedef struct {
    MENU_CTRL   ctrl;
    MENU_STACK  stack;
    INT8U       act_flag;
} MENU;


//单选框数据类型
typedef struct {
    ITEM    item;
}RADIO;

我的菜单结构是基于链表和栈的。
/****************************************************************************
* Description:  菜单栈进栈
*
* Arguments:    无
*
* Returns:      无
*
* Notes:        无
****************************************************************************/
void HMI_MenuStackPush(MENU *menu)
{
    menu->stack.tbl[menu->stack.top].Menu = menu->ctrl.Menu;
    menu->stack.tbl[menu->stack.top].Beginning = menu->ctrl.Beginning;
    menu->stack.tbl[menu->stack.top].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->stack.top].Menu;
    menu->ctrl.Beginning = menu->stack.tbl[menu->stack.top].Beginning;
    menu->ctrl.Selected = menu->stack.tbl[menu->stack.top].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 + menu->ctrl.Selected];
        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_Config.language]);
    }
   
    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[menu->ctrl.Beginning + i]->Name[HMI_Config.language]);
        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(&current_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[0] = pID[0];
                for (i = 1; i < pID[0] + 1; i++)
                {
                    HMI_CardID[i] = pID[i];
                }
               
                //使卡进入休眠状态
                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(&current_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_;
}
半年前做的,忘差不多了,讲不清

出0入4汤圆

发表于 2015-7-19 17:12:46 | 显示全部楼层
对于菜单而言,还是面向屏幕好些,比面向事件的结构更清晰

出0入0汤圆

发表于 2016-8-16 15:37:03 | 显示全部楼层
后面我搞了个群,欢迎大家加一下:129063491
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-5-10 09:23

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表