聚丰项目 > 基于手势识别控制的音乐播放器
中科蓝讯公司推出的 AB32VG1 开发板,这个开发板自带 按键模块、audio 模块和 TF card 模块,基于该开发板结合 OLED12864 模块作为显示屏幕,加上 PAJ7620U2 手势识别模块,制作了一款基于手势识别的音乐播放器,这样就可以实现非接触式控制音乐播放,这个模块作为一个基础,之后也能构造出更炫酷的作品 该音乐播放器目前实现了如下功能: 1、可以通过 OLED12864 模块显示播放器信息。 2、可以通过按键控制音乐播放和设置一些信息。 3、读取 SD 卡的音乐文件并播放。 4、通过手势识别模块控制播放器
Eureka1024
分享Eureka1024
团队成员
Eureka1024 软件开发
按键模块使用如下 GPIO 引脚:
S2 -- PF1 (单击为向下选择)
S3 -- PF0(单击为确定,双击为返回)
S4 -- PA2(单击为向上选择)
使用按键软件包:multibutton
使用的引脚如下:
SDA -- PA6
SCL -- PA0
没有使用 u8g2 的驱动软件包,因为内存不够。使用了网上的软件 IIC 实现,做了一些修改。
使用的 SDIO 协议对应的引脚如下:
SD_CMD -- PB0
SD_CLK -- PB1
SD_DAT -- PB2
SD_DET -- PE5
使用到 RT-Thread 提供的 SDIO 驱动,使用到的组件和服务层有 DFS、Fatfs 和 POSIX。
VOUTRP -- DACR
VOUTLP -- DACL
FMANT -- FM_ANT
MICIN -- MICL/PF2
使用到的驱动 audio device, 软件包为 wavplayer(需要optparse软件包),可以用来播放 wav 格式的音乐文件。
SDA -- PE2
SCL -- PE3
INT -- PA5
使用了软件 IIC 驱动、PAJ7620软件包。
开发环境:
- RT-Thread 版本 latest(2021-10-20)
- RT-Thread Studio版本 V2.12
- AB32VG1 开发板 BSP 版本 V1.08
音乐播放器虽说功能不多,但是不同的状态切换还是蛮多的,所以理所当然会想到使用状态机的方式实现。
目前实现的状态主要包括如下几个,同时,该树状图也表明了状态间的转移关系。
网上关于状态机的实现有很多方法,我看了好多文章都是直接一个状态对应一个动作函数,但是应用在音乐播放器上会有点问题,因为如果你一个状态对应一个函数的话,如果你的音乐歌曲有几百首,那岂不是有几百个函数,所以我对一般的状态机做了点改进。
如下代码所示,主要是状态转移表的实现,该表给出了所有状态对应的情况,以及状态转移的跳转位置。
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