聚丰项目 > 基于手势识别控制的音乐播放器
中科蓝讯公司推出的 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