网站设计大概价格,网站ftp,房地产最新信息,wordpress跟php一、前言
本文的OLED多级菜单UI为一个综合性的STM32小项目#xff0c;使用多传感器与OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换#xff0c;搭配DHT11#xff0c;RTC#xff0c;LED#xff0c;KEY等器…一、前言
本文的OLED多级菜单UI为一个综合性的STM32小项目使用多传感器与OLED显示屏实现智能终端的效果。项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换搭配DHT11RTCLEDKEY等器件实现高度智能化一体化操作。
后期自己打板设计结构可以衍生为智能手表等小玩意。目前项目属于裸机状态CPU占用率100%后期可能会加上RTOS系统。
二、硬件实物图 温度计 游戏机 三、硬件引脚图
OLED模块VCC -- 3.3VGND -- GNDSCL -- PB10SDA -- PB11DHT11模块DATA -- PB9VCC -- 3.3VGND -- GNDKEY模块这部分笔者直接使用了正点原子精英板上的KEY0 -- PE4KEY1 -- PE3KEY_UP -- PA0四、多级菜单
随着工业化和自动化的发展如今基本上所有项目都离不开显示终端。而多级菜单更是终端显示项目中必不可少的组成因素其实 TFT-LCD 屏幕上可以借鉴移植很多优秀的开源多级菜单GUI比如LVGL而0.96寸的OLED屏幕上通常需要自己去适配和编程多级菜单。 网上的普遍采用的多级菜单的方案是基于索引或者结构树其中索引法居多。索引法的优点可阅读性好拓展性也不错查找的性能差不多是最优就是有点占用内存空间。
4.1 索引法多级菜单实现
网上关于索引法实现多级菜单功能有很多基础教程笔者就按照本项目中的具体实现代码过程给大家讲解一下索引法实现多级菜单。特别说明本项目直接使用了正点原子的精英板作为核心板所以读者朋友复现代码还是很简单的。
首先基于索引法实现多级菜单的首要条件是先确定项目中将使用到几个功能按键比如向前向后确定退出等等本项目中笔者使用到了3个按键下一个next确定enter退出back。所以接下首先定义一个结构体结构体中一共有5个变量32分别为当前索引序号current向下一个next确定enter退出back当前执行函数void。其中标红的为需要设计的按键笔者这里有3个标绿的则为固定的索引号与该索引下需要执行的函数。
typedef struct
{u8 current; //当前状态索引号u8 next; //向下一个u8 enter; //确定6u8 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_indextable[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) 2) (func_index ! 6)){func_indextable[func_index].enter; //按键enter按下后的索引号OLED_Clear();}if(KEY_Scan(1) 3){func_indextable[func_index].back; //按键back按下后的索引号OLED_Clear(); }current_operation_indextable[func_index].current_operation; //执行当前索引号所对应的功能函数(*current_operation_index)();//执行当前操作函数
}//按键函数
u8 KEY_Scan(u8 mode)
{static u8 key_up1;if(mode)key_up1; if(key_up(KEY00||KEY10||WK_UP1)){HAL_Delay(100); //消抖key_up0;if(KEY00)return 1;else if(KEY10)return 2;else if(WK_UP1)return 3;}else if(KEY01KEY11WK_UP0)key_up1; return 0;
}说明2点
1由于是目前本项目是裸机状态下运行的所以CPU占用率默认是100%的所以这里使用按键支持连按时对于菜单的切换更好些。
2可能部分索引号下的执行函数需要使用到已经定义的3个按键比如本项目中的DInogame中。所以可以在需要差别化的索引号下去屏蔽原先的按键功能。如下 if((KEY_Scan(1) 1) (func_index ! 6)) //屏蔽掉索引6下的情况适配游戏{ func_indextable[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) 2) (func_index ! 6)) //屏蔽掉索引6下的情况适配游戏{func_indextable[func_index].enter; //按键enter按下后的索引号OLED_Clear();}3笔者这里是使用全屏刷新去切换功能界面同时没有启用高级算法去加速显示所以可能在切换界面的时候效果一般。读者朋友可以试试根据自己的UI情况使用局部刷新这样可能项目会更加丝滑一点。
本项目中的菜单索引图 4.2 内部功能实现简化智能手表
OLED就是正常的驱动与显示有能力的读者朋友可以使用高级算法去加速OLED屏幕的刷新率可以使自己的多级菜单切换起来更丝滑。
唯一需要注意的点就是需要去制作菜单里面的UI图标注意图片大小是否合适 如果是黑白图片的话可以直接使用PCtoLCD2002完美版进行取模 4.3 KEY按键
KEY按键注意消抖建议裸机情况下支持连续按动同时注意自己实际硬件情况去进行编程电阻是否存在上拉或者下拉。 4.4 DinoGame实现 谷歌公司最近比较流行的小游戏笔者之前有文章进行了STM32的成功复刻。博客地址基于STM32的小游戏——谷歌小恐龙Chrome Dino Game_混分巨兽龙某某的博客-CSDN博客_谷歌恐龙
4.5 LED控制和DHT11模块
LED和DHT11模块其实都属于外设控制这里读者朋友可以根据自己的实际情况去取舍。需要注意的是尽可能适配一下自己多级菜单外设控制也需要注意一下按键安排可以参考笔者项目的设计。
五、CubeMX配置
1、RCC配置外部高速晶振精度更高——HSE 2、SYS配置Debug设置成Serial Wire否则可能导致芯片自锁 3、I2C2配置这里不直接使用CubeMX的I2C2使用GPIO模拟PB10CLKPB11SDA 4、RTC配置年月日时分秒 5、TIM2配置由上面可知DHT11的使用需要us级的延迟函数,HAL库自带只有ms的所以需要自己设计一个定时器 6、KEY按键配置PE3PE4和PA0设置为端口输入开发板原理图 7、时钟树配置 8、文件配置 六、代码
6.1 OLED驱动代码
此部分OLED的基本驱动函数笔者使用的是I2C驱动的0.96寸OLED屏幕。所以首先需要使用GPIO模拟I2C通讯。随后使用I2C通讯去驱动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);#endifoled.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;daIIC_Byte;OLED_SCLK_Clr();for(i0;i8;i){mda;// OLED_SCLK_Clr();mm0x80;if(m0x80){OLED_SDIN_Set();}else OLED_SDIN_Clr();dada1;OLED_SCLK_Set();OLED_SCLK_Clr();}}
/**********************************************
// IIC Write Command
**********************************************/
void IIC_WriteCommand(unsigned char IIC_Command)
{IIC_Start();IIC_WriteByte(0x78); //Slave address,SA00IIC_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(m0;m8;m){OLED_WR_Byte(0xb0m,0); //page0-page1OLED_WR_Byte(0x00,0); //low column start addressOLED_WR_Byte(0x10,0); //high column start addressfor(n0;n128;n){OLED_WR_Byte(fill_Data,1);}}
}//坐标设置
void OLED_SetPos(unsigned char x, unsigned char y)
{ OLED_WR_Byte(0xb0y,OLED_CMD);OLED_WR_Byte(((x0xf0)4)|0x10,OLED_CMD);OLED_WR_Byte((x0x0f),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(i0;i8;i){OLED_WR_Byte (0xb0i,OLED_CMD); //设置页地址0~7OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址for(n0;n128;n)OLED_WR_Byte(0,OLED_DATA);} //更新显示
}
void OLED_On(void)
{u8 i,n;for(i0;i8;i){OLED_WR_Byte (0xb0i,OLED_CMD); //设置页地址0~7OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址for(n0;n128;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 c0,i0;cchr- ;//得到偏移后的值if(xMax_Column-1){x0;yy2;}if(Char_Size 16){OLED_SetPos(x,y);for(i0;i8;i)OLED_WR_Byte(F8X16[c*16i],OLED_DATA);OLED_SetPos(x,y1);for(i0;i8;i)OLED_WR_Byte(F8X16[c*16i8],OLED_DATA);}else {OLED_SetPos(x,y);for(i0;i6;i)OLED_WR_Byte(F6x8[c][i],OLED_DATA);}
}//m^n函数
u32 oled_pow(u8 m,u8 n)
{u32 result1;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 enshow0;for(t0;tlen;t){temp(num/oled_pow(10,len-t-1))%10;if(enshow0t(len-1)){if(temp0){
// OLED_ShowChar(x(size2/2)*t,y, ,size2);OLED_ShowChar(x(size2/2)*t,y,0,size2);continue;}else enshow1;}OLED_ShowChar(x(size2/2)*t,y,temp0,size2);}
}//显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
{unsigned char j0;while (chr[j]!\0){ OLED_ShowChar(x,y,chr[j],Char_Size);x8;if(x120){x0;y2;}j;}
}6.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();#endifdinogame.c代码
#include oled.h
#include oledfont.h
#include stdlib.h/***********功能描述显示显示BMP图片128×64起始点坐标(x,y),x的范围0127y为页的范围07*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{unsigned int j0;unsigned char x,y;if(y1%80) yy1/8;else yy1/81;for(yy0;yy1;y){OLED_SetPos(x0,y);for(xx0;xx1;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 i128) 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[(xpos)%ground_length]);IIC_WaitAck();}IIC_Stop();pos pos speed;//if(posground_length) pos0;
}// 绘制云朵
void OLED_DrawCloud()
{static int pos 128;static char height0;char speed 3;unsigned int i0;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, 1height);}else{OLED_SetPos(pos, 1height);}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 j0;unsigned char x, y;unsigned char byte;dino_dir;dino_dir dino_dir%2;for(y0; y2; y){OLED_SetPos(16, 6y);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 j0;unsigned char x, y;unsigned char byte;if (pos length 0){oled_drawbmp_block_clear(0, 6, speed);pos 128;}for(y0; y2; y){if(pos 0){start_x -pos;OLED_SetPos(0, 6y);}else{OLED_SetPos(pos, 6y);}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 i0, j0;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(y0; y2; y){if(pos 0){start_x -pos;OLED_SetPos(0, 6y);}else{OLED_SetPos(pos, 6y);}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 j0;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 (dir0){height speed_arr[speed_idx];speed_idx --;if (speed_idx0) speed_idx 0;}if (dir1){height - speed_arr[speed_idx];speed_idx ;if (speed_idxsizeof(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(y0; y3; 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 j0;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);
}6.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.hunsigned 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 (height0 || 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_pos16 cactus_pos 32) || (cactus_pos cactus_length16 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;}}6.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();#endifmenu.c
#include menu.h
#include oled.h
#include gpio.h
#include dinogame.h
#include control.h
#include DHT11.h
#include rtc.hRTC_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_indextable[func_index].next; //按键next按下后的索引号OLED_Clear(); }if((KEY_Scan(1) 2) (func_index ! 6)){func_indextable[func_index].enter; //按键enter按下后的索引号OLED_Clear();}if(KEY_Scan(1) 3){func_indextable[func_index].back; //按键back按下后的索引号OLED_Clear(); }current_operation_indextable[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_up1;if(mode)key_up1; if(key_up(KEY00||KEY10||WK_UP1)){HAL_Delay(100); //消抖key_up0;if(KEY00)return 1;else if(KEY10)return 2;else if(WK_UP1)return 3;}else if(KEY01KEY11WK_UP0)key_up1; 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界面中的电池与信号目前都还处于贴图状态后期笔者会加上库仑计测量电池电量等。文章中指出了需要注意的地方与可以改进的点感兴趣的朋友可以彼此交流交流。