Copy Link
Add to Bookmark
Report

Assembly Language for Veggies (And C programmers) Part 5

Welcome to 1992! It is now several months since the last ASMVEG series... In this time I have been learning a bit about Graphics on the PC, and mucking about with Turbo Pascal, however it has now come time to again put words onscreen about dear old Assembler once more...

Last issue, we looked at The TSR, and started mentioning some tricky things that are important to programming TSRs... This issue i'd like to delve into some simpler work by presenting some simple routines for doing "Workhorse" stuff - ya know, stuff you take for granted like screen clearing and file reading/writing etc etc which everyone always seems to assume you know..

Oh, if you have a WordProcessor there with adjustable tabstops, set them to 20 spaces between tabs or the listings will look shitty!

OK, here goes...

Screen Clear

A simple routine for a simple job: Clears the screen in the current screen mode. Homes cursor on text screens, homes logical cursor on graphics screens.

Clear_screen:         push ax 
mov ah,0fh
int 010h
mov ah,0
int 010h
pop ax
ret

Very easy! Does all the usual jazz like resetting the colours to their defaults etc. This is a standard sort of thing that I use in just about every program I can ever remember writing... it works, it's reliable, and let's face it, who wants to clear the screen faster??

Go "Beep"

Yet another ridiculous task. Make a beep! This beep is the standard PC beep, such as you get when you type a text file full of CTRL-G's to the screen or fill uo your keyboard buffer, and is instantly recognisable as the work of the BIOS..

Go_Beep:         push ax 
push bx
mov ah,0eh
mov al,07
mov bh,0
int 010h
pop bx
pop ax
ret

Call this as often as you like, but remember that your whole program is paused for the length of the tone.

What Video?

Ever get sick of asking users their video card type? Why not auto-detect it yourself and TELL them what you detected... simple as pie! These video bios functions were built in to assist the programmer and take the hassle of asking the user what his card is.. buggered if I know why game writers don't just use this stuff themselves.... maybe they're stupid C programmers?

Detect_VGA:         mov ax,01a00h 
int 010h
cmp al,01ah

( if AL=0a1h, then VGA is present )

If you have a MCGA (PS/2) system, this will detect the MCGA. To differentiate between MCGA and true VGA is a difficult thing.. if you specifically want to do something other than 320x200x256 mode then you'll be needing to detect a SVGA anyhow, so it doesn't really matter.

Detect_EGA:         mov ah,012h 
mov bl,010h
int 010h
cmp bl,010h

( if BL=010h then EGA is NOT present )

NB: if BL is not 010h, then it will indicate this:

 BL = 0,  64k EGA RAM 
BL = 1, 128k EGA RAM
BL = 2, 192k EGA RAM
BL = 3, 256k EGA RAM

