【技術分享】單片機模擬NS手柄 半自動完成太鼓達人曲目

一、前言

1.1 項目由來

前些天,在b站上看到有人分享單片機模擬NS手柄,在《精靈寶可夢》、《異度之刃》等遊戲中實現自動操作的視頻。我是個有着多年“鼓齡”的太鼓達人玩家,於是產生想法,將該方案用於自動完成太鼓達人曲目,實現類似TAS的效果。經過一週的實驗,取得了一定的成果:開發板通過USB TypeB轉TypeC轉接線連接至Switch遊戲機的USB接口上,系統能夠在操作者手動給出曲目開始信號(按下按鍵模塊上的特定按鍵)的情況下,自動完成鬼難度6星的一首曲目,獲得了“全良”的成績,驗證了方案的可行性。展示視頻如下:

[自制] 單片機模擬Switch控制器_半自動完成太鼓達人曲目《戀》_20200224

備用鏈接

下面將簡單介紹我的完成過程

1.2 前期調研工作

項目參考了以下鏈接中的內容:

“當單片機取代橡皮筋——解放雙手,放飛雙眼,我的寶可夢自動化成果”

“在 Switch上使用 Arduino Uno R3 開發板模擬連續 A 鍵”

此類項目基本是基於:https://github.com/progmem/Switch-Fightstick 二次開發而成。其中,單片機模擬USB功能的實現,主要依靠LUFA開源框架的使用,它可以讓AVR單片機模擬成想要的USB設備。移植工作並不複雜,初始化函數無需自己修改;只需要自行編寫業務代碼即可。

1.3 測試曲目選擇

測試曲目的選擇方面:

  • 不能過於簡單,否則達不到驗證的目的。曲目在“鬼”難度中選擇;
  • 爲方便驗證,簡化編碼工作,曲目速度不能過快或存在過多變化。最好是一個速度從頭到尾;
  • 所選曲目不可過長,否則一方面會帶來很多編碼工作,另一方面也會受到單片機存儲空間的限制。

綜合考慮,選擇了鬼難度的《戀》這首曲目。這首曲目是日劇《逃避可恥但有用》的片尾曲,官方難度爲鬼6星,速度爲BPM158,全曲不變。曲目基本涵蓋了常用的節奏型,複雜度適中,適合驗證使用。

二、項目實現

2.1 硬件平臺

硬件平臺爲ARDUINO UNO R3開發板。特別要指出的是,此次開發中使用的是開發板上用作USB接口芯片的ATmega16U2(下圖中紅框圈出),並不是常規情況下用來開發的核心芯片ATMEGA328P。爲避免干擾,減少功耗,可以將其從開發板上去除(下圖中綠框圈出)。

通過研究硬件原理圖可以發現,板子上的TX、RX兩顆LED是連接在ATmega16上的,可供我們使用,項目中將用它來指示演奏音符的種類(鼓心、鼓邊);同時,開發板上還提供了一些排針,連接至單片機的IO口上。我們將用來連接外部按鍵模塊,用於輸入。開發板原理圖中ATMEGA16U2相關部分如下:

完成搭建的測試系統如圖所示:

2.2 工具鏈及開發環境

單片機程序的編譯環境爲WinAVR-20100110,在windows和Linux下均可使用。爲開發方便,本項目選擇在Windows系統下進行開發。
本工程沒有現成的集成開發環境(如Keil、Visual Studio等)可以使用,必須手寫makefile,通過終端執行make指令進行編譯。
於是,本項目的軟件開發工作選擇宇宙第一的文本編輯器——微軟Visual Studio Code進行。不但代碼編輯、makefile編輯非常方便,還自帶終端,可以隨時進行生成(make)、清理(clean)等操作,如下圖。

項目進行make(生成)操作後,會生成用於執行的hex文件。下載hex文件使用Flip軟件,通過USB線連接開發板和電腦,之後手動短接單片機的RESET引腳和GND(地)(如下圖),待系統將設備枚舉成功後,再進行下載,如下圖。

2.3 軟件實現

2.3.1 軟件流程設計

擬定軟件工作流程如下:

  1. 初始化(IO初始化、USB初始化等)。完成初始化後,循環執行以下 2 3 兩項工作:
  2. 單片機持續檢測按鍵模塊上的SW1按鍵(連接至單片機PB2引腳),如果按下,則向Switch輸出“A鍵按下”指令。即,用按鍵模塊的SW1按鍵,模擬了Switch的A鍵。安排此功能,主要用於在菜單中進行曲目和難度的確認;以及演奏完畢的成績確認。
  3. 單片機持續檢測按鍵模塊上的SW4按鍵(連接至單片機PB1引腳),如果按下,則立即調用“演奏”函數,按照程序的設計,向Switch有序發送按鍵信號,進行曲目的演奏。

接下來介紹軟件實現過程中的幾個關鍵點

2.3.2 音符時長標定

·音符和休止符

