当前位置:首页 > 公众号精选 > 漫长当下

在上篇文章中对多级菜单式GUI进行了简单的介绍,也给出了实际的演示视频,可以看出实际效果中我使用的文字作为选项,但不管是文字选项还是图标选项都是一样的,只不过是显示内容不一样而已,只需要更改一个函数就可以随意转换,今天讲讲源码实现相关的内容吧。

先贴上源码:(注意绘线、绘框、绘制位图提供的方法不一定可用,最好自己提供)

#ifndef __M_GUI_H
#define __M_GUI_H

#include 
#include 

#ifndef COUNTOF
#define COUNTOF(a) (sizeof(a) / sizeof(*(a)))
#endif

#ifndef NULL
#define NULL ((void *)0)
#endif

typedef uint8_t  M_U8;
typedef int8_t   M_S8;
typedef uint16_t M_U16;
typedef int16_t  M_S16;
typedef uint32_t M_U32;
typedef int32_t  M_S32;

typedef struct sTitle *tTitle;
typedef struct shmi *thmi;
typedef struct sMenu *tMenu;
typedef struct sItem *tItem;

struct sTitle{
    //标题名
  char *str;
    //行坐标
  M_U16 x;
    //列坐标
  M_U16 y;
    //字体宽
//  M_U8 f_w;
    //字体高
//  M_U8 f_h;
};

/*按键响应后的操作*/
struct shmi{
    void (*hmi_up)(void);
    void (*hmi_down)(void);
    void (*hmi_left)(void);
    void (*hmi_right)(void);
    void (*hmi_enter)(void);
    void (*hmi_exit)(void);
};

struct sItem{
    //选项
  tTitle pszTitle;
    //子菜单
  tMenu psSubMenu;
};

struct sMenu {
    //菜单标题
  tTitle pszTitle;
    //子选项
  tItem psItem;
    //子选项数量
  M_S8 nItems;
    //当前选项索引
  M_S8 idx;
    //单页要排列的行
  M_S8 nRow;
    //单页要排列的列
  M_S8 nCol;
    //交互
  thmi hmi;

  //内容
    void (*pfContent)(void);
    //事件
    volatile M_S32 event;
    //上级菜单
    tMenu last;
};

/*外部按键状态获取*/
typedef struct {
    M_U32 (*up)(void);
    M_U32 (*down)(void);
    M_U32 (*left)(void);
    M_U32 (*right)(void);
    M_U32 (*enter)(void);
    M_U32 (*exit)(void);
}keyTypeDef,*tkey;

/*2d 图形绘制*/
typedef struct {
    //点亮像素点
    void (*pset)(M_U16, M_U16, M_U8);
    //清屏
    void (*pclear)(void);
    //绘制线
    void (*pdrawLine)(M_U16, M_U16, M_U16, M_U16, M_U8);
    //绘制框
    void (*pdrawFrame)(M_U16, M_U16, M_U16, M_U16, M_U8);
    //绘制位图
    void (*pdrawBitMap)(M_U16, M_U16, M_U16, M_U16, M_U8 *);
    //字串显示
    void (*pputString)(M_U16, M_U16, M_U8, void *);
}opsTypeDef,*tops;

typedef struct {
    tMenu active;
    tkey key;
    tops ops;
}MENU_GUI;

extern const struct shmi hmi_def[];

//gui初始化
void gui_init(MENU_GUI *g, opsTypeDef *op, keyTypeDef *k);
//gui选择
void gui_select(MENU_GUI *g);
//设置当前激活菜单
void gui_set_active(tMenu m);
//获取当前激活菜单
tMenu gui_get_active(void);
//发送信号到指定菜单
void gui_send_sig(tMenu obj);
//按键响应后调用相应的处理函数(周期调用周期应该不小于键盘扫描周期)
void gui_key_scan(void);
//gui刷新(周期调用,时间越短响应越佳)
void gui_flush(void);