WARNING: Use this AFTER Detect_VGA because a VGA will also return this information as well (After all, a vGA emulates EGA don't it?!?!)

OK.. if it's not EGA or VGA then it MUST be either CGA or MONO ... if it's a mone, the screen MUST be in mode 7, if it's CGA it will be in another mode..

Detect_CGA_MONO:         mov ah,0fh 
int 010h
cmp al,7

( if AL=7, MONO is present, else must be CGA )

Furthermore: if BOTH a MONO and ANY colour card are present, and you want to make sure, first detect the Default card... if you find a colour card, try calling setvideomode and try setting mode 7. Next, read the mode back. if you're in mode 7, MONO is present. if not, then you only got colour. If you detect MONO as default and you don't find VGA or EGA, try setting mode 3 to test for CGA. If you can set mode 3 then CGA is present, but not default.

KeyPressed?

Moving right along.... the BIOS has some good keypress routines indeed, in fact I have never needed to do anything keyboardish and not been able to use the BIOS for it... oh, and the BIOS is very quick with the keyboard too - definitely suitable for use where high speed is required.

Is_A_Key_Pressed:         mov ah,1 
int 016

If the Z (Zero) flag is set, then NO keys have been pressed. If the Z flag is NOT set a key has been pressed. This does not retrieve a key from the keyboard buffer, it just lets you know there's one there to be read later on.

Next Key in Buffer

Retrieve_keypress:         mov ah,0 
int 016

This gets the next key that has been pressed and puts the "Main byte" in AL, and the "Auxiliary Byte" in AH. normally AL holds the ASCII code for the key pressed, but if the key is not a normal key (like a function or arrow key) then AL=0 and AH holds the so called EXTENDED keycode... tables of extended keycodes should be in any good ASM book - Norton's has it for one. Note, if you call this service BEFORE a key is ready, the routine pauses till one key is hit, which freezes the program in effect... saves a look for key, until pressed, then read key loop though.

Keyboard Lights (AT Computers ONLY!)

Ever wanted to stuff with the keyboard lights? Once again the BIOS will help us there... At absolute memory location 0040:0017 - that's right - the BIOS data table...

Here is the layout of that byte:

  bit#    7      6    5     4      3    2      1      0 
Insert Caps Num Scroll ALT CTRL L.SHI R.SHI

Altering any of these bits will alter the computer's idea of what's what... changing bits 6-4 will toggle the 3 lights on the KB.. all courtesy of the BIOS.. neat and quick... Fiddling with INS changes the INS state, but mucking with alt, ctrl and the shifts is not on, otherwise you REVERSE the action of that key; held down becomes like released etc... if you turned them all opposite to what they normally are, then to type normally one would have to hold down ALT, CTRL and both shift keys!!

Reading a file to memory

One of the hardest routines to master is file access. Here it is simplified.

Filename         db 'filexxxx.!!!',0 
Handle dw 0
memseg dw 07000h

Load_File: mov ah,03dh
mov al,0
mov dx,offset filename
int 021h

This section opens the file for Read/Write access... changing AL changes the access thus:

   AL = 0    Read/Write 
AL = 1 Write Only
AL = 2 Read Only

Obviously the filename is held in an ASCIIZ string at Filename. ASCIIZ stands for ASCII with a 0 on the end basically.. the 0 tells DOS where the end of the string is.

         jc FAIL

If carry is set on return, the open operation FAILED. The file must exist and be able to be opened for this operation to work. An error code will be passed in AX - again see Norton's or other for errorcode listings.

         mov handle,ax

DOS has what is called a handle passed into AX if the open was successful.. each file has a unique handle, and there may be up to the number of files liste din the FILES= statement in Config.sys open at any one time. note that StdIn, StdOut, StdErr and Null are always open by DOS and are used when "piping" program output to and from files. (DIR > files.lst for example) so normally the handle for the fist open file will be 0005. We must store whatever we get back in any case so this is done.

At this stage we have simply requested DOS to get us a file. We haven't read it yet.

read_from_file:         mov dx,word ptr cs:[memseg] 
push dx
pop ds
mov dx,0
mov ah,03fh
mov bx,word ptr cs:[handle]
mov cx,0ffffh
int 021h
jc FAIL
cmp ax,0ffffh
jnz end_read

mov dx,word ptr cs:[memseg]
add dx,01000h
mov word ptr cs:[memseg],dx
jmp read_from_file

end_read: push cs
pop ds

Now this is quite complex and takes a while to understand.... read up on how the registers are being used... basically the file is loaded in 64k chunks (Because that's the biggest DOS can do in one go) to the 64k chunk of memory located at DS:DX. This program happens to hard code the load address at 07000h but normally you'd change that to the place you'd requested from DOS. It also assumes to start at offset 0 in that segment... change DX if you start part way into the segment. The routine attempts to load 64k; and will load up to the limit of a less-than-64k file. Again, C is set if there is an error same as the open routine. Next, AX holds the bytes read. if AX is less than 0ffffh then less than 64k was read and we're at the end of the file. If so, jump out. If not, we increment the memory segment and jump out.

Note that DS has to be changed to suit the routine, thus the use of CS: override... the CS override makes sure the data we're storing does in the right segment (remember that normally DS=CS, but since we're changing DS, we must override the normal use of DS and make it use CS so it looks in the right segment)

At this stage our file is loaded and memseg holds the final segment of the file, whilst ax holds the number of bytes in the last segment. simple subtraction will provide the full file size thus:

Loaded file size (in bytes)   =    ((memseg-inital segment)*64k)+ax

NB: the final number will be a double word - 32 bits long, so don't try and load it into a register, or even work it out this way!!

After we are finished with the file we must tell DOS so. We must call the Close File function to tell dos to return the Handle to the unused pool and also to save any changes we may have written to the file physically to disk. ALWAYS CLOSE THE FILE!!! Even if you get errors, CLOSE THAT FILE!! This is the ONLY way to avoid your program making chop suey of the disk drive's files!!

Close_File:         mov ah,03eh 
mov bx,handle
int 021h

The file is now closed. Again Carry reports an error, but the only possible error is ax=6 which is invalid handle (Caused by feeding it a handle number that is not in use!) otherwise AX = 0 on exit.

Writing Files to disk

Writing a file is identical to reading it... Open the file, write the data, then close it... just use the different functions as listed in your ASM book. Use function 3d to write an existing file. Function 3c is used to write a new, non existant file, or to completely trash the contents of an existing file.

I/O ports present

What Serial and Parallel ports are there? Memory locations 0040:0000 thru 0040:000F hold the addresses of the first four Serial ad parallel ports viz:

0040:0000 | f8 03 | f8 02 | e8 03 | e8 02 | bc 03 | 78 03 | 78 02 | 00 00 | 
| COM1: | COM2: | COM3: | COM4: | LPT1: | LPT2: | LPT3: | LPT4: |

So, for example, to find the address of LPT1:, simply code:

         push ds 
mov ax,040h
push ax
pop ds
mov dx,word ptr[8]
pop ds

DX now holds the address of LPT1:. You should remember that these ports are 16 bit addresses, so use a 16 bit register, and also remember that in PC's the Most significant bit is stored second.

If you find 00 00 stored then there are no more ports to be found - in the example above for example there are but 3 parallel ports. This information is placed by the BIOS so once again it is quite reliable, and also multitasking friendly to read these bytes. The only side effect is that sometimes non-standard serial ports will not show up here (ie serial ports at non standard addresses) - although drivers that usually come with these ports usually update this area if needed. BBS Fossil drivers are one such driver that will, for example.

Reading AT CMOS Settings

Everyone wants to know this!!

CMOS_INFO         db 03fh dup(?) 

Read_CMOS: push ds
pop es
mov cx,03fh
mov bl,00h
mov di,offset CMOS_INFO
cli

read_in: mov dx,0070h
mov al,bl
out dx,al
mov dx,0071h
in al,dx
stosb
inc bl
loop read_in
sti

This routine basically outputs a byte representing the byte in the CMOS we want to read to port 0070h, then reads in the byte from port 0071h. It does this 03fh times to read the entire CMOS to memory.

The Layout of the CMOS is thus:

byte#         Funtion 

Real Time Clock Bytes

0 Current Second in BCD format
1 Second Alarm in BCD
2 Current Minuute in BCD
3 Minute alarm in BCD
4 Current hour in BCD
5 Hour alarm in BCD
6 Current day of week in BCD
7 Current date in BCD
8 Current month in BCD
9 Current year in BCD

Status bytes

a,b,c,d Status bytes A,B,C & D

System configuration

e Diagnostic Status
f ShutDown Reason
10 Diskette drive types where:
bit 7-4 drive 0 (A:) bit 3-0 drive 1 (B:) where
0000b = none
0001b = 360k
0010b = 1.2M
0011b = 720k
0100b = 1.44M

11 fixed disk drive 0 type (0 = none, 1-46 = type)
12 fixed disk drive 1 type (0 = none, 1-46 = type)

13 reserved

14 equipment byte where:
bit 7-6 = number of drives, where 00b = 1, 01b = 2
bit 5-4 = display, where:
00b = reserved
01b = cga 40 cols
10b = cga 80 cols
11b = cga in mono mode
bit 3-2 reserved
bit 1 = math co present
bit 0 = diskette drive present

15 Base memory in k, low byte
16 Base memory in k, high byte
17 Extended memory in k, low byte
18 Extended memory in k, high byte

19 disk type of first fixed disk
1a disk type of second fixed disk

1b-2dh reserved

2e high byte checksum of bytes 10-2dh
2f low byte checksum of bytes 10-2dh

30 Low byte actual Extended memory size
31 high byte actual Extended memory size

32 century in BCD

33 information flag

34-3f reserved

This list is based on an early phoenix BIOS. Newer bios's will have the reserved areas changed, and any bytes after 2fh are fair game.. Use this info at your own peril because there is no guarantee of manufacturer compatibility - Compaq and amstrad being but two known misconformers. All BIOS's with "Type 47" user Hard Drive types will undoubtedly be non-standard too.

BCD Refers to binary coded decimal number format. This format is made to store the numbers 0-9 only. Imagine hex, but just omit the letters.. you got BCD..

Viz:   8d = 00001000 bcd 
11d = 00101000 bcd
99d = 10011001 bcd
47d = 01000111 bcd

Not as efficient bit-wize as binary (the biggest 8 bit number in BCD is 99) but simple to decode - split into high and low nibbles and treat as binary.

FastWrite String to Screen

Sick of DOS int 21h function 09h write string? Try this one - it's a BIOS string writer that's about twice as fast as DOS.. you may find it worth using for although it's longer than dear old dos into 21, it's surely miles the quicker! It is designed as a "drop in" replacement for dos int 21, function 09, and takes the same setup, ie $ terminated string pointed to by DS:DX. All registers are preserved in this routine.

write_bios_string:         push ax 
push bx
push si
mov si,dx

loop_write: lodsb
cmp al,'$'
jz exit_write

mov ah,0eh
mov bx,0
int 010h
jmp loop_write

exit_write: pop si
pop bx
pop ax
ret

Err... well that kinda brings to a close this ASMVEG... I've brought to light a few routines I was meaning to hand out long ago, and added a couple more I just thought of :-)

Next time round i'll probably have some more code in runnable form, and will be looking at a specific program .. i'm not sure exactly what yet :-) but it will be coming... Perhaps we'll look at making musical sounds and perhaps we will look at low level disk structures or something... i'm not exactly sure yet....

Anyhow, until the next VEG, keep hacking!

.\\erlin



← previous
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT