本篇万字,博客最细,oled多级菜单代码解析,与实现教程,指针实现(含源码)!!!

目录

教程前言                   

多级菜单基本知识

驱动文件创建

​编辑

​编辑

​编辑

定义菜单数据类型代码解析

按键代码解析

菜单数据赋值代码解析

菜单按键切换显示代码解析

 项目工程移植地址


教程前言                   

                                前言:编写不易,请勿搬运,仅供学习参考!!!

多级菜单基本知识

        oled多级菜单,在oled应用里面较为常用,很多时候都需要编写一个oled多级菜单,通过按键来跟用户交互或者功能实现。本篇讲解实现代码,从细节原理讲起。

        多级菜单,每个菜单,可能有 父菜单子菜单兄弟菜单,这些属性展现了层次关系,或者树形关系,这些关系使用 指针 来实现。

父菜单 last当前菜单的上一级菜单
子菜单 child进入当前菜单出现的菜单
兄弟菜单 next当前页面共同展示的菜单

驱动文件创建

        这里文件的话需要两个,按键跟菜单,放这两个的函数和声明数据,创建Key跟Menu两个文件

        文件内声明两个 .c 跟 .h文件。

        创建完成在keil5里面添加.c文件和添加头文件路径,

        在文件里面的bsp文件夹下面,找到刚才定义的.c文件然后选中,点击add添加,把Key.c添加进去,然后menu.c重复这个步奏。

        然后我们添加头文件路径,选择Key的路径之后,在重复一次吧menu的路径选择进去,这样就完成了,头文件路径的添加。

       预处理指进行宏定义,和.c文件中引入.h文件,在Key文件和menu文件中,这么些,图片如下,

        然后Key文件中也这么写,重复一下就好了。

定义菜单数据类型代码解析

        使用指针来创建多级菜单的好处是,可以在程序运行的时候,创建 修改 连接不同的菜单,或者通过多级菜单,直接改程序里面的参数,都是可以的,首先定义一个多级菜单。

typedef struct 
{struct Menu_data * last;//上一个菜单struct Menu_data * next;//下一个菜单struct Menu_data * child;//子菜单void (*display_oled)(void);//函数指针指向每个选项的显示函数void (*execute_oled)(void);//函数指针指向选项的执行函数
}Menu_data;

        这里其中一个问题是,下面着句代码里面去掉 struct 这个关键字行不行?

struct MenuItem* parent;

        不行,typedef定义的结构体在声明结束之后才有效,也就是在 06 行之后才有效,在结构体内部引用也就是 自引用 的时候,必须加 struct 关键字 加了关键字的 MenuItem 编译器才知道你是结构体。

        这里定义结构体里面属性类型,都是指针类型,成员属性非得用指针嘛,改成结构体属性类型不行嘛。

01 typedef struct MenuItem {
02     char name;                // 菜单项名称
03     struct MenuItem parent;   // 父菜单
04     struct MenuItem child;    // 子菜单
05     struct MenuItem sibling;  // 同级菜单
06 } MenuItem;

        这里有一个bug,分析一下,在MenuItem这个结构体里面有 struct MenuItem child 这样一个属性,是一个结构体这个结构体又包含结构体包含的结构体也会包含结构体,这样就导致了无限嵌套下去。

       这里使用指针是因为,指针存放的是指向结构体变量的地址,同时大小确定,这个地址可以为空,就不会导致无限嵌套问题。其次每个菜单必须与其他菜单关联,这种关联就是指针实现的。

        这里同时完成对,每个菜单的赋值,同时选出展示在oled上面的菜单。

// 示例菜单结构
MenuItem menu1 = {"Option 1", NULL, NULL, &menu2};
MenuItem menu2 = {"Option 2", NULL, &submenu1, &menu3};
MenuItem menu3 = {"Option 3", NULL, NULL, NULL};
MenuItem submenu1 = {"Submenu 1-1", &menu2, NULL, NULL};MenuItem* currentMenu = &menu1;  // 展示在oled上面菜单

        这里 menu1  menu2 menu3 是同级菜单,menu2有一个submenu1的子菜单。