//绘点
void gui_draw_point(M_U16 x, M_U16 y, M_U8 c);
//清屏
void gui_clear(void);
//绘线
void gui_draw_line(M_U16 x1, M_U16 y1, M_U16 x2, M_U16 y2, M_U8 c);
//绘框
void gui_drawFrame(M_U16 x1, M_U16 y1, M_U16 x2, M_U16 y2, M_U8 c);
//绘制位图
void gui_draw_bitmap(M_U16 x, M_U16 y, M_U16 h, M_U16 w, M_U8 *p);
//字串显示
void gui_put_string(M_U16 x, M_U16 y, M_U8 f,void *str);

#endif/*__M_GUI_H*/

#include "./m_gui/m_gui.h"

static MENU_GUI *gui;

void gui_init(MENU_GUI *g, opsTypeDef *op, keyTypeDef *k)
{
    g->key = k;
    g->ops = op;
    g->active = NULL;
}

void gui_select(MENU_GUI *g)
{
    gui = g;
}

void gui_set_active(tMenu m)
{
    gui->active = m;
}

tMenu gui_get_active(void)
{
    return gui->active;
}

void gui_send_sig(tMenu obj)
{
    if(obj->event) { return ; }

    obj->event++;
}

void gui_key_scan(void)
{
    if(!gui->key) { return ; }
    if(!gui->active) { return ; }
    if(!gui->active->hmi) { return ; }

    if(gui->key->up && gui->key->up())
    {
        gui->active->hmi->hmi_up();
    }

    if(gui->key->down && gui->key->down())
    {
        gui->active->hmi->hmi_down();
    }

    if(gui->key->left && gui->key->left())
    {
        gui->active->hmi->hmi_left();
    }

    if(gui->key->right && gui->key->right())
    {
        gui->active->hmi->hmi_right();
    }

    if(gui->key->enter && gui->key->enter())
    {
        gui->active->hmi->hmi_enter();
    }

    if(gui->key->exit && gui->key->exit())
    {
        gui->active->hmi->hmi_exit();
    }
}

/*菜单项显示*/
void gui_item_flush(void)
{
    M_U32 i = 0,tmpIdx = 0,tmp = 0;
  tTitle pTmp2 = NULL;

    if(!gui->active) { return ; }

  tmp = gui->active->nRow * gui->active->nCol;
  /*如果单页行列元素小于最大选项数*/
  if(tmp && (tmp < gui->active->nItems))
  {
    //计算当前索引对应页的开始选项
    i = gui->active->idx - (gui->active->idx % tmp);
    tmpIdx = i;
  }

  for( ;(i < gui->active->nItems) && (i < (tmp + tmpIdx));i++)
  {
    pTmp2 = gui->active->psItem[i].pszTitle;
    if(pTmp2 && pTmp2->str)
    {
      gui->ops->pputString(pTmp2->x, pTmp2->y, i == gui->active->idx?1:0, pTmp2->str);
    }
  }
}

void gui_flush(void)
{
    if(gui->active->event > 0)
    {
        gui->active->event--;

        gui->ops->pclear();

        if(gui->active->pfContent)
        {
            gui->active->pfContent();
        }

        gui_item_flush();
    }
}

void gui_key_up(void)
{
  /*如果索引为 0 则直接退出*/
    if(gui->active->idx <= 0) { return ; }

    /*如果索引小于当前页列数则跳到第一列,否则跳到上一行对应列*/
    gui->active->idx -= gui->active->nCol;
    if(gui->active->idx < 0)
    {
        gui->active->idx = 0;
    }

    gui_send_sig(gui->active);
}

void gui_key_down(void)
{
  /*如果当前索引不小于最大选项数则直接退出*/
  if(!(gui->active->idx < (gui->active->nItems - 1))) { return ; }

  /*如果跳到下一行对应列大于了最大选项数则跳到最后一个元素,否则跳到下一行对应列*/
    gui->active->idx += gui->active->nCol;
    if(gui->active->idx > gui->active->nItems - 1)
    {
        gui->active->idx = gui->active->nItems - 1;
    }

    gui_send_sig(gui->active);
}

