硬件

硬件选型

  • STM32F103C8T6最小核心板
  • 0.96寸四脚OLED屏幕IIC接口
  • 普通按键5个

硬件连线

  • SCL ---- PA1

  • SDA ---- PA2

  • KEY_UP ---- PA4

  • KEY_DOWN ---- PA5

  • KEY_LEFT ---- PA3

  • KEY_RIGHT ---- PA6

  • KEY_OK ---- PA7

代码开源链接

百度网盘

链接:https://pan.baidu.com/s/1W4dIgTYgQv7Pp4iX-QnwTg
提取码:k7p4

Gitee

屏幕使用-GUI设计: 一些单片机控制屏幕的项目,和一些GUI界面设计 stm32驱动的oled屏等等 (gitee.com)

软件开发

基础功能

Systick延时

我使用的是正点原子的代码,进行了简单的修改,但大体一样,可用正点原子代码。具体修改看我代码,不修改也可用。

5ms定时器

使用的定时器3做的一个5ms的定时器中断。定时器中断中放置按键扫描函数。

按键扫描

也是参考的正点原子的按键扫描函数,但是没有使用延时函数,使用的计数延时来达到按键消抖的目的。5ms扫描一次,大于2就延时10ms。该函数放在定时器5ms的中断函数中。这里按键扫描给一个按键按下的标志位,例如isKeyUp,按键按下就将它置1。

/*** @brief 按键扫描函数* * @param mode 模式为1就是连续扫描,为0就是单次*/
void KeyScan(u8 mode)
{static int keyCount = 0;static int keyState = 0;if(mode == 1) keyState=0;if (keyState == 0 && (KEY_UP == 0||KEY_DOWN == 0||KEY_LEFT == 0||KEY_RIGHT == 0||KEY_OK == 0)){    keyCount++;if(keyCount>2){keyState = 1;keyCount=0;if(KEY_UP == 0) KeyUp();else if(KEY_DOWN == 0) KeyDown();else if (KEY_LEFT == 0) KeyLeft();else if (KEY_RIGHT == 0) KeyRight();else if (KEY_OK == 0) KeyOk();}}else if (KEY_UP == 1 && KEY_DOWN == 1 && KEY_LEFT == 1 && KEY_RIGHT == 1 && KEY_OK == 1){keyState = 0;}
}void KeyUp()
{if(isKeyUp == 0)isKeyUp=1;LED=!LED;
}

oled的简单使用

oled使用的是中景园电子的代码,可以在项目代码中查看。

多级菜单设计

定义菜单项结构体

//菜单页参数结构体
struct MenuProperty_t
{u8 MenuLen;//当前菜单页菜单项总个数u8 scrollBarLen;//滚动条长度,由于都是用的16SIZE的字符,所以一个菜单页最多四个菜单项,五个菜单项滚动条就为1
};//菜单项结构体
struct Menu_t{struct MenuProperty_t *MenuProperty;//当前菜单项所在菜单页的参数u8 displayString[15];//当前菜单项的字符void (*func1) (void);//当前菜单项的功能函数void (*func2) (void);//当前菜单项的功能函数struct Menu_t *fatherMenu;//当前菜单项的父级菜单项struct Menu_t *childrenMenu;//当前菜单项的子级菜单项
};

定义一个菜单页

主界面菜单,算一级菜单,主界面一般可以拿来画一些好玩的UI设计,我这个项目做的是一个时钟设计。这个菜单页就只有一个菜单项,滚动条为0。由于初始化不能先填入未初始化的数据,所以他的子菜单项初始化先设定为NULL。

//主UI
struct MenuProperty_t MainUIProperty={1,0};
struct Menu_t MainUI=
{&MainUIProperty,"MainUI       " ,NULL,NULL,NULL};

主菜单,算二级菜单,拿来做我想要显示的数据项分类,父菜单就是MainUI,子菜单项初始化先设定为NULL。注意字符串尽量写15个字符,用空格也要占位,使得后面数据好刷新。这个菜单页就有四个菜单项,滚动条为0。