按键代码解析

        在设计oled多级菜单里面,需要按键来控制菜单的 上 下 确认 返回 来控制菜单页面,但是为了节省引脚在程序里面,只涉及了两个引脚,分别用来切换菜单,跟执行功能函数,所以这里我们需要初始化两个引脚,同时写两个按键检测函数。

void Key_Init(void) // 初始化按键引脚
{GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 开时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);// 初始化 GPIOAGPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 根据需要设置为合适的模式GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_8;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;GPIO_Init(GPIOA, &GPIO_InitStruct);GPIO_SetBits(GPIOA, GPIO_Pin_8);GPIO_SetBits(GPIOA, GPIO_Pin_9);// 初始化 GPIOCGPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 设置为推挽输出GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; // 仅针对 PC13GPIO_Init(GPIOC, &GPIO_InitStruct);// 设置为高电平GPIO_ResetBits(GPIOC, GPIO_Pin_13);
}

        这里的话,因为选择,开发板自带的pc13灯的亮灭作为执行函数,所以顺带着pc13这个引脚给初始化了,然后写两个按键检测函数用来检测按键是否被按下,这个初始化函数写法里面,有讲究的是下面这一句,当同时填入两个引脚的时候,初始化会把两个引脚都进行初始化。

    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_8;

        然后写两个按键检测函数,同时附带两个返回值,通过检测返回值的数值用来检测按键是否被按下,这里的话 Key1_flag这个标志位不能用static这个关键字来修饰,如果修饰的话,会延长这个变量的声明周期,自动保存上一次的值,导致程序错误。

int Key1_Scanf(void) 
{		int Key1_Flag = 0;if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == RESET) {delay_ms(1000); // 去抖动if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_8) == RESET) {Key1_Flag++; // 更新状态return Key1_Flag; // 返回当前状态}}return Key1_Flag; // 返回当前状态(未改变)
}int Key2_Scanf(void)
{                                      int Key2_Flag= 0 ;if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9) == RESET){delay_ms(100);if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_9) == RESET){Key2_Flag++;return 	Key2_Flag;}}return 0;
}

菜单数据赋值代码解析

        在文章的开头每个菜单结构体定义的时候,每个结构体类型有着自己的,oled显示函数,执行功能函数,这里需要对oled菜单的每个选项进行结构体赋值,赋值每个选项自己的显示函数,执行函数,同时还有的是每个选项的 父菜单 同级菜单 子菜单 这些都需要传到结构体数据里面。

Menu_data control_sub_option = {NULL,NULL,NULL,NULL,sub_menu_3};
Menu_data close_sub_option = {NULL,&control_sub_option,NULL,Led_Close,sub_menu_2};
Menu_data open_sub_option = {NULL,&close_sub_option,NULL,Led_Open,sub_menu_1};
Menu_data Init_sub_option = {NULL,&open_sub_option,NULL,NULL,sub_menu_0};//菜单类型结构体数据
Menu_data control_option ={NULL,&Init_sub_option,NULL,NULL,menu_3};
Menu_data close_option = {NULL,&control_option,NULL,Led_Close,menu_2};
Menu_data open_option = {NULL,&close_option,NULL,Led_Open,menu_1};
Menu_data Init_option = {NULL,&open_option,NULL,NULL,menu_0};//没有光标选项卡

        同时还需要,定义穿进去结构体数据里面的显示函数,和执行功能函数,在传参的时候直接写进去函数名字就可以了。

