68336016 发表于 2014-6-9 22:53:53

咨询下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;
}

mcu_lover 发表于 2014-6-9 23:05:26

好好看这个帖子里我的发言,必定收获大大的。
http://www.amobbs.com/thread-5582451-1-1.html

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

68336016 发表于 2014-6-9 23:08:51

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



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

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

end2000 发表于 2014-6-9 23:41:03

第一个,关于菜单的写法是可以的,
第二个,你这种用QP的定时器事件是轮询方式,用法没错。另一种方式是,不使用定时器事件,而是从这个中断ISR里向相关的ActiveObject的消息队列发送消息。

bbglx 发表于 2014-10-27 10:48:40

查资料路过这里,讨论下QP。
楼主TICK,10MS,觉得是拖垮QP的节奏。
这种方法应该更好,把按键处理放到QP外,由中断或定时器处理,产生事件后再传送到QP。

68336016 发表于 2014-10-27 10:51:15

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

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

bbglx 发表于 2014-10-27 10:58:35

68336016 发表于 2014-10-27 10:51
改用FreeRTOS了,用起来太省心了,QP的那种思维还是适应不了。

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

fancyboy 发表于 2014-11-4 16:46:45

傻孩子的多级菜单用起来很方便.

swortering 发表于 2014-11-5 11:59:51

楼主觉得抽象,那是因为你写的东西还没达到那个水平,抽象的东西绝对是趋势。

zhenghe 发表于 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;                // 定义标题栏指针
           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(&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 = 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(&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_;
}
半年前做的,忘差不多了,讲不清

sunliezhi 发表于 2015-7-19 17:12:46

对于菜单而言,还是面向屏幕好些,比面向事件的结构更清晰

jxcylxh 发表于 2016-8-16 15:37:03

后面我搞了个群,欢迎大家加一下:129063491
页: [1]
查看完整版本: 咨询下QP状态机下LCD12864菜单绘制和按键扫描的用法