Last active
April 5, 2023 14:38
An 8086 assembly program that plays a happy birthday song with MASM under dosbox.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; Happy Birthday in MASM | |
STACK SEGMENT PARA STACK | |
STACK_AREA DW 100h DUP(?) | |
STACK_TOP EQU $-STACK_AREA | |
STACK ENDS | |
DATA SEGMENT PARA | |
; message is a string | |
MESSAGE DB 'Happy birthday, ' | |
DB 'chang' ; change it to your name | |
DB '$' ; '$' is the terminator for dos print | |
; frequency of music notes | |
G1 EQU 3043 | |
A1 EQU 2711 | |
B1 EQU 2415 | |
C2 EQU 2280 | |
D2 EQU 2031 | |
E2 EQU 1809 | |
F2 EQU 1715 | |
G2 EQU 1521 | |
; para 1: sol sol la sol DO si | |
PARA1 DW G1, G1, A1, G1, C2, B1 | |
; para 2: sol sol la sol RE DO | |
PARA2 DW G1, G1, A1, G1, D2, C2 | |
; para 3: sol sol SOL ME DO si la | |
PARA3 DW G1, G1, G2, E2, C2, B1, A1 | |
; para 4: FA FA MI DO RE DO | |
PARA4 DW F2, F2, E2, C2, D2, C2 | |
; total song with 4 paragraphs | |
NUM_PARA DW 4 | |
PARA_HEAD DW PARA1, PARA2, PARA3, PARA4 | |
PARA_LEN DW 6, 6, 7, 6 | |
DURATION DW 10 | |
DATA ENDS | |
CODE SEGMENT | |
ASSUME CS:CODE, DS:DATA, SS:STACK | |
; TICK: wait until the timer ticks | |
TICK PROC | |
PUSH AX | |
PUSH ES | |
MOV AX, 40h | |
MOV ES, AX | |
MOV AX, ES:[6Ch] ; read hardware clock ticks from 0040h:6Ch | |
TICK_WAIT: | |
CMP AX, ES:[6Ch] ; until clock ticks increase | |
JE TICK_WAIT | |
POP ES | |
POP AX | |
RET | |
TICK ENDP | |
; DELAY AX: delay for at least AX ticks | |
DELAY PROC | |
PUSH AX | |
PUSH CX | |
MOV CX, AX | |
DELAY_COUNT: | |
CALL TICK | |
LOOP DELAY_COUNT | |
POP CX | |
POP AX | |
RET | |
DELAY ENDP | |
; PLAY(freq, dur): play note of a frequency and lasts a duration | |
PLAY PROC | |
PUSH BP | |
MOV BP, SP ; use BP to address argument stack | |
ADD BP, 4 ; skip BP and ra | |
PUSH AX | |
; setup speaker: send 0B6h to port 43h | |
MOV AL, 0B6h | |
OUT 43h, AL | |
; setup frequency: send to port 42h | |
MOV AX, [BP+02h] ; load frequency | |
OUT 42h, AL ; send low byte | |
MOV AL, AH | |
OUT 42h, AL ; send high byte | |
; start beep: set bit 1 and 0 to high of port 61h | |
IN AL, 61h | |
OR AL, 03h | |
OUT 61h, AL | |
; delay for duration | |
MOV AX, [BP+00h] ; load duration | |
CALL DELAY | |
; stop beep: set bit 1 and 0 to low of port 61h | |
IN AL, 61h | |
AND AL, 0FCh | |
OUT 61h, AL | |
POP AX | |
POP BP | |
RET 4 | |
PLAY ENDP | |
MAIN PROC | |
; setup stack and data segment register | |
MOV AX, STACK | |
MOV SS, AX | |
MOV SP, STACK_TOP | |
MOV AX, DATA | |
MOV DS, AX | |
; outer loop: para | |
XOR CX, CX ; set CX=0 | |
LP_PARA: | |
MOV BX, CX | |
SHL BX, 1 ; offset for para head | |
; inner loop: note | |
PUSH CX | |
XOR CX, CX | |
LP_NOTE: | |
MOV SI, CX | |
SHL SI, 1 ; offset inside para | |
; load freq and dur of the note | |
PUSH BX | |
MOV BX, PARA_HEAD[BX] | |
MOV AX, [BX+SI] ; AX=para_i[j] | |
POP BX | |
PUSH AX ; freq | |
MOV AX, WORD PTR DURATION | |
PUSH AX ; dur | |
CALL PLAY | |
MOV AX, PARA_LEN[BX] | |
INC CX | |
CMP CX, AX | |
JB LP_NOTE | |
POP CX | |
; break between paras | |
MOV AX, WORD PTR DURATION | |
CALL DELAY | |
; next para | |
INC CX | |
CMP CX, WORD PTR NUM_PARA | |
JB LP_PARA | |
; print message | |
MOV AH, 9 | |
LEA DX, MESSAGE | |
INT 21h | |
; return to dos | |
MOV AX, 4C00h | |
INT 21h | |
MAIN ENDP | |
CODE ENDS | |
END MAIN |
运行环境
播放声音
这部分课程没有讲过,仅作补充。
8086 汇编播放音乐需操作 Speaker 硬件,使用 IN
和 OUT
指令来交互。操作步骤详见 PLAY
子程序,解读如下:
- 向
43h
端口传送0B6h
选择 Speaker ; - 向
42h
端口按先低后高的顺序分两个字节传送音符频率; - 将
61h
端口的值的低两位置高,开启 Speaker 开始播放; - Sleep 一段时间,当前音符保持播放;
- 将
61h
端口的值的低两位置低,停止 Speaker 播放。
其中 Sleep 借助了硬件时钟,这里采用直接读取 0040h:6Ch
地址获取时钟的 tick 次数,循环等待指定的 tick 数。
参考资料:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A first-meeting gift for the X86 Assembly course~
Happy birthday to @lightweigh on 2023-03-24 and wish you a great semester.