很久之前,写过一个简易的多级菜单,如今回头看,我都不敢承认那玩意是自己写的,于是打算重新写过一个,并且做成一个简易的GUI。原来用的OLED,现在改成了TFT(ST7789驱动)。但是答题的思路还是不变的。

先说说思路,由于选择的是一个没有触摸的屏幕,于是就要加上物理按键。这样才能操作屏幕嘛。
按照面向对象的思想,我将整个GUI结构分为三类:按键,显示,动作。

  1. 按键用来控制屏幕,进行上滑下滑切入切出菜单;
  2. 显示则作为每一级菜单的背景,并且将菜单设置为静态显示(即每次切换只显示一次);
  3. 动作则用来链接各种显示功能。例如一级菜单切入二级菜单这么个动作;
  4. 除此之外就是各种动态效果,这部分暂时还未开工。

接下来看看menu.c文件的内容

#include #include "string.h"#include "lcd_init.h"#include "lcd.h"#include "main.h"#include "menu.h"//多级菜单的级数#defineMenu_Select3//选项显示缓存区char buf[100];//按键标志初始值uint8_t List_Number = 1;//主次菜单切换static uint8_t Menu_Change = 0;//菜单刷新标志位static uint8_t Menu_Refresh_Flag = 0;//up/down控制按键标志位static uint8_t UD_Action_Flag = 0;extern unsigned char BMP_1[];extern unsigned char BMP_2[];extern unsigned char BMP_3[];extern unsigned char BMP_4[];extern unsigned char BMP_5[];extern unsigned char BMP_6[];extern unsigned char BMP_7[];extern unsigned char BMP_8[];extern unsigned char BMP_9[];/******************************************************************************* @brief绘制一条垂线或者横线* @paramx1,y1 起始坐标x2,y2 终点坐标color 颜色* @retval 无******************************************************************************/void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color){u16 i = 0;if (x1 == x2){for (i = y1; i <= y2; i++){LCD_DrawPoint(x1, i, color);}}if (y1 == y2){for (i = x1; i <= x2; i++){LCD_DrawPoint(i, y1, color);}}}/******************************************************************************* @brief绘制一个方框* @paramx1,y1 起始坐标x2,y2 终点坐标color 颜色* @retval 无******************************************************************************/void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color){Draw_Line(x1, y1, x2, y1, color);Draw_Line(x1, y1, x1, y2, color);Draw_Line(x1, y2, x2, y2, color);Draw_Line(x2, y1, x2, y2, color);}/******************************************************************************* @brief绘制选项框* @paramx1,y1 起始坐标x2,y2 终点坐标color 颜色index 选项框的厚度* @retval 无******************************************************************************/void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index){u16 i = 0;for (i = 0; i < index; i++){Draw_Rectangle(x1 + i, y1 + i, x2 - i, y2 - i, color);}}/******************************************************************************* @brief绘制窗体* @paramx1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List选项框个数* @retval 无 ******************************************************************************/void Draw_Windows(void){Draw_Box(0, 0, 240, 240, LBBLUE, 5);Draw_Box(0, 36, 240, 41, LBBLUE, 5);sprintf(buf, " WINDOWS1");LCD_ShowString(5, 5, (uint8_t*)buf, WHITE, LBBLUE, 32, 0);}/******************************************************************************* @brief绘制圆角框* @paramx0,y0 圆心坐标color 颜色length 框体长度* @retval 无 ******************************************************************************/void Draw_Circal_Box(u16 x0, u16 y0, u8 r, u16 color, u8 length){int a, b;a = 0;b = r;while (a <= b){LCD_DrawPoint(x0 - b, y0 - a, color);LCD_DrawPoint(x0 - b, y0 + a, color);LCD_DrawPoint(x0 - a, y0 + b, color);LCD_DrawPoint(x0 - a, y0 - b, color);LCD_DrawPoint(x0 + b + length, y0 - a, color);LCD_DrawPoint(x0 + b + length, y0 + a, color);LCD_DrawPoint(x0 + a + length, y0 - b, color);LCD_DrawPoint(x0 + a + length, y0 + b, color);a++;if ((a * a + b * b) > (r * r))//判断要画的点是否过远{b--;}}LCD_DrawLine(x0, y0 - r, x0 + length, y0 - r, color);LCD_DrawLine(x0, y0 + r, x0 + length, y0 + r, color);}/******************************************************************************* @brief绘制实心圆* @paramx0,y0 圆心坐标color 颜色* @retval 无 ******************************************************************************/void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color){int a, b, c;a = 0;b = r;for (c = r; c > 0; c--){Draw_Circle(x0, y0, c - 1, color);}}/******************************************************************************* @brief显示初始菜单,自己任意配置* @paramx,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:0非叠加模式1叠加模式Box_Height 选项框的高度* @retval 无******************************************************************************/void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height){sprintf(buf,"DISPLAY");LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"VOICE");LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"NOTICE");LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"BATTERY");LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"MEMORY");LCD_ShowString(x, y + ( Box_Height * 4 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"ABOUT");LCD_ShowString(x, y + ( Box_Height * 5 ), (uint8_t *)buf, fc, bc, sizey, mode);}/******************************************************************************* @brief显示二级菜单,自己任意配置* @paramx,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:0非叠加模式1叠加模式Box_Height 选项框的高度* @retval 无******************************************************************************/void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height){sprintf(buf,"DISPLAY");LCD_ShowString(x, y + ( Box_Height * 0 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"VOICE");LCD_ShowString(x, y + ( Box_Height * 1 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"NOTICE");LCD_ShowString(x, y + ( Box_Height * 2 ), (uint8_t *)buf, fc, bc, sizey, mode);sprintf(buf,"BATTERY");LCD_ShowString(x, y + ( Box_Height * 3 ), (uint8_t *)buf, fc, bc, sizey, mode);}/******************************************************************************* @brief显示三级菜单,自己任意配置* @paramx,y 显示坐标fc 字的颜色bc 字的背景色sizey 字号mode:0非叠加模式1叠加模式Box_Height 选项框的高度* @retval 无******************************************************************************/void Display_Third_Menu(void){LCD_ShowPicture( 25, 15, 50, 50, BMP_1);//1LCD_ShowPicture( 95, 15, 50, 50, BMP_2);//2LCD_ShowPicture(175, 15, 50, 50, BMP_3);//3LCD_ShowPicture( 15, 95, 50, 50, BMP_4);//4LCD_ShowPicture( 95, 95, 50, 50, BMP_5);//5LCD_ShowPicture(175, 95, 50, 50, BMP_6);//6LCD_ShowPicture( 15, 175, 50, 50, BMP_7);//7LCD_ShowPicture( 95, 175, 50, 50, BMP_8);//8LCD_ShowPicture(175, 175, 50, 50, BMP_9);//9}/******************************************************************************* @brief注册up控制按键* @paramMenu_List:控制的列表项数目* @retval 无* eg:一个页面有6个选项,就让 Menu_List = 6 ******************************************************************************/void Button_Up_Click(uint8_t Menu_List){if (Button_Click_Event_1){if (List_Number > 1)List_Number = List_Number - 1;else if (List_Number == 1)List_Number = Menu_List;elseList_Number = List_Number;UD_Action_Flag = 2;//显示列表项标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}}/******************************************************************************* @brief注册down控制按键* @param控制的列表项数目* @retval 无 ******************************************************************************/void Button_Down_Click(uint8_t Menu_List){if (Button_Click_Event_2){if (List_Number < Menu_List)List_Number = List_Number + 1;else if (List_Number == Menu_List)List_Number = 1;elseList_Number = List_Number;UD_Action_Flag = 1;//显示列表项标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}}/******************************************************************************* @brief注册菜单切入控制按键* @paramDesk_Num 菜单级数* @retval 无****************************************************************************/void Button_Menu_Enter(u8 Desk_Num){LCD_ShowIntNum(200,220,Menu_Change,1,WHITE,BLACK,16);//切入菜单,即进入下一级菜单if(Button_Click_Event_3){Action_Menu_Change(0);//载入下一级菜单动作Menu_Refresh_Flag = 1;if (Menu_Change < Desk_Num - 1)Menu_Change = Menu_Change + 1;else if (Menu_Change == Desk_Num - 1)Menu_Change = Desk_Num - 1;//显示菜单级标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}}/******************************************************************************* @brief注册菜单切出控制按键* @paramDesk_Num 菜单级数* @retval 无****************************************************************************/void Button_Menu_Exit(u8 Desk_Num){LCD_ShowIntNum(200, 220, Menu_Change, 1, WHITE, BLACK, 16);//切出菜单,即返回上一级菜单if (Button_Click_Event_4){Action_Menu_Change(1);//载入上一级菜单的动作Menu_Refresh_Flag = 1;if (Menu_Change > 0)Menu_Change = Menu_Change - 1;if (Menu_Change < Desk_Num - 2)Menu_Change = 0;//显示菜单级标号LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);}}/******************************************************************************* @brief注册菜单切换控制按键搭配的动作* @paramselect 事件标志* @retval 无****************************************************************************/u8 List = 0,List2 = 0;void Action_Menu_Change(u8 select){switch (select){case 0:switch (Menu_Change){case 0:List = List_Number;List_Number = 1;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;case 1:List2 = List_Number;List_Number = 1;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;//case ...3级、4级菜单}break;case 1:switch (Menu_Change){case 1:List_Number = List;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;case 2:List_Number = List2;LCD_Fill(0, 0, LCD_W, LCD_H, BLACK);break;//case ...3级、4级菜单}default:break;}}/******************************************************************************* @brief注册选项框上滑动作* @paramx1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List选项框个数* @retval 无 ******************************************************************************/void Action_Box_Up(u16 x1,u16 y1,u16 x2,u16 y2,uint8_t Menu_List, uint8_t Box_Height){if(UD_Action_Flag == 2){UD_Action_Flag = 0;if(List_Number >= 1 && List_Number < Menu_List){LCD_DrawLine(x1, y2+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);LCD_DrawLine(x1, y1+Box_Height*(List_Number), x1, y2+Box_Height*(List_Number), BLACK);LCD_DrawLine(x2, y1+Box_Height*(List_Number), x2, y2+Box_Height*(List_Number), BLACK);printf("%d",List_Number);}if(List_Number == Menu_List){LCD_DrawRectangle(x1, y1 + Box_Height*(0), x2, y2+Box_Height*(0), BLACK);}}}/******************************************************************************* @brief注册选项框下滑动作* @paramx1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List选项框个数* @retval 无 ******************************************************************************/void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height){if (UD_Action_Flag == 1){UD_Action_Flag = 0;if (List_Number > 1 && List_Number <= Menu_List){LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x2, y1 + Box_Height * (List_Number - 2), BLACK);LCD_DrawLine(x1, y1 + Box_Height * (List_Number - 2), x1, y2 + Box_Height * (List_Number - 2), BLACK);LCD_DrawLine(x2, y1 + Box_Height * (List_Number - 2), x2, y2 + Box_Height * (List_Number - 2), BLACK);printf("%d", List_Number);}if (List_Number == 1){LCD_DrawRectangle(x1, y1 + Box_Height * (Menu_List - 1), x2, y2 + Box_Height * (Menu_List - 1), BLACK);}}}/******************************************************************************* @brief绘制选项框* @paramx1,y1 起始坐标x2,y2 终点坐标Box_Height 选项框的高度Menu_List选项框个数* @retval 无 ******************************************************************************/void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height){LCD_DrawRectangle(x1, y1 + Box_Height * (List_Number - 1), x2, y2 + Box_Height * (List_Number - 1), BLUE);}/******************************************************************************* @brief注册菜单* @param无* @retval 无 ******************************************************************************/void Menu_Display_Control(void){Button_Menu_Enter(Menu_Select);//菜单等级控制Button_Menu_Exit(Menu_Select);switch (Menu_Change){case 0:if (Menu_Refresh_Flag == 1){Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);//注册一个0级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(3, 0, 200, 30, 6, 30);//注册选项框Action_Box_Up(3, 0, 200, 30, 6, 30); //添加选项框的上滑动作Action_Box_Down(3, 0, 200, 30, 6, 30); //添加选项框的下滑动作Button_Down_Click(6);//注册down控制按钮Button_Up_Click(6);//注册up控制按钮break;case 1:if (Menu_Refresh_Flag == 1){Display_Second_Menu(10, 3, RED, BLACK, 32, 0, 59);//注册一个1级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(3, 0, 150, 59, 4, 59);//注册选项框Action_Box_Up(3, 0, 150, 59, 4, 59); //添加选项框的上滑动作Action_Box_Down(3, 0, 150, 59, 4, 59); //添加选项框的下滑动作Button_Down_Click(4);//注册down控制按钮Button_Up_Click(4);//注册up控制按钮break;case 2:if (Menu_Refresh_Flag == 1){Display_Third_Menu();//注册一个2级菜单Menu_Refresh_Flag = 0;}Draw_Option_Box(0, 0, 80, 79, 3, 79);//注册选项框Action_Box_Up(0, 0, 80,79, 3, 79); //添加选项框的上滑动作Action_Box_Down(0, 0, 80, 79, 3, 79); //添加选项框的下滑动作Button_Down_Click(3);//注册down控制按钮Button_Up_Click(3);//注册up控制按钮break;default:Menu_Refresh_Flag = 0;break;}}

