Beep/Bell en ensamblador

He tenido algo de tiempo para entretenerme programando, una de esas cosas, que resultan prácticamente inútiles, pero con las que puedes llegar a disfrutar. Estaba revisando nuevamente JWASM como hice hace algunos años en FPS en ensamblador, y se me ocurrió rememorar viejos conocimientos con este programilla.

Se trata de BEEP, un programa, en realidad dos, que hace sonar la «campana» o bell, del altavoz interno del PC.

La versión fácil, es usar los servicios de DOS. Resulta que el juego de caracteres ASCII, incluye el carácter 7 (BEL), que es un carácter de control no imprimible, y que según acabo de descubrir, se viene usando desde nada menos que 1870. Así que ese carácter, se incorporó a los terminales de consola (TTY), y por herencia, llego a DOS, Windows, y OS/X.

.model tiny
.8086
 
.data
 
.code
org 100h
.startup
 
mov ah, 02h		; Character output
mov dl, 07h		; Bell code
int 21h
 
.exit
end

Todo muy sencillo. Llamamos al servicio 02h de la interrupción 21h (DOS), y le hacemos «imprimir» el carácter 7, para que suene la campanita. El resultado, es un increíblemente diminuto archivo .COM de 10 bytes. No en vano, apenas tiene 4 instrucciones, 2 mov, y 2 int (uno de ellos camuflado en la macro .exit).

La segunda versión, es la que me gusta más, pues prescinde en absoluto de los servicios de DOS, y la BIOS, para acceder directamente al hardware. Esto lo hace de dos formas, en la primera, accede al altavoz interno (Internal PC Speaker), cosa que se hace desde los puertos de entrada/salida 43h y 42h, y que desde los tiempos de Outlaw, tenía olvidado. Es extraño, porque el altavoz interno, va conectado al chip 8253, que es el que actúa como reloj en tiempo real, y que genera pulsos a una frecuencia de 1.193.180 Hz. Lo que espera que le enviemos, es una frecuencia sobre cuantos pulsos esperar, antes de mandar otra nota, es decir, es una frecuencia inversa al tono a enviar. Como el beep o bell estándar, está definido a una frecuencia de 440 Hz, debemos dividir 1193180 entre 440, o sea 2711.

Después, lo que debemos hacer, es esperar durante un segundo. Para ello, hay soluciones cutres, como hacer un bucle ejecutando NOP, u otras instrucciones, que además de consumir CPU inútilmente, no es de fiar, porque dependiendo de la velocidad del procesador, el bucle tardará más o menos tiempo en ejecutarse. Podríamos haber optado por usar el servicio de la BIOS de la interrupción 1Ah para obtener el número de ticks que han pasado desde la medianoche (Get tick count), pero es mucho más bonito, hacerlo directamente leyendo del area de memoria reservada para la BIOS, porque en 0040h:006Ch (timer counter), tenemos ese mismo valor. 4 bytes que nos indican cuantos ticks han pasado desde la medianoche, recordando que 18,2 ticks, son 1 segundo.

Finalmente, apagamos el altavoz interno para que cese el sonido, y salimos a DOS.

.model tiny
.8086
 
.data
 
.code
org 100h
.startup
 
; 440Hz sound
mov al, 86h
out 43h, al
mov ax, (1193180 / 440)
out 42h, al
mov al, ah
out 42h, al 
in al, 61h
or al, 00000011b
out 61h, al
 
; Wait 1 second
xor ax, ax
mov es, ax
mov di, 046Ch
mov ax, es:[di]
add ax, 18
.repeat
.until ax == es:[di]
 
; Turn off speaker
in al, 61h
and al, 11111100b
out 61h, al
 
.exit
end

Resultado, un ejecutable de tipo COM, de 47 bytes, y si las he contado bien, 20 instrucciones. Es decir, 5 veces más grande que usando DOS, pero en contra de lo que pueda parecer, mucho más eficiente, y mucho más portable, pues no requeriría DOS, absolutamente para nada.

Puedes descargar el código fuente, y los ejecutables de ambas versiones aquí (1 Kb. en formato ZIP).

6 comentarios en “Beep/Bell en ensamblador”

  1. Fernando, con el 6502 hice mis pinitos, y bueno también con el 68000, pero no llegué a dominarlos como el Z-80 o el 8086. Coincido en lo de las librerías, ya fuera QBasic, Pascal, o C. Eso de mejorar las partes críticas, y reescribirlas para dar un rendimiento casi de código máquina, pese a estar programado la mayoría en un lenguaje de alto nivel, era genial.

    Busca esos programitas, que te hará feliz encontrarlos! Además, yo también querré verlos.

  2. A mi también me encantaba el ensamblador, de hecho llegué a programar en la misma época para el 8086, 8051 y 6502… pero lo que más me entretuvo fue hacer librerías para QBasic y para Pascal.

    Tengo que recuperar esos códigos y publicarlos algún día…

  3. Como Win64 ya no tiene máquina virtual DOS, no lo he podido probar sobre ese entorno. Sí que lo probé en DOSBox, y con un PC ejecutando MS-DOS 7.1, y todo correcto. Creo que lo que cuentas, es más bien un problema de emulación Windows. Me sorprende, porque la primera versión, son puras llamadas DOS, fácilmente emulables, y que debería funcionar sin problemas.

    La segunda versión en cambio, tiene muchos accesos a nivel de sistema, tanto hardware, como de bajo nivel, y ahí si que la emulación debería estar más lograda para funcionar. En cambio ya te digo que con DOSBox, y con VirtualBox que acabo de probarlo, ningún problema.

    Sobre el ensamblador, a la mayoría de gente le ocurre como a ti. Debo ser la excepción, porque desde que comencé a trastear con él en tiempos del Spectrum, aunque hayan pasado años, o casi décadas sin tocarlo, cuando vuelvo, en seguida vuelvo a estar familiarizado con él. Imagino que siempre me ha gustado esto de relacionarme con máquinas usando su propio idioma. Me encanta esa sensación.

  4. Buff Guti, ahora entiendo por qué ensamblador me superaba. En el segundo párrafo ya me he perdido 😀 Por cierto, las he ejecutado y ninguna de las dos funciona, ni siquiera llamándolas directamente desde el DOS. Desde Windows sale un mensaje de instrucción no permitida. Quiero creer que están hechos para 16bits, no?

Deja un comentario