单片机实现屏幕界面,多层菜单

6 篇文章 0 订阅
5 篇文章 1 订阅

单片机实现屏幕界面,多层菜单

简介

  1. 编写环境:Keil μVision 5
  2. 硬件设备:STM32F103C8T6、Nokia 5110屏幕,EC11旋转编码器
  3. 本界面控件理论上不限制屏幕、不限制单片机型号(未验证)
  4. 移植方便

代码分析

数据结构

  1. 行元素结构体
typedef struct{
	uint16_t enterViewIndex;//按下确定键跳转的界面
	char * text;			//当前行显示的文本
	HandlerFunc handler;	//显示函数
}RowListTypeDef;

数组最大长度:65536
每个元素占用12字节
因为此数据不需要修改,所以可使用constcode(C51)修饰

HandlerFunc是函数指针,此函数即可作为行元素的显示函数,又可作为按键处理函数,其类型为:

typedef void(*HandlerFunc)(uint16_t index, char* p, uint8_t key);   

三个形参的作用分别是:

@param index: 指向此函数的RowListTypeDef在数组中的下标

@param p: 指向当前RowListTypeDef元素的text指针指向的字符串

@param key: 若按下按键的值大于等于6(KEY_ADD),则此形参会是非0值(KEY_NONE);若小于6,则传入0(KEY_NONE)

  1. 界面结构体
typedef struct {
	const RowListTypeDef * const list;//指向当前层所指向的行元素
	uint16_t lengthOfList;		//指向的行元素的长度
	uint16_t parentViewIndex;	//本元素所属层的标号
	uint16_t startRow; //记录在上一层时的开始行索引
	uint8_t currRow;   //记录在上一层时的行索引
}ViewListTypeDef;

数组最大长度:65535(0~65534) —— 65535代表RowListTypeDef元素没有指向的ViewListTypeDef元素

每个元素占用12字节

定义ViewListTypeDef型数组是可以使用VIEW_MEMBER_FORMAT(x)帮助编写;如:

ViewListTypeDef menu[] = {
	VIEW_MEMBER_FORMAT(rowListHome),
	VIEW_MEMBER_FORMAT(rowListSettingRoot),
	VIEW_MEMBER_FORMAT(rowListView1),
	VIEW_MEMBER_FORMAT(rowListView2),
	VIEW_MEMBER_FORMAT(rowListView3),
VIEW_MEMBER_FORMAT(rowListView1_1),
};

其中VIEW_MEMBER_FORMAT宏定义为

#define ROW_LENGTH(x)			    ((uint16_t)(sizeof(x)/sizeof(RowListTypeDef)))
#define VIEW_MEMBER_FORMAT(x)       {x,ROW_LENGTH(x),0,0,0}
  1. 游标结构体
//游标,只需要定义一个即可    ==> 8字节(byte)
typedef struct {
	uint8_t currRow;			       	//当前指向元素
	uint8_t keyval;				        //记录按键
  uint16_t currViewIndex;		        //当前指向层
	uint16_t startRow;	                //屏幕第一行显示的行元素索引
	uint16_t rowNum;		            //记录当前层的行元素数
}CursorTypeDef;

函数作用

本控件函数很少,只有两个,即:

  1. void View_Init(ViewListTypeDef * v, CursorTypeDef * c)

此函数的作用是初始化界面控件:将用户定义好的ViewListTypeDef数组的地址和CursorTypeDef地址初始化到控件

  1. void View_Loop(void)

此函数作用是在处理界面数据。注意:需要将此函数放入主循环中,每隔一段时间调用一次

间隔时间典型值是100ms。

注意:并不是本控件消耗的时间多,而是屏幕驱动程序消耗的时间太多,本人的屏幕驱动是模拟的SPI时序而不是单片机硬件SPI,故屏幕驱动消耗的时间太多。控件每次需要不到1000个机器周期,而驱动程序是其上百倍。

若使用硬件外设驱动屏幕,则可以将间隔时间适当调小一点,同时注意不要低于屏幕刷新周期。

界面设计

设计界面时只需要定义几个数组即可。

首先定义RowListTypeDef类型数组,根据界面数定义数组个数,根据每个界面包含的行元素数定义每个数组的长度。

然后定义ViewListTypeDef类型数组,定义一个即可,数组长度是界面数决定的。

例如,设计这样一个界面:

home
root
view1
view2
view3
view1-1
rowHome
row1
row2
row3
row1-1
row1-2
row1-3
row1-4
row1-5
row1-6
row1-7
row1-8
row1-9
row2-1
row2-2
row2-3
row2-4
row2-5
row2-6
row2-7
row2-8
row3-1
row3-2
row3-3
row3-4
row3-5
row3-6
row3-7
row3-8
row3-9
row3-10
row3-11
row3-12
row3-13
row3-14
row3-15
row1-1-1
row1-1-2
row1-1-3
row1-1-4
row1-1-5
row1-1-6
row1-1-7
row1-1-8

