

                  |====================================|
                  |                                    |
                  |   TELEMACHOS proudly presents :    |
                  |                                    |
                  |    Part 6 of the PXD trainers  -   |
                  |                                    |
                  |        Interrupts, timer and       |
                  |          keyboard handler          |
                  |                                    |
                  |====================================|

         ___---__-->   The Peroxide Programming Tips   <--__---___

<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>


Intoduction
-----------

Hi folks... I guess some of you have given up on me but NO WAY! It have been
about 3 month since my last tutorial - thats one quarter of a year, 91 days
and one phone-bill :(

The reason why it have been so long is pretty simple. I now study music at a
school where I live currently - away from my computer and my Inet :(
I only get a chance to get near my computer once every one or two weeks to
check my mail and chat with a few friends. Thats also why my reply time on your
mail have been so long - sorry. But now its christmas and im home on vacation
so why not waste some time in front of my monitor :)

I would like to thank all of you who has sent me mail with suggestions for
future tutorials, questions or simply to tell me you liked my work. Its things
like that that really makes me feel like writing more tutorials.

This one will be on a lot of different things - lots of theory and not so much
graphics im afraid. But I promise you that the stuff in this tutorial will be
well worth the time nessesary to read the text / understand it.
Well - to split the tutorial up :

   -  Interrupts : what they are, how to use them and how to make them.
   -  Keyboard handler : how to write one and how to link keys to procedures.
   -  The PIT clock timer : introduction to the PC clock and how to use it.

Of cause - when trying to cover such a big area in one tutorial some of the
stuff will only be mentioned briefly so if you want to know more about some
of the stuff go find other more specific texts.

The next tutorial will probably be on sound programming - using the Sound
Blaster card.


If you want to get in contact with me, there are several ways of doing it :

1) E-mail me               :      tm@image.dk

2) Snail mail me           :      Kasper Fauerby
                                  Saloparken 226
                                  8300 Odder
                                  Denmark

3) Call me  (Voice ! )     :    +45 86 54 07 60


Get this serie from the major demo-related FTP-sites - currently :

  GARBO ARCHIVES  (forgot the address)  :  /pc/programming/

  ftp.teeri.oulu.fi  :   /msdos/programming/docs/

  ftp.cdrom.com      :  something with demos/incomming/code.....

Or grap it from my homepage :

   Telemachos' Codin' Corner   http://www.image.dk/~tm



WHAT IS INTERRUPTS AND HOW TO THEY WORK
----------------------------------------

An interrupt is, as they name slightly indicates, a piece of code that
interrupts the CPU from what it currently was doing. After the interrupt has
been executed it returns control to the CPU which continues doing its stuff
from the point where it was interrupted.

There is a total of 256 interrupts available numbered from 0 to 255. To each
of these interrupt numbers a piece of code is attached that are executed when
the interrupt is called. These routines can be located in RAM, the DOS-kernel
or in ROM-BIOS.
Each of these 256 interrupt numbers can have subfunctions that usually is
selected by loading the AH register with the function number. As an example
we can take a look at int 10h which we frequently use for all sorts of
screen manipulation / output.
The following code sets the graphic mode :

    Procedure SetMCGA;
    Assembler;
    asm
     mov ax,13h
     int 10h
    end;

Now - the statement 'mov ax,13h' is actually the same as :

     mov ah,0
     mov al,13h

So, when we wants to set a screen mode we use int 10h - subfunction 0.

The memory location of these routines are stored in something called the
interrupt vector table. This table is 1024 bytes long and is located in the
very beginning of the PCs addressable memory - ie the vector table occupies
the memory cells from $0000 to $3FF. For each of the 256 entries in this table
is a FAR pointer to the location of the interrupt routine matching the
interrupt number. The FAR-pointers are stored as dwords and are 32bit addresses
with the least significant 16 bits being the segment address and the most
significant 16 bits being the offset in that segment. The processor switches
dwords so that the word containing the offset (bits 0 to 15 in the dword)
is stored before the word containing the segment (bits 16 to 31 in the dword)
in memory.
The same goes for ordinary words - here the processor switches the to bytes
in the word so the word is stored with the least significant byte before the
most significant byte.
This sounds pretty confusing - I know... but its important to remember if
you feel like tampering with the vector table yourself. To visualize :

              a dword with 32 bits = 2 words = 4 bytes

        bit  |31----------------|--------------------0|
             | offset  (word)   | segment (word)      |
             |                  |                     |
        bit  |15-------|-------0|31--------|--------16|
             |  byte   |  byte  |  byte    |  byte    |
             |         |        |          |          |
        bit  |7-------0|15-----8|7--------0|15-------8|
