C语言----基于旋转编码器按键的菜单结构

       参考:https://blog.csdn.net/yangluoning/article/details/9313677

      在不使用STemwin的情况下,做显示界面,菜单结构将会是很重要的东西。因为如果显示逻辑复杂,当遇上需要修改的界面的时候,复杂、混乱的显示逻辑(没错,就是那种很多switch if语句的结构),将会使人抓狂。在参考了“傻孩子菜单框架”之后自己整理了一个菜单框架,主要应用在如:EC11旋转编码器上。这东西是什么,自己淘宝就知道了了。。。。

       编码器提供3种不同的逻辑:左旋转、右旋转、确定。

       这点跟我参考的菜单框架有点不同的,他的是4个按键逻辑。所以相应的要对菜单结构体进行改动。

       显示逻辑:

      (1)当检测到对应按键码时,进入菜单结构体中的按键处理函数,函数根据按键码的不同,要么移动菜单游标切换深度。要么加、减标志位。

      (2)标志位为一个16bit数组如(u16 m1_Main[2];),具有2个元素。元素0为当前的标志位状态,程序主要运用的是元素0。元素1用来保存上一次标志位的状态。如按键处理函数改变了“m1_Main[0]”的值,只要将它与“m1_Main[1]”对比,如果不同则代表这个标志位被程序改变了,将会触发此标志位管理的显示功能。这一过程类似于单片机的中断。检测到标志位改变则触发相应的中断事件。
        (3)  界面深度处理:在最高深度时,按下“ENTER”就会进入下一深度,最低深度则会进入上一深度。那么中间的深度呢?我进行了一个处理,就是在中间的深度,如果当前的标志位达到最大值,则会跳转到上一深度。如果不是,则跳转下一深度。在VS上运行下面的代码,跳转到“布类、速度、返回”的位置就很明了。当显示“返回”时,“m2_Mode”这个标志位达到最大值“2”, 此时按下“ENETER”会跳转到第一深度,如果是“布类”、“速度”,则会跳转到对应的下一深度。

       

       所以,这个菜单结构采用的是“消息->消息处理”的机制,按键可以改变标志位数值以及移动菜单游标到不同的结构体成员。实现同深度以及不同深度的结构体成员之间的跳转。

       下面代码在Visual Studio 2015下编译运行通过,代码是一个简单的洗衣机菜单的例子。结构如下图:

C语言----基于旋转编码器按键的菜单结构_第1张图片

画面上有2个位置会显示内容,分别是位置1跟位置2。这两个位置显示的内容会根据我们的按键操作进行切换,比方位置一“模式”可以切换成显示“布类”,也可以切换成显示“牛仔”。具体可以运行以下代码:

#include 
#include   
#include  

/************* LCD显示框架相关定义 **************/
#define Null 0  

#define u8    unsigned char
#define u16   unsigned short
#define u32   unsigned int

//比较标志位
#define CompareF(Flag)   (*Flag == *(Flag+1)) 
//同化标志位
#define ClearF(Flag)     (*(Flag+1) = *Flag)

#define LEFT   1
#define RIGHT  3
#define ENTER  2

void  MFunc_MD(u8 key);
void  MFunc_TB(u8 key);

//菜单标志位结构体
struct MFlag
{
	u16 m1_Main[2];
	u16 m2_Mode[2];
	u16 m2_Water[2];
	u16 m3_type[2];
	u16 m3_speed[2];
};

//菜单结构体
struct MenuItem
{ 
	u16   MN;        //当前菜单标志位最大值
	u16  *MF;        //菜单标志位
	void (*Func)(u8 key);  //菜单功能函数
	struct MenuItem  *Next;
	struct MenuItem  *Prev;
};


//声明菜单结构体变量
struct MenuItem  m1_Main[1];
struct MenuItem  m2_Option[2];
struct MenuItem  m3_Mode[2];

struct MenuItem  *pMenu;

//声明标志位结构体变量
struct MFlag  MenuF =
{
	{ 0,0 },
	{ 0,0 },
	{ 0,0 },
	{ 0,0 },
	{ 0,0 },
};