//主菜单
struct MenuProperty_t menuMainProperty={4,0};
struct Menu_t menuMain[4]=
{{&menuMainProperty,"last menu     ", NULL,NULL, &MainUI,NULL},{&menuMainProperty,"Animal        ", NULL,NULL, &MainUI,NULL},{&menuMainProperty,"Pid           ", NULL,NULL, &MainUI,NULL},{&menuMainProperty,"Time set      ", NULL,TimeSetInit, &MainUI,NULL}
};

animal的子菜单,算3级菜单,这个就是真的想要显示的animal菜单项的数据。父菜单就是menuMain,子菜单项初始化先设定为NULL。这个菜单页就有六个菜单项,滚动条长度为2,因为一面最多显示4个,滚动一下往下移一个。

注意:要是你定义的是单个项,取地址就要加&,要是定义的数组,就可以用数组名取该数组首地址。

//animal的子菜单
struct MenuProperty_t setMenu1Property={6,2};
struct Menu_t setMenu1[6]=
{{&setMenu1Property,"last menu     ",NULL,NULL,menuMain,NULL},{&setMenu1Property,"bull          ",NULL,NULL,menuMain,NULL},{&setMenu1Property,"bird          ",NULL,NULL,menuMain,NULL},{&setMenu1Property,"dog           ",NULL,NULL,menuMain,NULL},{&setMenu1Property,"bow           ",NULL,NULL,menuMain,NULL},{&setMenu1Property,"fish          ",NULL,NULL,menuMain,NULL}
};

OLED页面刷新函数

刷新页面信息,要是在主页面就清空一下在画图,要是没有在主页面,使用覆盖来达到刷新的效果。