曲目的完成,實際上就是按照譜面演奏音符和休止符。具體到該項目,進行設計如下:

  • 音符的演奏爲,輸出相應的按鍵信號(鼓心“咚”音色輸出B鍵、鼓邊“咔”音色輸出A鍵),之後,等待(延時)一定的時間,使得時值完整;
  • 休止符的演奏爲,按照正確的時值,等待(延時)一定的時間。 爲實現上述功能,我們首先要得到“一次輸出動作”的最短時間。
·單次輸出動作實驗結果

我們在只使用B鍵用於輸出“咚”音色,只使用A鍵用於輸出“咔”音色的情況下,要完成一次完整、無誤判的按鍵信號輸出動作,需要進行以下步驟(以輸出一次B鍵爲例):

	HID_Task(B);
	USB_USBTask();
	_delay_ms(10);
	_delay_ms(10);
	
	HID_Task(PAUSE);
	USB_USBTask();
	_delay_ms(10);
	_delay_ms(10);

首先調用一次HID_Task(B)及USB_USBTask()函數進行輸出,之後延時20毫秒;之後調用HID_Task(PAUSE)及 USB_USBTask()輸出一個“PAUSE”信號,再延時20毫秒。總共需要40毫秒左右。

·使用邏輯分析儀進行音符時長標定

在明確了上述信息後,我們就可以對曲目中使用的音符、休止符進行標定了。曲目速度爲BPM158,4/4拍。經過計算,得到如下結果:

  • 四分音符、休止符時長爲379.7毫秒(一拍)
  • 八分音符、休止符時長爲189.87毫秒
  • 十六分音符、休止符時長爲93.9毫秒
  • 三十二分音符、休止符時長爲47.46毫秒。曲目中的滾奏(“黃條”)暫定使用三十二分音符演奏

爲減少編碼量,節約存儲空間,本項目中,使用毫秒作爲最小計時單位。以演奏四分音符的“咚”音色爲例,我們應當進行的操作如下:

  • 首先輸出一次B鍵,約40毫秒;
  • 再延時379.7-40 ≈ 340毫秒。

演奏四分休止符時,應進行的操作如下:

  • 延時379.7-40 ≈ 340毫秒。

由於毫秒級延時精度有限,我們需要對實際的輸出時間進行測量,以得到不同音符的誤差,便於進行補償。由於家中條件所限,沒有示波器,我們使用邏輯分析儀進行標定工作,用以確定“演奏音符”操作中,完成按鍵輸出後,需要延時的毫秒數;以及“演奏空拍”操作中,需要延時的毫秒數。完成連接的硬件如下圖所示:

邏輯分析儀通過杜邦線,連接至單片機的IO口。測量時可以先將該IO口拉低,然後進行待測操作,之後再將IO口拉高。低電平的持續時間即爲待測操作佔用的時間。IO口翻轉佔用的時間爲微秒級別,忽略不計。下圖爲測量上個章節提到的“單次按鍵輸出”操作的結果圖。從波形中可以看出,低電平(待測操作)持續時間爲40.0733毫秒。

經過測試,我們可以得到各音符、休止符演奏時的實際時間,如下表所示(單位 毫秒)。我們根據測量結果,首先對部分延時的毫秒數進行修正,並記錄下修正完畢依然殘留的誤差。

2.3.3 曲譜數據建立及演奏函數設計

進行如下設計:用16位整數(unsigned short int)組成的數組來描述曲目。
數字“1”表示演奏“咚”音色,數字2表示演奏“咔”音色,數字“5”表示曲目結束。其餘數字則表示延時相應的毫秒數,如378表示將會調用_delay_ms()函數延時378毫秒。
爲方便編碼,設計宏定義如下:

//休止符
#define R4  (378) 
#define R8  (189)
#define R16 (95)
#define R32 (47)  

//演奏音符 1爲演奏don 2爲演奏ka 
#define PLAY_DON  1
#define PLAY_KA   2
#define HITDUR (40)

#define D4  PLAY_DON,(R4-HITDUR+1)  //修正
#define D8  PLAY_DON,(R8-HITDUR)
#define D16 PLAY_DON,(R16-HITDUR)
#define D32 PLAY_DON,(R32-HITDUR)

#define K4  PLAY_KA,(R4-HITDUR) 
#define K8  PLAY_KA,(R8-HITDUR)
#define K16 PLAY_KA,(R16-HITDUR)

曲目方面,圖片格式的曲譜可以在太鼓達人wiki上獲得,如圖所示:

以前四小節爲例,圖中所示爲:

