STM32+OLED屏多级菜单显示(三)

这篇具有很好参考价值的文章主要介绍了STM32+OLED屏多级菜单显示(三)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        前面两章实现了OLED屏幕显示的基本功能,这一章就做一个多级菜单显示功能

        单片机选择STM32F103C8T6最小系统板,OLED屏选择0.96寸OLED显示器,除了单片机和OLED屏之外,还需要三个按键(下一位键、确认键和返回键),当然一个按键也可以(单击、双击和长击完成),为了提高可玩性这里就只使用一个按键。

        1.1   STM32+OLED屏初始化(一) 

        1.2  STM32+OLED屏显示字符串、汉字、图片(二)

        1.3  STM32+OLED屏多级菜单显示(三)

        1.4  STM32+OLED屏(软件IIC+位带+帧缓冲区)刷新速率优化(四) 

1.多级菜单

        多级菜单是一种用户界面设计,它将信息和选项组织为层次结构,使得用户可以快速找到所需的选项。多级菜单的实现基于两种方案索引法树结构法,索引法阅读性好,扩展性不错,查找性最优,但是比较占用内存,并且一旦选项过多就会造成逻辑混乱

        下面展示使用索引法设计的多级目录(黑色的板子是底板,可以理解为面包板),首先是一级目录:

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

        其次,二级目录:

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

        最后,三级目录:

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

2.typedef用法

        在使用多级界面之前,首先要了解一些基础知识。typedef是C/C++中的一个关键字,用来给一个已存在的数据类型(比如int、float、struct等)取一个新的别名(自定义数据类型)。通过typedef声明的别名,可以像原类型一样被使用,但具有更直观、更易读的含义,从而提高代码可读性和可维护性。typedef的使用格式为:

typedef 原类型 新类型

举个例子,如:
typedef int MyInt;
MyInt a = 10;//等效int a = 10

        再看这个例子,将一个结构体定义为Point类型的别名,使用时就可以使用Point代替这个结构体。这个例子中的第三行代码创建了一个Point类型的结构体变量p,并为其成员变量赋值,除此之外,还有更多的定义方式,如数组、函数指针、enum等

typedef struct {
    int x;
    int y;
} Point;

Point p;
p.x = 1;
p.y = 2;

2.函数指针

        简单来说,函数指针是指向函数的指针变量,它存储了函数的入口地址,可以用来调用函数。在C语言中,函数指针的定义格式为:返回值类型 (*函数指针名称)(参数列表)。

        在下面的例子中,定义了一个函数指针func_ptr,它指向同样返回int类型的函数add。将add函数的地址赋给了函数指针func_ptr后,就可以使用func_ptr来调用add函数,和直接使用add(3,5)是等效的

#include <stdio.h>

int add(int a, int b) 
{
    return a + b;
}

int main() 
{
    int result;
    int (*func_ptr)(int, int);  // 定义函数指针

    func_ptr = add;             // 将函数的地址赋给函数指针

    result = func_ptr(3, 5);  // 使用函数指针调用函数
    printf("%d\n", result);        // 输出结果:8

    return 0;
}

3.定义多级菜单数据显示类型

        前面的基础知识讲完了,现在正式开始使用索引法实现多级菜单的第一步,定义多级菜单的数据类型,通过typedef声明的结构体设计界面菜单功能的数据类型,当前索引序号、三个按键、当前执行的函数指针,索引序号表示界面页码,要进入界面就要输入它的索引序号(就像是在宾馆房间编号,要进入房间就要知道它的房间编号),通过按键赋值索引序号达到跳转的目的(给房间编号的前台服务员),最后是执行的函数指针指向要执行的函数(可以理解为通往房间的路径)

typedef struct
{
	uint8_t CurrentNum;	//当前索引序号:页码
	uint8_t Enter;		//确认键
	uint8_t Next;		//下一个
	uint8_t Return;		//返回键
	void (*Current_Operation)(void);//当前操作(函数指针)
}Menu_table_t;

        根据上面自定义的数据类型,定义一个一维数组taskTable[ ],一维数组中的元素的数据类型就是Menu_table_t(就像我定义一个int array[] = { 0 ,},0的数据类型就是int),taskTable的元素中的元素与自定义数据类型里的数据类型统一对应,就像最后一个函数指针元素指向菜单界面函数,简单说明一下,taskTable[0] = {0, 1, 0, 0, Menu_Innterface},

//界面调度表 //假定 int array[] = { 0, };
Menu_table_t taskTable[] =
{
    //菜单界面函数 -- 一级界面
    {0, 1, 2, 3, Menu_Interface}, 
};

/**
  * @brief  菜单界面函数
  * @param  无
  * @retval 无
  */
void Menu_Interface(void)
{
	char buff[50];
	
	RTC_Get_StdTime(RTC_GetCounter());
	
	sprintf(buff,"%0.2d:%0.2d:%0.2d",RTC_CLOCK.hour, RTC_CLOCK.min, RTC_CLOCK.sec);
	OLED_ShowString(4, 10,  24, buff);
	
	sprintf(buff,"Date:%0.4d-%0.2d-%0.2d",RTC_CLOCK.year,RTC_CLOCK.mon,RTC_CLOCK.day);
	OLED_ShowString(1, 6,  16, buff);
}

        所以,数据类型就是这样对应的,第一个为索引序号,中间三个为按键,最后一个执行函数,将它的首地址赋值给指针函数

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

4.菜单逻辑顺序表

        通过上述的对应关系,可以制定一张菜单逻辑顺序,通过中间的三个按键将索引序号赋值给任务调度序号,当在一级界面时,按下确认键就通过索引序号把务调度序号切换为1,进入到二级界面的第一个元素了,按下下一位键和返回键

uint8_t taskIndex = 0;	//任务调度序号
//任务调度表
Menu_table_t taskTable[] =
{
    //菜单界面函数 -- 一级界面
    {0, 1, 0, 0, Menu_Interface}, 
    //功能界面函数 -- 二级界面
    {1, 4, 2, 0, Function_Interface1},
    {2, 5, 3, 0, Function_Interface2},
    {3, 6, 1, 0, Function_Interface3},
	//功能设置界面函数 -- 三级界面
	{4, 4, 4, 1, Function_Interface4},
	{5, 5, 5, 2, Function_Interface5},
	{6, 6, 6, 3, Function_Interface6},
};

        用三张张图简单的说明一下,第一张图,当前页面为0,按下确认键跳到索引序号为1的元素

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

 第二张图,当前页面为1,按下确认键跳到索引序号为4的元素

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

第三张图,当前页面为6,按下返回键跳到索引序号为3的元素 

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

5.按键切换索引序号

        编写一个按键值获取函数,按键将索引序号赋值给任务调度序号,任务调度表根据任务调度序号执行运行界面,前面有说过,最后一个元素只是指向运行函数的指针(也就是路径),真正的执行需要解引用,也就是最后的执行函数

keyval = Keyval_Scan();
if(keyval == 2) {//双击
	taskIndex = taskTable[taskIndex].Enter;//双击表示确认键
	OLED_Clear();
}
else if(keyval == 1) {//单击
	taskIndex = taskTable[taskIndex].Next;//单击表示下一位键
}
else if(keyval == 10) {//长击
	taskIndex = taskTable[taskIndex].Return;//长击表示返回键
	OLED_Clear();
}

taskTable[taskIndex].Current_Operation();//执行函数

       按键获取函数,按下按键30ms的按键抖动,2s内松开视为有效单击,0.5s后无按键按下,结算总共按下几次有效单击,单击返回1,双击返回2,三击返回3,长击返回10;用不到三击以上的按键,而且十击与长按都返回10,之后可能会出巨大的事故问题

uint8_t Keyval_Scan(void)
{
	static uint16_t key_state, key_time, key_cnt;
	uint8_t key_return = 0;
	
	switch(key_state) {
		case 0://按键状态0:判断有无按键按下
			if(KEY_STATA0) {
				key_time = 0;//按键按下时,按键计时器清0
				key_state = 1;//按键状态置1
			}
			break;
		case 1://按键状态1:按键消抖
			if(KEY_STATA0) {
				delay_ms(1);
				if(key_time++ >= 30) 
					key_state = 2;//当按键按下超过30ms时,进入按键单击状态
			}
			else key_state = 0;//松开按键,视为误触
			break;
		case 2://按键状态2:单击或长击
			if(!KEY_STATA0) {
				key_time = 0;
				key_cnt++;//按键计数器自增
				key_state = 3;//此时按键松开,表示已经单击,进入下个状态
			}
			else {
				delay_ms(1);
				if(key_time++ >= 2000) {
					key_cnt = 0;
					key_return = 10;
					key_state = 4;
				}
			}
			break;
		case 3://按键状态3:按键多击
			if(!KEY_STATA0) {
				delay_ms(1);
				if(key_time++ >= 500) {//松开按键0.5s无按键按下,返回按键计数器
					key_return = key_cnt;
					key_cnt = 0;
					key_state = 0;
				}
			}
			else {
				key_state = 0;
			}
			break;
		case 4://按键状态4:等待长按按键释放
			if(!KEY_STATA0) key_state = 0;
			break;
	}
	
	return key_return;
}	

         对应不确定什么时候到的接收,最好使用按键中断,这边使用按键中断与定时器完成单击、双击和长击,中间加了一个互斥锁g_APPTOIN_MUTEX,防止中断切换界面时运行程序出错,常规初始化配置就不写了,直接上中断函数的代码; --  使不使用中断由读者自己选择

