在这一次的专业技能高级实训课程中,我们小组选定了基于OLED菜单的音乐播放器+时钟这一个题目。我们小组在分析选题后,认为菜单依托于功能实现会获得更好的效果,能够更好锻炼我们的能力。这一次的实践是基于Mega2560单片机来实现的,本作品使用的模块有:蜂鸣器,按键,0.96英寸OLED显示屏。
本作品实现的功能有:按键能够对菜单进行前进后退,上下移动选中菜单当前页功能项,在音乐播放页,能够播放或暂停,能够切换下一首上一首。在时钟计时页,能够开始计时停止计时,能够调整时间,能够显示时间在OLED,能够记录时钟设置。在显示模式设置中,可以更改反显的模式,为白色或者黑色。
由于嵌入式是软件硬件结合,而硬件相关信息不方便贴出来。这里给出代码以提供思路,倘若是师弟师妹,直接复制那就是可以直接运行的。代码在本文末尾给出。
软件设计图
我们实现了music目录下的song_list、clock目录下的alarm和timer以及Disp_mode下的反显设置(反白或者反黑)。正计时、息屏和恢复出厂设置由于时间和事务原因没有实现。上电显示首页,首页为一张bmp图片取模图标。eeprom部分出现了bug,只能录入一个闹钟。此外需要特别注意的是eeprom本来是没东西的,应该先直接给那个地址写入一次00-00-00后再进行之后的实验!
菜单软件总体设计结构图
按键
引入key.h,得知是矩阵键盘哪个按键按下,在菜单函数根据按键进入不同的按键函数,用于对全局变量的修改,对结构体指针变量的移动等。我采用了树结构双亲表示法,在结构体数组项中存放它的父母的下标和当前菜单页最大下标和最小下标。前进后退时更改结构体指针变量的属性。
K1_1 |
K1_2 |
K1_3 |
K1_4 |
K2_1 |
K3_1 |
K2_2 |
菜单前进、播放暂停音乐 |
菜单后退 |
菜单项下移、下一首音乐 |
菜单项上移、上一首音乐 |
时钟小时、分钟、秒增加 |
时钟小时、分钟、秒减少 |
切换时钟调整模式 |
表 1 按键作用表
OLED模块
在本次实训中,我们采用OLED模块进行菜单和时钟的显示。我将OLED显示相关的内容封装为MenuDisp()函数,并在主循环每10ms调用。显示的菜单项分为两种情况,特殊页和一般页。比如首页就是特殊页,当进入首页时,不显示菜单项,而是调用显示图片的函数。一般页分为四种情况:
- 当前页只有一项,此时显示在第一行,反显第一行。
- 当前页只有两项,此时选中第一项,则反显第一行。选中第二项则反显第二行的位置。
- 当前页只有三项,此时选中第一项,则反显第一行。选中第二项则反显第二行的位置。选中第三项反显第二行。
- 当前页大于三项。此时若选中页最小下标,则反显第一行,二三行显示的内容是它的下标加1加2的兄弟;若选中当前页最大下标,反显第三行,一二行显示的内容是它的下标减1减2的兄弟;若不是最大或最小下标,一律反显第二行,选中第二行,一三行显示的分别是它的下标减1加1的兄弟。
菜单的选中实际上由在主循环开始之前定义的结构体指针变量完成。一开始结构体指针指向数组名,也就是下标为0的地址。随着按键按下,Node *n的指向被修改,显示的内容包括它本身和它的兄弟,我们可以利用n->index,也就是n的下标去结构体数组中找到他的兄弟。
OLED模块软件结构图
蜂鸣器模块
音乐播放器使用无源蜂鸣器实现。实现无源蜂鸣器,需要用到TIM3中断。设置好Timer3并初始化,当TIM3的计数值达到预设值时,该中断会被触发。
定义了音符的频率和持续时间的关系,如低音1、低音2等。使用队列q存储待播放的音乐数据。数组t和d,分别存储音符的频率和持续时间。
当检测到有音乐需要播放时,根据音符的频率和持续时间计算出下一个音符的节拍值,并启动定时器播放该音符。当整个音乐播放完成后,切换到下一首音乐。
要实现音乐播放,首先需要将音乐数据手动存放到数组中,如音符频率、持续时间等。
在主循环中,根据用户的操作来控制音乐的播放和停止。
图 8 蜂鸣器软件结构图
EEPROM
通过IIC把一写重要的数据保存到EEPROM中,如把屏幕设置、歌曲信息、时钟和计时等信息利用IIC按页写、位写方式来保存在EEPROM指定地址。确保一些重要数据掉电后不丢失,在下一次上电时恢复上一次的操作信息。
图 9 eeprom软件结构图
算法设计
图 10 基于树结构双亲指针表示法的结构体数组
在主循环开始前,建树,定义结构体指针变量,指向数组名。之后在主循环中调用Key_Control()、Timing()和MenuDisp()。
由Key_Control()对菜单各按键功能函数进行调度。读取到有按键按下后,如果是有安排功能的按键,则调用对应的函数。如果是特殊页才有的效果,那就判断是否到达特殊页,再调用对应函数。此外,按下按键后OLED才刷新,反显的位置才更新一次。
MenuDip()负责显示项的文本,主要是由结构体指针变量的指向决定OLED当前要显示的内容。在时钟页时,要将整型变量变成字符串,调用TurnToStr(char num[]),将小时分钟秒转换字符串,小于10的时候补零。
Timing()负责计时。由于只实现了倒计时,调用此函数只能进行减操作,当小时分钟秒都为零时,暂停计时。中途退出计时也清零并暂停计时。若上一次写入了eeprom,则在新的保存之前,进入都默认为设定的计时数。计时中途可以暂停,保存时暂时停止计时,按下start继续计时。
4 测试