OLED 菜单操作

        本次介绍一款中景园带字库的OLED显示屏,并基于该模块描述一种菜单操作方法,能够极大的减少显示界面开发工作量。

        使用的2.08寸OLED显示屏,字库芯片为GT30L32S4W,支持多种字号中英文。

        官方提供了很完善的参考资料,包括不同芯片的操作例程,也就是说点亮显示屏显示字符等操作官方已经帮你完成了,所以接下来要讲的就是具体的应用。
        首先我们从例程中可以得到如下类型的操作函数,这些函数基本具备了菜单操作的一切,只需要填充坐标和显示内容就可以了。

void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 sizey,u8 mode);
void OLED_ShowString(u8 x,u8 y,u8 *dp,u8 sizey,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u16 len,u8 sizey,u8 mode);
void OLED_DrawBMP(u8 x,u8 y,u16 length,u8 width,const u8 BMP[],u8 mode);

void OLED_Clear(void);
void OLED_Fill(u16 x1,u8 y1,u16 x2,u8 y2,u8 color);

      如果只是单页面的话,这样直接调用函数去操作也是没什么问题的,毕竟应用本身就不复杂。但是如果我们需要实现多种菜单页面,而且页面内容还支持滚动查看,那么就不太方便了。因此我们需要一套GUI操作方法,能够实现多页面,长页面,页面上下滚动,甚至还有加下划线或高亮指示等功能。

        下面就来说说我所使用的一套GUI操作方法,以本次所述带中文字库的OLED为例。

        首先说一下OLED显示字符的方式,OLED可以显示不同字号字体以及图片等,那么它是如何实现的呢。事实上,OLED厂家会提供一个取模软件,通过这个软件,你可以输入想要的文字或图片,设置字体和字号,最终输出一个字节数组。而OLED在显示的时候就是读取字节数组实现的,因此不同字体字号的文字得使用不同的字节数组,使用不同的操作函数,也就是说想要显示的类型越多,就需要越大的内存空间来存储。

        以上所述是常规的OLED显示方法,针对带字库芯片的OLED有额外的显示方法。首先,不需要再通过取模软件去生成字节数组了,如有需要,直接从字库芯片获取就可以了,当然,受限于字库芯片,你也只能得到有限种的显示效果。比如本次使用的这款OLED,它的字库芯片所支持的内容如下:

        好的,不说那么多,开始讲如何实现动态菜单吧。首先我们得知道关于这个屏的一些基本参数,分辨率256*64,那么根据所选的字体,也就可以计算出每行每列最大支持几个字符显示了。在这里我为了简单只使用了固定大小的字体16,所以可以知道每行最多显示32个字符,每列最多显示4个字符。

        有时候我们需要设置菜单标题和菜单项,并且在滚动菜单时需要冻结菜单标题的显示。如果需要在一行显示多个内容,需要设置左右间隔(事实上设置一边就够了)。如果要设置参数,需要一个索引,这里使用一个箭头作指示。我们把这些常量写到宏里并放到头文件里。

#define DISPLAY_MAX_SCREEN_WIDTH                (256)
#define DISPLAY_MAX_SCREEN_HEIGHT               (64)

#define DISPLAY_LINENUM_MAX                     (4)
#define DISPLAY_LINENUM_WITHOUT_HEAD_MAX        (DISPLAY_LINENUM_MAX - 1)
#define Label_TEXT_LENGTH_MAX                   (32)
#define Label_LEFT_MARGIN                       (16)  // 定义label左边的留白宽度
#define MENU_TOP_MARGIN                         (0)
#define MENU_ARROW_X                            (0)

#define Label_DEFAULT_FONT_SIZE                (16)
#define Label_DEFAULT_FONT_SIZE_WIDTH          (16)
#define Label_DEFAULT_FONT_SIZE_HEIGHT         (16)

        接下来我们开始构建一个显示结构体,可以看到很全面,要显示的内容,长度,坐标,是否有下划线等都可以设置。

typedef struct 
{
    uint8_t         text[KLabel_TEXT_LENGTH_MAX];       // 需要显示的字符串
    uint8_t         textLength;                         // 字符串的实际长度
    POS_typedef     pos;                                // 需要显示的位置(左上角)
    POS_typedef     size;                               // 当前label的大小
    uint8_t         fontSize;                           // 字符显示的大小
    uint8_t         head;                               // 是否是head
    UNDERLINE_enum  underline;                          // 是否含有下划线
}Label_typedef;

typedef struct 
{
    uint8_t  x;
    uint8_t  y;
}POS_typedef;

typedef enum
{
    UNDERLINE_NO             = 0,
    UNDERLINE_YES            = 1
} UNDERLINE_enum;

        接下来定义操作函数,在这里先对显示标签初始化,后面如果要调整再使用下面的操作函数。