uint8_t g_APPTOIN_MUTEX = 1;//程序互斥锁
void EXTI0_IRQHandler(void)
{
	static uint8_t Keystate = 0;	//静态按键状态 0 -- 松开  1 -- 按下
	
	if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
		if(!KEY_STATA0 && Keystate) {
			Keystate = 0;			//松开按键
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			if(g_Time_Count <= 3) {
				printf("...keyval, dither time = %d\r\n", g_Time_Count);
			}
			else if((g_Time_Count > 3) && (g_Time_Count < 200)) {
				if(++g_Keyval_Count >= 2) {//双击
					taskIndex = taskTable[taskIndex].Enter;//双击表示确认键
					OLED_Clear();
					printf("double keyval!\r\n");
				};
			}
			if((g_Keyval_Count >= 2) || (g_Keyval_Count == 0)) {
				g_Keyval_Count = 0;
				g_APPTOIN_MUTEX = 1;//关闭互斥
			}
			else { 
				g_Time_Count = 0;		//清空定时器溢出次数
				TIM_Cmd(TIM2, ENABLE);	//启动定时器
			}
		}
		else if(KEY_STATA0 && !Keystate){
			
			Keystate = 1;			//按下按键
			g_Time_Count = 0;		//清空定时器溢出次数
			TIM_Cmd(TIM2, ENABLE);	//启动定时器
			g_APPTOIN_MUTEX = 0;	//开启互斥
		}
		EXTI_ClearITPendingBit(EXTI_Line0);	//清除中断标志
	}
}
//#include "key.h"

#define KEY_STATA0	!!GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
extern uint8_t g_APPTOIN_MUTEX;
void KEY_Init(void);

        定时器也一样,直接上中断函数的代码

