概括
看到这个板子的硬件配置的时候,就想到了可以拿了做一个音乐播放器的设备,想起了多年前使用过的MP3,目标就是想通过这个板子做一个类似以前的 MP3 的设备,用来听听音乐。
目前实现的功能如下:打钩的是目前已经实现的,别的是想要实现,但是目前还没有实现的。
开发环境
- RT-Thread版本:4.0.3
- IDE:RT-Thread Studio (版本: 2.1.0)
- 下载和调试工具:Downloader v1.9.7 (中科蓝迅提供:下载地址)
- 开发板:中科蓝讯 AB32VG1开发板
- AB32VG1软件包:1.04
软硬件底层配置
按键
板子上有三个用户按键,对于 S4 按键,由于 MICR 引脚不能作为输入使用,故使用杜邦线将其连接到PA0。
如下图标注所示:三个按键的按下的操作分别为 up、entry、down。
不重复造轮子~
对于按键的检测,使用软件包 multibutton 来实现,该软件包能够很方便的检测多种按键情况。
MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。
对于该软件的更多功能介绍和如何使用,请查看:《MultiButton》,为了不增加篇幅,我就不赘述了。
如下所示,在 RT-Thread Studio 中的 RT-Thread Settings 中勾选对应的软件包即可。
目前只使用了该软件的按键单击事件、双击事件的检测。
OLED12864 模块
如上图图所示,该OLED12864 模块使用的是 IIC 通信,由于我对 AB32VG1 的硬件 I2C 不熟,所以选择了软件 I2C 来控制屏幕的显示。
本来打算直接使用 u8g2 这个强大的软件包来驱动这块屏幕的,结果发现该软件包占用的空间实在是太大了,这块芯片扛不住,所以放弃了。最终选择采用厂家提供的例程直接修改,其实也就只需要配好对应的 SCL 和 SDA 引脚,很庆幸,很快就改好了。
原理图上,对应连接的引脚如下所示。
SD 卡模块
SD卡其实我不太会用,刚开始我以为会花很多时间在上面折腾,但是 RT-thread 针对该开发板提供的软件包就完全实现了 SD 卡的配置,完全不需要我动手,原理图我都没看。
在配置文件上勾选使能 SDCARD 即可,如下:
使能 SDCARD,会同时使能了如下几个组件和服务,这些组合使得 SD 卡能够作为文件系统,方便我们使用统一的接口去读取 SD 卡的内容。
AUDIO 模块
同样由于软件包足够完善,使得我们不需要考虑太多底层的东西。
如下勾选即可:
但是如果要播放音乐格式的文件,还需要使用 WavPlayer软件包 ,该软件包配合 AUDIO 驱动,能够播放 wav 格式的音乐文件。
值得说明的是,由于依赖关系,勾选该软件包会同时使能 optparse 软件包。
音乐播放器的实现
音乐播放器虽说功能不多,但是不同的状态切换还是蛮多的,所以理所当然会想到使用状态机的方式实现。
目前实现的状态主要包括如下几个,同时,该树状图也表明了状态间的转移关系。
状态机的实现
网上关于状态机的实现有很多方法,我看了好多文章都是直接一个状态对应一个动作函数,但是应用在音乐播放器上会有点问题,因为如果你一个状态对应一个函数的话,如果你的音乐歌曲有几百首,那岂不是有几百个函数,所以我对一般的状态机做了点改进,由于没看过别人 MP3 的实现代码,所以不知道是否会有更好的方法来实现,目前只能将就使用了。
如下代码所示,主要是状态转移表的实现,该表给出了所有状态对应的情况,以及状态转移的跳转位置。
typedef struct
{
uint8_t coordinate; //当前状态索引号
uint8_t back; //返回(双击确定键)
void (*enter_operation)(int8_t*); //当前状态执行的操作
/* 当 entry键按下时 sub_base_addr提供子状态的基地址索引,sub_offset_addr提供偏移量,两者相加, 得到下一个状态 */
uint8_t sub_base_addr; //子状态基地址
uint8_t sub_offset_addr; //子状态偏移地址 (用于记录下一步要跳转到的子状态)
uint8_t leaf_node_flag; //标志是否为叶子节点(最底层的状态),方便在该状态下按确定可以实现返回功能
} Menu_table;
Menu_table table[]=
{ //索引 - back - function(当前) - 子状态基地址(固定) - 子状态偏移地址 - 标志是否为叶子节点
{ 0, 0, (*load_menu), 1, 0, 0}, //0加载界面
{ 1, 1, (*main_menu), 2, 0, 0}, //1主菜单界面
{ 2, 1, (*playlists), 4, 0, 0}, //歌单
{ 3, 1, (*settings_list), 5, 0, 0}, //设置
//播放的子菜单
{ 4, 2, (*music_play), 0, 0, 0}, //音乐播放控制:
//设置菜单的子菜单
{ 5, 3, (*volume_control), 0, 1, 1}, //音量控制
{ 6, 3, (*language_setting), 0, 1, 1}, //语言设置
{ 7, 3, (*brightness_setting), 0, 1, 1}, //亮度设置
};
状态转移主要是按键来实现的,每个按键按下,就会触发状态转移,状态转移的代码如下所示:
if (rt_event_recv(&sys_event,
(BUTTON_PRE_FLAG | BUTTON_ENTRY_FLAG | BUTTON_2ENTRY_FLAG | BUTTON_NEXT_FLAG),
RT_EVENT_FLAG_OR,
RT_WAITING_FOREVER, &e) == RT_EOK)
{
//按键 PRE
if (rt_event_recv(&sys_event,
BUTTON_PRE_FLAG,
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_NO, &e) == RT_EOK)
{
table[func_index].sub_offset_addr -= 1;
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
//按键 ENTRY
if (rt_event_recv(&sys_event,
BUTTON_ENTRY_FLAG,
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_NO, &e) == RT_EOK)
{
if(table[func_index].leaf_node_flag)
{
func_index = table[func_index].back; //返回上一级菜单位置
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
else if(func_index == 2) //音乐播放则特殊点(所有音乐进入同一个状态)
{
k = table[func_index].sub_offset_addr;
func_index = 4;
table[func_index].sub_offset_addr = k;
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
else
{
func_index = table[func_index].sub_base_addr + table[func_index].sub_offset_addr;
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
}
//按键 ENTRY double click
if (rt_event_recv(&sys_event,
BUTTON_2ENTRY_FLAG,
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_NO, &e) == RT_EOK)
{
func_index = table[func_index].back;
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
//按键 NEXT
if (rt_event_recv(&sys_event,
BUTTON_NEXT_FLAG,
RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,
RT_WAITING_NO, &e) == RT_EOK)
{
table[func_index].sub_offset_addr += 1;
table[func_index].enter_operation(&table[func_index].sub_offset_addr);
}
音乐播放展示
首先将音乐文件拷入 SD 卡内的 music 文件,要注意的音乐的格式是 wav 格式,mp3 格式是不支持的,音乐的文件名目前暂时只支持英文字符,别的文件名会显示乱码,建议将就改改,中文的使用拼音呗,将就一下。
接着就可以开始愉快的播放音乐了,由于是使用的是耳机,声音录不出来,很尴尬,所以正在找可以接耳机口外放的装备,找到了再补一个视频。话说这个板子播放的音质真滴不错。
下面是录的视频:
心得体会
使用 RT-Thread 提供的 BSP 真的太方便了,利用RT-Thread现有的软件包,不需要考虑硬件底层就可以开发出比较复杂的功能,真心强大。
由于这段时间比较忙,所以这个 DIY 项目只完成了基本功能,很多扩展功能也没来得及做,后面慢慢补充了。
很高兴能参加这个 DIY 项目,不仅白嫖板子,还能通过这个有趣的项目提高自己的技能。