聚豐項目 > 基于手勢識別控制的音樂播放器
中科藍(lán)訊公司推出的 AB32VG1 開發(fā)板,這個開發(fā)板自帶 按鍵模塊、audio 模塊和 TF card 模塊,基于該開發(fā)板結(jié)合 OLED12864 模塊作為顯示屏幕,加上 PAJ7620U2 手勢識別模塊,制作了一款基于手勢識別的音樂播放器,這樣就可以實現(xiàn)非接觸式控制音樂播放,這個模塊作為一個基礎(chǔ),之后也能構(gòu)造出更炫酷的作品 該音樂播放器目前實現(xiàn)了如下功能: 1、可以通過 OLED12864 模塊顯示播放器信息。 2、可以通過按鍵控制音樂播放和設(shè)置一些信息。 3、讀取 SD 卡的音樂文件并播放。 4、通過手勢識別模塊控制播放器
Eureka1024
分享Eureka1024
團(tuán)隊成員
Eureka1024 軟件開發(fā)
按鍵模塊使用如下 GPIO 引腳:
S2 -- PF1 (單擊為向下選擇)
S3 -- PF0(單擊為確定,雙擊為返回)
S4 -- PA2(單擊為向上選擇)
使用按鍵軟件包:multibutton
使用的引腳如下:
SDA -- PA6
SCL -- PA0
沒有使用 u8g2 的驅(qū)動軟件包,因為內(nèi)存不夠。使用了網(wǎng)上的軟件 IIC 實現(xiàn),做了一些修改。
使用的 SDIO 協(xié)議對應(yīng)的引腳如下:
SD_CMD -- PB0
SD_CLK -- PB1
SD_DAT -- PB2
SD_DET -- PE5
使用到 RT-Thread 提供的 SDIO 驅(qū)動,使用到的組件和服務(wù)層有 DFS、Fatfs 和 POSIX。
VOUTRP -- DACR
VOUTLP -- DACL
FMANT -- FM_ANT
MICIN -- MICL/PF2
使用到的驅(qū)動 audio device, 軟件包為 wavplayer(需要optparse軟件包),可以用來播放 wav 格式的音樂文件。
SDA -- PE2
SCL -- PE3
INT -- PA5
使用了軟件 IIC 驅(qū)動、PAJ7620軟件包。
開發(fā)環(huán)境:
- RT-Thread 版本 latest(2021-10-20)
- RT-Thread Studio版本 V2.12
- AB32VG1 開發(fā)板 BSP 版本 V1.08
音樂播放器雖說功能不多,但是不同的狀態(tài)切換還是蠻多的,所以理所當(dāng)然會想到使用狀態(tài)機(jī)的方式實現(xiàn)。
目前實現(xiàn)的狀態(tài)主要包括如下幾個,同時,該樹狀圖也表明了狀態(tài)間的轉(zhuǎn)移關(guān)系。
網(wǎng)上關(guān)于狀態(tài)機(jī)的實現(xiàn)有很多方法,我看了好多文章都是直接一個狀態(tài)對應(yīng)一個動作函數(shù),但是應(yīng)用在音樂播放器上會有點(diǎn)問題,因為如果你一個狀態(tài)對應(yīng)一個函數(shù)的話,如果你的音樂歌曲有幾百首,那豈不是有幾百個函數(shù),所以我對一般的狀態(tài)機(jī)做了點(diǎn)改進(jìn)。
如下代碼所示,主要是狀態(tài)轉(zhuǎn)移表的實現(xiàn),該表給出了所有狀態(tài)對應(yīng)的情況,以及狀態(tài)轉(zhuǎn)移的跳轉(zhuǎn)位置。
typedef struct { uint8_t coordinate; //當(dāng)前狀態(tài)索引號 uint8_t back; //返回 void (*enter_operation)(int8_t* ); //指向執(zhí)行函數(shù),作為當(dāng)前狀態(tài)執(zhí)行的操作 //當(dāng) entry鍵按下時 sub_base_addr提供子狀態(tài)的基地址索引,sub_offset_addr提供偏移量,兩者相加,得到下一個狀態(tài) uint8_t sub_base_addr; //子狀態(tài)基地址 uint8_t sub_offset_addr; //子狀態(tài)偏移地址 (用于記錄下一步要跳轉(zhuǎn)到的子狀態(tài)) uint8_t leaf_node_flag; //標(biāo)志是否為葉子節(jié)點(diǎn)(最底層的狀態(tài)),方便在該狀態(tài)下按確定可以實現(xiàn)返回功能 } Menu_table; //針對下表,一些想法:其實可以增加一個變量來實現(xiàn)樹的收縮的(解決音樂播放時,點(diǎn)擊任意一首歌進(jìn)入的是同一個狀態(tài),目前是enter事件給特殊,如果還有,就思考了) //初始化菜單所處在的各個狀態(tài) Menu_table table[]= { //索引 - back - function(當(dāng)前) - 子狀態(tài)基地址(固定) - 子狀態(tài)偏移地址 - 標(biāo)志是否為葉子節(jié)點(diǎn) { 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}, //設(shè)置 //播放的子菜單 { 4, 2, (*music_play), 0, 0, 0}, //音樂播放控制:(2-1) //設(shè)置菜單的子菜單 { 5, 3, (*volume_control), 0, 1, 1}, //音量控制 { 6, 3, (*language_setting), 0, 1, 1}, //語言設(shè)置 { 7, 3, (*brightness_setting), 0, 1, 1}, //亮度設(shè)置 };
狀態(tài)轉(zhuǎn)移主要是按鍵來實現(xiàn)的,每個按鍵按下,就會觸發(fā)狀態(tài)轉(zhuǎn)移,狀態(tài)轉(zhuǎn)移的代碼如下所示:
/* 菜單界面顯示線程入口函數(shù) */ 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) //音樂播放則特殊點(diǎn)(所有音樂進(jìn)入同一個狀態(tài)) { 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