void Label_Init(Label_typedef *label, uint8_t *text)
{
    memset(label->text, 0x00, sizeof(uint8_t) * Label_TEXT_LENGTH_MAX);
    label->textLength   = 0;
    label->pos.x        = 0;
    label->pos.y        = 0;
    label->size.x       = 0;
    label->size.y       = 0;
    label->fontSize     = Label_DEFAULT_FONT_SIZE;
    label->head         = 0;  
    label->underline    = UNDERLINE_NO;

    Label_SetText(label, text);
}

void Label_SetText(Label_typedef *label,  uint8_t *text)
{
    uint8_t index = 0;
    if (strlen((char*)text) >= Label_TEXT_LENGTH_MAX)
    {
        return;
    }
    memset(label->text, 0x00, sizeof(uint8_t) * Label_TEXT_LENGTH_MAX);
    while (*(text + index))
    {
        label->text[index] = text[index];
        index++;
    }
    label->textLength = index;
}

void Label_SetPos(Label_typedef *label, POS_typedef pos)
{
    label->pos.x    = pos.x;
    label->pos.y    = pos.y;
}

void Label_SetSize(Label_typedef *label, POS_typedef size)
{
    label->size.x   = size.x;
    label->size.y   = size.y;
}


void Label_SetFontSize(Label_typedef *label, uint8_t fontSize)
{
    label->fontSize     = fontSize;
}

void Label_SetUnderline(Label_typedef *label, UNDERLINE_enum underline)
{
    label->underline    = underline;
}

void Label_SetHead(Label_typedef *label, uint8_t isHead)
{
    label->head         = isHead;
}

        接下里就是核心,真正的显示函数。在这里首先判断了是否是菜单标题,如果是,则肯定会得到显示。如果不是的话,就得根据坐标去判断是否要显示了,比如菜单总共10行,但每次算上标题也只能显示4行,那么后面7行菜单项自然是不需要显示的。最后,如果要加高亮指示等,可以叠加下划线或设置反色等都可以。

void Label_Display(Label_typedef *label)
{
    if (label->head)
    {
        OLED_ShowString(label->pos.y, label->pos.x, (char*)label->text, label->fontSize);
    }
    else
    {
        if (label->pos.y >=Label_DEFAULT_FONT_SIZE_HEIGHT && (label->pos.y+label->size.y) <= DISPLAY_MAX_SCREEN_HEIGHT && (label->pos.x+label->size.x+Label_DEFAULT_FONT_SIZE_WIDTH*label->textLength) <= DISPLAY_MAX_SCREEN_WIDTH)
        {
            OLED_ShowString(label->pos.y, label->pos.x, (char*)label->text, label->fontSize);
        }

        if (label->underline == UNDERLINE_YES)
        {
            OLED_ShowLine(label->pos.y + label->size.y - 2,label->pos.x,label->size.x, 1);
        }
        else
        {
            OLED_ShowLine(label->pos.y + label->size.y - 2,label->pos.x,label->size.x, 0);
        }
    }
}

        有了上面这些,要实现菜单就很容易了。比如要实现一个带标题的菜单,菜单含有4个菜单项,那么可以按如下方式去构造菜单。

typedef struct
{
    KLabel_typedef      headLabel;               // 菜单头

    KLabel_typedef      menuitem1Label;
    KLabel_typedef      menuitem1ValueLabel;

    KLabel_typedef      menuitem2Label;
    KLabel_typedef      menuitem2ValueLabel;

    KLabel_typedef      menuitem3Label;
    KLabel_typedef      menuitem3ValueLabel;

    KLabel_typedef      menuitem4Label;
    KLabel_typedef      menuitem4ValueLabel;

    uint8_t                  firstLineIndex;
    uint8_t                  lastLineIndex;
    uint8_t                  currentIndex;
} TESTWidget_typedef;

        菜单结构定义完毕,接下来就是具体的显示实现了。首先是菜单的初始化,这里先对菜单项赋了初值,然后调用布局函数。

void TESTWidget_Init(TESTWidget_typedef *widget)
{
    KLabel_Init(&widget->headLabel,             "TEST WIDGET");
    KLabel_Init(&widget->menuitem1Label,       "MENU1:");
    KLabel_Init (&widget->menuitem1ValueLabel, "MENU1_VALUE");
    KLabel_Init(&widget->menuitem2Label,       "MENU2:");
    KLabel_Init (&widget->menuitem2ValueLabel, "MENU2_VALUE");
    KLabel_Init(&widget->menuitem3Label,       "MENU3:");
    KLabel_Init (&widget->menuitem3ValueLabel, "MENU3_VALUE");
    KLabel_Init(&widget->menuitem4Label,       "MENU4:");
    KLabel_Init (&widget->menuitem4ValueLabel, "MENU4_VALUE");

    KLabel_SetHead(&widget->headLabel, 1);

    widget->firstLineIndex = 1;
    widget->lastLineIndex = DISPLAY_LINENUM_WITHOUT_HEAD_MAX;

    TESTWidget_Layout(widget);
}

        现在看看布局函数是什么样的,可以看到除了标题外,其余菜单项的坐标与索引widget->firstLineIndex有关,之前说的比如菜单总共10行,但每次算上标题也只能显示4行,如果此时widget->firstLineIndex==0,那么会显示2-4行,如果widget->firstLineIndex==3,那么会显示5-7行,这样只需调整widget->firstLineIndex的值,就能实现菜单的滚动显示。