uint16_t g_Time_Count;//TIM溢出次数 -- 10ms  	Tout(溢出时间) = (ARR+1)(PSC+1)/Tclk(时钟分割)
uint8_t g_Keyval_Count;//按键计数器
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) {
		//进入中断表示定时器计数(CNT)溢出, 自增(10ms溢出一次, 可通过配置ARR, PSC和Tclk自定义溢出时间)
		g_Time_Count++;				
		
		if((g_Time_Count >= 200) && KEY_STATA0) {
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			g_Time_Count = 0;		//清空溢出次数
			g_Keyval_Count = 0;		//清空按键计数
			taskIndex = taskTable[taskIndex].Return;//长击表示返回键
			OLED_Clear();
			printf("long keyval!\n");
			g_APPTOIN_MUTEX = 1;
		}
		else if((g_Time_Count >= 50) && !KEY_STATA0 && (g_Keyval_Count == 1)) {
			TIM_Cmd(TIM2, DISABLE);	//关闭定时器
			g_Time_Count = 0;		//清空溢出次数
			g_Keyval_Count = 0;		//清空按键计数
			taskIndex = taskTable[taskIndex].Next;//单击表示下一位键
			printf("short keyval!\n");
			g_APPTOIN_MUTEX = 1;
		}
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
//#include "time.h"

extern uint16_t g_Time_Count;
extern uint8_t g_Keyval_Count;
void TIME_Init(uint16_t psc, uint16_t arr);

6.界面设计

        使用了三级界面,一级界面显示时间,利用sprintf函数将时间数据写入buff数组中,再显示到OLED屏幕上,完成显示

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

        二级界面提供功能选项,因为有三个功能选项所以用到三个函数,>>箭头在那个地方就表示选择那个功能,这三个功能分别表示字符串、汉字和图片

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

stm32 oled多级菜单,stm32,嵌入式硬件,单片机  stm32 oled多级菜单,stm32,嵌入式硬件,单片机  stm32 oled多级菜单,stm32,嵌入式硬件,单片机

        三级界面显示内容,效果就不一个一个展示了,直接看后面的操作演示

stm32 oled多级菜单,stm32,嵌入式硬件,单片机

         最后,显示代码:

//#inlude "oled_show.c"

uint8_t taskIndex = 0;	//初始任务
//任务调度表
Menu_table_t taskTable[] =
{
    //菜单界面函数 -- 一级界面
    {0, 1, 0, 1, Menu_Interface}, 
    //功能界面函数 -- 二级界面
    {1, 4, 2, 0, Function_Interface1},
    {2, 5, 3, 0, Function_Interface2},
    {3, 6, 1, 0, Function_Interface3},
	//功能设置界面函数 -- 三级界面
	{4, 4, 4, 1, Function_Interface4},
	{5, 5, 5, 2, Function_Interface5},
	{6, 6, 6, 3, Function_Interface6},
};

/**
  * @brief  菜单界面函数
  * @param  无
  * @retval 无
  */
void Menu_Interface(void)
{
	char buff[50];
	
	RTC_Get_StdTime(RTC_GetCounter());
	
	sprintf(buff,"%0.2d:%0.2d:%0.2d",RTC_CLOCK.hour, RTC_CLOCK.min, RTC_CLOCK.sec);
	OLED_ShowString(4, 10,  24, buff);
	
	sprintf(buff,"Date:%0.4d-%0.2d-%0.2d",RTC_CLOCK.year,RTC_CLOCK.mon,RTC_CLOCK.day);
	OLED_ShowString(1, 6,  16, buff);
	
}

/**
  * @brief  功能界面函数
  * @param  无
  * @retval 无
  */
void Function_Interface1(void)
{
	OLED_ShowString(0, 0, 16, ">>String");
	OLED_ShowString(2, 0, 16, "  Chinese");
	OLED_ShowString(4, 0, 16, "  ImageBMG");
}
void Function_Interface2(void)
{
	OLED_ShowString(0, 0, 16, "  String");
	OLED_ShowString(2, 0, 16, ">>Chinese");
	OLED_ShowString(4, 0, 16, "  ImageBMG");
}
void Function_Interface3(void)
{
	OLED_ShowString(0, 0, 16, "  String");
	OLED_ShowString(2, 0, 16, "  Chinese");
	OLED_ShowString(4, 0, 16, ">>ImageBMG");
}

/**
  * @brief  功能设置界面函数
  * @param  设置有三种状态
  * @retval 无
  */
void Function_Interface4(void)
{
	OLED_ShowString(1, 0, 8, "ABCD");
	OLED_ShowString(2, 0, 16, "ABCD");
	OLED_ShowString(4, 0, 24, "ABCD");
}
void Function_Interface5(void)
{   
	OLED_ShowChinese(3,16*2,0);// 点
	OLED_ShowChinese(3,16*3,1);// 个
	OLED_ShowChinese(3,16*4,2);// 赞
	OLED_ShowChinese(3,16*5,3);// 吧
	OLED_ShowChinese(3,16*6,4);// ! 
}
void Function_Interface6(void)
{
	OLED_ShowImageBMG();
}

7.主函数

         在main.c文件中,不使用中断,cnt是为了OLED屏幕刷新慢一点,不影响按键而设计的

//#include "main.c"

int main(void)
{
	uint8_t keyval = 0, cnt = 0;
	KEY_Init();
	OLED_Init();
	Usart_Init();
	MyRTC_Init();
	
	printf("多级界面\r\n");
	while (1) {
		keyval = Keyval_Scan();
		if(keyval == 2) {
			printf("double\r\n");
			taskIndex = taskTable[taskIndex].Enter;//双击表示确认键
			OLED_Clear();
		}
		else if(keyval == 1) {
			printf("press\r\n");
			taskIndex = taskTable[taskIndex].Next;//单击表示下一位键
		}
		else if(keyval == 10) {
			printf("long\r\n");
			taskIndex = taskTable[taskIndex].Return;//长击表示返回键
			OLED_Clear();
		}
		if(cnt++ >= 200) {
			cnt = 0;
			taskTable[taskIndex].Current_Operation();//执行函数
		}
	}
}

        在main.c文件中,使用中断,只需要一个互斥锁和一个执行函数就可以了,按键按下时,互斥锁开启,不执行执行函数,和上面的cnt用法相似

//#include "main.c"

int main(void)
{
	KEY_Init();
	TIME_Init(72, 10000);
	OLED_Init();
	Usart_Init();
	MyRTC_Init();
	
	printf("多级界面\r\n");
	while (1) {

		if(g_APPTOIN_MUTEX) {
			
			taskTable[taskIndex].Current_Operation();//执行函数
		}
	}
}

操作演示:

WeChat_20231205093715

源码分享:链接:https://pan.baidu.com/s/1CujKS8I7-eok3y9y3CxkUA?pwd=457e 
提取码:457e文章来源地址https://www.toymoban.com/news/detail-768212.html

到了这里,关于STM32+OLED屏多级菜单显示(三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • stm32实现0.96oled图片显示,菜单功能

    本期内容,我们将学习0.96寸oled的进阶使用,展示图片,实现菜单切换等功能,关于oled的基础内容,这里我不做介绍,大家可以学习 : 夜深人静学32系列17——OLED ,里面讲述了oled的基本知识并实现了一个简单页面的编写。 这个菜单一共有三个等级, 一级菜单:原神启动界

    2024年02月03日
    浏览(126)
  • STM32——OLED菜单

    简介:首先在我的51 I2C里面有OLED详细讲解,本期代码从51OLED基础上移植过来的,可以先看完那篇文章,在看这个,然后按键我是用的定时器扫描不会堵塞程序,可以翻开我的文章有单独的定时器按键扫描,DHT11文章也有,我的菜单从一级界面点进去二级界面,二级界面开启的内

    2024年02月19日
    浏览(40)
  • STM32OLED呈现主界面菜单任务的编程思路以及模板

    目录 一、效果展示(按键控制界面切换) 二、编程模板 1.头文件 2.库文件 三、编程思路 四、使用说明 OLED三级界面效果展示 1.先填充好每一个要显示的界面,确保界面显示没问题。 2.确保条件能触发,我这里选择的是4个按键控制。 3.先编写界面的横向切换,如下图所示。

    2024年02月16日
    浏览(36)
  • STM32之OLED显示

    一、模块介绍 1、常见的显示设备 LED、数码管、点阵、LCD屏(1602/12864)、OLED屏(消费电子) 2、OLED屏的概述 OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。因为具备 轻薄、省电等 特性,因此从2003 年开始,这种显示

    2024年01月17日
    浏览(40)
  • stm32+OLED显示数据

    理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能: 显示自己的学号和姓名; 显示AHT20的温度和湿度; 上下或左右的滑动显示长字符。 OLED(Organic Light Emitting Display,中文名有机发光显示器)是指有机半导体材料和发光材料在电场驱动下,通过载流子注

    2024年02月02日
    浏览(31)
  • STM32-OLED显示屏

    *本文采用的OLED显示屏为4针脚IIC显示屏,4针脚分别为GND,VCC,SCL,SDA OLED.C OLED_Font.h main.c

    2024年02月05日
    浏览(43)
  • 基于stm32的oled显示

    目录 1.显示原理  2.接口配置 8080并行接口模式 spi四线 3.程序控制 基于stm32的oled显示(以ALINETEK0.96寸OLED模块为例) ALINETEK0.96寸OLED模块由SSD1306驱动芯片控制: SSD1306的显存总共为128*64bit大小,分为八页,每页为128个字节,共计128*68位,以此对应屏幕的168*64像素,将显存的各位

    2024年02月05日
    浏览(38)
  • STM32-OLED小数显示函数

    ​ OLED即有机发光管(Organic Light-Emitting Diode,OLED)。OLED显示技术具有自发光、广视角、几乎无穷高的对比度、较低功耗、极高反应速度、可用于绕曲性面板、使用温度范围广、构造及制程简单等有点,被认为是下一代的平面显示屏新兴应用技术。 ​ OLED显示和传统的LCD显示不同

    2024年02月12日
    浏览(37)
  • STM32 OLED 显示原理的讲解以及OLED显示汉字与图片的代码

    本文主要涉及OLED显示原理的讲解以及OLED显示汉字与图片的代码。 OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display,OELD) 。 OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板

    2024年02月04日
    浏览(42)
  • STM32——OLED显示屏(4)

    目录 一、调试方式 二、OLED简介 三、OLED硬件电路 四、OLED驱动函数 五、OLED显示屏使用 1、硬件接线图 2、添加OLED驱动函数 3、程序 4、实物展示  六、Keil软件自带调试模式         对于单片机的编程而言,经常会遇到一个很大的问题,就是程序调试,单片机不像电脑,电

    2024年02月03日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包