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).