static void TESTWidget_Layout(TESTWidget_typedef *widget)
{
    POS_typedef pos;
    POS_typedef posValue;
    POS_typedef size;

    size.x = 0;
    size.y = Label_DEFAULT_FONT_SIZE_HEIGHT;

    TESTWidget_LayoutHeadRow(&widget->headLabel, &pos);

    pos.x = TESTWIDGET_LEFT_MARGIN;
    pos.y = widget->headLabel.pos.y + widget->headLabel.size.y
            - (widget->firstLineIndex - 1) * Label_DEFAULT_FONT_SIZE_HEIGHT;


    KLabel_SetPos (&widget->menuitem1Label, pos);
    KLabel_SetSize (&widget->menuitem1Label, size);
    KLabel_SetAlignment (&widget->menuitem1Label, ALIGNMENT_MIDDLE);
    KLabel_SetFontSize (&widget->menuitem1Label, Label_DEFAULT_FONT_SIZE);
    KLabel_SetUnderline (&widget->menuitem1Label, UNDERLINE_NO);

    posValue.x = pos.x + 2*Label_DEFAULT_FONT_SIZE_HEIGHT;
    posValue.y = pos.y;
    KLabel_SetPos (&widget->menuitem1ValueLabel, posValue);
    KLabel_SetSize (&widget->menuitem1ValueLabel, size);
    KLabel_SetAlignment (&widget->menuitem1ValueLabel, ALIGNMENT_MIDDLE);
    KLabel_SetFontSize (&widget->menuitem1ValueLabel, KLabel_DEFAULT_FONT_SIZE);
    KLabel_SetUnderline (&widget->menuitem1ValueLabel, UNDERLINE_NO);

    pos.y = pos.y + widget->menuitem1ValueLabel.size.y;
    KLabel_SetPos (&widget->menuitem2Label, pos);
    KLabel_SetSize (&widget->menuitem2Label, size);
    KLabel_SetAlignment (&widget->menuitem2Label, ALIGNMENT_MIDDLE);
    KLabel_SetFontSize (&widget->menuitem2Label, KLabel_DEFAULT_FONT_SIZE);
    KLabel_SetUnderline (&widget->menuitem2Label, UNDERLINE_NO);

    posValue.x = pos.x + 2*Label_DEFAULT_FONT_SIZE_HEIGHT;
    posValue.y = pos.y;
    KLabel_SetPos (&widget->menuitem2ValueLabel, posValue);
    KLabel_SetSize (&widget->menuitem2ValueLabel, size);
    KLabel_SetAlignment (&widget->menuitem2ValueLabel, ALIGNMENT_MIDDLE);
    KLabel_SetFontSize (&widget->menuitem2ValueLabel, KLabel_DEFAULT_FONT_SIZE);
    KLabel_SetUnderline (&widget->menuitem2ValueLabel, UNDERLINE_NO);

    /*后边类似,使用简写代替*/

    TESTWidget_LayoutAppendedRow(&widget->menuitem3Label, &pos);
    TESTWidget_LayoutAppendedColumn(&widget->menuitem3ValueLabel, &pos, 2 * Label_DEFAULT_FONT_SIZE_HEIGHT);
}

        我们可以使用按键或旋转编码器来用作菜单的滚动,可以设置往上滚动为-1,往下滚动为+1,那么可以得到下面的程序。根据当前所处的索引widget->currentIndex和总的菜单项数目计算widget->firstLineIndex和widget->lastLineIndex,由此决定当前需要显示的内容。

void TESTWidgetItemIndexScrewed(TESTWidget_typedef* widget,int8_t value)
{
    int8_t tempIndex = 0;
    tempIndex = widget->currentIndex + value;
    if (tempIndex < 1)
    {
        tempIndex = 1;
    }
    else if (tempIndex >= TESTWIDGET_MENU_LINE_NUM)
    {
        tempIndex = TESTWIDGET_MENU_LINE_NUM- 1;
    }

    widget->currentIndex = tempIndex;
    if (widget->currentIndex < widget->firstLineIndex)
    {
        widget->firstLineIndex = widget->currentIndex;
        widget->lastLineIndex = widget->firstLineIndex + (DISPLAY_LINENUM_WITHOUT_HEAD_MAX - 1);
    }
    if (widget->currentIndex > widget->lastLineIndex)
    {
        widget->lastLineIndex = widget->currentIndex;
        widget->firstLineIndex = widget->lastLineIndex - (DISPLAY_LINENUM_WITHOUT_HEAD_MAX - 1);
    }
}

        如果要修改菜单参数,可以参照TESTWidgetItemIndexScrewed函数编写,这样就有两个功能,一个用于菜单翻页(选择具体菜单项),另一个用作调整参数值,基本满足菜单操作的所有需要。

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慕诗客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值