void gui_key_left(void)
{
    /*如果到了首选项,则跳到尾选项*/
    gui->active->idx--;
    if(gui->active->idx < 0)
    {
        gui->active->idx = gui->active->nItems - 1;
    }

    gui_send_sig(gui->active);
}

void gui_key_right(void)
{
    /*如果到了尾选项,则跳到首选项*/
    gui->active->idx++;
    if(gui->active->idx > gui->active->nItems - 1)
    {
        gui->active->idx = 0;
    }

    gui_send_sig(gui->active);
}

void gui_key_enter(void)
{
    if(!gui->active->psItem[gui->active->idx].psSubMenu) { return ; }

    //先保存当前菜单到下级菜单的last中
    gui->active->psItem[gui->active->idx].psSubMenu->last = gui->active;
    //将下级菜单作为激活菜单
    gui->active = gui->active->psItem[gui->active->idx].psSubMenu;

    gui_send_sig(gui->active);
}

void gui_key_exit(void)
{
    if(!gui->active->last) { return ; }

    //读取上级菜单
    gui->active = gui->active->last;

    gui_send_sig(gui->active);
}

const struct shmi hmi_def[] = {
    {
        .hmi_up = gui_key_up,
        .hmi_down = gui_key_down,
        .hmi_left = gui_key_left,
        .hmi_right = gui_key_right,
        .hmi_enter = gui_key_enter,
        .hmi_exit = gui_key_exit,
    },
};

/*
* 绘点
* x,y点坐标
* c绘线还是消线
*/

void gui_draw_point(M_U16 x, M_U16 y, M_U8 c)
{
    if(gui->ops->pset)
    {
        gui->ops->pset(x,y,c);return ;
    }
}

/*
* 清屏
*/

void gui_clear(void)
{
    if(gui->ops->pclear)
    {
        gui->ops->pclear();return ;
    }
}

/*
* 绘线
* x1,y1起始坐标
* x2,y2结束坐标
* c绘线还是消线
*/