/**************** 菜单结构体变量的定义 *****************/
//一级深度菜单结构体
struct MenuItem  m1_Main[1] =
{
	{2, &MenuF.m1_Main[0] ,MFunc_TB,m2_Option, Null },
};

//二级深度菜单结构体
struct MenuItem  m2_Option[2] =
{
	{3, &MenuF.m2_Mode[0] , MFunc_MD, &m3_Mode[0], m1_Main },
	{5, &MenuF.m2_Water[0], MFunc_TB, Null,        m1_Main },
};
 
//三级深度菜单结构体
struct MenuItem  m3_Mode[2] =
{
	{5, &MenuF.m3_type[0],  MFunc_TB, Null, &m2_Option[0] },
	{5, &MenuF.m3_speed[0], MFunc_TB, Null, &m2_Option[0] },
};


/****************** 菜单跳转函数的定义 *******************/

//底部以及顶部菜单结构体成员的按键处理函数
//struct MenuItem  *Next;  其中一个为空,使用此菜单函数
//struct MenuItem  *Prev;
void  MFunc_TB(u8 key)
{
	switch (key)
	{
		case LEFT:  if(*pMenu->MF>0)  *pMenu->MF -= 1;                   //当前标志位-1

				   break;

		case RIGHT: if (*pMenu->MF < (pMenu->MN - 1))  *pMenu->MF += 1;  //当前标志位+1

				   break;

		case ENTER:  if (pMenu->Prev == Null)	pMenu = pMenu->Next + *pMenu->MF;  //跳转到下一深度
			     else			pMenu = pMenu->Prev;               //跳转到上一深度

				   *(pMenu->MF + 1) += 1;             //刷新当前标志位,否则切换深度后不显示当前选项

				   break;
	}
}

//	struct MenuItem  *Next; 都不为空,使用此菜单功能函数,此函数跳转深度后会将跳转前的菜单成员的标志位清零
//  struct MenuItem  *Prev;
void  MFunc_MD(u8 key)
{
	switch (key)
	{
		case LEFT:  if (*pMenu->MF>0)  *pMenu->MF -= 1;                  //当前标志位-1

					break;

		case RIGHT: if (*pMenu->MF < (pMenu->MN - 1))  *pMenu->MF += 1;  //当前标志位+1

					break;

		case ENTER: if (*pMenu->MF > (pMenu->MN - 2))          
			   {
			        *pMenu->MF = 0;                         //清零当前标志位,下一次进入该选项从0开始显示
			        *(pMenu->MF+1) = 0;                     //清零当前标志位,下一次进入该选项从0开始显示
			        pMenu = pMenu->Prev + *pMenu->Prev->MF; //跳转到上一深度
			   }
			    else
			   {
			        pMenu = pMenu->Next + *pMenu->MF;       //跳转到下一深度
			   }

				*(pMenu->MF + 1) += 1;             

			        break;
	}
}

/****************** 显示功能函数的定义 *******************/

//同化标志位
void  ClearFlag(void)
{
    ClearF(MenuF.m1_Main);
	ClearF(MenuF.m2_Mode);
	ClearF(MenuF.m2_Water);
	ClearF(MenuF.m3_speed);
	ClearF(MenuF.m3_type);
}

void D_MenuCur(void) 
{
	printf("菜单游标位置: ");

	switch (MenuF.m1_Main[0])
	{
		case 0: printf("位置1\r\n");  break;
		case 1: printf("位置2\r\n");  break;
		default: break;
	}                
}