memory
location     |    0    |   1    |    2     |    3     |


So interrupt number 0 will have its procedure located at the point indicated
in entry number 0 in the vector table - ie. at the address specified at memory
location $00 to $03. The address for interrupt 1 will be at memory location
$04 to $07 and so on up to addresses $3FC to $3FF which completes the table and
is the address of of interrupt 255.


THE TWO TYPES OF INTERRUPTS
----------------------------

The interrupts can be split into two main groups of interrupts - the software
interrupts and the hardware interrupts.

The software interrupts :

A software interrupt is activated by the assembly instruction INT followed by
an interrupt number or from the pascal procedure INTR (that just calles the
assembly instruction). In other words software interrupts are called by the
programmer just like an ordinary procedure. It interrupts the main program,
executes its routine and returns to the main program - just like any other
procedure. The difference between procedures and software interrupts is that
with interrupts you can call procedures without knowing the exact memory
location of the code. We use this when calling the BIOS-routines fx. for screen
output, screen-mode changes, mouse programming and so on. This way our program
will execute on most machines even though their system components are different.
Software interrupts are like a nice toolbox stuffed with pre-made routines
available to you.

The hardware interrupts :

The harware interrupts cannot be called by the programmer with the INT
instruction. They execute when started by a piece of hardware.

The hardware interrupts are splitted into two main groups : the external
hardware interrupts and the internal hardware interrupts.
The external hardware interrupts are generated by external hardware components
like the keyboard, the disk drive and other such devices. The internal hardware
interrupts comes from components on the motherboard itself like the PIT clock
timer.
The hardware interrupts are MOST interresting for us programmers as they allow
us to define code for the hardware connected to the computer. I'll just list
a few uses of the hardware interrupts - some of which we will take a closer
look at later in this tutorial.

  - The keyboard : We could program the keyboard to execute certain procedures
    when a certain key is pressed. This way these routines will work as
    interrupts and therefor run TOTALLY independent of the main program.
    Fx. if we were writing an arcade game with baddies and our hero. Then we
    could link fx. the arrow keys to our procedures that moves our hero and
    consentrate about the baddies and their movement in the main program.

  - The keyboard : If we were writing a TSR program we could use the keyboard
    interrupt to activate the TSR when a certain key is pressed.

  - The timer : We could turn the situation with the arcade game around and use
    the PCs timer interrupt to move the baddies around at a certain speed and
    then consentrate on moving our hero in the main program.

  - The timer : When playing a sound through a soundcard it is important to
    play the sample at a constant frequency. We could use the timer interrupt
    to "feed" the soundcard with the correct information at the right time.
    The timer is almost ALWAYS used in the various module players avaiable
    around.

As you see there are plenty of uses for the hardware interrupts. In the next
section I'll show you how to code an interrupt for the keyboard.


THE KEYBOARD HANDLER
---------------------

When a key is pressed on the keyboard numerous things happen :

1) The keyboard operates through port nr. 60h.
When a key is pressed the keys scancode is sent to this port where we can read
it to find out which key has been pressed.

2) An interrupt 9 is generated.

3) The interrupt activates a routine called the keyboard handler. Basicly the
keyboard handler reads the value from port 60h, acknowledge the interrupt and
reacts upon the keypress.

Each key has - as mentioned - a scancode that is placed in the least
significant 7 bits of the byte read from port 60h. The 8th bit holds
information on what just have happend. 0 = key was just pressed, 1 = key was
just released. If you fx press a key - say 'A', scancode 30 - and keep it down
interrupt 9 are generated all the time and the value 30 can be read from the
port 60h. When you then release the key another int 9 is generated and the
value 30+128 = 158 can be read from port 60h. Now the 8th bit is on.

