基于 RT-Thread 和 AB32VG1 实现的音乐播放器

概括

看到这个板子的硬件配置的时候,就想到了可以拿了做一个音乐播放器的设备,想起了多年前使用过的MP3,目标就是想通过这个板子做一个类似以前的 MP3 的设备,用来听听音乐。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hbWmF4tT-1621094499137)(开发文档.assets/image-20210515180655142.png)]
目前实现的功能如下:打钩的是目前已经实现的,别的是想要实现,但是目前还没有实现的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OV8iHBSs-1621094351259)(开发文档.assets/image-20210515234134908.png)]

开发环境

  • RT-Thread版本:4.0.3

  • IDE:RT-Thread Studio (版本: 2.1.0)

  • 下载和调试工具:Downloader v1.9.7 (中科蓝迅提供:下载地址)

  • 开发板:中科蓝讯 AB32VG1开发板

  • AB32VG1软件包:1.04
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x48rAFia-1621094351261)(开发文档.assets/image-20210515174250073.png)]

软硬件底层配置

按键

板子上有三个用户按键,对于 S4 按键,由于 MICR 引脚不能作为输入使用,故使用杜邦线将其连接到PA0。

如下图标注所示:三个按键的按下的操作分别为 up、entry、down。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dbj4Ti8-1621094351280)(开发文档.assets/image-20210515174112984.png)]
不重复造轮子~

对于按键的检测,使用软件包 multibutton 来实现,该软件包能够很方便的检测多种按键情况。

MultiButton 是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰。

对于该软件的更多功能介绍和如何使用,请查看:《MultiButton》,为了不增加篇幅,我就不赘述了。

如下所示,在 RT-Thread Studio 中的 RT-Thread Settings 中勾选对应的软件包即可。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zg7MTctc-1621094351282)(开发文档.assets/image-20210515175043319.png)]

目前只使用了该软件的按键单击事件、双击事件的检测。

OLED12864 模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykh5Kf2A-1621094351284)(开发文档.assets/image-20210515180655142.png)]

如上图图所示,该OLED12864 模块使用的是 IIC 通信,由于我对 AB32VG1 的硬件 I2C 不熟,所以选择了软件 I2C 来控制屏幕的显示。

本来打算直接使用 u8g2 这个强大的软件包来驱动这块屏幕的,结果发现该软件包占用的空间实在是太大了,这块芯片扛不住,所以放弃了。最终选择采用厂家提供的例程直接修改,其实也就只需要配好对应的 SCL 和 SDA 引脚,很庆幸,很快就改好了。

原理图上,对应连接的引脚如下所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FIb1LbVd-1621094351286)(开发文档.assets/image-20210515223219909.png)]

SD 卡模块

SD卡其实我不太会用,刚开始我以为会花很多时间在上面折腾,但是 RT-thread 针对该开发板提供的软件包就完全实现了 SD 卡的配置,完全不需要我动手,原理图我都没看。

在配置文件上勾选使能 SDCARD 即可,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ej1aieWL-1621094351287)(开发文档.assets/image-20210515223643256.png)]

使能 SDCARD,会同时使能了如下几个组件和服务,这些组合使得 SD 卡能够作为文件系统,方便我们使用统一的接口去读取 SD 卡的内容。

在这里插入图片描述

AUDIO 模块

同样由于软件包足够完善,使得我们不需要考虑太多底层的东西。

如下勾选即可:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ClWMEhlC-1621094351293)(开发文档.assets/image-20210515224258855.png)]
但是如果要播放音乐格式的文件,还需要使用 WavPlayer软件包 ,该软件包配合 AUDIO 驱动,能够播放 wav 格式的音乐文件。
在这里插入图片描述
值得说明的是,由于依赖关系,勾选该软件包会同时使能 optparse 软件包。

音乐播放器的实现

音乐播放器虽说功能不多,但是不同的状态切换还是蛮多的,所以理所当然会想到使用状态机的方式实现。

目前实现的状态主要包括如下几个,同时,该树状图也表明了状态间的转移关系。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WF215G2F-1621094351295)(开发文档.assets/image-20210515225601193.png)]

状态机的实现

网上关于状态机的实现有很多方法,我看了好多文章都是直接一个状态对应一个动作函数,但是应用在音乐播放器上会有点问题,因为如果你一个状态对应一个函数的话,如果你的音乐歌曲有几百首,那岂不是有几百个函数,所以我对一般的状态机做了点改进,由于没看过别人 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 项目,不仅白嫖板子,还能通过这个有趣的项目提高自己的技能。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值