• 基于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个),标绿的则为固定的索引号该索引下需要执行的函数

    1. typedef struct
    2. {
    3. u8 current; //当前状态索引号
    4. u8 next; //向下一个
    5. u8 enter; //确定
    6. u8 back; //退出
    7. void (*current_operation)(void); //当前状态应该执行的操作
    8. } Menu_table;

            接下来就是定义一个数组去决定整个项目菜单的逻辑顺序利用索引号

    1. Menu_table table[30]=
    2. {
    3. {0,0,1,0,(*home)}, //一级界面(主页面) 索引,向下一个,确定,退出
    4. {1,2,5,0,(*Temperature)}, //二级界面 温湿度
    5. {2,3,6,0,(*Palygame)}, //二级界面 游戏
    6. {3,4,7,0,(*Setting)}, //二级界面 设置
    7. {4,1,8,0,(*Info)}, //二级界面 信息
    8. {5,5,5,1,(*TestTemperature)}, //三级界面:DHT11测量温湿度
    9. {6,6,6,2,(*ControlGame)}, //三级界面:谷歌小恐龙Dinogame
    10. {7,7,9,3,(*Set)}, //三级界面:设置普通外设状态 LED
    11. {8,8,8,4,(*Information)}, //三级界面:作者和相关项目信息
    12. {9,9,7,3,(*LED)}, //LED控制
    13. };

            这里解释一下这个数组中各元素的意义,由于我们在前面先定义了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函数

            再接下就是按键处理函数

    1. uint8_t func_index = 0; //主程序此时所在程序的索引值
    2. void Menu_key_set(void)
    3. {
    4. if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的情况,适配游戏
    5. {
    6. func_index=table[func_index].next; //按键next按下后的索引号
    7. OLED_Clear();
    8. }
    9. if((KEY_Scan(1) == 2) && (func_index != 6))
    10. {
    11. func_index=table[func_index].enter; //按键enter按下后的索引号
    12. OLED_Clear();
    13. }
    14. if(KEY_Scan(1) == 3)
    15. {
    16. func_index=table[func_index].back; //按键back按下后的索引号
    17. OLED_Clear();
    18. }
    19. current_operation_index=table[func_index].current_operation; //执行当前索引号所对应的功能函数
    20. (*current_operation_index)();//执行当前操作函数
    21. }
    22. //按键函数
    23. u8 KEY_Scan(u8 mode)
    24. {
    25. static u8 key_up=1;
    26. if(mode)key_up=1;
    27. if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
    28. {
    29. HAL_Delay(100); //消抖
    30. key_up=0;
    31. if(KEY0==0)return 1;
    32. else if(KEY1==0)return 2;
    33. else if(WK_UP==1)return 3;
    34. }else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
    35. return 0;
    36. }

    说明2点:

    (1)由于是目前本项目是裸机状态下运行的,所以CPU占用率默认是100%的,所以这里使用按键支持连按时,对于菜单的切换更好些。

    (2)可能部分索引号下的执行函数,需要使用到已经定义的3个按键(比如,本项目中的DInogame中)。所以,可以在需要差别化的索引号下去屏蔽原先的按键功能。如下:

    1. if((KEY_Scan(1) == 1) && (func_index != 6)) //屏蔽掉索引6下的情况,适配游戏
    2. {
    3. func_index=table[func_index].next; //按键next按下后的索引号
    4. OLED_Clear();
    5. }
    6. if((KEY_Scan(1) == 2) && (func_index != 6)) //屏蔽掉索引6下的情况,适配游戏
    7. {
    8. func_index=table[func_index].enter; //按键enter按下后的索引号
    9. OLED_Clear();
    10. }

    (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:

    1. #ifndef __OLED_H
    2. #define __OLED_H
    3. #include "main.h"
    4. #define u8 uint8_t
    5. #define u32 uint32_t
    6. #define OLED_CMD 0 //写命令
    7. #define OLED_DATA 1 //写数据
    8. #define OLED0561_ADD 0x78 // OLED I2C地址
    9. #define COM 0x00 // OLED
    10. #define DAT 0x40 // OLED
    11. #define OLED_MODE 0
    12. #define SIZE 8
    13. #define XLevelL 0x00
    14. #define XLevelH 0x10
    15. #define Max_Column 128
    16. #define Max_Row 64
    17. #define Brightness 0xFF
    18. #define X_WIDTH 128
    19. #define Y_WIDTH 64
    20. //-----------------OLED IIC GPIO进行模拟----------------
    21. #define OLED_SCLK_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_RESET) //GPIO_ResetBits(GPIOB,GPIO_Pin_10)//SCL
    22. #define OLED_SCLK_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, GPIO_PIN_SET) //GPIO_SetBits(GPIOB,GPIO_Pin_10)
    23. #define OLED_SDIN_Clr() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_RESET) // GPIO_ResetBits(GPIOB,GPIO_Pin_11)//SDA
    24. #define OLED_SDIN_Set() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_11, GPIO_PIN_SET) // GPIO_SetBits(GPIOB,GPIO_Pin_11)
    25. //I2C GPIO模拟
    26. void IIC_Start();
    27. void IIC_Stop();
    28. void IIC_WaitAck();
    29. void IIC_WriteByte(unsigned char IIC_Byte);
    30. void IIC_WriteCommand(unsigned char IIC_Command);
    31. void IIC_WriteData(unsigned char IIC_Data);
    32. void OLED_WR_Byte(unsigned dat,unsigned cmd);
    33. //功能函数
    34. void OLED_Init(void);
    35. void OLED_WR_Byte(unsigned dat,unsigned cmd);
    36. void OLED_FillPicture(unsigned char fill_Data);
    37. void OLED_SetPos(unsigned char x, unsigned char y);
    38. void OLED_DisplayOn(void);
    39. void OLED_DisplayOff(void);
    40. void OLED_Clear(void);
    41. void OLED_On(void);
    42. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size);
    43. u32 oled_pow(u8 m,u8 n);
    44. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2);
    45. void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size);
    46. #endif

    oled.c:

    1. #include "oled.h"
    2. #include "asc.h" //字库(可以自己制作)
    3. #include "main.h"
    4. /********************GPIO 模拟I2C*******************/
    5. //注意:这里没有直接使用HAL库中的模拟I2C
    6. /**********************************************
    7. //IIC Start
    8. **********************************************/
    9. void IIC_Start()
    10. {
    11. OLED_SCLK_Set() ;
    12. OLED_SDIN_Set();
    13. OLED_SDIN_Clr();
    14. OLED_SCLK_Clr();
    15. }
    16. /**********************************************
    17. //IIC Stop
    18. **********************************************/
    19. void IIC_Stop()
    20. {
    21. OLED_SCLK_Set() ;
    22. OLED_SDIN_Clr();
    23. OLED_SDIN_Set();
    24. }
    25. void IIC_WaitAck()
    26. {
    27. OLED_SCLK_Set() ;
    28. OLED_SCLK_Clr();
    29. }
    30. /**********************************************
    31. // IIC Write byte
    32. **********************************************/
    33. void IIC_WriteByte(unsigned char IIC_Byte)
    34. {
    35. unsigned char i;
    36. unsigned char m,da;
    37. da=IIC_Byte;
    38. OLED_SCLK_Clr();
    39. for(i=0;i<8;i++)
    40. {
    41. m=da;
    42. // OLED_SCLK_Clr();
    43. m=m&0x80;
    44. if(m==0x80)
    45. {OLED_SDIN_Set();}
    46. else OLED_SDIN_Clr();
    47. da=da<<1;
    48. OLED_SCLK_Set();
    49. OLED_SCLK_Clr();
    50. }
    51. }
    52. /**********************************************
    53. // IIC Write Command
    54. **********************************************/
    55. void IIC_WriteCommand(unsigned char IIC_Command)
    56. {
    57. IIC_Start();
    58. IIC_WriteByte(0x78); //Slave address,SA0=0
    59. IIC_WaitAck();
    60. IIC_WriteByte(0x00); //write command
    61. IIC_WaitAck();
    62. IIC_WriteByte(IIC_Command);
    63. IIC_WaitAck();
    64. IIC_Stop();
    65. }
    66. /**********************************************
    67. // IIC Write Data
    68. **********************************************/
    69. void IIC_WriteData(unsigned char IIC_Data)
    70. {
    71. IIC_Start();
    72. IIC_WriteByte(0x78); //D/C#=0; R/W#=0
    73. IIC_WaitAck();
    74. IIC_WriteByte(0x40); //write data
    75. IIC_WaitAck();
    76. IIC_WriteByte(IIC_Data);
    77. IIC_WaitAck();
    78. IIC_Stop();
    79. }
    80. void OLED_WR_Byte(unsigned dat,unsigned cmd)
    81. {
    82. if(cmd)
    83. {
    84. IIC_WriteData(dat);
    85. }
    86. else
    87. {
    88. IIC_WriteCommand(dat);
    89. }
    90. }
    91. void OLED_Init(void)
    92. {
    93. HAL_Delay(100); //这个延迟很重要
    94. OLED_WR_Byte(0xAE,OLED_CMD);//--display off
    95. OLED_WR_Byte(0x00,OLED_CMD);//---set low column address
    96. OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
    97. OLED_WR_Byte(0x40,OLED_CMD);//--set start line address
    98. OLED_WR_Byte(0xB0,OLED_CMD);//--set page address
    99. OLED_WR_Byte(0x81,OLED_CMD); // contract control
    100. OLED_WR_Byte(0xFF,OLED_CMD);//--128
    101. OLED_WR_Byte(0xA1,OLED_CMD);//set segment remap
    102. OLED_WR_Byte(0xA6,OLED_CMD);//--normal / reverse
    103. OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
    104. OLED_WR_Byte(0x3F,OLED_CMD);//--1/32 duty
    105. OLED_WR_Byte(0xC8,OLED_CMD);//Com scan direction
    106. OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset
    107. OLED_WR_Byte(0x00,OLED_CMD);//
    108. OLED_WR_Byte(0xD5,OLED_CMD);//set osc division
    109. OLED_WR_Byte(0x80,OLED_CMD);//
    110. OLED_WR_Byte(0xD8,OLED_CMD);//set area color mode off
    111. OLED_WR_Byte(0x05,OLED_CMD);//
    112. OLED_WR_Byte(0xD9,OLED_CMD);//Set Pre-Charge Period
    113. OLED_WR_Byte(0xF1,OLED_CMD);//
    114. OLED_WR_Byte(0xDA,OLED_CMD);//set com pin configuartion
    115. OLED_WR_Byte(0x12,OLED_CMD);//
    116. OLED_WR_Byte(0xDB,OLED_CMD);//set Vcomh
    117. OLED_WR_Byte(0x30,OLED_CMD);//
    118. OLED_WR_Byte(0x8D,OLED_CMD);//set charge pump enable
    119. OLED_WR_Byte(0x14,OLED_CMD);//
    120. OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
    121. HAL_Delay(100);
    122. OLED_FillPicture(0x0);
    123. }
    124. /********************************************
    125. // OLED_FillPicture
    126. ********************************************/
    127. void OLED_FillPicture(unsigned char fill_Data)
    128. {
    129. unsigned char m,n;
    130. for(m=0;m<8;m++)
    131. {
    132. OLED_WR_Byte(0xb0+m,0); //page0-page1
    133. OLED_WR_Byte(0x00,0); //low column start address
    134. OLED_WR_Byte(0x10,0); //high column start address
    135. for(n=0;n<128;n++)
    136. {
    137. OLED_WR_Byte(fill_Data,1);
    138. }
    139. }
    140. }
    141. //坐标设置
    142. void OLED_SetPos(unsigned char x, unsigned char y)
    143. { OLED_WR_Byte(0xb0+y,OLED_CMD);
    144. OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
    145. OLED_WR_Byte((x&0x0f),OLED_CMD);
    146. }
    147. //开启OLED显示
    148. void OLED_DisplayOn(void)
    149. {
    150. OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
    151. OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
    152. OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
    153. }
    154. //关闭OLED显示
    155. void OLED_DisplayOff(void)
    156. {
    157. OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
    158. OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
    159. OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
    160. }
    161. //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
    162. void OLED_Clear(void)
    163. {
    164. u8 i,n;
    165. for(i=0;i<8;i++)
    166. {
    167. OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
    168. OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
    169. OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
    170. for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA);
    171. } //更新显示
    172. }
    173. void OLED_On(void)
    174. {
    175. u8 i,n;
    176. for(i=0;i<8;i++)
    177. {
    178. OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)
    179. OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址
    180. OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址
    181. for(n=0;n<128;n++)OLED_WR_Byte(1,OLED_DATA);
    182. } //更新显示
    183. }
    184. //在指定位置显示一个字符,包括部分字符
    185. //x:0~127
    186. //y:0~63
    187. //mode:0,反白显示;1,正常显示
    188. //size:选择字体 16/12
    189. void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 Char_Size)
    190. {
    191. unsigned char c=0,i=0;
    192. c=chr-' ';//得到偏移后的值
    193. if(x>Max_Column-1){x=0;y=y+2;}
    194. if(Char_Size ==16)
    195. {
    196. OLED_SetPos(x,y);
    197. for(i=0;i<8;i++)
    198. OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
    199. OLED_SetPos(x,y+1);
    200. for(i=0;i<8;i++)
    201. OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
    202. }
    203. else {
    204. OLED_SetPos(x,y);
    205. for(i=0;i<6;i++)
    206. OLED_WR_Byte(F6x8[c][i],OLED_DATA);
    207. }
    208. }
    209. //m^n函数
    210. u32 oled_pow(u8 m,u8 n)
    211. {
    212. u32 result=1;
    213. while(n--)result*=m;
    214. return result;
    215. }
    216. //显示2个数字
    217. //x,y :起点坐标
    218. //len :数字的位数
    219. //size:字体大小
    220. //mode:模式 0,填充模式;1,叠加模式
    221. //num:数值(0~4294967295);
    222. void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size2)
    223. {
    224. u8 t,temp;
    225. u8 enshow=0;
    226. for(t=0;t
    227. {
    228. temp=(num/oled_pow(10,len-t-1))%10;
    229. if(enshow==0&&t<(len-1))
    230. {
    231. if(temp==0)
    232. {
    233. // OLED_ShowChar(x+(size2/2)*t,y,' ',size2);
    234. OLED_ShowChar(x+(size2/2)*t,y,'0',size2);
    235. continue;
    236. }else enshow=1;
    237. }
    238. OLED_ShowChar(x+(size2/2)*t,y,temp+'0',size2);
    239. }
    240. }
    241. //显示一个字符号串
    242. void OLED_ShowString(u8 x,u8 y,u8 *chr,u8 Char_Size)
    243. {
    244. unsigned char j=0;
    245. while (chr[j]!='\0')
    246. { OLED_ShowChar(x,y,chr[j],Char_Size);
    247. x+=8;
    248. if(x>120){x=0;y+=2;}
    249. j++;
    250. }
    251. }

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

            该部分为整个项目代码的核心部分之,任何一个游戏都是需要去绘制构建游戏的图形以及模型的。好的游戏往往都具有很好的游戏模型精美UI,很多3A大作都具备这样的特性。

    dinogame.h:

    1. #ifndef __DINOGAME_H
    2. #define __DINOGAME_H
    3. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[]);
    4. void OLED_DrawBMPFast(const unsigned char BMP[]);
    5. void oled_drawbmp_block_clear(int bx, int by, int clear_size);
    6. void OLED_DrawGround();
    7. void OLED_DrawCloud();
    8. void OLED_DrawDino();
    9. void OLED_DrawCactus();
    10. int OLED_DrawCactusRandom(unsigned char ver, unsigned char reset);
    11. int OLED_DrawDinoJump(char reset);
    12. void OLED_DrawRestart();
    13. void OLED_DrawCover();
    14. #endif

    dinogame.c代码:

    1. #include "oled.h"
    2. #include "oledfont.h"
    3. #include "stdlib.h"
    4. /***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
    5. void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
    6. {
    7. unsigned int j=0;
    8. unsigned char x,y;
    9. if(y1%8==0) y=y1/8;
    10. else y=y1/8+1;
    11. for(y=y0;y
    12. {
    13. OLED_SetPos(x0,y);
    14. for(x=x0;x
    15. {
    16. OLED_WR_Byte(BMP[j++],OLED_DATA);
    17. }
    18. }
    19. }
    20. // 快速绘制图像
    21. void OLED_DrawBMPFast(const unsigned char BMP[])
    22. {
    23. unsigned int j = 0;
    24. unsigned char x, y;
    25. for (y = 0; y < 8; y++)
    26. {
    27. OLED_SetPos(0, y);
    28. IIC_Start();
    29. IIC_WriteByte(0x78);
    30. IIC_WaitAck();
    31. IIC_WriteByte(0x40);
    32. IIC_WaitAck();
    33. for (x = 0; x < 128; x++)
    34. {
    35. IIC_WriteByte(BMP[j++]);
    36. IIC_WaitAck();
    37. }
    38. IIC_Stop();
    39. }
    40. }
    41. void oled_drawbmp_block_clear(int bx, int by, int clear_size)
    42. {
    43. unsigned int i;
    44. OLED_SetPos(bx, by);
    45. IIC_Start();
    46. IIC_WriteByte(0x78);
    47. IIC_WaitAck();
    48. IIC_WriteByte(0x40);
    49. IIC_WaitAck();
    50. for (i = 0; i < clear_size; i++)
    51. {
    52. if (bx + i>128) break;
    53. IIC_WriteByte(0x0);
    54. IIC_WaitAck();
    55. }
    56. IIC_Stop();
    57. }
    58. void OLED_DrawGround()
    59. {
    60. static unsigned int pos = 0;
    61. unsigned char speed = 5;
    62. unsigned int ground_length = sizeof(GROUND);
    63. unsigned char x;
    64. OLED_SetPos(0, 7);
    65. IIC_Start();
    66. IIC_WriteByte(0x78);
    67. IIC_WaitAck();
    68. IIC_WriteByte(0x40);
    69. IIC_WaitAck();
    70. for (x = 0; x < 128; x++)
    71. {
    72. IIC_WriteByte(GROUND[(x+pos)%ground_length]);
    73. IIC_WaitAck();
    74. }
    75. IIC_Stop();
    76. pos = pos + speed;
    77. //if(pos>ground_length) pos=0;
    78. }
    79. // 绘制云朵
    80. void OLED_DrawCloud()
    81. {
    82. static int pos = 128;
    83. static char height=0;
    84. char speed = 3;
    85. unsigned int i=0;
    86. int x;
    87. int start_x = 0;
    88. int length = sizeof(CLOUD);
    89. unsigned char byte;
    90. //if (pos + length <= -speed) pos = 128;
    91. if (pos + length <= -speed)
    92. {
    93. pos = 128;
    94. height = rand()%3;
    95. }
    96. if(pos < 0)
    97. {
    98. start_x = -pos;
    99. OLED_SetPos(0, 1+height);
    100. }
    101. else
    102. {
    103. OLED_SetPos(pos, 1+height);
    104. }
    105. IIC_Start();
    106. IIC_WriteByte(0x78);
    107. IIC_WaitAck();
    108. IIC_WriteByte(0x40);
    109. IIC_WaitAck();
    110. for (x = start_x; x < length + speed; x++)
    111. {
    112. if (pos + x > 127) break;
    113. if (x < length) byte = CLOUD[x];
    114. else byte = 0x0;
    115. IIC_WriteByte(byte);
    116. IIC_WaitAck();
    117. }
    118. IIC_Stop();
    119. pos = pos - speed;
    120. }
    121. // 绘制小恐龙
    122. void OLED_DrawDino()
    123. {
    124. static unsigned char dino_dir = 0;
    125. unsigned int j=0;
    126. unsigned char x, y;
    127. unsigned char byte;
    128. dino_dir++;
    129. dino_dir = dino_dir%2;
    130. for(y=0; y<2; y++)
    131. {
    132. OLED_SetPos(16, 6+y);
    133. IIC_Start();
    134. IIC_WriteByte(0x78);
    135. IIC_WaitAck();
    136. IIC_WriteByte(0x40);
    137. IIC_WaitAck();
    138. for (x = 0; x < 16; x++)
    139. {
    140. j = y*16 + x;
    141. byte = DINO[dino_dir][j];
    142. IIC_WriteByte(byte);
    143. IIC_WaitAck();
    144. }
    145. IIC_Stop();
    146. }
    147. }
    148. // 绘制仙人掌障碍物
    149. void OLED_DrawCactus()
    150. {
    151. char speed = 5;
    152. static int pos = 128;
    153. int start_x = 0;
    154. int length = sizeof(CACTUS_2)/2;
    155. unsigned int j=0;
    156. unsigned char x, y;
    157. unsigned char byte;
    158. if (pos + length <= 0)
    159. {
    160. oled_drawbmp_block_clear(0, 6, speed);
    161. pos = 128;
    162. }
    163. for(y=0; y<2; y++)
    164. {
    165. if(pos < 0)
    166. {
    167. start_x = -pos;
    168. OLED_SetPos(0, 6+y);
    169. }
    170. else
    171. {
    172. OLED_SetPos(pos, 6+y);
    173. }
    174. IIC_Start();
    175. IIC_WriteByte(0x78);
    176. IIC_WaitAck();
    177. IIC_WriteByte(0x40);
    178. IIC_WaitAck();
    179. for (x = start_x; x < length; x++)
    180. {
    181. if (pos + x > 127) break;
    182. j = y*length + x;
    183. byte = CACTUS_2[j];
    184. IIC_WriteByte(byte);
    185. IIC_WaitAck();
    186. }
    187. IIC_Stop();
    188. }
    189. oled_drawbmp_block_clear(pos + length, 6, speed); // 清除残影
    190. pos = pos - speed;
    191. }
    192. // 绘制随机出现的仙人掌障碍物
    193. int OLED_DrawCactusRandom(unsigned char ver, unsigned char reset)
    194. {
    195. char speed = 5;
    196. static int pos = 128;
    197. int start_x = 0;
    198. int length = 0;
    199. unsigned int i=0, j=0;
    200. unsigned char x, y;
    201. unsigned char byte;
    202. if (reset == 1)
    203. {
    204. pos = 128;
    205. oled_drawbmp_block_clear(0, 6, speed);
    206. return 128;
    207. }
    208. if (ver == 0) length = 8; //sizeof(CACTUS_1) / 2;
    209. else if (ver == 1) length = 16; //sizeof(CACTUS_2) / 2;
    210. else if (ver == 2 || ver == 3) length = 24;
    211. for(y=0; y<2; y++)
    212. {
    213. if(pos < 0)
    214. {
    215. start_x = -pos;
    216. OLED_SetPos(0, 6+y);
    217. }
    218. else
    219. {
    220. OLED_SetPos(pos, 6+y);
    221. }
    222. IIC_Start();
    223. IIC_WriteByte(0x78);
    224. IIC_WaitAck();
    225. IIC_WriteByte(0x40);
    226. IIC_WaitAck();
    227. for (x = start_x; x < length; x++)
    228. {
    229. if (pos + x > 127) break;
    230. j = y*length + x;
    231. if (ver == 0) byte = CACTUS_1[j];
    232. else if (ver == 1) byte = CACTUS_2[j];
    233. else if(ver == 2) byte = CACTUS_3[j];
    234. else byte = CACTUS_4[j];
    235. IIC_WriteByte(byte);
    236. IIC_WaitAck();
    237. }
    238. IIC_Stop();
    239. }
    240. oled_drawbmp_block_clear(pos + length, 6, speed);
    241. pos = pos - speed;
    242. return pos + speed;
    243. }
    244. // 绘制跳跃小恐龙
    245. int OLED_DrawDinoJump(char reset)
    246. {
    247. char speed_arr[] = {1, 1, 3, 3, 4, 4, 5, 6, 7};
    248. static char speed_idx = sizeof(speed_arr)-1;
    249. static int height = 0;
    250. static char dir = 0;
    251. //char speed = 4;
    252. unsigned int j=0;
    253. unsigned char x, y;
    254. char offset = 0;
    255. unsigned char byte;
    256. if(reset == 1)
    257. {
    258. height = 0;
    259. dir = 0;
    260. speed_idx = sizeof(speed_arr)-1;
    261. return 0;
    262. }
    263. if (dir==0)
    264. {
    265. height += speed_arr[speed_idx];
    266. speed_idx --;
    267. if (speed_idx<0) speed_idx = 0;
    268. }
    269. if (dir==1)
    270. {
    271. height -= speed_arr[speed_idx];
    272. speed_idx ++;
    273. if (speed_idx>sizeof(speed_arr)-1) speed_idx = sizeof(speed_arr)-1;
    274. }
    275. if(height >= 31)
    276. {
    277. dir = 1;
    278. height = 31;
    279. }
    280. if(height <= 0)
    281. {
    282. dir = 0;
    283. height = 0;
    284. }
    285. if(height <= 7) offset = 0;
    286. else if(height <= 15) offset = 1;
    287. else if(height <= 23) offset = 2;
    288. else if(height <= 31) offset = 3;
    289. else offset = 4;
    290. for(y=0; y<3; y++) // 4
    291. {
    292. OLED_SetPos(16, 5- offset + y);
    293. IIC_Start();
    294. IIC_WriteByte(0x78);
    295. IIC_WaitAck();
    296. IIC_WriteByte(0x40);
    297. IIC_WaitAck();
    298. for (x = 0; x < 16; x++) // 32
    299. {
    300. j = y*16 + x; // 32
    301. byte = DINO_JUMP[height%8][j];
    302. IIC_WriteByte(byte);
    303. IIC_WaitAck();
    304. }
    305. IIC_Stop();
    306. }
    307. if (dir == 0) oled_drawbmp_block_clear(16, 8- offset, 16);
    308. if (dir == 1) oled_drawbmp_block_clear(16, 4- offset, 16);
    309. return height;
    310. }
    311. // 绘制重启
    312. void OLED_DrawRestart()
    313. {
    314. unsigned int j=0;
    315. unsigned char x, y;
    316. unsigned char byte;
    317. //OLED_SetPos(0, 0);
    318. for (y = 2; y < 5; y++)
    319. {
    320. OLED_SetPos(52, y);
    321. IIC_Start();
    322. IIC_WriteByte(0x78);
    323. IIC_WaitAck();
    324. IIC_WriteByte(0x40);
    325. IIC_WaitAck();
    326. for (x = 0; x < 24; x++)
    327. {
    328. byte = RESTART[j++];
    329. IIC_WriteByte(byte);
    330. IIC_WaitAck();
    331. }
    332. IIC_Stop();
    333. }
    334. OLED_ShowString(10, 3, "GAME", 16);
    335. OLED_ShowString(86, 3, "OVER", 16);
    336. }
    337. // 绘制封面
    338. void OLED_DrawCover()
    339. {
    340. OLED_DrawBMPFast(COVER);
    341. }

    5.3 谷歌小恐龙的运行控制代码

    control.h:

    1. #ifndef __CONTROL_H
    2. #define __CONTROL_H
    3. int get_key();
    4. void Game_control();
    5. #endif

    control.c:

    1. #include "control.h"
    2. #include "oled.h"
    3. #include "dinogame.h"
    4. #include "stdlib.h"
    5. unsigned char key_num = 0;
    6. unsigned char cactus_category = 0;
    7. unsigned char cactus_length = 8;
    8. unsigned int score = 0;
    9. unsigned int highest_score = 0;
    10. int height = 0;
    11. int cactus_pos = 128;
    12. unsigned char cur_speed = 30;
    13. char failed = 0;
    14. char reset = 0;
    15. int get_key()
    16. {
    17. if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0)
    18. {
    19. HAL_Delay(10); //延迟
    20. if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4)==0)
    21. {
    22. return 2;
    23. }
    24. }
    25. if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0)
    26. {
    27. HAL_Delay(10); //延迟
    28. if(HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3)==0)
    29. {
    30. return 1;
    31. }
    32. }
    33. if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1)
    34. {
    35. HAL_Delay(10); //延迟
    36. if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0)==1)
    37. {
    38. return 3;
    39. }
    40. }
    41. return 0;
    42. }
    43. void Game_control()
    44. {
    45. while(1)
    46. {
    47. if(get_key() == 3) //wk_up按键按下强制退出一次循环
    48. {
    49. break;
    50. }
    51. if (failed == 1)
    52. {
    53. OLED_DrawRestart();
    54. key_num = get_key();
    55. if (key_num == 2)
    56. {
    57. if(score > highest_score) highest_score = score;
    58. score = 0;
    59. failed = 0;
    60. height = 0;
    61. reset = 1;
    62. OLED_DrawDinoJump(reset);
    63. OLED_DrawCactusRandom(cactus_category, reset);
    64. OLED_Clear();
    65. }
    66. continue;
    67. }
    68. score ++;
    69. if (height <= 0) key_num = get_key();
    70. OLED_DrawGround();
    71. OLED_DrawCloud();
    72. if (height>0 || key_num == 1) height = OLED_DrawDinoJump(reset);
    73. else OLED_DrawDino();
    74. cactus_pos = OLED_DrawCactusRandom(cactus_category, reset);
    75. if(cactus_category == 0) cactus_length = 8;
    76. else if(cactus_category == 1) cactus_length = 16;
    77. else cactus_length = 24;
    78. if (cactus_pos + cactus_length < 0)
    79. {
    80. cactus_category = rand()%4;
    81. OLED_DrawCactusRandom(cactus_category, 1);
    82. }
    83. if ((height < 16) && ( (cactus_pos>=16 && cactus_pos <=32) || (cactus_pos + cactus_length>=16 && cactus_pos + cactus_length <=32)))
    84. {
    85. failed = 1;
    86. }
    87. OLED_ShowString(35, 0, "HI:", 12);
    88. OLED_ShowNum(58, 0, highest_score, 5, 12);
    89. OLED_ShowNum(98, 0, score, 5, 12);
    90. reset = 0;
    91. cur_speed = score/20;
    92. if (cur_speed > 29) cur_speed = 29;
    93. HAL_Delay(30 - cur_speed);
    94. // HAL_Delay(500);
    95. key_num = 0;
    96. }
    97. }

    5.4 多级菜单核心代码:

    menu.h:

    1. #ifndef __MENU_H
    2. #define __MENU_H
    3. #include "main.h"
    4. #define u8 unsigned char
    5. //按键定义
    6. #define KEY0 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_4) //低电平有效 KEY0
    7. #define KEY1 HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_3) //低电平有效
    8. #define WK_UP HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) //高电平有效
    9. typedef struct
    10. {
    11. u8 current; //当前状态索引号
    12. u8 next; //向下一个
    13. u8 enter; //确定
    14. u8 back; //退出
    15. void (*current_operation)(void); //当前状态应该执行的操作
    16. } Menu_table;
    17. //界面UI
    18. void home();
    19. void Temperature();
    20. void Palygame();
    21. void Setting();
    22. void Info();
    23. void Menu_key_set(void);
    24. u8 KEY_Scan(u8 mode);
    25. void TestTemperature();
    26. void ConrtolGame();
    27. void Set();
    28. void Information();
    29. void LED();
    30. void RTC_display();
    31. #endif

    menu.c:

    1. #include "menu.h"
    2. #include "oled.h"
    3. #include "gpio.h"
    4. #include "dinogame.h"
    5. #include "control.h"
    6. #include "DHT11.h"
    7. #include "rtc.h"
    8. RTC_DateTypeDef GetData; //获取日期结构体
    9. RTC_TimeTypeDef GetTime; //获取时间结构体
    10. //UI界面
    11. //主页
    12. /****************************************************/
    13. //UI库
    14. /****************************************************/
    15. void (*current_operation_index)();
    16. Menu_table table[30]=
    17. {
    18. {0,0,1,0,(*home)}, //一级界面(主页面) 索引,向下一个,确定,退出
    19. {1,2,5,0,(*Temperature)}, //二级界面 温湿度
    20. {2,3,6,0,(*Palygame)}, //二级界面 游戏
    21. {3,4,7,0,(*Setting)}, //二级界面 设置
    22. {4,1,8,0,(*Info)}, //二级界面 信息
    23. {5,5,5,1,(*TestTemperature)}, //三级界面:DHT11测量温湿度
    24. {6,6,6,2,(*ConrtolGame)}, //三级界面:谷歌小恐龙Dinogame
    25. {7,7,9,3,(*Set)}, //三级界面:设置普通外设状态 LED
    26. {8,8,8,4,(*Information)}, //三级界面:作者和相关项目信息
    27. {9,9,7,3,(*LED)}, //LED控制
    28. };
    29. uint8_t func_index = 0; //主程序此时所在程序的索引值
    30. void Menu_key_set(void)
    31. {
    32. if((KEY_Scan(1) == 1) && (func_index != 6))
    33. {
    34. func_index=table[func_index].next; //按键next按下后的索引号
    35. OLED_Clear();
    36. }
    37. if((KEY_Scan(1) == 2) && (func_index != 6))
    38. {
    39. func_index=table[func_index].enter; //按键enter按下后的索引号
    40. OLED_Clear();
    41. }
    42. if(KEY_Scan(1) == 3)
    43. {
    44. func_index=table[func_index].back; //按键back按下后的索引号
    45. OLED_Clear();
    46. }
    47. current_operation_index=table[func_index].current_operation; //执行当前索引号所对应的功能函数
    48. (*current_operation_index)();//执行当前操作函数
    49. }
    50. void home()
    51. {
    52. RTC_display();
    53. OLED_DrawBMP(0,0,20,3,signal_BMP);
    54. OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
    55. OLED_DrawBMP(112,0,128,2,gImage_engery);
    56. OLED_DrawBMP(4,6,20,8,gImage_yes);
    57. OLED_DrawBMP(12,4,28,6,gImage_left);
    58. OLED_DrawBMP(40,2,88,8,gImage_home);
    59. OLED_DrawBMP(99,4,115,6,gImage_right);
    60. OLED_DrawBMP(107,6,123,8,gImage_back);
    61. }
    62. void Temperature()
    63. {
    64. RTC_display();
    65. OLED_DrawBMP(0,0,20,3,signal_BMP);
    66. OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
    67. OLED_DrawBMP(112,0,128,2,gImage_engery);
    68. OLED_DrawBMP(4,6,20,8,gImage_yes);
    69. OLED_DrawBMP(12,4,28,6,gImage_left);
    70. OLED_DrawBMP(40,2,88,8,gImage_temp);
    71. OLED_DrawBMP(99,4,115,6,gImage_right);
    72. OLED_DrawBMP(107,6,123,8,gImage_back);
    73. }
    74. void Palygame()
    75. {
    76. RTC_display();
    77. OLED_DrawBMP(0,0,20,3,signal_BMP);
    78. OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
    79. OLED_DrawBMP(112,0,128,2,gImage_engery);
    80. OLED_DrawBMP(4,6,20,8,gImage_yes);
    81. OLED_DrawBMP(12,4,28,6,gImage_left);
    82. OLED_DrawBMP(40,2,88,8,gImage_playgame);
    83. OLED_DrawBMP(99,4,115,6,gImage_right);
    84. OLED_DrawBMP(107,6,123,8,gImage_back);
    85. }
    86. void Setting()
    87. {
    88. RTC_display();
    89. OLED_DrawBMP(0,0,20,3,signal_BMP);
    90. OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
    91. OLED_DrawBMP(112,0,128,2,gImage_engery);
    92. OLED_DrawBMP(4,6,20,8,gImage_yes);
    93. OLED_DrawBMP(12,4,28,6,gImage_left);
    94. OLED_DrawBMP(40,2,88,8,gImage_setting);
    95. OLED_DrawBMP(99,4,115,6,gImage_right);
    96. OLED_DrawBMP(107,6,123,8,gImage_back);
    97. }
    98. void Info()
    99. {
    100. RTC_display();
    101. OLED_DrawBMP(0,0,20,3,signal_BMP);
    102. OLED_DrawBMP(20,0,36,2,gImage_bulethouch);
    103. OLED_DrawBMP(112,0,128,2,gImage_engery);
    104. OLED_DrawBMP(4,6,20,8,gImage_yes);
    105. OLED_DrawBMP(12,4,28,6,gImage_left);
    106. OLED_DrawBMP(40,2,88,8,gImage_info);
    107. OLED_DrawBMP(99,4,115,6,gImage_right);
    108. OLED_DrawBMP(107,6,123,8,gImage_back);
    109. }
    110. //按键函数,不支持连按
    111. u8 KEY_Scan(u8 mode)
    112. {
    113. static u8 key_up=1;
    114. if(mode)key_up=1;
    115. if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
    116. {
    117. HAL_Delay(100); //消抖
    118. key_up=0;
    119. if(KEY0==0)return 1;
    120. else if(KEY1==0)return 2;
    121. else if(WK_UP==1)return 3;
    122. }else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
    123. return 0;
    124. }
    125. void TestTemperature()
    126. {
    127. DHT11();
    128. }
    129. void ConrtolGame()
    130. {
    131. Game_control();
    132. }
    133. void Set()
    134. {
    135. OLED_ShowString(0,0,"Peripherals: Lights",16);
    136. OLED_ShowString(0,2,"Status: Closed",16);
    137. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
    138. }
    139. void Information()
    140. {
    141. OLED_ShowString(0,0,"Author:Sneak",16);
    142. OLED_ShowString(0,2,"Date:2022/8/23",16);
    143. OLED_ShowString(0,4,"Lab: Multi-level menu",16);
    144. }
    145. void LED()
    146. {
    147. OLED_ShowString(0,0,"Peripherals: Lights",16);
    148. OLED_ShowString(0,2,"Status: Open",16);
    149. HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
    150. }
    151. void RTC_display() //RTC????
    152. {
    153. /* Get the RTC current Time */
    154. HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
    155. /* Get the RTC current Date */
    156. HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);
    157. /* Display date Format : yy/mm/dd */
    158. /* Display time Format : hh:mm:ss */
    159. OLED_ShowNum(40,0,GetTime.Hours,2,16); //hour
    160. OLED_ShowString(57,0,":",16);
    161. OLED_ShowNum(66,0,GetTime.Minutes,2,16); //min
    162. OLED_ShowString(83,0,":",16);
    163. OLED_ShowNum(93,0,GetTime.Seconds,2,16); //seconds
    164. }

    六、项目演示

    多级菜单(简化版智能手表)

    总结与代码开源

            总结:本项目目前还处于最初代版本,十分简易,后期笔者将抽时间去精进优化该多级菜单项目。其中,UI界面中的电池与信号目前都还处于贴图状态,后期笔者会加上库仑计测量电池电量等。文章中指出了需要注意的地方与可以改进的点,感兴趣的朋友可以彼此交流交流。(积分够的朋友可以支持一下,如果不够的话,点个关注,评论区留下邮箱,笔者看到会尽快发送项目代码。

    代码地址:基于STM32的OLED多级菜单项目(简化版智能手表)-嵌入式文档类资源-CSDN文库

  • 相关阅读:
    微信小程序通过 wxministore 实现类似于vuex的全局装填数据管理
    Unity中神秘的Transform和transform(小写)的关系
    1.docker的基本使用
    mac磁盘清理的方法大全
    爱上开源之golang入门至实战-第二章语言基础
    java的for循环中遇到异常抛出后继续循环执行
    java基于微信小程序的点餐外卖系统 uniapp 小程序
    图书管理系统路径不对
    TF-PLA 转铁蛋白修饰聚乳酸 Transferrin-PEG-PLA
    dart特殊符号语法(一)
  • 原文地址:https://blog.csdn.net/black_sneak/article/details/126442559