Some keys have *EXTENDED* scancodes which means that the port will return
224 and then during the NEXT int 9 the EXTENDED scancode. Ie. if you read
224 from port 60h in your keyboard handler you know the key pressed was an
EXTENDED key and you will have to read the extended scancode during next
interrupt.

I have grapped this listing of the scancodes from PCGPE - thanx to Mark Feldman

Scan                                   Scan
Code Key                               Code Key
             
 1   ESC                               44   Z
 2   1                                 45   X
 3   2                                 46   C
 4   3                                 47   V
 5   4                                 48   B
 6   5                                 49   N
 7   6                                 50   M
 8   7                                 51   , <
 9   8                                 52   . >
10   9                                 53   / ?
11   0                                 54   RIGHT SHIFT
12   - _                               55   *            (KEYPAD)
13   = +                               56   LEFT ALT
14   BACKSPACE                         57   SPACEBAR
15   TAB                               58   CAPSLOCK
16   Q                                 59   F1
17   W                                 60   F2
18   E                                 61   F3
19   R                                 62   F4
20   T                                 63   F5
21   Y                                 64   F6
22   U                                 65   F7
23   I                                 66   F8
24   O                                 67   F9
25   P                                 68   F10
26   [ {                               69   NUMLOCK      (KEYPAD)
27   ] }                               70   SCROLL LOCK
28   ENTER (RETURN)                    71   7 HOME       (KEYPAD)
29   LEFT CONTROL                      72   8 UP         (KEYPAD)
30   A                                 73   9 PGUP       (KEYPAD)
31   S                                 74   -            (KEYPAD)
32   D                                 75   4 LEFT       (KEYPAD)
33   F                                 76   5            (KEYPAD)
34   G                                 77   6 RIGHT      (KEYPAD)
35   H                                 78   +            (KEYPAD)
36   J                                 79   1 END        (KEYPAD)
37   K                                 80   2 DOWN       (KEYPAD)
38   L                                 81   3 PGDN       (KEYPAD)
39   ; :                               82   0 INSERT     (KEYPAD)
40   ' "                               83   . DEL        (KEYPAD)
41   ` ~                               87   F11
42   LEFT SHIFT                        88   F12


The following is a list of all the extended key scan codes in numerical
order:

Scan                                   Scan
Code Key                               Code Key
        
28   ENTER        (KEYPAD)              75   LEFT         (NOT KEYPAD)
29   RIGHT CONTROL                      77   RIGHT        (NOT KEYPAD)
42   PRINT SCREEN (SEE TEXT)            79   END          (NOT KEYPAD)
53   /            (KEYPAD)              80   DOWN         (NOT KEYPAD)
55   PRINT SCREEN (SEE TEXT)            81   PAGE DOWN    (NOT KEYPAD)
56   RIGHT ALT                          82   INSERT       (NOT KEYPAD)
71   HOME         (NOT KEYPAD)          83   DELETE       (NOT KEYPAD)
72   UP           (NOT KEYPAD)         111   MACRO
73   PAGE UP      (NOT KEYPAD)



Well.... lets say we want to code a keyboard handler that clears the screen
every time we press space. We would do the following :


{$F+}  {force FAR calls}
PROCEDURE KeyboardHandler;
INTERRUPT;
VAR
 scancode : byte;
BEGIN
  scancode := port[$60];

  if (scancode = 57) then Clrscr
   else
   begin
     asm
      pushf
     end;

    OldKbdhandler;
   end;

  { Acknowledge the interrupt }
  Port[$20] := $20;
end;
{$F-}

This handler clears the screen if space is pressed otherwise it lets the old
keyboard handler take a look at the key. This is usefull if we only want to
program SOME of the keys. Remember to acknowledge the interrupt by assigning
$20 to port[$20]. If you dont the computer will hang.

As you see writing interrupts are just like writing ordinary procedures. They
can call other non-interrupt procedures defined in the program, you can use
variables, constants and so on - only difference is the keyword INTERRUPT;
after the procedure-head.

To set this New keyboardhandler up we do :

We need a global var to contain the original keyboard handler - IF we want to
save it. And mostly we do so we can set it back once our program terminates


var
 OLDkbdhandler : procedure;

Then we save the old handler :
The keyboard interrupt is int 9 - hence the first parameter.

      GetIntVec($9, @OLDkbdhandler);

and set the new handler up :

      SetIntVec($9, Addr(KeyboardHandler));


From now on each time you press space the program will clear the screen.
When we are finished we restore the old keyboardhandler by :

     SetIntVec($9, @OLDKbdhandler);

Congratulations - you have now coded your first customized hardware interrupt!
Piece of cake, ehh ???

Check out the sample program PXDKEYB.PAS to see how we can use this information
to do something REALLY usefull - like blocking certain keys from functioning -
(who said the Pause key ?? )





THE PIT CLOCK TIMER AND ITS USES
---------------------------------

The PIT chip is a timer located on your motherboard. It performs 3 tasks on
your system and therefore it operates through 3 channels.

Channel 0 takes care of updating the system clock. This channel runs at
18.2 Hz which is the lowest frequency available in the PIT chip (more on this
later). This is the channel we want to use as it is easy to combine our own
purposes with updating the system clock.

Channel 1 takes care of doing a DMA memory refreshment which is vital if we
dont wanna crash the computer. We'll leave this channel alone for now.

Channel 2 is connected to the speaker and is used to make sure the speaker
output is a square wave so that the tone heard is constant.

The PIT chip runs with an internal rate of 1193181Hz. Each of the channels
are loaded with a counter-value that decrements by one every clock-cycle. When
the counter reaches 0 various things can happen - in channel 0 an interrupt 8
is called.
So, to set the frequency of a channel we have to calculate the counter value
for the channel. Normally Channel 0 is loaded with the highest available
counter value : 65536 (yes, the max. value of a word) - making it run at
1193181Hz / 65536 = 18.206 Hz. This is as mentioned before the lowest
frequency available in a channel.
If we fx. wanted to run at 150Hz we calculate the counter value this way :

    counter_value = 1193181Hz / 150Hz = 7954.54 (rounded to 7955)

This basicly means that if we wants to call a procedure 150 times a second we
set the counter to 7955 and if we only wants to call it 18 times a second we
leave it at 65536. If we wants to call a procedure LESS than 18 times a second
- fx only 1 time a second - we have to be a little smart. One solution could
be to set the frequency to 100Hz and then have a counter in our timer-handler
that only executed the procedure once every 100 calls.


The PIT operates through 4 ports :

    port[$40] : channel 0's counter value (read/write)
    port[$41] : channel 1's counter value (read/write)
    port[$42] : channel 2's counter value (read/write)
    port[$43] : a control byte that sets the various modes of the PIT (write)

In the control byte every bit has a special meaning :

bit 0   : Set this to 0 always.
bit 1-3 : This selects the mode-number (0-5). We'll use mode 2 in this text.
bit 4-5 : How to load the counter ports. As the ports can only take in byte
          values it is a problem to set the counter to fx. 7955. Therefor
          a couple of loading modes is avaiable :
           0 : Counter Latching.
           1 : Load LSB (least significant byte) only.
           2 : Load MSB (most significant byte) only.
           3 : Load LSB and THEN MSB.
          We use mode 3.
bit 6-7 : Select channel
           0 : Channel 0
           1 : Channel 1
           2 : Channel 2


OK - now we are ready to do some timer-code. One problem though is the fact
that if we mess with channel 0 we'll mess up the computers clock. The clock
is as mentioned earlier updated once every 65536 clock-cycles. This means we
have to keep track of the total nuber of clockcycles passed in our program
and each time 65536 cycles have passed we call the original Timer-procedure.

We define a few global vars :


var
 OLDTimerHandler : Procedure;
 Counter_value   : longint;
 Total_ticks     : longint;
 loop            : byte;

Then we write our new timer interrupt (int 8) :
We want it to write 'Peroxide Rulz' once every second to the screen. We decide
to set the frequency to 100Hz so we have to define a global loop-counter to
keep track of if its time to write the line.

PROCEDURE MyTimer;
INTERRUPT;
begin
 if (loop = 100) then
  begin
   writeln('Peroxide Rulz');
   loop := 0;
  end
   else inc(loop);

Total_ticks := total_ticks + Counter_value;
if (Total_ticks >= 65536) then
  begin
    asm
     pushf
    end;
    OLDTimerHandler;
   Total_ticks := Total_ticks - 65536;
  end
   else
  port[$20] := $20;
end;


As with the keyboard handler REMEMBER to acknowledge for the interrupt -
otherwise the computer will hang. As the interrupt is called once every
counter_value numbers of clockticks the total_ticks var is increased by that
number. If this value equals (or is greater) than 65536 it is time to update
the clock. Afterwards we decrement the total_ticks by 65356. We DO NOT set
it to zero as one might think. Should the Total_ticks var be greater than
65536 when the clock is updated we would slow the time down by setting it to
zero.

OK - now we want to set the frequency of channel 0 to what we desire. First
thing to worry about is the control byte.
We want to :
   - Set channel 0 active   (set bit 6 and 7 to 0)
   - Load LSB and THEN MSB  (set bit 4 and 5 to value 3 - ie turn both on)
   - Use PIT mode 2         (set bit 1-3 to value 2 - ie turn bit 2 on)
   - Keep bit 0 off.

bit      7      6        5       4        3       2        1        0
      |------|-------|-------|--------|-------|--------|--------|--------|
      |  0   |   0   |   1   |   1    |   0   |   1    |   0    |   0    |
      |------|-------|-------|--------|-------|--------|--------|--------|
value   128     64       32      16       8        4        2       1


The total value of the control byte is : 4 + 16 + 32 = 52. This is the number
we pass to port[$43].


      PROCEDURE SetTimer(freq : word);
      BEGIN
         Total_ticks := 0;
         Counter_value := 1193181 DIV freq;

         Port[$43] := 52;
         Port[$40] := Counter_value MOD 256;
         Port[$40] := Counter_value DIV 256;
      END;

Remember Counter_value MOD 256 equals the LSB of the total counter_value and
Counter_value DIV 256 equals the MSB of the value.

To restore to original values :

    PROCEDURE RestoreTimer;
    BEGIN
      Port[$43] := 52;
      Port[$40] := 0;
      Port[$40] := 0;
    END;

OK... now we just need to set up the interrupt.
Like with the keyboard handler :

GetIntVec($8,@OLDTimerHandler);
SetIntVec($8,Addr(MyTimer));

and to restore the handler :

SetIntVec($8, @OLDTimerHandler);

Thats all there is to it.... go experiment.
Check out PXDTIMER.EXE to see all this compiled and running with something
else in the foreground.


LAST REMARKS
-------------

Well, that's about all for now.
Hope you found this doc useful - and BTW : If you DO make anything public using
these techniques please mention me in your greets or where ever you se fit.
I DO love to see my name in a greeting :=)

Now you hold in your hands a very powerful weapon - but it is up to you to
figure out how to use it!
The principle of linked keys HAS been used in the past when making arcade games
so just get working! Mail me YOUR version of Pacman, Dynablaster or Bubble
Bobble :) Im looking forward to see it....
The timer is obviously perfect for the matter of timing graphics to sound -
experiment.

HINT HINT HINT HINT !!!!

Check out the help available on the pascal command 'Keep'. Using this function
you can make your programs stay resident after termination - perfect for
creating TSR / ISRs. If you can find out how to stuff the keyboard buffer you
could fx. create a memory overlay that lets you reassign key functions in
your favourite games. If you always wanted the MAP key in DOOM to be 'm'
instead of TAB......  go for it. If you experience any trouble dont hesitate
to mail me...


But what to do now ??
If you have any good ideas for a subject you wish to see a tutorial on please
mail me. If I like the idea (and know anything about it :)  ) I'll write a
tut on it.
But please be patient. As I'm still at my music school it might take a while
between the tutorials.
Next tutorial will probably be on how to program the SB card but people also
asked me to do more graphics...

MAIL ME!!


Marry christmas - and a happy new year....

  Telemachos - December '97.
