聚丰项目 > 基于手势识别控制的音乐播放器

基于手势识别控制的音乐播放器

中科蓝讯公司推出的 AB32VG1 开发板,这个开发板自带 按键模块、audio 模块和 TF card 模块,基于该开发板结合 OLED12864 模块作为显示屏幕,加上 PAJ7620U2 手势识别模块,制作了一款基于手势识别的音乐播放器,这样就可以实现非接触式控制音乐播放,这个模块作为一个基础,之后也能构造出更炫酷的作品 该音乐播放器目前实现了如下功能: 1、可以通过 OLED12864 模块显示播放器信息。 2、可以通过按键控制音乐播放和设置一些信息。 3、读取 SD 卡的音乐文件并播放。 4、通过手势识别模块控制播放器

Eureka1024 Eureka1024

分享
0 喜欢这个项目
团队介绍

Eureka1024 Eureka1024

团队成员

Eureka1024 软件开发

分享
项目简介
中科蓝讯公司推出的 AB32VG1 开发板,这个开发板自带 按键模块、audio 模块和 TF card 模块,基于该开发板结合 OLED12864 模块作为显示屏幕,加上 PAJ7620U2 手势识别模块,制作了一款基于手势识别的音乐播放器,这样就可以实现非接触式控制音乐播放,这个模块作为一个基础,之后也能构造出更炫酷的作品 该音乐播放器目前实现了如下功能: 1、可以通过 OLED12864 模块显示播放器信息。 2、可以通过按键控制音乐播放和设置一些信息。 3、读取 SD 卡的音乐文件并播放。 4、通过手势识别模块控制播放器
硬件说明

使用到的模块如下:


2.1、按键模块(GPIO)

按键模块使用如下 GPIO 引脚:

  • S2 -- PF1 (单击为向下选择)

  • S3 -- PF0(单击为确定,双击为返回)

  • S4 -- PA2(单击为向上选择)

使用按键软件包:multibutton

2.2、OLED12864 模块(IIC 协议)

使用的引脚如下:

  • SDA --  PA6

  • SCL --  PA0

没有使用 u8g2 的驱动软件包,因为内存不够。使用了网上的软件 IIC 实现,做了一些修改。


2.3、SD 卡模块(SDIO协议)

使用的 SDIO 协议对应的引脚如下:

  • SD_CMD -- PB0

  • SD_CLK -- PB1

  • SD_DAT -- PB2

  • SD_DET -- PE5

使用到 RT-Thread 提供的 SDIO 驱动,使用到的组件和服务层有 DFS、Fatfs 和 POSIX。


2.4、AUDIO 模块

  • VOUTRP -- DACR

  • VOUTLP -- DACL

  • FMANT -- FM_ANT

  • MICIN -- MICL/PF2

使用到的驱动 audio device, 软件包为 wavplayer(需要optparse软件包),可以用来播放 wav 格式的音乐文件。


2.5、手势识别模块(PAJ7620模块)-- IIC协议

  • SDA -- PE2

  • SCL -- PE3

  • INT -- PA5

使用了软件 IIC 驱动、PAJ7620软件包。




软件说明

开发环境:

- RT-Thread 版本 latest(2021-10-20)

- RT-Thread Studio版本  V2.12

- AB32VG1 开发板 BSP 版本 V1.08


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


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


image.png


网上关于状态机的实现有很多方法,我看了好多文章都是直接一个状态对应一个动作函数,但是应用在音乐播放器上会有点问题,因为如果你一个状态对应一个函数的话,如果你的音乐歌曲有几百首,那岂不是有几百个函数,所以我对一般的状态机做了点改进。


如下代码所示,主要是状态转移表的实现,该表给出了所有状态对应的情况,以及状态转移的跳转位置。

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;


//针对下表,一些想法:其实可以增加一个变量来实现树的收缩的(解决音乐播放时,点击任意一首歌进入的是同一个状态,目前是enter事件给特殊,如果还有,就思考了)
//初始化菜单所处在的各个状态
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},   //音乐播放控制:(2-1)

    //设置菜单的子菜单
    {  5,    3,      (*volume_control),  0,             1,           1},   //音量控制
    {  6,    3,    (*language_setting),  0,             1,           1},   //语言设置
    {  7,    3,  (*brightness_setting),  0,             1,           1},   //亮度设置
};


状态转移主要是按键来实现的,每个按键按下,就会触发状态转移,状态转移的代码如下所示:

/* 菜单界面显示线程入口函数 */
static void menu_thread_entry(void *parameter)
{
    rt_uint32_t e;
    int8_t k;

    while(1)
    {
        if (rt_event_recv(&control_event,
                           (UP_FLAG | ENTRY_FLAG | RETURN_FLAG | DOWN_FLAG),
                           RT_EVENT_FLAG_OR,
                           RT_WAITING_FOREVER, &e) == RT_EOK)
        {
            //向上
            if (rt_event_recv(&control_event,
                               UP_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);
            }

            //确定
            if (rt_event_recv(&control_event,
                               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);
                }
            }

             //返回
             if (rt_event_recv(&control_event,
                                RETURN_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);
             }


            //向下
            if (rt_event_recv(&control_event,
                               DOWN_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);
            }
        }
    }
}
演示效果

演示效果如下:

以下是代码的 gitee 地址:

https://gitee.com/Eureka1024/MusicPlayerBasedOnGestureRecognition

评论区(0 )