void DisplayRefreash(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{int i = 0;static u8 lastSelectItem=0;//记录上次索引if(nowMenu==&MainUI)//当回到主菜单时,由于没有全占屏,所以全部清屏,再画{OLED_Clear();MainUiSet();}else { OLED_ShowChar(0,lastSelectItem*16, ' ',16,1);//清除上次索引箭头OLED_ShowChar(0,selectItem*16,     '>',16,1);//画出这次索引箭头for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++){OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);}}OLED_Refresh();lastSelectItem = selectItem;
}

OLED数据刷新函数

当每个页面的数据要刷新时,就只需要把上一次的数据覆盖就行了,所以每次就要写满一行的字符,或者你每行的字符长度相同也可以达到相同的目的。

void DisplayRefreashData(struct Menu_t *nowMenu,u8 selectItem,u8 scrollBar)
{int i = 0;for(i=0;i<(nowMenu->MenuProperty->MenuLen-nowMenu->MenuProperty->scrollBarLen);i++){OLED_ShowString(8,i*16,nowMenu[i+scrollBar].displayString,16,1);}OLED_Refresh();
}

刷新数据前,数据的更改可以通过Sprintf函数来重新定义每个菜单项的显示字符串

void GuiDataDisplayRefresh()
{if(menuPoint == setMenu1){sprintf((char*)setMenu1[1].displayString,"bull  %3d     ",count1);sprintf((char*)setMenu1[2].displayString,"bird  %3d     ",count2);sprintf((char*)setMenu1[3].displayString,"dog   %3d     ",count3);sprintf((char*)setMenu1[4].displayString,"bow   %3d     ",count4);sprintf((char*)setMenu1[5].displayString,"fish  %3d     ",count5);DisplayRefreashData(menuPoint,selectItem,scrollBar);}else if(menuPoint==&MainUI){MainUiSet();OLED_Refresh();}
}

菜单初始化

主要拿来初始化一些菜单项的子菜单,以及当前菜单的指针指向。

全局变量

menuPoint    当前菜单指向地址
selectItem   当前索引 0-3
scrollBar    当前滚动条所在位置,最上处为0
void GuiInit()
{MainUI.childrenMenu = menuMain;menuMain[1].childrenMenu = setMenu1;menuMain[2].childrenMenu = setMenu2;menuMain[3].childrenMenu = setMenu3;menuPoint = &MainUI;DisplayRefreash(menuPoint,selectItem,scrollBar);
}

按键控制函数

上下按键主要拿来切换现在的索引和滚动条

左右键主要拿来实现功能函数

void GuiControl()
{if(isKeyUp==1)//上键按下{isKeyUp=0;//标志位清零selectItem--;//当前菜单在当前菜单页的索引--if(selectItem<0&&scrollBar!=0)//小于0,但是滚动条不在0,就减滚动条{selectItem = 0;scrollBar--;}else if(selectItem<0&&scrollBar==0)//小于0,滚动条也在0,就将索引移到最后,滚动条到最大{selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;scrollBar  = menuPoint->MenuProperty->scrollBarLen;}DisplayRefreash(menuPoint,selectItem,scrollBar);//刷新显示}else if(isKeyDown==1)//和上键差不多{isKeyDown=0;selectItem++;//假如索引大于最大值,但是滚动条不在最大值,保持索引最大值,滚动条++if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar!=menuPoint->MenuProperty->scrollBarLen){selectItem = menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen;scrollBar++;}//假如索引大于最大值,滚动条在最大值,移动到第一个位置else if(selectItem>(menuPoint->MenuProperty->MenuLen-1-menuPoint->MenuProperty->scrollBarLen)&&scrollBar==menuPoint->MenuProperty->scrollBarLen){selectItem=0;scrollBar =0;}DisplayRefreash(menuPoint,selectItem,scrollBar);}else if(isKeyLeft==1){//假如当前菜单的func1不为空,执行相关函数if(menuPoint[selectItem+scrollBar].func1!=NULL){menuPoint[selectItem+scrollBar].func1();}isKeyLeft=0;DisplayRefreash(menuPoint,selectItem,scrollBar);}else if(isKeyRight==1){if(selectItem==0 && scrollBar==0 && menuPoint[selectItem].fatherMenu!=NULL)//假如索引为零而且父菜单不为空,指向父指针{menuPoint = menuPoint[selectItem].fatherMenu;}else if(menuPoint[selectItem+scrollBar].childrenMenu!=NULL)//假如该索引子菜单页不为空,指向子菜单{if(menuPoint[selectItem+scrollBar].func2!=NULL)//假如当前菜单的func2不为空,执行相关函数{menuPoint[selectItem+scrollBar].func2();}menuPoint = menuPoint[selectItem+scrollBar].childrenMenu;selectItem = 0;}else if(menuPoint[selectItem+scrollBar].func2!=NULL)//假如当前菜单的func2不为空,执行相关函数{menuPoint[selectItem+scrollBar].func2();}isKeyRight=0;DisplayRefreash(menuPoint,selectItem,scrollBar);}else if(isKeyOk==1){isKeyOk=0;DisplayRefreash(menuPoint,selectItem,scrollBar);}GuiDataDisplayRefresh();
}

项目参考

STM32F1多级菜单代码讲解_哔哩哔哩_bilibili

STM32+0.96OLED的多级菜单设计相关推荐

  1. 基于链表的多级菜单设计

    基于链表的多级菜单设计 前言 主体 前言 最近在做一个简单的界面,需要用到多级菜单,一开始使用的是传统的索引法,在修改时比较乱,在网上有用链表写的,虽然一致都在用c语言,却很少使用链表,于是今天早上便 ...

  2. 分享一个按键液晶多级菜单设计方法,工控行业中沿用多年,屡试不爽,附带本人一个调试通过

    转自:https://www.amobbs.com/forum.php?mod=viewthread&tid=4001689 typedef struct { uchar KeyStateIn ...

  3. 基于状态机的LCD多级菜单设计

    文章地址链接1:https://blog.csdn.net/embedded_guzi/article/details/35835755 文章地址链接2:http://eeskill.com/arti ...

  4. 基于STM32的OLED多级菜单GUI实现(简化版智能手表)

    前言:本文的OLED多级菜单UI为一个综合性的STM32小项目,使用多传感器与OLED显示屏实现智能终端的效果.项目中的多级菜单UI使用了较为常见的结构体索引法去实现功能与功能之间的来回切换,搭配DH ...

  5. 基于STM32F407的简易菜单设计+LCD+按键

    基于STM32F407的简易多级菜单设计+LCD+按键 实现原理 主要使用 双向链表 结构实现的菜单: 结构体包含7个变量,分别是菜单中功能项的个数,当前菜单标题,菜单中各功能项标题,功能项的类型,然 ...

  6. STM32简易多级菜单(数组查表法)

    单片机开发中,有时会用到屏幕来显示内容,当需要逐级显示内容时,就需要使用多级菜单的形式了. 1 多级菜单 多级菜单的实现,大体分为两种设计思路: 通过双向链表实现 通过数组查表实现 总体思路都是把菜单 ...

  7. 【STM32】基于STM32F103C8T6的水质检测系统设计(声光报警、多级菜单)

    需求 1.检测参数:水温.TDS.浊度.PH 2.超出阈值声光报警 3.LCD显示目标参数的测量结果 4.测量模式:单参数测量.所有参数表同时测量 切换方式:按键切换 原理 单总线技术 单总线技术采用 ...

  8. django权限二(多级菜单的设计以及展示)

    多级权限菜单设计级标题栏 我们现在只有数据展示,要进入其他url还需要手动的输入路径,非常的麻烦,所以我们要设计 一个导航栏以及侧边多级菜单栏,这个展示是通过stark组件的设计的增删改查页面,而 每 ...

  9. Vue 里,多级菜单要如何设计才显得专业?

    老生常谈了! 虽然我们是 Java 猿,但是写起来前端代码也不含糊!今天我想来和大家聊聊这个前端的动态菜单,要如何设计才显得专业!还是以我们的 TienChin 项目为例,大家一起来看看. 先来一张截 ...

最新文章

  1. NOIP2017 列队
  2. 是无数像老钟叔的p8u8
  3. plsql 自动查询最后页_一次SQL查询优化思考过程(900W+数据,从17s到300ms)
  4. 北乐博客装饰分享CSS+HTML+js
  5. 2018-2019-1 20165319 《信息安全系统设计基础》第四周学习总结
  6. 如不指定存储类型c语言,总结C语言的五种存储类型
  7. Java——Eclipse快捷键大全
  8. 大数据技术原理与应用学习笔记(一)
  9. mac下解压war包
  10. unity3d meshBaker 基本的使用
  11. 消息中间件RabbitMQ
  12. 五种方法教你预防ddos攻击
  13. 电商运营风向标:数据分析。
  14. 本地生活O2O行业已经逐渐渗透到日常生活中
  15. python匿名函数调用_(Python) 函数、匿名函数
  16. linux及安全期中总结——20135227黄晓妍
  17. python 将函数封装成pyd或者so文件,调用该文件
  18. vue实现搜索框搜索新增_基于Vue.js实现简单搜索框
  19. VINS_FUSION
  20. Node 之父 Ryan Dahl说:Node 失误太多无力回天,Deno 前景明朗。NodeJS要完蛋吗?

热门文章

  1. 法语书信开篇语结束语句型参考
  2. 视频融合协议安防监控系统EasyCVR支持大华SDK接入设备录像下载流程说明
  3. Python合并Excel相同连续单元格(已排序)
  4. 用python做一个计数器_Python写一个UP主计数器(送界面定制指南)
  5. Java实现欧几里得法求最大公约数GCD
  6. 从月薪 2000 到月入 20 万,我这一路上的可复制与不可复制
  7. 什么是天线的方向图?
  8. 利用 matlab 完全消音 进行歌曲人声提取 超简单 四行代码
  9. 学习笔记(02):学校网管员培训视频教程-三层组网模型
  10. IBM Thinkpad的感动,十五岁的生日