void D_location1(void)
{
	printf("位置1: ");

	//比较m2_Mode[0]与m2_Mode[1]是否相等
	if(CompareF(MenuF.m2_Mode))
	{
		switch (MenuF.m2_Mode[0])
		{
		    case 0: printf("布类");  break;
		    case 1: printf("速度");  break;
		    case 2: printf("返回上层");  break;
		}
	}

	if (CompareF(MenuF.m3_speed))
	{
		switch (MenuF.m3_speed[0])
		{
			case 0: printf("最慢");  break;
			case 1: printf("慢速");  break;
			case 2: printf("中速");  break;
			case 3: printf("快速");  break;
			case 4: printf("最快");  break;
		}
	}

	if (CompareF(MenuF.m3_type))
	{
		switch (MenuF.m3_type[0])
		{
			case 0: printf("牛仔");  break;
			case 1: printf("帆布");  break;
			case 2: printf("尼龙");  break;
			case 3: printf("涤纶");  break;
			case 4: printf("棉布");  break;
		}
	}

	if (CompareF(MenuF.m1_Main))
	{
		if (MenuF.m1_Main[0] == 0)
		{
			printf("模式");
		}
	}

	printf("\r\n");
}

void D_location2(void)
{
	printf("位置2: ");

	if (CompareF(MenuF.m2_Water))
	{
		switch (MenuF.m2_Water[0])
		{
			case 0: printf("最低");  break;
			case 1: printf("低");  break;
			case 2: printf("中等");  break;
			case 3: printf("高");  break;
			case 4: printf("最高");  break;
		}
	}

	if (CompareF(MenuF.m1_Main))
	{
		if (MenuF.m1_Main[0] == 1)
		{
			printf("水量");
		}
	}

	printf("\r\n");
}

//输出画面
void printfGUI(void)
{
	printf("****************************\r\n");
	printf("                            \r\n");

	D_MenuCur();
	D_location1();
	D_location2();

	printf("                            \r\n");
	printf("****************************\r\n\r\n");
}



//打印标志位
void printfLoc(void)
{
	printf("m1_Main = {%04x,%04x};\r\n",  MenuF.m1_Main[0],  MenuF.m1_Main[1]);
	printf("m2_Mode = {%04x,%04x};\r\n",  MenuF.m2_Mode[0],  MenuF.m2_Mode[1]);
	printf("m2_Water ={%04x,%04x};\r\n",  MenuF.m2_Water[0], MenuF.m2_Water[1]);
	printf("m3_type = {%04x,%04x};\r\n",  MenuF.m3_type[0],  MenuF.m3_type[1]);
	printf("m3_speed ={%04x,%04x};\r\n",  MenuF.m3_speed[0], MenuF.m3_speed[1]);

	printf("\r\n");
}

//画面初始化
void GUI_Init(void)
{
	printf("****************************\r\n");
	printf("                            \r\n");

	printf("菜单游标位置: ");
	printf("位置1\r\n");
	printf("位置1: ");
	printf("模式\r\n");
	printf("位置2: ");
	printf("水量");

	printf("                            \r\n");
	printf("****************************\r\n\r\n");
}


int main(void)
{
	u8 temp = 0;

	pMenu = &m1_Main;

	GUI_Init();  //初始化界面

	while (1)
	{
		temp = _getch();
	

		switch (temp)
		{
		  case '1': (*(pMenu->Func))(LEFT);     break;
		  case '3': (*(pMenu->Func))(RIGHT);    break;
		  case '2': (*(pMenu->Func))(ENTER);    break;

		  default:  break;
		}
		
		printfGUI();
		ClearFlag();  //清零标志位
		printfLoc();  //打印标志位
	}
}


运行截图:

C语言----基于旋转编码器按键的菜单结构_第2张图片

C语言----基于旋转编码器按键的菜单结构_第3张图片


      当按下键盘的‘1’、‘2’、‘3’,会打印出当前的标志位,并且显示位置上面会换成标志位对应的字符。

在实际应用中,把打印字符换成输出对应图片即可。

      另外,不要问我为什么菜单游标从“位置2”移到“位置1”,位置2的字符会消失。VS的字符输出功能是,显示你当前输出的内容,你移动到游标位置1,位置2的内容没有改变。。。

      真正的LCD显示,你只需要更改你移动的地方,其他不变的地方,进行改动是需要刷图片浪费资源的。。。。如果变换一个标志位需要改动好几个位置的显示,自己修改一下“void D_location1(void)”等的显示功能函数。变动里面的判断逻辑即可。


你可能感兴趣的:(C语言)