STM32多级界面搭建
在实际的项目之中,需要用单片机在LCD/OLED中搭建一个3级的图形界面。本文的工程文件我将上次传到百度网盘,链接在最后的部分。
一、方案选择
方案一:使用if条件语句写一个界面,但是这种方法在两级界面的时候还可以,在多级界面的时候代码的逻辑上的复杂度会大幅增加,不太使用。
方案二:使用嵌入式图形库lvgl等,但是这个对新手不太友好。
方案三:使用结构体变量存储界面信息,可以是使得多级界面也可以逻辑清晰。
二、实现效果
主界面: 菜单界面:
功能界面:
三、程序设计
3-1宏定义变量设计
·界面标志位: 在头文件中首先需要定义一个结构体来存放界面标志位,包括现在界面的状态,当前运行的任务状态,以及当前任务是否处于运行状态。
·界面变量: 创建三个枚举变量,Main_Interface,Menu_Interface,Task_Interface,来表示目前界面所在的位置,是主界面还是菜单界面还是任务界面。
·任务变量: 创建n个任务,每个任务提供一个代号,方便识别。
#ifndef __GUI_H__
#define __GUI_H__
typedef struct
{
uint8_t Interface_Mark;
uint8_t Task_Mark;
uint8_t Run_Task;
} Mark;
enum
{
Main_Interface,
Menu_Interface,
Task_Interface,
};
enum
{
F_ONE,
F_TWO,
F_THREE,
F_FOUR,
Stopping,
};
extern Mark Mark_Sign;
void Interface_APP(Mark *Mark_Sign);
void Interface(Mark *Mark_Sign,uint8_t keys);
void GUI_init(void);
#endif
3-2控制函数设计
主函数调用: 使用定义的结构体来显示界面,即显示初始界面。
显示界面函数: 主函数调用,根据Mark_Sign->Interface_Mar变量来确实显示当前所在的界面。
功能切换函数:主函数调用,通过扫描按键,当按下对应按键时,执行相应的函数,如确认进入函数,返回退出函数,功能切换函数。
void GUI_init(void)
{
Interface_APP(&Mark_Sign);
}
void Interface_APP(Mark *Mark_Sign)
{
switch (Mark_Sign->Interface_Mark)
{
case Main_Interface:
Main_Interface_APP();
break;
case Menu_Interface:
Menu_Interface_APP();
break;
case Task_Interface:
Function_Menu_APP(Mark_Sign);
break;
default:
break;
}
}
void Interface(Mark *Mark_Sign,uint8_t keys)
{
if(keys == KEY1_PRES)
{
key_Inter(Mark_Sign);
}
if(keys == KEY2_PRES)
{
key_Cancel(Mark_Sign);
}
if(keys == KEY3_PRES)
{
key_Up_move(Mark_Sign);
}
if(keys == KEY4_PRES)
{
key_Down_move(Mark_Sign);
}
}
不同界面的显示函数: 主界面,菜单界面,功能界面
static unsigned char Lcd_Disp_String[21];
void Main_Interface_APP(void)
{
LCD_DisplayStringLine(Line1 ,(unsigned char *)"Main_Interface_APP");
LCD_DisplayStringLine(Line2 ,(unsigned char *)"ADC-LED-REF");
}
void Menu_Interface_APP(void)
{
LCD_DisplayStringLine(Line1 ,(unsigned char *)"Menu_Interface_APP");
}
void Function_Menu_APP(Mark *Mark_Sign)
{
LCD_DisplayStringLine(Line1 ,(unsigned char *)"Function_Menu_APP ");
if(Mark_Sign->Task_Mark == F_ONE )
{
LCD_DisplayStringLine(Line2 ,(unsigned char *)"ADC ");
sprintf((char*)Lcd_Disp_String, " RES38_vol: %6.3f ",getADC1()*(3.3/4096));
LCD_DisplayStringLine(Line4, Lcd_Disp_String);
}
}
按键控制函数: 确认 返回 切换 其中keys_number用于切换功能时候,选择对应功能的时候记录用,因为up和down函数在切换的时候,需要作出循环的效果,既可以到了上限F_FOUR之后就,继续向上时,重新回到F_ONE, 也可以在到了下限F_ONE之后就,继续向下时,重新回到F_FOUR。
uint8_t keys_number;
void key_Inter(Mark *Mark_Sign)
{
if(Main_Interface == Mark_Sign->Interface_Mark)
{
Mark_Sign->Interface_Mark = Menu_Interface;
Mark_Sign->Task_Mark = F_ONE;
keys_number = 0;
}
else if(Menu_Interface == Mark_Sign->Interface_Mark)
{
Mark_Sign->Interface_Mark = Task_Interface;
switch(Mark_Sign->Task_Mark)
{
case F_ONE:
Mark_Sign->Run_Task = F_ONE;
break;
case F_TWO:
Mark_Sign->Run_Task = F_TWO;
break;
case F_THREE:
Mark_Sign->Run_Task = F_THREE;
break;
case F_FOUR:
Mark_Sign->Run_Task = F_FOUR;
break;
default:
break;
}
}
else if(Task_Interface == Mark_Sign->Interface_Mark)
{
switch(Mark_Sign->Run_Task)
{
case F_ONE:
break;
case F_TWO:
break;
case F_THREE:
break;
case F_FOUR:
break;
default:
break;
}
}
}
void Menu_cancel(uint8_t keys_number,Mark *Mark_Sign);
void Menu_input(uint8_t keys_number,Mark *Mark_Sign);
void key_Cancel(Mark *Mark_Sign)
{
if(Menu_Interface == Mark_Sign->Interface_Mark)
{
Mark_Sign->Interface_Mark = Main_Interface;
}
else if(Task_Interface == Mark_Sign->Interface_Mark)
{
Mark_Sign->Interface_Mark = Menu_Interface;
Mark_Sign->Run_Task = Stopping;
Menu_cancel(keys_number,Mark_Sign);
}
}
void key_Up_move(Mark *Mark_Sign)
{
if(Mark_Sign->Interface_Mark==Menu_Interface)
{
if(keys_number==0) keys_number = 4;
keys_number--;
Menu_cancel(keys_number,Mark_Sign);
}
else if(Mark_Sign->Run_Task == F_ONE)
{
}
}
void key_Down_move(Mark *Mark_Sign)
{
if(Mark_Sign->Interface_Mark==Menu_Interface)
{
if(keys_number==4) keys_number=0;
keys_number++;
Menu_input(keys_number,Mark_Sign);
}
else if(Mark_Sign->Run_Task == F_ONE)
{
}
}
切换时的效果: 不管是LCD 还是OLED在切换界面的时候,如果不及时清理的话,都会保留上一次的印记,因此在任务变更,界面改变的时候都要及时清理。
==而且可以在任务界面的时候可以写一个光标来表示,即将运行的任务是什么。==这样便于观看。
void Menu_cancel(uint8_t keys_number,Mark *Mark_Sign)
{
if(keys_number==3)
{
Mark_Sign->Task_Mark = F_FOUR;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"TEST ");
}
else if(keys_number==2)
{
Mark_Sign->Task_Mark = F_THREE;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"FRE ");
}
else if(keys_number==1)
{
Mark_Sign->Task_Mark = F_TWO;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"LED ");
}
else if(keys_number<=0)
{
Mark_Sign->Task_Mark = F_ONE;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"ADC ");
keys_number=0;
}
}
void Menu_input(uint8_t keys_number,Mark *Mark_Sign)
{
if(keys_number==1)
{
Mark_Sign->Task_Mark = F_TWO;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"LED ");
}
else if(keys_number==2)
{
Mark_Sign->Task_Mark = F_THREE;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"FRE ");
}
else if(keys_number==3)
{
Mark_Sign->Task_Mark = F_FOUR;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"AAA ");
}
else if(keys_number>=4)
{
Mark_Sign->Task_Mark = F_ONE;
LCD_DisplayStringLine(Line2 ,(unsigned char *)"ADC ");
}
}
3-3主函数调用设计
主函数调用: 在主函数先定义一个结构体Mark Mark_Sign = {Main_Interface,0,0};来表示初始界面为主界面。之后调用gui_init函数来显示初始界面。
在while循环中不断读取按键值和显示界面函数来实现显示界面的动态更新。
#include "main.h"
#include "adc.h"
#include "usart.h"
#include "gpio.h"
#include "lcd.h"
#include "stdio.h"
#include "gui.h"
Mark Mark_Sign = {Main_Interface,0,0};
void SystemClock_Config(void);
unsigned char Lcd_Disp_String[21];
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_ADC1_Init();
MX_ADC2_Init();
LCD_Init();
LCD_Clear(White);
LCD_SetBackColor(White);
LCD_SetTextColor(Black);
LCD_Clear(White);
LCD_DisplayStringLine(Line1 ,(unsigned char *)" hello dma. ");
GUI_init();
while (1)
{
Interface_APP(&Mark_Sign);
Interface(&Mark_Sign, KEY_Scan(0));
}
}
四、总结
这是一个用结构体写的一个基础多级界面的框架,如果你使用的界面大于或是小时3级的话,这时候就需要对结构的中的界面枚举变量进行更改。根据整个设计你所喜欢的界面叭。总体来说来时不错的
本文的工程文件我将上次传到百度网盘。链接: https://pan.baidu.com/s/1QNpKRsyHVr-UyCGsyvkLBw . 提取码:vsh2
此文本工程参考了链接: 基于STM32之OLED菜单界面框架搭建,特此感谢。
|