基于STM32的OLED多级菜单GUI实现(简化版智能手表)

        前言:本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DHT11,RTC,LED,KEY等器件实现高度智能化一体化操作。后期自己打板设计结构,可以衍生为智能手表等小玩意。目前,项目属于裸机状态CPU占用率100%),后期可能会加上RTOS系统。(本项目源码在本文末尾进行开源!

        硬件实物图:

效果图:

        温度计:

         游戏机:

引脚连接:

OLED模块:

VCC --> 3.3V

GND --> GND

SCL --> PB10

SDA --> PB11

DHT11模块:

DATA --> PB9

VCC --> 3.3V

GND --> GND

KEY模块(这部分笔者直接使用了正点原子精英板上的):

KEY0 --> PE4

KEY1 --> PE3

KEY_UP --> PA0

一、多级菜单

        随着工业化和自动化的发展,如今基本上所有项目都离不开显示终端。而多级菜单更是终端显示项目中必不可少的组成因素,其实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 Data
**********************************************/
void IIC_WriteData(unsigned char IIC_Data)
{IIC_Start();IIC_WriteByte(0x78);			//D/C#=0; R/W#=0IIC_WaitAck();IIC_WriteByte(0x40);			//write dataIIC_WaitAck();IIC_WriteByte(IIC_Data);IIC_WaitAck();IIC_Stop();
}void OLED_WR_Byte(unsigned dat,unsigned cmd)
{if(cmd){IIC_WriteData(dat);}else{IIC_WriteCommand(dat);}
}void OLED_Init(void)
{HAL_Delay(100);		//这个延迟很重要OLED_WR_Byte(0xAE,OLED_CMD);//--display offOLED_WR_Byte(0x00,OLED_CMD);//---set low column addressOLED_WR_Byte(0x10,OLED_CMD);//---set high column addressOLED_WR_Byte(0x40,OLED_CMD);//--set start line addressOLED_WR_Byte(0xB0,OLED_CMD);//--set page addressOLED_WR_Byte(0x81,OLED_CMD); // contract controlOLED_WR_Byte(0xFF,OLED_CMD);//--128OLED_WR_Byte(0xA1,OLED_CMD);//set segment remapOLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverseOLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 dutyOLED_WR_Byte(0xC8,OLED_CMD);//Com scan directionOLED_WR_Byte(0xD3,OLED_CMD);//-set display offsetOLED_WR_Byte(0x00,OLED_CMD);//OLED_WR_Byte(0xD5,OLED_CMD);//set osc divisionOLED_WR_Byte(0x80,OLED_CMD);//OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode offOLED_WR_Byte(0x05,OLED_CMD);//OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge PeriodOLED_WR_Byte(0xF1,OLED_CMD);//OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartionOLED_WR_Byte(0x12,OLED_CMD);//OLED_WR_Byte(0xDB,OLED_CMD);//set VcomhOLED_WR_Byte(0x30,OLED_CMD);//OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enableOLED_WR_Byte(0x14,OLED_CMD);//OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panelHAL_Delay(100);	OLED_FillPicture(0x0);}/********************************************
// OLED_FillPicture
********************************************/
void OLED_FillPicture(unsigned char fill_Data)
{unsigned char m,n;for(m=0;m<8;m++){OLED_WR_Byte(0xb0+m,0);		//page0-page1OLED_WR_Byte(0x00,0);		//low column start addressOLED_WR_Byte(0x10,0);		//high column start addressfor(n=0;n<128;n++){OLED_WR_Byte(fill_Data,1);}}
}//坐标设置
void OLED_SetPos(unsigned char x, unsigned char y)
{ 	OLED_WR_Byte(0xb0+y,OLED_CMD);OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);OLED_WR_Byte((x&0x0f),OLED_CMD);
}
//开启OLED显示
void OLED_DisplayOn(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//关闭OLED显示
void OLED_DisplayOff(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{u8 i,n;for(i=0;i<8;i++){OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);} //更新显示
}
void OLED_On(void)
{u8 i,n;for(i=0;i<8;i++){OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA);} //更新显示
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示
//size:选择字体 16/12
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
{unsigned char c=0,i=0;c=chr-' ';//得到偏移后的值if(x>Max_Column-1){x=0;y=y+2;}if(Char_Size ==16){OLED_SetPos(x,y);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);OLED_SetPos(x,y+1);for(i=0;i<8;i++)OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);}else {OLED_SetPos(x,y);for(i=0;i<6;i++)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}
}//m^n函数
u32 oled_pow(u8 m,u8 n)
{u32 result=1;while(n--)result*=m;return result;
}//显示2个数字
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//mode:模式	0,填充模式;1,叠加模式
//num:数值(0~4294967295);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
{u8 t,temp;u8 enshow=0;for(t=0;t<len;t++){temp=(num/oled_pow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){
//				OLED_ShowChar(x+(size2/2)*t,y,' ',size2);OLED_ShowChar(x+(size2/2)*t,y,'0',size2);continue;}else enshow=1;}OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);}
}//显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{unsigned char j=0;while (chr[j]!='\0'){		OLED_ShowChar(x,y,chr[j],Char_Size);x+=8;if(x>120){x=0;y+=2;}j++;}
}

5.2 谷歌小恐龙游戏图形绘制代码

        该部分为整个项目代码的核心部分之,任何一个游戏都是需要去绘制构建游戏的图形以及模型的。好的游戏往往都具有很好的游戏模型精美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文库

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/172408.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习 | 开山篇】打造坚实基础、Kaggle 登榜之路

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

Linux中部署MongoDB

在 是一个必要的过程&#xff0c;因为MongoDB是一种流行的NoSQL数据库&#xff0c;它可以在大多数操作系统上使用。在本文中&#xff0c;我们将介绍如何在CentOS 8上部署MongoDB。 MongoDB的下载 您可以从MongoDB官网上下载最新的MongoDB版本。使用以下命令下载MongoDB&#…

SSF-CNN:空间光谱融合的卷积光谱图像超分网络

SSF-CNN: SPATIAL AND SPECTRAL FUSION WITH CNN FOR HYPERSPECTRAL IMAGE SUPER-RESOLUTION 文章目录 SSF-CNN: SPATIAL AND SPECTRAL FUSION WITH CNN FOR HYPERSPECTRAL IMAGE SUPER-RESOLUTION简介解决问题网络框架代码实现训练部分运行结果 简介 ​ 本文提出了一种利用空…

MYSQL 及 SQL 注入

文章目录 前言什么是sql注入防止SQL注入Like语句中的注入后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Mysql &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现…

Adversarial Attack on Graph Structured Data(2018 PMLR)

Adversarial Attack on Graph Structured Data----《图结构数据的对抗攻击》 摘要 基于图结构的深度学习已经在各种应用中显示出令人兴奋的结果。然而&#xff0c;与图像或文本对抗攻击和防御的大量研究工作相比&#xff0c;此类模型的鲁棒性却很少受到关注。在本文中&#xf…

⑩⑧【MySQL】InnoDB架构、事务原理、MVCC多版本并发控制

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ InnoDB存储引擎 ⑩⑧【MySQL】详解InnoDB存储引…

Day42力扣打卡

打卡记录 统计子串中的唯一字符&#xff08;找规律&#xff09; 链接 大佬的题解 class Solution:def uniqueLetterString(self, s: str) -> int:ans total 0last0, last1 {}, {}for i, c in enumerate(s):total i - 2 * last0.get(c, -1) last1.get(c, -1)ans tot…

【数据中台】开源项目(2)-Dbus数据总线

1 背景 企业中大量业务数据保存在各个业务系统数据库中&#xff0c;过去通常的同步数据的方法有很多种&#xff0c;比如&#xff1a; 各个数据使用方在业务低峰期各种抽取所需数据&#xff08;缺点是存在重复抽取而且数据不一致&#xff09; 由统一的数仓平台通过sqoop到各个…

网络爬虫(Python:Requests、Beautiful Soup笔记)

网络爬虫&#xff08;Python&#xff1a;Requests、Beautiful Soup笔记&#xff09; 网络协议简要介绍一。OSI参考模型二、TCP/IP参考模型对应关系TCP/IP各层实现的协议应用层传输层网络层 HTTP协议HTTP请求HTTP响应HTTP状态码 Requests&#xff08;Python&#xff09;Requests…

【Vue】@keyup.enter @v-model.trim的用法

目录 keyup.enter v-model.trim 情景一&#xff1a; 情景二&#xff1a; keyup.enter 作用&#xff1a;监听键盘回车事件 上一篇内容&#xff1a; 记事本 https://blog.csdn.net/m0_67930426/article/details/134630834?spm1001.2014.3001.5502 这里有个添加任务的功能&…

ESP32控制数码管实现数字叠加案例

经过了几个小时的接线和代码实现终于搞定了代码&#xff0c;贴出来大家参考下 import machine import time# 定义4个Led的引脚 led1 machine.Pin(5,machine.Pin.OUT) led2 machine.Pin(18,machine.Pin.OUT) led3 machine.Pin(19,machine.Pin.OUT) led4 machine.Pin(21,mac…

手摸手vue2+Element-ui整合Axios

后端WebAPI准备 跨域问题 为了保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源,称为同源策略,同源策略是浏览器安全的基石 同源策略( Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能 所谓同源(即指在同一个域)就是两个页面具…

Idea常用的快捷键

快捷键 快速生成main()方法&#xff1a;psvm&#xff0c;回车 快速生成输出语句&#xff1a;sout&#xff0c;回车 ctrlz撤回&#xff0c;ctrlshiftz取消撤回 ctrlr替换 CtrlAltspace(内容提示&#xff0c;代码补全等) ctrl句号。最小化方法&#xff0c;恢复最小化方法。 …

【数据中台】开源项目(2)-Moonbox计算服务平台

Moonbox是一个DVtaaS&#xff08;Data Virtualization as a Service&#xff09;平台解决方案。 Moonbox基于数据虚拟化设计思想&#xff0c;致力于提供批量计算服务解决方案。Moonbox负责屏蔽底层数据源的物理和使用细节&#xff0c;为用户带来虚拟数据库般使用体验&#xff0…

Tabular特征选择基准

学术实验中的表格基准通常是一小组精心选择的特征。相比之下,工业界数据科学家通常会收集尽可能多的特征到他们的数据集中,甚至从现有的特征中设计新的特征。为了防止在后续的下游建模中过拟合,数据科学家通常使用自动特征选择方法来获得特征子集。Tabular特征选择的现有基准…

JavaFX开发调用AWT创建系统托盘MenuItem菜单中文乱码

打开系统托盘MenuItem只能显示英文字符和中文显示方框 解决办法&#xff1a; 打开Edit Configurations… 选择Mofidy options 勾选Add VM options 在VM optios中填入以下代码 -Dfile.encodingGBK

【MySQL | TCP】宝塔面板结合内网穿透实现公网远程访问

文章目录 前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 宝塔面板的简易操作性&#xff0c;使得运维难度降低&#xff0c;简化了Linux命令行进行繁琐的配置&#x…

Oracle的安装及使用流程

Oracle的安装及使用流程 1.Win10安装Oracle10g 1.1 安装与测试 安装版本&#xff1a; OracleXEUniv10.2.1015.exe 步骤参考&#xff1a;oracleXe下载与安装 安装完成后测试是否正常 # 输入命令连接oracle conn sys as sysdba; # 无密码&#xff0c;直接按回车 # 测试连接的s…

我的第一次SACC之旅

今年有很多第一次&#xff0c;第一次作为“游客”参加DTCC&#xff08;中国数据库大会&#xff09;&#xff0c;第一次作为讲师参与ACDU中国行&#xff08;成都站&#xff09;&#xff0c;第一次参加OB年度发布会&#xff08;包含DBA老友会&#xff09;&#xff0c;而这次是第一…

leetcode面试经典150题——32 串联所有单词的子串(中等+困难)

题目&#xff1a; 串联所有单词的子串(1中等) 描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&…