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 ;; see http://www.ctyme.com/intr/int-10.htm for interrupts start: mov sp, 0x2000 ; initialise stack pointer ;; 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 10h ;; 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 mov ah, 0x02 ; set cursor position mov bh, 0x00 ; page number mov dh, HEIGHT ; row (bottommost row) mov dl, 0x00 ; col (leftmost col) int 0x10 .loop: ;; read a character, ASCII in al mov ah, 0x00 ; Read character int 0x16 ; call UART_send_char mov ah, 0x1 mov dx, 0x0 int 0x14 cmp al, 0x08 ; backspace je change_color_mode cmp al, 0x1b ; esc je beep cmp al, 0x0d ; enter je .nl jmp nonewline ; all other characters .nl: call newline jmp .loop ;; Prints a character in al to the serial port ;; ;; CLOBBERS ;; - ax ;; - dx ;UART_send_char: ; mov ah, 0x01 ; transmit character ; mov dx, 0x0 ; COM1 ; int 0x14 ; ; ret ;; 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 start.loop ;; 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 mov ah, 0x02 ; set cursors position mov bh, 0x0 ; page number mov dh, HEIGHT ; row mov dl, 0x00 ; col int 0x10 ret ;; Plays a note on the speaker for a given duration ;; ;; CLOBBERS ;; - ax ;; - bx ;; - cx beep: call read_duration ; get duration from keyboard (in al) mov ah, 0 push ax ; store on stack ;; 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, 0b10110110 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, 61h ; Preserve previous value or al, 00000011b ; Set bits 1 and 0 (enable speaker, input = channel 2) out 61h, al ; Send new value ;; mov bx, 50000 ; Pause for duration of note. ;; multiply duration by 3125 pop bx ; pull duration value from stack into bx push ax ; save ax (contains garbage?) mov ax, 3125 mul bx mov bx, ax ; move dur * 3125 into bx pop ax ; restore ax ;; play note for a certain duration .pause1: mov ecx, 65535 .pause2: dec cx ; count down jne .pause2 ; if not zero keep counting down dec bx ; decrement duration counter jne .pause1 ; keep doing that until it is 0 in al, 61h ; Preserve previous value and al, 11111100b ; Clear bits 1 and 0 (disable speaker, clear channel select) out 61h, al ; Send new value jmp start.loop ;; 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 ;; 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 start.loop .noeol: ;; Move cursor forward inc dl mov ah, 0x02 ; set cursor position, dh (row) already set, dl (col) updated on prev line int 0x10 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, 0x4 ; 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 ;; 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