assemblyx86dostasmbeep

PC Speaker program works odd in MS-DOS


I am making a small game in MS-DOS using Assembly language and compiling with Borland Turbo Assembler 5.0, currently I am in phase of testing different parts of the game separately (like text and graphics screens as well as PC Speaker sound). While testing out onboard PC Speaker sound (I'm using VMware Worstation Player for emulation), the program compiles just fine, but first of all, it never ends (gets stuck, I have to reboot the virtual machine to get out of stuck loop), second of all, the sound is still on (gets cleared when rebooted, thankfully), and third of all, the frequency is totally different than I expected.

This is my code:

; Testovací sekvence hry Lodě (LODE.COM)
; Testing sequence of game Lodě (Boats; LODE.COM)

P8086           ; Výběr procesoru  // CPU
MODEL TINY      ; => ZASAH.COM

DATASEG
Beep    DW 0BA7h        ; Frekvenční číslo 2983 = 400 Hz      // Frequency number
Len     DW 0001h        ; HIWORD => Délka 100 ms = 100 000 us // Length of the beep
        DW 86A0h        ; LOWORD

UDATASEG
Stck    LABEL WORD

CODESEG
        STARTUPCODE

        MOV     SP,OFFSET Stck  ; Odložme si stack  // Save our stack

        CALL    Begin

        MOV     AH,4Ch
        INT     21h
        RET

Begin PROC NEAR
        mov     bx,[OFFSET Beep]        ; Nastavme si frekvenci 400 Hz  // Set frequency
        CALL    BeepStrt
        mov     cx,[OFFSET Len]         ; Nastavme si 100 ms prodlevu   // Set delay
        mov     dx,[OFFSET Len+1]
        CALL    Delay
        CALL    BeepEnd
        ret
Begin ENDP

BeepStrt PROC NEAR
        in      al,61h          ; Stáhnout hodnotu portu 61h  // See value of port 61h
        or      al,03h          ; Zapnout bity 0 a 1          // Set bits 0 and 1
        out     61h,al          ; Poslat zpět do portu 61h    // Update port 61h
        ret
BeepStrt ENDP

BeepEnd PROC NEAR
        in      al,61h          ; Stáhnout hodnotu portu 61h  // See value of port 61h
        and     al,0FCh         ; Vypnout bity 0 a 1          // Reset bits 0 and 1
        out     61h,al          ; Poslat zpět do portu 61h    // Update port 61h
        ret
BeepEnd ENDP

Delay PROC NEAR
        mov     ah,86h          ; BIOS funkce prodlevy                // BIOS delay func
                                ; Právě teď máme nastavenou prodlevu  // We now have delay set
        int     15h             ; Provedeme                           // Let's do it
        ret
Delay ENDP
END

No matter if I use the offset Len +1 or +2, it didn't even work when I fed it the values directly. When I was testing it few years ago with standard MS-DOS program DEBUG.EXE, it worked perfectly. It doesn't with TASM. I want to use TASM, because it's really pain in the *** to write it all with DEBUG. Source code file and compiling using TASM (and of course linking with TLINK) is much easier and much more convenient. Allas, the end result should always be the same, right?

This is the screenshot of it get stuck: Compiled PC Speaker test program gets stuck

And I've got my informations from these sources:

One thing actually popped in my head. What will happen if the actual program exceeds 64k size? Will TASM/TLINK prevent it from compiling and/or linking and tells that the resulting program is too big, or it will compile up to 64k and rest will be truncated? I've put myself to a challenge to build entire program to fit in 64k size. Otherwise, I can always rename .EXE to .COM, it will work anyway. However, it makes sense for a program used as a command, rather than a simple game.


Solution

  • First Problem

    This data:

    Len     DW 0001h        ; HIWORD => Délka 100 ms = 100 000 us // Length of the beep
            DW 86A0h        ; LOWORD
    

    ..and this code:

        mov     cx,[OFFSET Len]         ; Nastavme si 100 ms prodlevu   // Set delay
        mov     dx,[OFFSET Len+1]
        CALL    Delay
    

    ..need to be more like:

    Len     DW 0001h        ; LOWORD => Délka 100 ms = 100 000 us // Length of the beep
            DW 86A0h        ; HIWORD
    

    ..and:

        mov     cx,[OFFSET Len]         ; Nastavme si 100 ms prodlevu   // Set delay
        mov     dx,[OFFSET Len+2]
        CALL    Delay
    

    Because of these bugs you get a delay of 0xA0001000 microseconds (about 44 minutes).

    Second Problem

    This code:

    BeepStrt PROC NEAR
        in      al,61h          ; Stáhnout hodnotu portu 61h  // See value of port 61h
        or      al,03h          ; Zapnout bity 0 a 1          // Set bits 0 and 1
        out     61h,al          ; Poslat zpět do portu 61h    // Update port 61h
        ret
    BeepStrt ENDP
    

    ..enables the "timer 2 gate" and "speaker data"; which allows the timer to control the speaker.

    It does not setup the timer. Because nothing sets up the timer you end up with "random whatever who-knows-what" (a different frequency to 400 Hz, or silence).

    If you look at the page you linked to ( http://www.intel-assembler.it/portale/5/make-sound-from-the-speaker-in-assembly/8255-8255-8284-asm-program-example.asp ); you've basically skipped the entire bottom half of it.

    What will happen if the actual program exceeds 64k size?

    That depends on how you compiled/linked it. There's several different memory models (see http://www.c-jump.com/CIS77/ASM/Directives/D77_0030_models.htm ). Depending on which memory model you use; either it'll crash when you exceed 64 KiB, or it'll crash because of "segmentation bugs" (e.g. forgetting to use the right segment override prefix somewhere) before you ever get close to exceeding 64 KiB. There are many extremely good reasons why everyone stopped using DOS (and real mode) 25 years ago; and this is one of them. ;-)