在小型的单片机设备中,需要使用LCD菜单进行人机交互。对于这种交互的实现,一来无需像在手机上做APP一样,做出花哨的界面效果;二来这种小设备也不支持那样太消耗资源的界面系统。所以这种场合下,需要我们亲自使用C语言去编写一套精简的界面框架代码。
- 定义菜单项的数据类型
一个菜单项,有自身的唯一标志码ID,和显示的字符串ItemDisplayStr,和选中它时执行的函数ItemFun。但仅仅这样还不够,因为很多菜单项之间是有逻辑关联的。在一个菜单项下点击跟进、回退,都会转到不同的页面。所以还需要有表示菜单项逻辑关系的父菜单项ItemFather、子菜单项ItemChildren、下一个菜单项ItemNext、上一个菜单项ItemLast。下文给出了示意图。
- 实现菜单项机制
要实现菜单项机制,需要实现四个基础功能:
1,菜单项跟进
2,菜单项回退
3,菜单项上选
4,菜单项下选
为了设计这些基本功能,先定义一个起示范作用的菜单组织。理论上,每个菜单项的ID可以随意制定。但实际上,为了便于管理,可规定,菜单项编号为12位数字。前4位为菜单项级数,中4位为父菜单项在列表中的次序(无父菜单项则为0),后四位为本菜单项在列表中的次序。这种规定限制了一个父菜单项在同一级下最多挂9999个子菜单项,整个菜单体最多不超过9999级深度。不过一般情况下这完全足够了。下图给出了一个示范性菜单,方框内注明了菜单的显示字符和唯一ID。
根据这种菜单的组织图,可以知道:
- 菜单跟进,其实就是从所有的菜单栏目中找到和目前菜单项的ItemChildren一致的菜单项ID,就得到了子菜单项。菜单项上选、下选、回退同理。
- 定义菜单数据类型
一个菜单,是由若干个菜单项构成的。我们需要定义一个菜单类型,来管理菜单项列表等内容。注意菜单和菜单项的区别:菜单项只是单个单个的条目,而菜单是管理若干个菜单项的类型。下图解释了这一区别。菜单类型由两个成员组成,一个是菜单项列表的起始地址st_lcd_item_list,一个是当前菜单项的地址st_lcd_item_current。
其实这样已经可以完成界面的功能了。但这样还不够完美,因为某些美观上的需求,比如:
1,菜单回退的时候要能回到先前的箭头位置,“恢复现场”。
2,菜单在某一级时,要显示出这一级的菜单列表。如果LCD行数不够,需要翻页显示。
对于需求1,推荐的做法是设计一个栈,每跟进就压栈,每回退就出栈,即把现场使用栈的形式保存起来。现场的主要内容是箭头位置。
对于需求2,循环搜索菜单项的ItemLast和ItemNext,直到到达LCD顶部和底部即可停止搜索。
为了在电脑上模拟,假设有一个函数LCD_OnxyPrint可以在指定坐标显示字符串。把下图红色方框当做一个LCD 128*64的液晶,可见实现了菜单功能。
源代码分两个文件,LCD_Demo.h和LCD_Demo.c,如下:
/*---------------------------------LCD_Demo.h----------------------------------*/
#ifndef __LCD_DEMO_H__
#define __LCD_DEMO_H__
//液晶屏尺寸参数
#define LCD_LINE_START 0
#define LCD_LINE_END 3
#define LCD_COLUMN_START 0
#define LCD_COLUMN_END 15
#define STACK_ARROW_SIZE 256
//-------------菜单项-----------------------
typedef void *(*ItemFun_Typedef )(void *);
typedef struct ST_LCD_ITEM
{
int ItemID;
int ItemLast;
int ItemNext;
int ItemFather;
int ItemChildren;
char *ItemDisplayStr;
ItemFun_Typedef ItemFun;
}ST_LCD_ITEM;
//-------------菜单 -----------------------
typedef struct ST_LCD_MENU
{
ST_LCD_ITEM* st_lcd_item_list;
ST_LCD_ITEM* st_lcd_item_current;
}ST_LCD_MENU;
//-------------记忆栈--------------------
typedef struct ST_STACK
{
int* ArrowStackBufferStart;
int* ArrowStackBufferEnd;
int* ArrowStackTop;
}ST_STACK;
extern void MENU_ItemInitSet(ST_LCD_MENU* st_lcd_menu,ST_LCD_ITEM st_lcd_item[ ]);
extern ST_LCD_ITEM* MENU_Enter(ST_LCD_MENU* st_lcd_menu);
extern ST_LCD_ITEM* MENU_Exit(ST_LCD_MENU* st_lcd_menu);
extern ST_LCD_ITEM* MENU_Next(ST_LCD_MENU* st_lcd_menu);
extern ST_LCD_ITEM* MENU_Last(ST_LCD_MENU* st_lcd_menu);
#endif
/*-----------------------------------LCD_Demo.c---------------------------------*/
#include <stdio.h>
#include <windows.h>
#include <conio.h>
#include "LCD_Demo.h"
void LCD_OnxyPrint(char *buf, int startX, int startY)
{
HANDLE hd;
COORD pos;
pos.X = startX;
pos.Y = startY;
hd = GetStdHandle(STD_OUTPUT_HANDLE); /*获取标准输出的句柄*/
SetConsoleCursorPosition(hd, pos); /*设置控制台光标输出的位置*/
printf("%s",buf );
}
void LCD_Clear()
{
int i,j;
for(i=LCD_COLUMN_START;i<=LCD_COLUMN_END;i++)
for(j=LCD_LINE_START;j<=LCD_LINE_END;j++)
{
LCD_OnxyPrint(" ", i, j);
}
}
void PrintScore(ST_LCD_MENU* st_lcd_menu)
{
LCD_OnxyPrint("PrintScore:您的分数:100 ", 0, 5);
}
ST_LCD_ITEM st_lcd_item[ ] =
{
{000100000001,000000000000,000000000000,000000000000,000200010001,"学生成绩", MENU_Enter},
{000200010001,000000000000,000200010002,000100000001,000300010001,"英语", MENU_Enter},
{000300010001,000000000000,000300010002,000200010001,000000000000,"分数", PrintScore},
{000300010002,000300010001,000300010003,000200010001,000000000000,"排名", NULL},
{000300010003,000300010002,000300010004,000200010001,000000000000,"档位", NULL},
{000300010004,000300010003,000300010005,000200010001,000000000000,"是否及格", NULL},
{000300010005,000300010004,000000000000,000200010001,000000000000,"是否优秀", NULL},
{000200010002,000200010001,000200010003,000100000001,000300020001,"数学", MENU_Enter},
{000300020001,000000000000,000300020002,000200010002,000000000000,"分数", NULL},
{000300020002,000300020001,000000000000,000200010002,000000000000,"排名", NULL},
{000200010003,000200010002,000000000000,000100000001,000000000000,"语文", NULL},
{000000000000,000000000000,000000000000,000000000000,000000000000,"NULL", NULL},
};
void MENU_ItemInitSet(ST_LCD_MENU* st_lcd_menu,ST_LCD_ITEM st_lcd_item[ ] )
{
st_lcd_menu->st_lcd_item_list = &st_lcd_item[ 0];
st_lcd_menu->st_lcd_item_current = &st_lcd_item[ 0];
}
ST_LCD_ITEM* MENU_ItemSearch(ST_LCD_MENU* st_lcd_menu,int ItemID)
{
int i = 0;
ST_LCD_ITEM* pItem = NULL;
if(ItemID == 0)
{
return pItem;
}
while(st_lcd_menu->st_lcd_item_list[ i ].ItemID != 0)
{
if(st_lcd_menu->st_lcd_item_list[ i ].ItemID == ItemID)
{
pItem = &st_lcd_menu->st_lcd_item_list[ i ];
return pItem;
}
i++;
}
return pItem;
}
ST_LCD_ITEM* MENU_Trace(ST_LCD_MENU* st_lcd_menu,int item_id)
{
ST_LCD_ITEM* pItem = NULL;
pItem= MENU_ItemSearch( st_lcd_menu,item_id);
if(pItem!=NULL )
{
st_lcd_menu->st_lcd_item_current = pItem;
}
return pItem;
}
ST_LCD_ITEM* MENU_Enter(ST_LCD_MENU* st_lcd_menu)
{
return MENU_Trace(st_lcd_menu,st_lcd_menu->st_lcd_item_current->ItemChildren);
}
ST_LCD_ITEM* MENU_Exit(ST_LCD_MENU* st_lcd_menu)
{
return MENU_Trace(st_lcd_menu,st_lcd_menu->st_lcd_item_current->ItemFather);
}
ST_LCD_ITEM* MENU_Next(ST_LCD_MENU* st_lcd_menu)
{
return MENU_Trace(st_lcd_menu,st_lcd_menu->st_lcd_item_current->ItemNext);
}
ST_LCD_ITEM* MENU_Last(ST_LCD_MENU* st_lcd_menu)
{
return MENU_Trace(st_lcd_menu,st_lcd_menu->st_lcd_item_current->ItemLast);
}
//记住上一次箭头位置用的栈
void ST_STACK_Init(ST_STACK* stack,int* buffer_start,int* buffer_end)
{
stack->ArrowStackBufferStart = buffer_start;
stack->ArrowStackBufferEnd = buffer_end;
stack->ArrowStackTop = buffer_start;
}
void ST_STACK_DataIn(ST_STACK* stack,int Data)
{
if(stack->ArrowStackTop<= stack->ArrowStackBufferEnd)
{
*(stack->ArrowStackTop) = Data;
(stack->ArrowStackTop)++;
}
}
int ST_STACK_DataOut(ST_STACK* stack )
{
if(stack->ArrowStackTop > stack->ArrowStackBufferStart)
{
(stack->ArrowStackTop)--;
return *(stack->ArrowStackTop) ;
}
else
{
return LCD_LINE_START;
}
}
void main()
{
char key=0;
int i=0;
int ArrowLine = LCD_LINE_START;
ST_LCD_ITEM* p;
int item_stack_buffer[STACK_ARROW_SIZE];
ST_STACK item_stack;
ST_LCD_MENU menu;
ST_LCD_MENU menu_Dis;
MENU_ItemInitSet(&menu,st_lcd_item );
MENU_ItemInitSet(&menu_Dis,st_lcd_item );
ST_STACK_Init(&item_stack,&item_stack_buffer[0],&item_stack_buffer[STACK_ARROW_SIZE-1]);
while(1)
{
if(kbhit() != 0)
{
key = getch();
// printf("\nKey = %c \n",key);
if(key == 'w')
{
if(NULL != MENU_Last(&menu))
{
if(ArrowLine>LCD_LINE_START) ArrowLine--;
}
}
if(key == 's')
{
if(NULL!=MENU_Next(&menu))
{
if(ArrowLine<LCD_LINE_END) ArrowLine++;
}
}
if(key == 'a')
{
MENU_Exit(&menu);
ArrowLine = ST_STACK_DataOut(&item_stack );
}
if(key == 'd')
{
// MENU_Enter(&menu);
if(menu.st_lcd_item_current->ItemFun == MENU_Enter)
{
if(NULL!=menu.st_lcd_item_current->ItemFun(&menu))
{
ST_STACK_DataIn(&item_stack,ArrowLine);
ArrowLine = LCD_LINE_START;
}
}
else if(menu.st_lcd_item_current->ItemFun!=NULL)
{
menu.st_lcd_item_current->ItemFun(&menu);
}
}
//把菜单页面显示在屏幕上
LCD_Clear();
menu_Dis = menu;
LCD_OnxyPrint("◆",LCD_COLUMN_START, ArrowLine);
LCD_OnxyPrint(menu_Dis.st_lcd_item_current->ItemDisplayStr,LCD_COLUMN_START+2, ArrowLine);
for(i=ArrowLine-1;i>=LCD_LINE_START;i--)
{
p=MENU_Last(&menu_Dis);
if(p!=NULL)
{ LCD_OnxyPrint(" ",LCD_COLUMN_START, i);
LCD_OnxyPrint(p->ItemDisplayStr,LCD_COLUMN_START+2, i);
}
}
menu_Dis = menu;
for(i=ArrowLine+1;i<=LCD_LINE_END;i++)
{
p = MENU_Next(&menu_Dis);
if(p!=NULL)
{ LCD_OnxyPrint(" ",LCD_COLUMN_START, i);
LCD_OnxyPrint(p->ItemDisplayStr,LCD_COLUMN_START+2, i);
}
}
}
}
}