则需要这样定义数组

const RowListTypeDef rowListHome[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
  	{1,"home",NULL},
};

const RowListTypeDef rowListSRoot[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
	{2,"Row 1",NULL},
	{3,"Row 2",NULL},
	{4,"Row 3",NULL},
};

const RowListTypeDef rowListView1[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
	{5,"Row 1-1",NULL}, 
	{VIEW_NONE,"Row 1-2",NULL},
	{VIEW_NONE,"Row 1-3",NULL},
	{VIEW_NONE,"Row 1-4",NULL},
	{VIEW_NONE,"Row 1-5",NULL},
	{VIEW_NONE,"Row 1-6",NULL},
  	{VIEW_NONE,"Row 1-7",NULL},
  	{VIEW_NONE,"Row 1-8",NULL},
  	{VIEW_NONE,"Row 1-9",NULL},
};

const RowListTypeDef rowListView2[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
	{VIEW_NONE,"Row 2-1",NULL},
	{VIEW_NONE,"Row 2-2",NULL},
	{VIEW_NONE,"Row 2-3",NULL},
	{VIEW_NONE,"Row 2-4",NULL},
	{VIEW_NONE,"Row 2-5",NULL},
  	{VIEW_NONE,"Row 2-6",NULL},
  	{VIEW_NONE,"Row 2-7",NULL},
  	{VIEW_NONE,"Row 2-8",NULL},
};

const RowListTypeDef rowListView3[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
	{VIEW_NONE,"Row 3-1",NULL},
	{VIEW_NONE,"Row 3-2",NULL},
	{VIEW_NONE,"Row 3-3",NULL},
	{VIEW_NONE,"Row 3-4",NULL},
	{VIEW_NONE,"Row 3-5",NULL},
	{VIEW_NONE,"Row 3-6",NULL},
  	{VIEW_NONE,"Row 3-7",NULL},
  	{VIEW_NONE,"Row 3-8",NULL},
  	{VIEW_NONE,"Row 3-9",NULL},
  	{VIEW_NONE,"Row 3-10",NULL},
  	{VIEW_NONE,"Row 3-11",NULL},
  	{VIEW_NONE,"Row 3-12",NULL},
  	{VIEW_NONE,"Row 3-13",NULL},
  	{VIEW_NONE,"Row 3-14",NULL},
  	{VIEW_NONE,"Row 3-15",NULL},
};

const RowListTypeDef rowListView1_1[] = {  
	//{.enterViewIndex  |  .x  |  .text  |  .handler},
	{VIEW_NONE,"Row 1-1-1",NULL}, 
	{VIEW_NONE,"Row 1-1-2",NULL},
	{VIEW_NONE,"Row 1-1-3",NULL},
	{VIEW_NONE,"Row 1-1-4",NULL},
	{VIEW_NONE,"Row 1-1-5",NULL},
  	{VIEW_NONE,"Row 1-1-6",NULL},
	{VIEW_NONE,"Row 1-1-7",NULL},
  	{VIEW_NONE,"Row 1-1-8",NULL},
};

ViewListTypeDef menu[] = {
	//.currIndex | .parentViewIndex | .list | .lengthOfList | .display
	VIEW_MEMBER_FORMAT(rowListHome),
	VIEW_MEMBER_FORMAT(rowListSRoot),
	VIEW_MEMBER_FORMAT(rowListView1),
	VIEW_MEMBER_FORMAT(rowListView2),
	VIEW_MEMBER_FORMAT(rowListView3),
  	VIEW_MEMBER_FORMAT(rowListView1_1),
};

程序格式

程序需要定义一个全局变量游标CursorTypeDef,如:

CursorTypeDef cursor;

然后在main函数中调用控件初始化程序View_Init

View_Init(menu,&cursor);

在程序主循环中每隔一段时间调用程序View_Loop.例如每隔100ms调用一次

当有按键按下时,只需要根据按键的不同给cursor.keyval变量赋不同的值即可.例如:

rotaryval = ReadRotaryEncoder();
if(rotaryval == ROTARY_LEFT)
{
    cursor.keyval = KEY_UP;
}else if(rotaryval == ROTARY_RIGHT)
{
    cursor.keyval = KEY_DOWN;
}

其中按键值有以下:

#define KEY_NONE			0						//没有按下按键
#define KEY_ENTER			1						//按下<确定>键
#define KEY_RETURN			2						//按下<返回>键(返回上一层)
#define KEY_HOME			3						//按下<首页>键
#define KEY_DOWN			4						//按下<下>键
#define KEY_UP				5						//按下<上>键
#define KEY_ADD				6						//按下<加>键
#define KEY_SUB				7						//按下<减>键

代码文件

gitee:https://gitee.com/figght/zBitsView.git

GitHub:https://github.com/figght/zBitsView.git

  • 8
    点赞
  • 109
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值