本文將介紹如何在 CHIP-8 模擬器中實作延遲計時器(delay timer)與聲音計時器(sound timer)。兩者皆以 60 Hz 的頻率遞減,其中 delay timer 主要用於控制程式中的時間延遲與事件節奏,我們將透過 main loop 的睡眠機制模擬其遞減行為,並利用暫存器賦值進行編譯測試;Sound Timer 則採用相同的遞減機制,並在數值大於 0 時產生蜂鳴聲(beep)。
延遲計時器的規格與運作原理
根據規格書 2.5 – Timers & Sound(或也可參考 CHIP-8 規格介紹):
The delay timer is active whenever the delay timer register (DT) is non-zero. This timer does nothing more than subtract 1 from the value of DT at a rate of 60Hz. When DT reaches 0, it deactivates.
也就是說延遲計時器在暫存器數值不為零時處於啟用狀態。啟用後,該計時器會以 60 赫茲的頻率對其數值進行遞減(每次減 1),直到數值歸零後便會自動關閉。
實作
在正式的 CHIP-8 模擬器中,delay timer 應以 60 Hz 的頻率遞減(約每 16.67 毫秒減 1?)。不過在目前的初步實作階段,我們先使用 Sleep() 建立簡單的時間延遲,以確認計時器的遞減流程是否正常。此處暫時設定為 100 毫秒,主要用於方便觀察測試結果,之後再進一步調整為符合 60 Hz 的計時機制。
為了在程式中使用睡眠與後續的蜂鳴器功能,需要先在 main.c 中引入 Windows 標頭檔 <Windows.h>。
在主迴圈的末端,我們需要撰寫條件判斷式來檢查暫存器的延遲計時器數值:
- 檢查數值是否大於零:若暫存器中的
delay_timer數值大於 0,程式便會執行睡眠,我們在此設定睡眠 100 毫秒(Milliseconds)。 - 計時器數值減一:睡眠結束後,我們將暫存器中的
delay_timer數值減 1(使用-=運算子)。
if (chip8.registers.delay_timer > 0)
{
Sleep(100);
chip8.registers.delay_timer--;
printf("Delay timer: %d\n", chip8.registers.delay_timer);
}
整合測試
為了驗證延遲功能是否正常,我們可以在程式的起點手動將延遲計時器暫存器的數值設定為 255。
... struct chip8 chip8; chip8_init(&chip8); chip8.registers.delay_timer = 255; // 手動設定 ...
接著,我們在延遲邏輯處加上文字輸出來列印目前的狀態。
Delay timer: 254 Delay timer: 253 Delay timer: 252 Delay timer: 251 Delay timer: 250 Delay timer: 249 Delay timer: 248 Delay timer: 247 ... ...
完成程式碼修改後,進行專案編譯並執行。此時,您將會在終端機上觀察到延遲效果持續發生,直到暫存器的數值降至 0 為止,這代表我們的延遲計時器已經成功整合並能正常運作了。
聲音計時器的規格與運作原理
運作條件與遞減頻率(ST 暫存器)
根據 CHIP-8 規格 2.5 – Timers & Sound(或也可參考 CHIP-8 規格介紹):
The sound timer is active whenever the sound timer register (ST) is non-zero. This timer also decrements at a rate of 60Hz, however, as long as ST’s value is greater than zero, the Chip-8 buzzer will sound. When ST reaches zero, the sound timer deactivates.
The sound produced by the Chip-8 interpreter has only one tone. The frequency of this tone is decided by the author of the interpreter.
只要聲音計時器暫存器(Sound Timer Register,簡稱 ST)的數值不為零,聲音計時器就會處於啟用狀態。與延遲計時器相同,它也會以 60 Hz 的頻率持續對暫存器數值進行遞減。
蜂鳴器發聲機制
在遞減的過程中,只要聲音計時器暫存器的數值大於零,CHIP-8 的蜂鳴器(buzzer)就必須持續發出聲音。一旦暫存器數值遞減至零,聲音計時器便會自動關閉。至於發出的聲音音調,則完全由模擬器直譯器的開發者自行決定。
核心發聲功能與音訊頻率調校
基礎遞減發聲實作
撰寫一個簡單的判斷式:如果聲音計時器暫存器的數值大於零,就觸發嗶嗶聲(Beep)。
if (chip8.registers.sound_timer > 0)
{
Beep(8000, 100);
chip8.registers.sound_timer--;
}
將發音時間(duration)設定為 100 毫秒,並在每次發音後,將暫存器的數值減 1。這種設計會讓發聲動作伴隨著短暫延遲,隨後程式繼續執行。
頻率調整
在初次測試時,如果我們將嗶聲頻率設定為極高的 8000 Hz,發出的高頻噪音會非常刺耳。為了解決這個聽覺不適的問題,開發者可以自由調整頻率,例如將其降低至較為溫和的 1500 Hz、2500 Hz 或任何數值,以達到更舒適的音效體驗。
持續性聲音的改善
在基礎的實作方式中,會注意到嗶嗶聲在運行時可能是不連貫的。由於每次減少 1 便發聲一次,聲音聽起來會斷斷續續。
為了讓聲音輸出更加流暢且一致,我們可以對演算法進行微調:
- 計算總播放時間:不採用每次固定播放 100 毫秒的做法,而是將 100 毫秒乘以當前聲音計時器暫存器的數值。
- 直接歸零暫存器:在以該長度播放完畢後,直接將聲音計時器的數值設定為 0,而不再是一次一次地遞減。
if (chip8.registers.sound_timer > 0)
{
Beep(1500, 100 * chip8.registers.sound_timer); // 調整
chip8.registers.sound_timer = 0; // 調整
}
如果我們將暫存器預設為 30,程式將會持續發出約 3 秒鐘不中斷的連貫聲音。
... struct chip8 chip8; chip8_init(&chip8); chip8.registers.sound_timer = 30; // 將暫存器預設為 30 ...
例如將 sound_timer 設為 30,在目前每單位以 100 毫秒計算的測試方式下,便會得到約 3 秒的連續蜂鳴聲。不過這只是目前用於改善聲音連續性的簡化實作。
硬體相關建置終於完成啦!!!!
完成了所有底層硬體(包含記憶體、暫存器、堆疊、鍵盤、螢幕、字元集與雙計時器)的建置後,我們接下來的目標是建立模擬器的核心處理單元,負責從記憶體中提取並讀取指令集(instruction set),開始真正執行 CHIP-8 的遊戲程式。




