单色OLED屏可移植多级菜单式GUI(2)-源码

来源:漫长当下 显示与人机交互 21 次阅读
摘要:在上篇文章中对多级菜单式GUI进行了简单的介绍,也给出了实际的演示视频,可以看出实际效果中我使用的文字作为选项,但不管是文字选项还是图标选项都是一样的,只不过是显示内容不一样而已,只需要更改一个函数就可以随意转换,今天讲讲源码实现相关的内容吧。 先贴上源码:(注意绘线、绘框、绘制位图提供的方法不一定可用,最好自己提供) `#ifndef __M_GUI_H define __M_GUI_H inc

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

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

`#ifndef __M_GUI_H

define __M_GUI_H

include <string.h>

include <stdint.h>

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<dxabs; n++ )         {              y += dyabs;              if( y >= dxabs )              {                     y -= dxabs;                     drawy += sgndy;              }              drawx += sgndx;              gui->ops->pset(drawx, drawy,c);         }     }     else     {         for( n=0; n<dyabs; 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屏都是小屏,像分辨率是25664或者12832的,所以我采用的方案是全屏刷新,如果你觉得全屏刷新太慢了,你也可以增加回调函数方法,将静态内容和动态内容分开处理,这样对速度提升是非常大的,这也是OLED提供的便利。

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

点击下面的链接查看系列其它文章:

单色OLED屏可移植多级菜单式GUI(1)-简介

相关推荐
评论区

登录后即可参与讨论

立即登录