ass_messages/messages.asm

350 lines
8.4 KiB
NASM

org 0x7C00 ; The boot code on a drive always gets loaded at address 0x7C00
bits 16 ; .bin defaults to 16 bits
WIDTH equ 0x4F ; 79
HEIGHT equ 0x18 ; 24
VIDEO_MODE equ 0x3 ; 80x25 screen, text mode
PIT_MODECOMMAND_PORT equ 0x43 ; programmable interval timer mode/command port (used for speaker)
PIT_CH2_DATA_PORT equ 0x42 ; PIT channel 2 data port
VIRTUAL_KBD_CHAR equ 0x09 ; character to detect if a command is comming from the virtual keyboard (htab)
;; see http://www.ctyme.com/intr/int-10.htm for interrupts
start:
mov sp, 0x2000 ; initialise stack pointer
;; Interrupt 0x14, ah=0
;; bit 7 - 5 baud rate
;; 0 0 0 110
;; 0 0 1 150
;; 0 1 0 300
;; 0 1 1 600
;; 1 0 0 1200
;; 1 0 1 2400
;; 1 1 0 4800
;; 1 1 1 9600 (used)
;; bit 4 - 3 parity
;; 0 0 none (used)
;; 0 1 odd
;; 1 1 even
;; bit 2 stop bits
;; 0 1
;; 1 2
;; bit 1 - 0 data bits
;; 1 0 7
;; 1 1 8
;; initialise serial port
mov ah, 0x0 ; initialise UART
mov al, 0b111_00_0_11 ; baud 9600, no parity, 1 stop bit, 8 data bits
mov dx, 0x0 ; COM1
int 0x14
;; Use color mode
mov ax, VIDEO_MODE
int 0x10
;; Clear screen
mov ah, 0x06 ; scroll screen up
mov al, 0x00 ; lines to scroll (0 = clear)
mov bh, 0x0F ; bg/fg colour
mov cx, 0x0000 ; upper row number, left col number (both 0)
mov dh, HEIGHT ; lower row number (24)
mov dl, WIDTH ; right col number (80)
int 0x10
;; Set cursor to bottom of screen
;; dh already contains HEIGHT
mov ah, 0x02 ; set cursor position
mov bh, 0x00 ; page number
mov dl, 0x00 ; col (leftmost col)
int 0x10
.loop:
;; read a character, ASCII in al
mov ah, 0x00 ; Read character
int 0x16
;; a special character will be sent before every command from the virtual keyboard
;; so if that character is detected go to the virtual_kbd_loop instead
cmp al, VIRTUAL_KBD_CHAR
je .virtual_kbd_loop_init
cmp al, 0x08 ; backspace
je change_color_mode
cmp al, 0x1B ; esc
je beep
;; print character to serial port
mov ah, 0x01 ; transmit char
mov dx, 0x00 ; COM1
int 0x14
cmp al, 0x0D ; enter (carriage return)
je .nl_lf
jmp nonewline ; all other characters
.nl_reset_state:
mov byte[is_from_virtual_kbd], 0x0 ; reset state variable
jmp .nl
;; BIOS keyboard routines read <enter> as CR which is usually not recognized
;; as actually beginning a new line, so anytime a CR is sent a LF is sent with
;; it
.nl_lf:
mov al, 0x0A ; line feed
mov ah, 0x01 ; transmit char
int 0x14
.nl:
call newline
jmp .loop
.virtual_kbd_loop_init:
mov byte[is_from_virtual_kbd], 0x1 ; update state variable
;; same as regular .loop except nothing is sent to serial
.virtual_kbd_loop:
;; read a character, ASCII in al
mov ah, 0x00
int 0x16
cmp al, 0x08 ; backspace
je change_color_mode
cmp al, 0x1B ; esc
je beep
;; an enter character signifies the end of a command
;; so a newline should get printed and then the program should resume
;; from the main .loop again
cmp al, 0x0D ; enter
je .nl_reset_state
jmp nonewline ; all other characters
;; Update the color_mode variable with a byte from the keyboard
;;
;; CLOBBERS
;; - ax
;; - bl
change_color_mode:
call readbyte ; get byte from keyboard (in al)
mov [color_mode], al ; store byte in color_mode variable
jmp compare_virtual_and_jump
;; Simulates going to a new line
;;
;; CLOBBERS
;; - ax
;; - bh
;; - cx
;; - dx
newline:
;; Scroll up window
mov ah, 0x06 ; scroll screen up
mov al, 0x01 ; lines to scroll (1)
mov bh, 0x0F ; bg/fg colour
mov cx, 0x0000 ; upper row num/left col num (both 0)
mov dh, HEIGHT ; lower row num (24)
mov dl, WIDTH ; right col num (80)
int 0x10
;; Move cursor to beginning of screen
;; dh already contains HEIGHT
mov ah, 0x02 ; set cursors position
mov bh, 0x00 ; page number
mov dl, 0x00 ; col
int 0x10
ret
;; Plays a note on the speaker for a given duration
;;
;; CLOBBERS
;; - ax
;; - bx
;; - cx
beep:
;; Read duration (0-16 supported)
mov ah, 0x00 ; read keypress
int 0x16
;; Read ASCII character is in al
;; the character must be normalised to a=1, b=2, ...
;; and not a=0, b=1, ... because during the waiting loop
;; see #NOTE_LOOP
;; the duration counter gets decremented BEFORE
;; it gets compared to zero
;; ('a' is 0x61)
sub al, 0x60 ; normalise (a=1, b=2, c=...)
;; interrupt 0x16, ah=0 returns the read scancode in ah
;; so ah must be reset
mov ah, 0
mov cx, ax ; save ax into cx temporarily
;; move 182 (0b10 11 011 0) into al
;; bit 7-6 = channel select (2, PC speaker)
;; bit 5-4 = access mode (lobyte/hibyte)
;; bit 3-1 = operating mode (square wave generator)
;; bit 0 = BCD/binary mode (16-bit binary)
mov al, 0b10_11_011_0
out PIT_MODECOMMAND_PORT, al ; write to PIT control port
;; Read byte and convert to a usable frequency
call readbyte ; read byte from keyboard (in al)
mov ah, 0 ; readbyte clobbers ax
sal ax, 5 ; multiply by 32
add ax, 1140
out PIT_CH2_DATA_PORT, al ; Output low byte.
mov al, ah
out PIT_CH2_DATA_PORT, al ; Output high byte
;; Port 0x61 does a whole lot but only the bottom 2 bits are relevant here
;; (speaker enable and speaker input select)
in al, 0x61 ; Preserve previous value
or al, 00000011b ; Set bits 1 and 0 (enable speaker, input = channel 2)
out 0x61, al ; Send new value
;; mov bx, 50000 ; Pause for duration of note.
;; multiply duration by 3125
;; (duration is in cx)
mov ax, 3125
mul cx ; multiply ax <- ax * cx
;; play note for a certain duration
.pause1:
mov ecx, 65535
.pause2:
dec cx ; count down
jne .pause2 ; if not zero keep counting down
;; #NOTE_LOOP
;; the duration gets decremented before being checked
;; hence why it was normalised to '`' and not 'a'
dec ax ; decrement duration counter
jne .pause1 ; keep doing that until it is 0
in al, 0x61 ; Preserve previous value
and al, 11111100b ; Clear bits 1 and 0 (disable speaker, clear channel select)
out 0x61, al ; Send new value
jmp compare_virtual_and_jump
;; Handles all other characters by writing them to the screen
;;
;; CLOBBERS
;; - ah
;; - bx
;; - cx
;; - dx
nonewline:
;; Write character
mov ah, 0x09 ; write character with attribute
mov bh, 0x00 ; page number
mov bl, [color_mode] ; BIOS colour code
mov cx, 0x01 ; amount of times to print character
int 0x10
;; Get current cursor position
;; bh already contains page number
;; returns dh = row, dl = col
mov ah, 0x03 ; get cursor position and shape
int 0x10
;; if the cursor is at the edge of the screen go to the next line
;; else move it forward once
cmp dl, WIDTH
jge .eol
jmp .noeol
.eol:
call newline
jmp compare_virtual_and_jump
.noeol:
;; Move cursor forward
;; bh already contains page number
;; dh already contains row
inc dl ; go to next col
mov ah, 0x02 ; set cursor position, dh (row) already set, dl (col) updated on prev line
int 0x10
jmp compare_virtual_and_jump
;; Compares the is_from_virtual_kbd state variable to 1 to check if the command
;; came from the virtual keyboard
;; if it did, jump to the virtual keyboard loop
;; if it didn't, jump to the main loop
compare_virtual_and_jump:
cmp byte[is_from_virtual_kbd], 0x01
je start.virtual_kbd_loop
jmp start.loop
;; Reads two characters from the keyboard buffer and converts them into a single byte
;;
;; The conversion happens as follows:
;; ASCII 'a' is subtracted from each character (so 'a' = 0, 'b' = 1, ...)
;; the first character is shifted left by 4
;; the shifted and non-shifted values are then or'ed together to form a byte
;;
;; RETURNS
;; A byte in al
;;
;; CLOBBERS
;; - ax
;; - bl
readbyte:
mov ah, 0x00 ; Read character
int 0x16
;; Read character is in al
sub al, 'a' ; Normalise
mov bl, al
shl bl, 0x04 ; First character is upper 4 bits
mov ah, 0x00 ; Read character
int 0x16
;; Read character is in al
sub al, 'a' ; Normalise
or al, bl ; Combine both characters
ret
;; Reads a duration from the keyboard
;;
;; RETURNS
;; A duration in al
;;
;; CLOBBERS
;; - ax
;read_duration:
; ;; Read duration (0-16 supported)
; mov ah, 0x00 ; read keypress
; int 0x16
;
; ;; Read character is in al
; sub al, 'a' ; normalise
;
; ret
;; Variable to store color mode information for writing to the screen
color_mode: db 0x02
;; State variable for detecting whether keystrokes are virtual or real
is_from_virtual_kbd: db 0x00
;; Magic number must be in the last 2 bytes of the sector so
;; fill the binary with zeroes until it is 510 bytes in size
;; (magic number will use up the remaining 2)
times 510 - ($ - $$) db 0
dw 0xAA55 ;; Magic number to mark sector as bootable