在樂理上表示的意義爲(譜例中普通符頭爲表示“咚” ×型符頭表示“咔”,由於例程中沒有給出左手上下左右按鍵的輸出方法,所以大音符簡化爲同音色小音符處理):
按照前述宏定義,則前四小節編碼爲:
const unsigned short music[] PROGMEM = 
{
    //M1
    D4,     K4,     D4,     K4,         
    //M2
    D4,     K4,     D8, K16,K16,K8, D8,     
    //M3
    D4,     D4,     D8, K8, K8, D8,
    //M4
    R8, D4, D4,  K8,  K16,K16,K16,K16,

代碼中爲方便查看,以小節爲單位分行編寫。依次類推,完成整首曲目的編碼。要注意的是,由於單片機的內存空間極其有限(僅512Byte),所以該數組不能像普通變量一樣放在RAM中,而必須存放在FLASH中。數組定義時需要加入PROGMEM宏進行標誌。

演奏函數設計爲:在遇到結束標誌之前,從數組中依次取出數字,判斷數字並做出相應的動作。代碼如下:

void play(void)
{
    unsigned short i = 0;
    while(1)
    {
      if(  pgm_read_word(&music[i]) == PLAY_DON)
      {
        PLAY_DON_B(); //通過B鍵演奏“咚”音色
      }
      else if(pgm_read_word(&music[i])== PLAY_KA )
      {
        PLAY_KA_A(); //通過A鍵演奏“咔”音色
      }
      else if (pgm_read_word(&music[i]) == MUS_END)
      {
        break;
      }
      else
      {
        _delay_ms(pgm_read_word(&music[i]));
      }

      i++;   

    }//while(1)
}//play()

爲了訪問FLASH中的數組數據,必須使用pgm_read_word()函數。相應的音色演奏函數如下(以PLAY_DON_B()函數爲例):

void PLAY_DON_B(void)
{
  RXLED_ON;
  HID_Task(B);
  USB_USBTask();
  _delay_ms(10);
  _delay_ms(10);

  HID_Task(PAUSE);
  USB_USBTask();
  _delay_ms(10);
  _delay_ms(10);

  RXLED_OFF;
}

在演奏時,通過宏定義RXLED_ON、RXLED_OFF,操作IO口,使用了LED進行指示。演奏“咚”時,使用的是RXLED;相應的,在PLAY_KA_A()函數中,使用的是TXLED。

2.3.4 時間誤差補償

由於選擇的最小時間單位爲僅爲毫秒,誤差會隨着演奏過程不斷積累,導致後半段演奏出現問題。所以,應當統計各段落的誤差,進行補償。
曲目中各音符單獨導致的誤差是已知的,我們將各個片段的音符進行統計,計算總誤差,再擬定各段落中補償修正的毫秒數,將累計殘餘誤差控制在1毫秒以內。如下表所示(單位 毫秒):

例如,前8小節中,經過統計,按照現有程序演奏完後,會比實際時間多出3.19毫秒。爲了儘可能減少誤差,就需要在8小節中平均的分配出用於補償的-3毫秒。補償時儘量選擇時長較長的休止符。完成補償的前八小節編碼如下:
const unsigned short music[] PROGMEM = 
{
    //M1
    D4,     K4,     D4,     K4,         
    //M2
    D4,     K4,     D8, K16,K16,K8, D8,     
    //M3
    D4,     D4,     D8, K8, K8, D8,
    //M4
    R8, D4, D4,  K8,  K16,K16,K16,K16,

    //M5
    D4,    R4-2,         R8, D4, R8,
    //M6
    D4,     R8, D4,    R8,    K4,
    //M7
    D4,   R4-1,      R8, D4,     R8,
    //M8
    D4,   R8, D4,   R8, K4,

以此類推,完成整首曲目的誤差修正。

2.3 實驗驗證

完成開發後進行驗證實驗。先在遊戲的“曲目選擇”中,將光標放到待選曲目上,連接開發板和Switch遊戲機,待USB設備枚舉完成後,按動按鍵模塊上的SW1,代替Switch的A鍵,進行曲目和難度確定。進入曲目後,在合適的時機,手動按動SW4按鍵,啓動輸出流程。經過測試,只要首個音符能夠抓出“良”,那麼整首曲目基本可以保證全良。測試成績如圖所示:

理論上,只要單片機存儲空間足夠,使用該系統可以全良《太鼓達人Switch版》上任意一首曲目。

三、後記

後續可以改進的點(咕咕咕):

  • 描述曲目用的數據結構可以優化。爲減少誤差,可改成使用兩個unsigned short int變量表示一次延時,分別用來表示毫秒級延時和微秒級延時,同時相對應的修改演奏函數。啓用微秒級的延時,可大大減少誤差,減小誤差補償方面的工作量。
  • 理論上可實現爲全自動系統。將Switch的視頻信號通過採集卡採集至上位機,上位機編寫實時圖像處理軟件,用以檢測第一個音符,在合適的時機通過串口向開發板發送開始信息,以代替人手操作。已有大神在《精靈寶可夢》中實現該技術方案。



項目完成後,我頗有些感慨。太鼓達人這款遊戲,給予了我太多,改變了我太多。還記得上大學時,經常在街機廳忘我地練習,一有時間就和同好們愉快地交流、競技。戰勝過自己,也取得過成績。甚至因爲太鼓,開始與音樂結緣。它讓我知道了自己的可能性,也讓我瞭解了自己的天花板。曾幾何時,也曾希望自己能夠像傳說中的大神們那樣,鮮衣怒馬,全良數百首曲目,功德圓滿。怎奈天資不足,又沒有條件保持一定強度的訓練,只好安心當一名娛樂玩家。現在,通過自己完成的單片機系統,代替自己來實現全良的夢想,也算是對自己的一種交代。

歡迎交流


聯繫方式
[email protected]
[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章