void menu_0 (void)	//主菜单光标第一行菜单显示函数
{OLED_ShowString(0,8,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(30, 42, 0, 16, 1);OLED_ShowChinese(46, 42, 2, 16, 1); OLED_ShowChinese(62, 42, 4, 16, 1);OLED_ShowChinese(78, 42, 5, 16, 1);//开关控制OLED_Refresh();
}void menu_1 (void)	//主菜单光标第一行菜单显示函数
{OLED_ShowString(22,10,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(30, 42, 0, 16, 1);OLED_ShowChinese(46, 42, 2, 16, 1); OLED_ShowChinese(62, 42, 4, 16, 1);OLED_ShowChinese(78, 42, 5, 16, 1);//开关控制OLED_Refresh();
}
void menu_2 (void)//主菜单光标第二行菜单显示函数
{		OLED_ShowString(22,26,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(30, 42, 0, 16, 1);OLED_ShowChinese(46, 42, 2, 16, 1); OLED_ShowChinese(62, 42, 4, 16, 1);OLED_ShowChinese(78, 42, 5, 16, 1);//开关控制OLED_Refresh();
}
void menu_3 (void)//主菜单光标第三行菜单显示函数
{OLED_ShowString(22,42,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(30, 42, 0, 16, 1);OLED_ShowChinese(46, 42, 2, 16, 1); OLED_ShowChinese(62, 42, 4, 16, 1);OLED_ShowChinese(78, 42, 5, 16, 1);//开关控制OLED_Refresh();
}
void sub_menu_0(void)
{OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_Refresh();
//    OLED_ShowString(30, 42, "退出", OLED_8X16);
}
void sub_menu_1(void)
{OLED_ShowString(22,10,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(62, 42, 6, 16, 1); OLED_ShowChinese(78, 42, 7, 16, 1); // 退出OLED_Refresh();
//    OLED_ShowString(30, 42, "退出", OLED_8X16);
}
void sub_menu_2(void)
{OLED_ShowString(22,26,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(62, 42, 6, 16, 1); OLED_ShowChinese(78, 42, 7, 16, 1); // 退出
//    OLED_ShowString(30, 42, "退出", OLED_8X16);OLED_Refresh();
}
void sub_menu_3(void)
{OLED_ShowString(22,42,(uint8_t *)"*",12,1);//6*12 “ABC”OLED_ShowChinese(30, 10, 0, 16, 1); OLED_ShowChinese(46, 10, 1, 16, 1); //开灯OLED_ShowChinese(30, 26, 2, 16, 1); OLED_ShowChinese(46, 26, 3, 16, 1); // 关灯OLED_ShowChinese(62, 42, 6, 16, 1); OLED_ShowChinese(78, 42, 7, 16, 1); // 退出
//    OLED_ShowString(30, 42, "退出", OLED_8X16);OLED_Refresh();}
void Led_Open(void)
{GPIO_SetBits(GPIOC,GPIO_Pin_13);
}void Led_Close(void)
{GPIO_ResetBits(GPIOC,GPIO_Pin_13);
}

        这里定义完显示函数跟执行功能函数,就直接可以传参进去了,然后再传参声明结构体类型的参数到时候需要特别注意的有一点,被取地址的结构体类型需要首先声明,如果没有声明,而进行取地址传入next这个参数,这个时候编译器会提示,这个结构体没有被定义,其实你有定义只不过是在下面定义了。

01 Menu_data close_sub_option = {NULL,&control_sub_option,NULL,Led_Close,sub_menu_2};
02 Menu_data control_sub_option = {NULL,NULL,NULL,NULL,sub_menu_3};

       如果我们换种写法这么些的时候,在01行的时候就会提示,&control_sub_option这个属性类型没有被定义,所以被取地址的结构体类型我们把他放到最上面进行声明

菜单按键切换显示代码解析

       然后菜单里面特别重要的是,当按键按下的时候切换光标位置,这里实现的方法是,检测按键按下,这个时候改变指针的指向,改到当前指向的结构体类型里面的 .next属性,让指针指向这个.next,同时执行新指向的函数显示函数这样,就完成了按键按下,同时oled刷新光标显示。

void display_menu(void)//通过两个按键检测 切换 Menu_data 类型进行显示 和执行相关功能函数
{	static Menu_data * current_ptr = &Init_option;//初始化指针指向主菜单页面	static int Key1_flag = 0;static int Key2_flag = 0;current_ptr->display_oled();	//显示方法进行执行Key1_flag = Key1_Scanf();								if(Key1_flag)//切换显示指针到下个一页面显示函数{current_ptr = current_ptr->next;//改变指针指向下一个结点}Key2_flag = Key2_Scanf();if(Key2_flag)//执行当前指针指向的数据功能函数{current_ptr->execute_oled();}
}

        上面有一点错误的是,当指针指向子页面的最后一个选项的时候,指针没有办法返回,因为上面最后一个选型没有,赋值.next属性,当时忘了,后面加上了,把最后一个菜单选项的.next值给赋值到主菜单就行了。

Menu_data control_sub_option = {NULL,&Init_option,NULL,NULL,sub_menu_3};

        当光标落在最后子菜单的最后一个选项的时候,这个时候按下按键,就会回到最初的页面,菜单页面也就完成了闭环显示,

        到这里的话基本也就讲完了,没什么需要注意的地方了,各位复刻的小伙伴有什么问题可以后后台私信我,学到东西的小伙伴可以留个赞,喜欢这种详细的教程的可以后台私信我,有什么问题也可以后台私信我,看到都会回复的。

 项目工程移植地址

        这里的话使用的是嘉立创的0.96寸oled工程模版,写的oled多级菜单函数,所以这里给出网址给出下载链接,给出引脚连接定义,大家下载完成之后,跟着写代码,完成复刻基本上没什么大问题。

0.96寸IIC单色屏 | 立创开发板技术文档中心

        点击进去下拉到页面底部,就是百度网盘链接地址,然后下载,打开工程就行了。

        这个是引脚接线图。

                                        欢迎指正,希望对你,有所帮助!!!

        编写不易,禁止搬运,禁止转载,违者必究,仅供学习,仅供参考,感谢理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/59226.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为HarmonyOS打造开放、合规的广告生态 - 贴片广告

场景介绍 贴片广告是一种在视频播放前、视频播放中或视频播放结束后插入的视频或图片广告。 接口说明 接口名 描述 loadAd(adParam: AdRequestParams, adOptions: AdOptions, listener: AdLoadListener): void 请求单广告位广告,通过AdRequestParams、AdOptions…

Leetcode137只出现一次的数字|| 及其拓展

简述: 虽然标题是这么描述的,但是我们不是一上来就解这道题,先看一下他的子题和扩展 子题:136. 只出现一次的数字 - 力扣(LeetCode) 扩展题: 所以我们由易到难,先来看第一道&#x…

【解决办法】无法使用右键“通过VSCode打开文件夹”

个人博客:苏三有春的博客 前言 作者的编程环境为VScode,工作时常使用VScode打开整个工程文件夹。如果先打开VScode再从VScode中选择文件夹打开效率太慢,作者一般使用的方式是右键文件夹,直接选择"通过code打开文件夹"…

数据揭秘:掌握K-means聚类算法的精髓与实践

数据揭秘:掌握K-means聚类算法的精髓与实践 在机器学习领域,聚类是一种探索性的数据挖掘技术,用于将数据集中的样本划分成若干个簇,使得同一簇内的样本相似度高,而不同簇之间的样本相似度低。本文将深入探讨聚类分析的…

ADNI蛋白质数据集下载

(我发现这个网站最近又更新了界面,现在变得很好看很简洁,但是有一些入口变了) 1.官网链接 https://ida.loni.usc.edu/home/projectPage.jsp?projectADNI 2.登录 选择ADNI(其实PPMI数据也是这样下的)&a…

【数据分享】2024年我国省市县三级的生活服务设施数量(46类设施/Excel/Shp格式)

人才市场、售票处、旅行社等生活服务设施的配置情况是一个城市公共基础设施完善程度的重要体现,一个城市生活服务设施种类越丰富,数量越多,通常能表示这个城市的公共服务水平越高! 本次我们为大家带来的是我国各省份、各地级市、…

彻底解决idea不识别java项目

需求背景 下载了一个java swing的项目,通过idea导入后,项目无法识别。打开java文件,也不会报错,也不编译。 无法识别效果图 可以看到左侧的菜单,项目是没有被识别。 打开java文件,可以看到没有识别,java的图标也没有出现。 解决方法 1、打开Project Structure 2、修改…

HTMLCSS:打造酷炫下载安装模拟按钮

效果演示 这段代码通过HTML和CSS创建了一个具有交互效果的下载按钮&#xff0c;当复选框被选中时&#xff0c;会触发一系列动画和样式变化&#xff0c;模拟了一个下载和安装的过程&#xff0c;包括圆形的动画、文本的显示和隐藏等。 HTML <div class"container&quo…

Multi Agents协作机制设计及实践

01 多智能体协作机制的背景概述 在前述博客中&#xff0c;我们利用LangChain、AutoGen等开发框架构建了一个数据多智能体的平台&#xff0c;并使用了LangChain的Multi-Agents框架。然而&#xff0c;在实施过程中&#xff0c;我们发现现有的框架存在一些局限性&#xff0c;这些…

ML2001-1 机器学习/深度学习 Introduction of Machine / Deep Learning

图片说明来自李宏毅老师视频的学习笔记&#xff0c;如有侵权&#xff0c;请通知下架 影片参考 【李宏毅】3.第一节 - (上) - 机器学习基本概念简介_哔哩哔哩_bilibili 1. 机器学习的概念与任务类型 概念&#xff1a;机器学习近似于寻找函数&#xff0c;用于处理不同类型的任…

90%会展主办方都会用的6款数字化工具

在会展行业&#xff0c;数字化转型已成为提升竞争力的关键。面对日益增长的运营成本和收入增长的瓶颈&#xff0c;主办方需要借助数字化工具来实现效率提升和成本控制。 今天介绍几种常见的数字化工具和应用方式。 一、线上展览平台 构建线上展览平台是会展主办方拓展线上销…

JMeter快速造数之数据导入导出

导入数据 输入表格格式如下 创建CSV Data Set Config 在Body Data中调用 { "username": "${email}", "password": "123456", "client_id": "00bb9dbfc67439a5d42e0e19f448c7de310df4c7fcde6feb5bd95c6fac5a5afc"…

渗透测试-快速获取目标中存在的漏洞(小白版)

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect 《Web安全》h…

[免费]基于Python的Django+Vue3在线考试系统【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的基于Python的DjangoVue3在线考试系统&#xff0c;分享下哈。 项目视频演示 【免费】基于Python的DjangoVue3在线考试系统 Python毕业设计_哔哩哔哩_bilibili 项目介绍 本论文提出并实现了一种基于Python…

Unity3D学习FPS游戏(9)武器音效添加、创建敌人模型和血条

前言&#xff1a;虽然已经实现了基本玩家操作&#xff0c;但是游戏运行起来并没有音效。既然是FPS游戏有了玩家和武器&#xff0c;肯定还得有敌人。本篇演示如何给武器添加音效和创建敌人。 武器音效添加和创建敌人 武器音效添加Audio Source代码控制 创建敌人目标敌人模型敌人…

雷池社区版新版本功能防绕过人机验证解析

前两天&#xff0c;2024.10.31&#xff0c;雷池社区版更新7.1版本&#xff0c;其中有一个功能&#xff0c;新增请求防重放 更新记录&#xff1a;hhttps://docs.waf-ce.chaitin.cn/zh/%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95 仔细研究了这个需求&#xff0c;…

PyCharm中pylint安装与使用

目录 1. 安装插件2. pycharm中使用该功能3. 命令行使用 1. 安装插件 然后重启 2. pycharm中使用该功能 3. 命令行使用 前提是先 pip install pylint pylint demo01.py下面红框内容的意思是&#xff0c;得到10分/ 满分10分&#xff0c;上次运行获得8.33分&#xff0c;经调整…

IDEA加载通义灵码插件及使用指南

安装通义灵码插件 登录通义灵码IDE插件 下载登录参考教程 https://help.aliyun.com/zh/lingma/user-guide/download-the-installation-guide 本地工程和企业知识库准备 请下载本地工程和知识库压缩包&#xff0c;并在本地解压缩&#xff0c;其中包含demoProject和知识库文件…

只因把 https 改成 http,带宽减少了 70%!

起因 是一个高并发的采集服务上线后&#xff0c;100m的上行很快就被打满了。因为这是一条专线&#xff0c;并且只有这一个服务在使用&#xff0c;所以可以确定就是它导致的。 但是&#xff01;这个请求只是一个 GET 请求&#xff0c;同时并没有很大的请求体&#xff0c;这是为…

黑马官网最新2024前端就业课V8.5笔记---CSS篇(2)

盒子模型 画盒子 目标:使用合适的选择器画盒子 属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"view…