void gui_draw_line(M_U16 x1, M_U16 y1, M_U16 x2, M_U16 y2, M_U8 c)
{
    M_S16 n, dx, dy, sgndx, sgndy, dxabs, dyabs, x, y, drawx, drawy;

    if ( x2 < x1 )
    {
        n = x2;
        x2 = x1;
        x1 = n;
    }
    if ( y2 < y1 )
    {
        n = y2;
        y2 = y1;
        y1 = n;
    }

    /* Is hardware acceleration available? */
    if ( gui->ops->pdrawLine )
    {
        gui->ops->pdrawLine(x1,y1,x2,y2,c);return ;
    }

    dx = x2 - x1;
    dy = y2 - y1;
    dxabs = (dx>0)?dx:-dx;
    dyabs = (dy>0)?dy:-dy;
    sgndx = (dx>0)?1:-1;
    sgndy = (dy>0)?1:-1;
    x = dyabs >> 1;
    y = dxabs >> 1;
    drawx = x1;
    drawy = y1;

    gui->ops->pset(drawx, drawy,c);

    if( dxabs >= dyabs )
    {
        for( n=0; n        {
             y += dyabs;
             if( y >= dxabs )
             {
                    y -= dxabs;
                    drawy += sgndy;
             }
             drawx += sgndx;
             gui->ops->pset(drawx, drawy,c);
        }
    }
    else
    {
        for( n=0; n        {
             x += dxabs;
             if( x >= dyabs )
             {
                    x -= dyabs;
                    drawx += sgndx;
             }
             drawy += sgndy;
             gui->ops->pset(drawx, drawy,c);
        }
    }
}

/*
* 绘框
* x1,y1起始坐标
* x2,y2结束坐标
* c绘线还是消线
*/

void gui_drawFrame(M_U16 x1, M_U16 y1, M_U16 x2, M_U16 y2, M_U8 c)
{
    /* Is hardware acceleration available? */
    if ( gui->ops->pdrawFrame )
    {
        gui->ops->pdrawFrame(x1,y1,x2,y2,c);return ;
    }

    gui_draw_line(x1,y1,x2,y1,c);
    gui_draw_line(x1,y2,x2,y2,c);
    gui_draw_line(x1,y1,x1,y2,c);
    gui_draw_line(x2,y1,x2,y2,c);
}

/*
* 绘制位图
* x,y起始坐标
* h,w位图高,宽
* p位图数据
*/

void gui_draw_bitmap(M_U16 x, M_U16 y, M_U16 h, M_U16 w, M_U8 *p)
{
    /*h:高,w:宽*/
    M_U32 i = 0,j = 0;
    M_U8 sx = 0,sy = 0;

    sx = x;
    sy = y;

    /* Is hardware acceleration available? */
    if ( gui->ops->pdrawBitMap )
    {
        gui->ops->pdrawBitMap(sx,sy,h,w,p);return ;
    }

    for(i = 0;i < (w + 7) / 8 * h;i++)
    {
        for(j = 0;j < w;j++)
        {
            gui->ops->pset(sx,sy + j,p[i + (j >> 3)] & (0x80 >> (j & (8 - 1))));
        }

        i += (j - 1) >> 3;
        sx++;
    }
}

/*
* 字串显示
* x,y起始坐标
* f:0正显,1反显
* str字串内容
*/

void gui_put_string(M_U16 x, M_U16 y, M_U8 f,void *str)
{
    /* Is hardware acceleration available? */
    if ( gui->ops->pputString )
    {
        gui->ops->pputString(x,y,f,str);return ;
    }
}

从源码可以看到,目前只实现了基本的菜单切换功能,肯定是不能够满足实际的开发需求,项目中可能会有数值修改、状态显示、屏保等各种功能,后续可能我会继续更新,如果你想自己实现也是非常方便的,只需要模仿hmi_def的实现就可以轻松的完成上述功能。

关于OLED显示相关的内容全部都是需要开发者自己实现的(源码中只有几个功能,需要扩展可以自己添加),这和其它GUI库区别很大,因为其它GUI库都只需要移植一个绘点函数或者再添加一个读点函数即可,但是我这样设计肯定有这样设计的理由,在实际项目中,我使用过多款OLED屏,其中有些屏是集成屏(也就是厂家已经完成了一次开发,我们只需要通过他给定的通信协议就可以进行二次开发),这样的屏好处就是使用简单,进行2D绘图或者汉字显示操作只需要发送相应的协议数据即可,上手是非常快的,坏处呢就是相对好处来说了,因为集成所以不够灵活,只能通过其给定的接口进行操作,出现问题也不太好解决;接触的还有一种屏就是裸屏,然后硬件电路这些全部都是自己完成,裸屏如果要显示中文,最好的办法就是加一块点阵字库芯片,当然你要是flash容量够大,你也可以直接将汉字点阵生成后全部存储到flash中;说到这里你应该明白为什么我要将所有OLED操作相关的接口全部留给开发者实现了,给开发者提供足够的自由度,可以很灵活的在这两类设计中随意切换。

还有一点不同的就是,此菜单设计并没有使用模拟显存,其中一个原因是如果你使用裸屏的方案,那么极有可能你就会使用模拟显存的方式来完成2D绘图等功能,如果再用一级缓存就会增大内存开销,另外一个原因就是与OLED的实时性特性有关。

由于我接触的OLED屏都是小屏,像分辨率是256*64或者128*32的,所以我采用的方案是全屏刷新,如果你觉得全屏刷新太慢了,你也可以增加回调函数方法,将静态内容和动态内容分开处理,这样对速度提升是非常大的,这也是OLED提供的便利。

今天的内容就讲这么多吧,下篇文章将会讲解此系列第一篇文章中视频演示的效果源码,以及移植的一些细节问题。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