重点:如何创建菜单

如果使用其中自带的选项框,那么只需要修改两处即可
一是 Action_Menu_Change(u8 select) 函数,在其中添加 List_Number 变量的继承;
二是Menu_Display_Control(void) 函数中注册新的菜单。
重点在第二个函数

  1. 首先要确定菜单级数
  2. 然后申请一个或多个新的菜单,并且在Menu_Refresh_Flag == 1的情况下申请,不然会不断刷新屏幕
  3. 然后注册选项框,注册选项框的动作,动作可以自行修改
  4. 最后注册按键

接下来是menu.h文件内容

#ifndef __menu_H_#define __menu_H_#include #include "string.h"#include "stdint.h"#include "main.h"#define Button_Click_Event_1HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET//up控制按键#define Button_Click_Event_2HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin) == GPIO_PIN_RESET//down控制按键#define Button_Click_Event_3HAL_GPIO_ReadPin(KEY3_GPIO_Port, KEY3_Pin) == GPIO_PIN_RESET//菜单切入控制按键#define Button_Click_Event_4HAL_GPIO_ReadPin(KEY4_GPIO_Port, KEY4_Pin) == GPIO_PIN_RESET//菜单切出控制按键void Display_Initial_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);void Display_Second_Menu(u16 x,u16 y,u16 fc,u16 bc,u8 sizey,u8 mode,u8 Box_Height);void Display_Third_Menu(void);void Draw_Box(u16 x1, u16 y1, u16 x2, u16 y2, u16 color, u16 index);void Draw_Line(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);void Draw_Rectangle(u16 x1, u16 y1, u16 x2, u16 y2, u16 color);void Draw_Option_Box(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);void Draw_Windows(void);void Draw_Circal_Box(u16 x0,u16 y0,u8 r,u16 color, u8 length);void Draw_Circal_Solid(u16 x0, u16 y0, u8 r, u16 color);void Button_Down_Click(uint8_t Menu_List);void Button_Up_Click(uint8_t Menu_List);void Button_Menu_Enter(u8 Desk_Num);void Button_Menu_Exit(u8 Desk_Num);void Action_Box_Up(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);void Action_Box_Down(u16 x1, u16 y1, u16 x2, u16 y2, uint8_t Menu_List, uint8_t Box_Height);void Action_Menu_Change(u8 select);void Menu_Display_Control(void);//void DA(void);#endif

最后是main.c的内容

在while循环里添加 Menu_Display_Control(); 即可,菜单的其他配置都在menu.c中配置完成。

int main(void){HAL_Init(); SystemClock_Config();MX_GPIO_Init();MX_USART2_UART_Init();LCD_Init();//LCD初始化LCD_Fill(0,0,LCD_W,LCD_H,BLACK);Display_Initial_Menu(10, 3, WHITE, BLACK, 24, 0, 30);//注册一个0级菜单,防止刚开机不显示东东西LCD_ShowIntNum(220, 220, List_Number, 1, WHITE, BLACK, 16);while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */Menu_Display_Control();}/* USER CODE END 3 */}

显示效果

B站视频效果