前言: 本文的 OLED多级菜单UI 为一个 综合性 的STM32 小名目 ,经常使用 多传感器 与 OLED显示屏 成功 智能终端 的成果。名目中的多级菜单UI经常使用了较为经常出现的 结构体索引法 去成功性能与性能之间的来回切换,搭配 DHT11,RTC,LED,KEY 等器件成功高度智能化一体化操作。前期自己打板设计结构,可以衍生为 智能手表 等小玩意。目前,名目属于 裸机形态 ( CPU占用率100% ),前期或许会加上 RTOS系统 。( 本名目源码在本文末尾启动开源! )
配件实物图:
温度计:
游戏机:
引脚衔接:
一、多级菜单
随着工业化和智能化的开展,如今基本上一切名目都离不开 显示终端 。而 多级菜单 更是终端显示名目中必无法少的组成起因,其实 TFT-LCD屏幕 上可以自创移植很多 低劣的开源多级菜单 ( GUI,比如:LVGL ),而 0.96寸的OLED屏幕 上通常须要自己去适配和编程多级菜单。
精巧的多级菜单:
网上的广泛驳回的多级菜单的计划是 基于索引 或许 结构树 ,其中, 索引法居多 。 索引法的好处 :可阅读性好,拓展性也不错,查找的性能差不多是最优,就是有点 占用内存空间 。
二、索引法多级菜单成功
网上关于索引法成功多级菜单性能有很多基础教程,笔者就依照 本名目 中的详细实现代码环节给大家解说一下 索引法成功多级菜单 。 特意说明: 本名目间接经常使用了误点原子的精英板作为外围板,所以读者好友复现代码还是很繁难的。
首先,基于 索引法成功多级菜单 的 首要条件 是先确定名目中将经常使用到 几特性能按键 ( 比如:向前,向后,确定,分开等等 )本名目中,笔者经常使用到了 3个按键 : 下一个(next) , 确定(enter) , 分开(back) 。所以,接下首先定义一个 结构体 ,结构体中一共有 5个变量 ( 3 + 2 ),区分为: 以后索引序号(current) , 向下一个(next) , 确定(enter) , 分开(back) , 以后口头函数(void) 。其中,标红的为须要设计的按键(笔者这里有3个),标绿的则为固定的 索引号 与 该索引下须要口头的函数 。
typedef struct
{u8 current; //以后形态索引号u8 next; //向下一个u8 enter; //确定u8 back; //分开void (*current_operation)(void); //以后形态应该口头的操作
} Menu_table;
接上去就是 定义一个数组 去选择整个名目菜单的 逻辑顺序 ( 应用索引号 )
Menu_table table[30]=
{{0,0,1,0,(*home)}, //一级界面(主页面) 索引,向下一个,确定,分开{1,2,5,0,(*Temperature)}, //二级界面 温湿度{2,3,6,0,(*Palygame)}, //二级界面 游戏{3,4,7,0,(*Setting)}, //二级界面 设置{4,1,8,0,(*Info)}, //二级界面 消息{5,5,5,1,(*TestTemperature)}, //三级界面:DHT11测量温湿度{6,6,6,2,(*ControlGame)}, //三级界面:谷歌小恐龙Dinogame{7,7,9,3,(*Set)}, //三级界面:设置普通外设形态 LED{8,8,8,4,(*Information)}, //三级界面:作者和关系名目消息{9,9,7,3,(*LED)}, //LED管理
};
这里解释一下这个数组中各元素的意义,因为咱们在前面先定义了 Menu_table 结构体 , 结构体成员变量 区分与 数组中元素 对应。比如: {0,0,1,0,(*home)} ,代表了 索引号为0 按向下键(next) 转入索引号为0, 按确定键(enter) 转入索引号为1, 按分开键(back) 转入索引号为0,索引号为0时 口头home函数
在举一个例子协助大家了解一下,比如,咱们以后程序处在 索引号为2(游戏界面) ,就会口头 Playgame函数 。此时,假设按下 next按键 ,程序以后索引号就会变为3,并且口头索引号为3时刻的 Setting函数 。假设按下 enter按键 ,程序以后索引号就会变为6,并且口头索引号为6时刻的 ControlGame函数 。假设按下 back按键 ,程序以后索引号就会变为0,并且口头索引号为0时刻的 home函数 。
再接下就是 按键解决函数 :
uint8_t func_index = 0; //主程序此时所在程序的索引值void Menu_key_set(void)
{if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的状况,适配游戏{ func_index=table[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) == 2) && (func_index != 6)){func_index=table[func_index].enter; //按键enter按下后的索引号OLED_Clear();}if(KEY_Scan(1) == 3){func_index=table[func_index].back; //按键back按下后的索引号OLED_Clear(); }current_operation_index=table[func_index].current_operation; //口头以后索引号所对应的性能函数(*current_operation_index)();//口头以后操作函数
}//按键函数
u8 KEY_Scan(u8 mode)
{static u8 key_up=1;if(mode)key_up=1; if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)){HAL_Delay(100); //消抖key_up=0;if(KEY0==0)return 1;else if(KEY1==0)return 2;else if(WK_UP==1)return 3;}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; return 0;
}
说明2点:
(1) 因为是目前本名目是 裸机形态 下运转的,所以 CPU占用率自动是100% 的,所以这里经常使用按键 允许连按 时,关于菜单的切换更好些。
(2) 或许部分索引号下的口头函数,须要经常使用到曾经定义的3个按键(比如,本名目中的DInogame中)。所以,可以 在须要差异化的索引号下去屏蔽原先的按键性能 。如下:
if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的状况,适配游戏{ func_index=table[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) == 2) && (func_index != 6)) //屏蔽掉索引6下的状况,适配游戏{func_index=table[func_index].enter; //按键enter按下后的索引号OLED_Clear();}
(3) 笔者这里是经常使用 全屏刷新 去切换性能界面,同时,没有启用初级算法去减速显示,所以或许在切换界面的时刻成果普通。读者好友可以试试依据自己的UI状况经常使用 部分刷新 ,这样或许名目会愈加丝滑一点。
本名目中的菜单索引图:
三、此名目外部性能成功(简化智能手表)
3.1 OLED显示
OLED就是反常的驱动与显示,有才干的读者好友可以经常使用初级算法去减速OLED屏幕的刷新率,可以使自己的多级菜单切换起来更丝滑。假设对OLED驱动与显示不太相熟的好友可以去看看自己另一篇博客:【剧烈介绍】基于stm32的OLED各种显示成功(含灵活图)_混分巨兽龙某某的博客-CSDN博客_stm32oled显示
惟一须要留意的点就是须要去制造 菜单外面的UI图标 (注用意片大小能否适宜):
假设是 黑色图片 的话,可以间接经常使用 PCtoLCD2002完美版 启动取模:
3.2 KEY按键
KEY按键 留意消抖(倡导裸机状况下允许延续按动) ,同时留意自己实践配件状况去启动编程( 电阻能否存在上拉或许下拉 )。
3.3 DinoGame成功
谷歌公司最近比拟盛行的小游戏,笔者之前有文章启动了STM32的成功复刻。博客地址:基于STM32的小游戏——谷歌小恐龙(Chrome Dino Game)_混分巨兽龙某某的博客-CSDN博客_谷歌恐龙
3.4 LED管理和DHT11模块
LED和DHT11模块 其实都属于 外设管理 ,这里读者好友可以依据自己的实践状况去取舍。须要留意的是尽或许适配一下自己多级菜单( 外设管理也须要留意一下按键布置,可以参考笔者名目的设计 )。
四、CubeMX性能
1、RCC性能外部高速晶振(精度更高)——HSE;
2、SYS性能:Debug设置成Serial Wire ( 否则或许造成芯片自锁 );
3、I2C2性能:这里不间接经常使用CubeMX的I2C2,经常使用GPIO模拟(PB10:CLK;PB11:SDA)
4、RTC性能:年月日,时分秒;
5、TIM2性能:由下面可知DHT11的经常使用须要us级的提前函数,HAL库自带只要ms的,所以须要自己设计一个定时器;
6、KEY按键性能:PE3,PE4和PA0设置为端口输入(开发板原理图)
7、时钟树性能:
8、文件性能
五、代码
5.1 OLED驱动代码
此部分OLED的基本驱动函数,笔者经常使用的是 I2C驱动的0.96寸OLED屏幕 。所以,首先须要经常使用 GPIO模拟I2C通讯 。随后,经常使用I2C通讯去驱动OLED。(此部分代码蕴含了屏幕驱动与基础显示,假设对OLED显示不太了解的好友可以去看看上文提到的笔者的另一篇文章)
oled.h:
#ifndef __OLED_H
#define __OLED_H#include "main.h"#define u8 uint8_t
#define u32 uint32_t#define OLED_CMD 0 //写命令
#define OLED_DATA 1 //写数据#define OLED0561_ADD 0x78 // OLED I2C地址
#define COM 0x00 // OLED
#define DAT 0x40 // OLED #define OLED_MODE 0
#define SIZE 8
#define XLevelL 0x00
#define XLevelH 0x10
#define Max_Column 128
#define Max_Row 64
#define Brightness 0xFF
#define X_WIDTH 128
#define Y_WIDTH 64//-----------------OLED IIC GPIO启动模拟----------------#define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET) //GPIO_ResetBits(GPIOB,GPIO_Pin_10)//SCL
#define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET) //GPIO_SetBits(GPIOB,GPIO_Pin_10)#define OLED_SDIN_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET) // GPIO_ResetBits(GPIOB,GPIO_Pin_11)//SDA
#define OLED_SDIN_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET) // GPIO_SetBits(GPIOB,GPIO_Pin_11)//I2C GPIO模拟
void IIC_Start();
void IIC_Stop();
void IIC_WaitAck();
void IIC_WriteByte(unsigned char IIC_Byte);
void IIC_WriteCommand(unsigned char IIC_Command);
void IIC_WriteData(unsigned char IIC_Data);
void OLED_WR_Byte(unsigned dat,unsigned cmd);//性能函数
void OLED_Init(void);
void OLED_WR_Byte(unsigned dat,unsigned cmd);void OLED_FillPicture(unsigned char fill_Data);
void OLED_SetPos(unsigned char x, unsigned char y);
void OLED_DisplayOn(void);
void OLED_DisplayOff(void);
void OLED_Clear(void);
void OLED_On(void);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);
u32 oled_pow(u8 m,u8 n);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2);
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size);#endif
oled.c:
#include "oled.h"
#include "asc.h" //字库(可以自己制造)
#include "main.h"/********************GPIO 模拟I2C*******************/
//留意:这里没有间接经常使用HAL库中的模拟I2C
/**********************************************
//IIC Start
**********************************************/
void IIC_Start()
{OLED_SCLK_Set() ;OLED_SDIN_Set();OLED_SDIN_Clr();OLED_SCLK_Clr();
}/**********************************************
//IIC Stop
**********************************************/
void IIC_Stop()
{OLED_SCLK_Set() ;OLED_SDIN_Clr();OLED_SDIN_Set();}void IIC_WaitAck()
{OLED_SCLK_Set() ;OLED_SCLK_Clr();
}
/**********************************************
// IIC Write byte
**********************************************/void IIC_WriteByte(unsigned char IIC_Byte)
{unsigned char i;unsigned char m,da;da=IIC_Byte;OLED_SCLK_Clr();for(i=0;i<8;i++){m=da;// OLED_SCLK_Clr();m=m&0x80;if(m==0x80){OLED_SDIN_Set();}else OLED_SDIN_Clr();da=da<<1;OLED_SCLK_Set();OLED_SCLK_Clr();}}
/**********************************************
// IIC Write Command
**********************************************/
void IIC_WriteCommand(unsigned char IIC_Command)
{IIC_Start();IIC_WriteByte(0x78); //Slave address,SA0=0IIC_WaitAck();IIC_WriteByte(0x00); //write commandIIC_WaitAck();IIC_WriteByte(IIC_Command);IIC_WaitAck();IIC_Stop();
}
/**********************************************
// IIC Write>外围部分之一,任何一个游戏都是须要去绘制和构建游戏的图形以及模型的。好的游戏往往都具有很好的游戏模型和精巧UI,很多3A大作都具有这样的特性。
dinogame.h:
#ifndef __DINOGAME_H
#define __DINOGAME_Hvoid OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
void OLED_DrawBMPFast(const unsigned char BMP[]);
void oled_drawbmp_block_clear(int bx, int by, int clear_size);
void OLED_DrawGround();
void OLED_DrawCloud();
void OLED_DrawDino();
void OLED_DrawCactus();
int OLED_DrawCactusRandom(unsigned char ver, unsigned char reset);
int OLED_DrawDinoJump(char reset);
void OLED_DrawRestart();
void OLED_DrawCover();#endif
dinogame.c代码:
#include "oled.h"
#include "oledfont.h"
#include "stdlib.h"/***********性能形容:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{unsigned int j=0;unsigned char x,y;if(y1%8==0) y=y1/8;else y=y1/8+1;for(y=y0;y<y1;y++){OLED_SetPos(x0,y);for(x=x0;x<x1;x++){OLED_WR_Byte(BMP[j++],OLED_DATA);}}
}// 极速绘制图像
void OLED_DrawBMPFast(const unsigned char BMP[])
{unsigned int j = 0;unsigned char x, y;for (y = 0; y < 8; y++){OLED_SetPos(0, y);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = 0; x < 128; x++){IIC_WriteByte(BMP[j++]);IIC_WaitAck();}IIC_Stop();}
}void oled_drawbmp_block_clear(int bx, int by, int clear_size)
{unsigned int i;OLED_SetPos(bx, by);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (i = 0; i < clear_size; i++){if (bx + i>128) break;IIC_WriteByte(0x0);IIC_WaitAck();}IIC_Stop();
}void OLED_DrawGround()
{static unsigned int pos = 0;unsigned char speed = 5;unsigned int ground_length = sizeof(GROUND);unsigned char x;OLED_SetPos(0, 7);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = 0; x < 128; x++){IIC_WriteByte(GROUND[(x+pos)%ground_length]);IIC_WaitAck();}IIC_Stop();pos = pos + speed;//if(pos>ground_length) pos=0;
}// 绘制云朵
void OLED_DrawCloud()
{static int pos = 128;static char height=0;char speed = 3;unsigned int i=0;int x;int start_x = 0;int length = sizeof(CLOUD);unsigned char byte;//if (pos + length <= -speed) pos = 128;if (pos + length <= -speed){pos = 128;height = rand()%3;}if(pos < 0){start_x = -pos;OLED_SetPos(0, 1+height);}else{OLED_SetPos(pos, 1+height);}IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = start_x; x < length + speed; x++){if (pos + x > 127) break;if (x < length) byte = CLOUD[x];else byte = 0x0;IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();pos = pos - speed;
}// 绘制小恐龙
void OLED_DrawDino()
{static unsigned char dino_dir = 0;unsigned int j=0;unsigned char x, y;unsigned char byte;dino_dir++;dino_dir = dino_dir%2;for(y=0; y<2; y++){OLED_SetPos(16, 6+y);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = 0; x < 16; x++){j = y*16 + x;byte = DINO[dino_dir][j];IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();}
}// 绘制仙人掌阻碍物
void OLED_DrawCactus()
{char speed = 5;static int pos = 128;int start_x = 0;int length = sizeof(CACTUS_2)/2;unsigned int j=0;unsigned char x, y;unsigned char byte;if (pos + length <= 0){oled_drawbmp_block_clear(0, 6, speed);pos = 128;}for(y=0; y<2; y++){if(pos < 0){start_x = -pos;OLED_SetPos(0, 6+y);}else{OLED_SetPos(pos, 6+y);}IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = start_x; x < length; x++){if (pos + x > 127) break;j = y*length + x;byte = CACTUS_2[j];IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();}oled_drawbmp_block_clear(pos + length, 6, speed); // 肃清残影pos = pos - speed;
}// 绘制随机发生的仙人掌阻碍物
int OLED_DrawCactusRandom(unsigned char ver, unsigned char reset)
{char speed = 5;static int pos = 128;int start_x = 0;int length = 0;unsigned int i=0, j=0;unsigned char x, y;unsigned char byte;if (reset == 1){pos = 128;oled_drawbmp_block_clear(0, 6, speed);return 128;}if (ver == 0) length = 8; //sizeof(CACTUS_1) / 2;else if (ver == 1) length = 16; //sizeof(CACTUS_2) / 2;else if (ver == 2 || ver == 3) length = 24;for(y=0; y<2; y++){if(pos < 0){start_x = -pos;OLED_SetPos(0, 6+y);}else{OLED_SetPos(pos, 6+y);}IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = start_x; x < length; x++){if (pos + x > 127) break;j = y*length + x;if (ver == 0) byte = CACTUS_1[j];else if (ver == 1) byte = CACTUS_2[j];else if(ver == 2) byte = CACTUS_3[j];else byte = CACTUS_4[j];IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();}oled_drawbmp_block_clear(pos + length, 6, speed);pos = pos - speed;return pos + speed;
}// 绘制腾跃小恐龙
int OLED_DrawDinoJump(char reset)
{char speed_arr[] = {1, 1, 3, 3, 4, 4, 5, 6, 7};static char speed_idx = sizeof(speed_arr)-1;static int height = 0;static char dir = 0;//char speed = 4;unsigned int j=0;unsigned char x, y;char offset = 0;unsigned char byte;if(reset == 1){height = 0;dir = 0;speed_idx = sizeof(speed_arr)-1;return 0;}if (dir==0){height += speed_arr[speed_idx];speed_idx --;if (speed_idx<0) speed_idx = 0;}if (dir==1){height -= speed_arr[speed_idx];speed_idx ++;if (speed_idx>sizeof(speed_arr)-1) speed_idx = sizeof(speed_arr)-1;}if(height >= 31){dir = 1;height = 31;}if(height <= 0){dir = 0;height = 0;}if(height <= 7) offset = 0;else if(height <= 15) offset = 1;else if(height <= 23) offset = 2;else if(height <= 31) offset = 3;else offset = 4;for(y=0; y<3; y++) // 4{OLED_SetPos(16, 5- offset + y);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = 0; x < 16; x++) // 32{j = y*16 + x; // 32byte = DINO_JUMP[height%8][j];IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();}if (dir == 0) oled_drawbmp_block_clear(16, 8- offset, 16);if (dir == 1) oled_drawbmp_block_clear(16, 4- offset, 16);return height;
}// 绘制重启
void OLED_DrawRestart()
{unsigned int j=0;unsigned char x, y;unsigned char byte;//OLED_SetPos(0, 0);for (y = 2; y < 5; y++){OLED_SetPos(52, y);IIC_Start();IIC_WriteByte(0x78);IIC_WaitAck();IIC_WriteByte(0x40);IIC_WaitAck();for (x = 0; x < 24; x++){byte = RESTART[j++];IIC_WriteByte(byte);IIC_WaitAck();}IIC_Stop();}OLED_ShowString(10, 3, "GAME", 16);OLED_ShowString(86, 3, "OVER", 16);
}
// 绘制封面
void OLED_DrawCover()
{OLED_DrawBMPFast(COVER);
}
5.3 谷歌小恐龙的运转管理代码
control.h:
#ifndef __CONTROL_H
#define __CONTROL_Hint get_key();
void Game_control();#endif
control.c:
#include "control.h"
#include "oled.h"
#include "dinogame.h"
#include "stdlib.h"unsigned char key_num = 0;
unsigned char cactus_category = 0;
unsigned char cactus_length = 8;
unsigned int score = 0;
unsigned int highest_score = 0;
int height = 0;
int cactus_pos = 128;
unsigned char cur_speed = 30;
char failed = 0;
char reset = 0;int get_key()
{if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0){HAL_Delay(10); //提前if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0){return 2;}}if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0){HAL_Delay(10); //提前if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0){return 1;}}if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1){HAL_Delay(10); //提前if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1){return 3;}}return 0;
}void Game_control()
{while(1){if(get_key() == 3) //wk_up按键按下强迫分开一次性循环{break;}if (failed == 1){OLED_DrawRestart();key_num = get_key();if (key_num == 2){if(score > highest_score) highest_score = score;score = 0;failed = 0;height = 0;reset = 1;OLED_DrawDinoJump(reset);OLED_DrawCactusRandom(cactus_category, reset);OLED_Clear();}continue;}score ++;if (height <= 0) key_num = get_key();OLED_DrawGround();OLED_DrawCloud();if (height>0 || key_num == 1) height = OLED_DrawDinoJump(reset);else OLED_DrawDino();cactus_pos = OLED_DrawCactusRandom(cactus_category, reset);if(cactus_category == 0) cactus_length = 8;else if(cactus_category == 1) cactus_length = 16;else cactus_length = 24;if (cactus_pos + cactus_length < 0){cactus_category = rand()%4;OLED_DrawCactusRandom(cactus_category, 1);}if ((height < 16) && ( (cactus_pos>=16 && cactus_pos <=32) || (cactus_pos + cactus_length>=16 && cactus_pos + cactus_length <=32))){failed = 1;}OLED_ShowString(35, 0, "HI:", 12);OLED_ShowNum(58, 0, highest_score, 5, 12);OLED_ShowNum(98, 0, score, 5, 12);reset = 0;cur_speed = score/20;if (cur_speed > 29) cur_speed = 29;HAL_Delay(30 - cur_speed);
// HAL_Delay(500);key_num = 0;}}
5.4 多级菜单外围代码:
menu.h:
#ifndef __MENU_H
#define __MENU_H#include "main.h"
#define u8 unsigned char//按键定义
#define KEY0 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) //低电平有效 KEY0
#define KEY1 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) //低电平有效
#define WK_UP HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) //高电平有效typedef struct
{u8 current; //以后形态索引号u8 next; //向下一个u8 enter; //确定u8 back; //分开void (*current_operation)(void); //以后形态应该口头的操作
} Menu_table;//界面UI
void home();
void Temperature();
void Palygame();
void Setting();
void Info();void Menu_key_set(void);
u8 KEY_Scan(u8 mode);void TestTemperature();
void ConrtolGame();
void Set();
void Information();void LED();
void RTC_display();#endif
menu.c:
#include "menu.h"
#include "oled.h"
#include "gpio.h"
#include "dinogame.h"
#include "control.h"
#include "DHT11.h"
#include "rtc.h"RTC_DateTypeDef GetData; //失掉日期结构体RTC_TimeTypeDef GetTime; //失掉期间结构体//UI界面
//主页
/****************************************************/
//UI库/****************************************************/void (*current_operation_index)(); Menu_table table[30]=
{{0,0,1,0,(*home)}, //一级界面(主页面) 索引,向下一个,确定,分开{1,2,5,0,(*Temperature)}, //二级界面 温湿度{2,3,6,0,(*Palygame)}, //二级界面 游戏{3,4,7,0,(*Setting)}, //二级界面 设置{4,1,8,0,(*Info)}, //二级界面 消息{5,5,5,1,(*TestTemperature)}, //三级界面:DHT11测量温湿度{6,6,6,2,(*ConrtolGame)}, //三级界面:谷歌小恐龙Dinogame{7,7,9,3,(*Set)}, //三级界面:设置普通外设形态 LED{8,8,8,4,(*Information)}, //三级界面:作者和关系名目消息{9,9,7,3,(*LED)}, //LED管理
};uint8_t func_index = 0; //主程序此时所在程序的索引值void Menu_key_set(void)
{if((KEY_Scan(1) == 1) && (func_index != 6)){ func_index=table[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) == 2) && (func_index != 6)){func_index=table[func_index].enter; //按键enter按下后的索引号OLED_Clear();}if(KEY_Scan(1) == 3){func_index=table[func_index].back; //按键back按下后的索引号OLED_Clear(); }current_operation_index=table[func_index].current_operation; //口头以后索引号所对应的性能函数(*current_operation_index)();//口头以后操作函数
}void home()
{RTC_display();OLED_DrawBMP(0,0,20,3,signal_BMP);OLED_DrawBMP(20,0,36,2,gImage_bulethouch);OLED_DrawBMP(112,0,128,2,gImage_engery);OLED_DrawBMP(4,6,20,8,gImage_yes);OLED_DrawBMP(12,4,28,6,gImage_left);OLED_DrawBMP(40,2,88,8,gImage_home);OLED_DrawBMP(99,4,115,6,gImage_right);OLED_DrawBMP(107,6,123,8,gImage_back);
}void Temperature()
{RTC_display();OLED_DrawBMP(0,0,20,3,signal_BMP);OLED_DrawBMP(20,0,36,2,gImage_bulethouch);OLED_DrawBMP(112,0,128,2,gImage_engery);OLED_DrawBMP(4,6,20,8,gImage_yes);OLED_DrawBMP(12,4,28,6,gImage_left);OLED_DrawBMP(40,2,88,8,gImage_temp);OLED_DrawBMP(99,4,115,6,gImage_right);OLED_DrawBMP(107,6,123,8,gImage_back);
}void Palygame()
{RTC_display();OLED_DrawBMP(0,0,20,3,signal_BMP);OLED_DrawBMP(20,0,36,2,gImage_bulethouch);OLED_DrawBMP(112,0,128,2,gImage_engery);OLED_DrawBMP(4,6,20,8,gImage_yes);OLED_DrawBMP(12,4,28,6,gImage_left);OLED_DrawBMP(40,2,88,8,gImage_playgame);OLED_DrawBMP(99,4,115,6,gImage_right);OLED_DrawBMP(107,6,123,8,gImage_back);
}void Setting()
{RTC_display();OLED_DrawBMP(0,0,20,3,signal_BMP);OLED_DrawBMP(20,0,36,2,gImage_bulethouch);OLED_DrawBMP(112,0,128,2,gImage_engery);OLED_DrawBMP(4,6,20,8,gImage_yes);OLED_DrawBMP(12,4,28,6,gImage_left);OLED_DrawBMP(40,2,88,8,gImage_setting);OLED_DrawBMP(99,4,115,6,gImage_right);OLED_DrawBMP(107,6,123,8,gImage_back);
}void Info()
{RTC_display();OLED_DrawBMP(0,0,20,3,signal_BMP);OLED_DrawBMP(20,0,36,2,gImage_bulethouch);OLED_DrawBMP(112,0,128,2,gImage_engery);OLED_DrawBMP(4,6,20,8,gImage_yes);OLED_DrawBMP(12,4,28,6,gImage_left);OLED_DrawBMP(40,2,88,8,gImage_info);OLED_DrawBMP(99,4,115,6,gImage_right);OLED_DrawBMP(107,6,123,8,gImage_back);
}//按键函数,不允许连按
u8 KEY_Scan(u8 mode)
{static u8 key_up=1;if(mode)key_up=1; if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)){HAL_Delay(100); //消抖key_up=0;if(KEY0==0)return 1;else if(KEY1==0)return 2;else if(WK_UP==1)return 3;}else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; return 0;
}void TestTemperature()
{DHT11();
}void ConrtolGame()
{Game_control();
}void Set()
{OLED_ShowString(0,0,"Peripherals: Lights",16);OLED_ShowString(0,2,"Status: Closed",16);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
}void Information()
{OLED_ShowString(0,0,"Author:Sneak",16);OLED_ShowString(0,2,"Date:2022/8/23",16);OLED_ShowString(0,4,"Lab: Multi-level menu",16);
}void LED()
{OLED_ShowString(0,0,"Peripherals: Lights",16);OLED_ShowString(0,2,"Status: Open",16);HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
}void RTC_display() //RTC????
{/* Get the RTC current Time */HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);/* Get the RTC current Date */HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);/* Display date Format : yy/mm/dd *//* Display time Format : hh:mm:ss */OLED_ShowNum(40,0,GetTime.Hours,2,16); //hourOLED_ShowString(57,0,":",16); OLED_ShowNum(66,0,GetTime.Minutes,2,16); //minOLED_ShowString(83,0,":",16); OLED_ShowNum(93,0,GetTime.Seconds,2,16); //seconds
}
六、名目展示
多级菜单(简化版智能手表)
总结与代码开源
总结:本名目目前还处于最后辈版本,十分繁难,前期笔者将抽期间去精进优化该多级菜单名目。其中,UI界面中的电池与信号目前都还处于贴图形态,前期笔者会加上库仑计测量电池电量等。文章中指出了须要留意的中央与可以改良的点,感兴味的好友可以彼此交换交换。(积分够的好友可以允许一下,假设不够的话,点个关注,评论区留下邮箱,笔者看到会尽快发送名目代码。)
代码地址:基于STM32的OLED多级菜单名目(简化版智能手表)-嵌入式文档类资源-CSDN文库