[rec.games.programmer] System timer reprogramming

densond@prism.CS.ORST.EDU (Dave Denson) (09/12/90)

I need to find out how to reprogram the IBM clock-tick timer to a faster rate.
Norton's Programming Guide gives little or no information on this.  I plan
to use int 08h to decrement a one-byte timer for game clocking purposes.

I can handle the programming to call the default int 08h handler at the proper
rate, but I'm not sure which ports (40h and 41h?) are used for the actual
setting of the clock rate.  I need a tick at 32 times the normal rate.

I would greatly appreciate any help,
		Dave

--
Dave Denson (densond@prism.cs.orst.edu) Consultant, UCS/Kerr Microcomputer Lab
"At a certain age some people's minds close up;
they live on their intellectual fat."      -- William Lyon Phelps

geiser@apollo.HP.COM (Wayne Geiser) (09/13/90)

In article <20232@orstcs.CS.ORST.EDU>, densond@prism.CS.ORST.EDU (Dave
Denson) writes:
|> I need to find out how to reprogram the IBM clock-tick timer to a
faster rate.
|> Norton's Programming Guide gives little or no information on this.  I plan
|> to use int 08h to decrement a one-byte timer for game clocking purposes.
|> 
|> I can handle the programming to call the default int 08h handler at
the proper
|> rate, but I'm not sure which ports (40h and 41h?) are used for the actual
|> setting of the clock rate.  I need a tick at 32 times the normal rate.
|> 
|> I would greatly appreciate any help,
|> 		Dave
|> 
|> --
|> Dave Denson (densond@prism.cs.orst.edu) Consultant, UCS/Kerr
Microcomputer Lab
|> "At a certain age some people's minds close up;
|> they live on their intellectual fat."      -- William Lyon Phelps

I've seen this question asked a number of times for a number of
different purposes.  I've distributed the following package to a few
people.  It appears that it is of more interest than I originally
thought.

The C program and accompanying article in the following shar is
designed to play "Take Me Out to the Ballgame" regardless of the clock
speed of the machine on which it is run.  In order to get the proper
granularity for the musical notes, the system clock had to sped up by a
factor of 4 (without, of course, affecting the time-of-day).

The code is entirely Microsoft C version 5.0.  It has been tested on a
variety of machines from an original 4.77 mHz PC to a 33 mHz 80386
clone.

If you find it of use or have any other comments/suggestions, please
drop me a line at:

P.O. Box 160
North Billerica, MA  01862

Note that I will only be at this net address for another week, after
that, I'll be unable to recieve your email for a (hopefully short)
time.

                                Wayne.

Wayne Geiser
Apollo Computer, Inc. - A subsidiary of Hewlett Packard
geiser@apollo.hp.com

"Every novel should have a beginning, a muddle, and an end."

        - Petere De Vries.

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	ballgame.c
#	c_sing.txt
# This archive created: Thu Sep 13 08:24:57 1990
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'ballgame.c'" '(5768 characters)'
if test -f 'ballgame.c'
then
	echo shar: "will not over-write existing file 'ballgame.c'"
else
sed 's/^	X//' << \SHAR_EOF > 'ballgame.c'
	X/*
	X    This program was originally written by Wayne Geiser.  It is hereby
	X    placed in the public domain.  Do with it as you will, but please
	X    give me credit if all or part of it is distributed by itself or as
	X    part of a larger package.
	X
	X    If you have any questions or comments, you may reach me at:
	X
	X                               P.O. Box 160
	X                         North Billerica, MA  01862
	X*/
	X
	X#include <conio.h>
	X#include <dos.h>
	X
	X/* The following are the frequencies for each note.  An S following the note
	X   means that the note is a sharp (#). */
	X#define S     0.0  /* Silence */
	X#define C1  261.63
	X#define CS1 277.18
	X#define D1  293.66
	X#define DS1 311.13
	X#define E1  329.63
	X#define F1  349.23
	X#define FS1 369.99
	X#define G1  392.0
	X#define GS1 415.3
	X#define A1  440.0
	X#define AS1 466.16
	X#define B1  493.88
	X#define C2  523.25
	X#define CS2 554.37
	X#define D2  587.33
	X#define DS2 622.25
	X#define E2  659.26
	X#define F2  698.46
	X#define FS2 739.99
	X#define G2  783.99
	X#define GS2 830.61
	X#define A2  880.0
	X#define AS2 932.33
	X#define B2  987.77
	X
	X/* The following are the durations of the notes.  To make the song go faster,
	X   make the definition of E smaller.  To make the song go slower, make the
	X   definition of E larger.  A P in the second position of the name means that
	X   this note is half again as large as the one preceding it. */
	X#define E  0.125   /* Eighth note */
	X#define EP (E*1.5)
	X#define Q  (E*2.0)
	X#define QP (E*3.0)
	X#define H  (Q*2.0)
	X#define HP (Q*3.0)
	X#define W  (H*2.0)
	X#define WP (H*3.0)
	X
	X/* The following define the duration of each note (or conversely, the amount
	X   of silence between notes. */
	X#define LAGATO  1.0
	X#define NORMAL  0.875
	X#define STACATO 0.75
	X
	X/* Changing this definition changes the duration of the notes for the entire
	X   song. */
	X#define NOTE_DUR NORMAL
	X
	X/* How much must we speed up the timer.  The value works fine for the tempo
	X   I've defined here.  If you play a faster song, you might consider
	X   increasing this value. */
	X#define CLOCK_INC 4
	X
	X#define NUM_NOTES 61
	Xstatic double notes[NUM_NOTES] = { C1,  C2,  A1, G1, E1, G1, D1,
	X                                   C1,  C2,  A1, G1, E1, G1, S,
	X                                   A1,  GS1, A1, E1, F1, G1, A1, F1, D1,
	X                                   A1,  A1,  A1, B1, C2, D2, B1, A1, G1,
	X                                   F1,  D1,  C1, C2, A1, G1, E1, G1, D1,
	X                                   C1,  C1,  C1, D1, F1, A1, S,
	X                                   A1,  B1,  C2, C2, C2, B1, A1, G1,
	X                                   FS1, G1,  A1, B1, C2
	X};
	Xstatic double sizes[NUM_NOTES] = { H,   Q,   Q,  Q,  Q,  HP, HP,
	X                                   H,   Q,   Q,  Q,  Q,  W,  E,
	X                                   Q,   Q,   Q,  Q,  Q,  Q,  H,  Q,  HP,
	X                                   H,   Q,   Q,  Q,  Q,  Q,  Q,  Q,  Q,
	X                                   Q,   Q,   H,  Q,  Q,  Q,  Q,  HP, HP,
	X                                   H,   Q,   Q,  Q,  Q,  HP, E,
	X                                   Q,   Q,   HP, HP, Q,  Q,  Q,  Q,
	X                                   Q,   Q,   HP, HP, W
	X};
	X
	Xvolatile long click = 0;
	Xvoid (interrupt far *orig_int) (void); /* function pointer to the
original interrupt */
	X
	Xvoid init_clock(void);
	Xvoid reset_clock(void);
	Xvoid sleep(double how_long); 
	Xvoid sound(double freq, double duration);
	X
	Xvoid main(void)
	X{
	X    int i;
	X    
	X    init_clock();
	X    for (i=0; i<NUM_NOTES; i++)
	X        sound(notes[i], sizes[i]);
	X    reset_clock();
	X}
	X
	Xvoid interrupt far new_int(void)
	X{
	X    if ((++click % CLOCK_INC) == 0)
	X        (*orig_int)();
	X    outp(0x20, 0x20);
	X}
	X
	Xvoid init_clock(void)
	X{
	X    /*
	X    	Increase the 8253 timer's channel 0 output by a factor of CLOCK_INC.
	X    */
	X    
	X    union {
	X        long divisor;
	X        unsigned char c[2];
	X    } count;
	X
	X    /* Install the new interrupt routine. */
	X    orig_int = _dos_getvect(0x08);
	X    _dos_setvect(0x08, new_int);
	X   
	X    /* Increase the timer speed. */
	X    count.divisor = (long) 65536/CLOCK_INC;
	X    outp(0x43, 0x36);                  /* tell 8253 that count is coming */
	X    outp(0x40, count.c[0]);            /* send low-order byte */
	X    outp(0x40, count.c[1]);            /* send high-order byte */
	X}
	X
	Xvoid reset_clock(void)
	X{
	X    /*
	X    	Return clock to it's original speed.
	X    */
	X
	X    /* Reinstall the old interrupt routine. */
	X    _dos_setvect(0x08, orig_int);
	X
	X    /* Slow down the clock. */
	X    outp(0x43, 0x36);                  /* tell 8253 that count is coming */
	X    outp(0x40, (char) 0);              /* send low-order byte */
	X    outp(0x40, (char) 0);              /* send high-order byte */
	X}
	X
	Xvoid sleep(double how_long)
	X{
	X    long i,
	X         orig,
	X         new;
	X
	X    orig = click;
	X    i = (long) ((how_long * (18.2 * CLOCK_INC)) + orig);
	X    new = click;
	X    while (new < i)
	X        new = click;
	X}
	X
	Xvoid sound(double freq, double duration)
	X{
	X    /*
	X        Beep the speaker using the specified frequency.
	X    */
	X    
	X    double off,
	X           play;
	X    union {
	X        long divisor;
	X        unsigned char c[2];
	X    } count;
	X    int p;
	X    
	X    play = duration * NOTE_DUR;
	X    off  = duration - play;
	X
	X    if (freq != S)
	X        count.divisor = (long) (1193180 / freq); /* compute the proper
count */
	X    else
	X        count.divisor = 0;
	X    outp(0x43, 0xb6);                  /* tell 8253 that count is coming */
	X    outp(0x42, count.c[0]);            /* send low-order byte */
	X    outp(0x42, count.c[1]);            /* send high-order byte */
	X    p = inp(0x61);                     /* get existing bit pattern */
	X    if (freq != S)
	X        outp(0x61, p | 3);             /* turn on bits 0 and 1 */
	X    else
	X        outp(0x61, p);
	X
	X    sleep(play);
	X    
	X    outp(0x61, p);                     /* restore original bits to
turn off speaker */
	X
	X    sleep(off);
	X
	X}
SHAR_EOF
if test 5768 -ne "`wc -c < 'ballgame.c'`"
then
	echo shar: "error transmitting 'ballgame.c'" '(should have been 5768
characters)'
fi
chmod +x 'ballgame.c'
fi
echo shar: "extracting 'c_sing.txt'" '(13330 characters)'
if test -f 'c_sing.txt'
then
	echo shar: "will not over-write existing file 'c_sing.txt'"
else
sed 's/^	X//' << \SHAR_EOF > 'c_sing.txt'
	X                                Making C Sing
	X                              by Wayne D. Geiser
	X
	X         As every PC owner is aware, the IBM PC line of computers has 
	X         the ability to play music.  Oh, it can't handle the
	X         sophisticated, multi-voice symphonies possible on machines 
	X         like the Amiga, but it will play a simple melody just the 
	X         same.
	X
	X         BASIC provides a fairly extensive interface to program music 
	X         and Turbo Pascal contains the rudimentary tools to emit 
	X         sound.  Microsoft C, however, includes no support for 
	X         generating sound with the exception of making a "beep" by 
	X         printing the "bell" character.
	X         
	X         For some of us, this simply isn't fancy enough for the types
	X         of programs we'd like to develop.  This article will attempt 
	X         to explain how I was able to teach my copy of Microsoft C to 
	X         sing.
	X
	X         Note that the code samples contained in this article have 
	X         been shown to compile and run with version 5.0 of Microsoft C 
	X         and should run unaltered on any true IBM compatible.
	X
	X         The 8253 timer chip -
	X
	X         This chip is the main timing circuit for the computer.  It 
	X         doesn't control the CPU speed, but most other functions are 
	X         controlled either directly or indirectly by this timer.  Its 
	X         major advantage to us is that it has a frequency of 1.19318 
	X         MHz regardless of the speed of the CPU in the machine.  This 
	X         will be important in creating programs that play a song at 
	X         the same speed regardless of whether they are run on an 
	X         original PC (whose CPU runs at 4.77 MHz) or the newest 80386 
	X         machine (whose CPU may be 20 MHz, 25 MHz, or more). 
	X
	X         In the PC/XT/AT family, this chip has three output channels.  
	X         PS/2 models have an additional output channel, but we won't 
	X         be concerned about that here.
	X
	X         Channel zero is the system clock timer.  This output channel 
	X         "clicks" 18.2 times every second.  Each time this happens, 
	X         hardware interrupt eight is generated.  This is the interrupt 
	X         that controls the time-of-day clock.  Note, also, that this 
	X         signal controls the circuits that turn off the floppy drive 
	X         motor.
	X
	X         Channel one is the memory refresh timer.  We won't be
	X         concerned with this channel either.
	X
	X         Channel two is used to control the speaker's output 
	X         frequency.  We'll be getting into exactly how this is done a 
	X         bit later.
	X
	X         There are three steps to creating a sound on the speaker.
	X
	X         1.     Tell the timer chip that the frequency will be coming.
	X
	X                The Microsoft C statement to accomplish this is:
	X
	X                outp(0x43, 0xb6);
	X
	X                0x43    is the port to which the data is to be 
	X                        written.
	X
	X                0xb6    is the data.  Bits 6 and 7 identify the 
	X                        channel in which we're interested (10 = 
	X                        channel 2).  Bits 4 and 5 identify the 
	X                        operation (11 = read/write least significant 
	X                        byte followed by most significant byte).
	X                        Bits 1, 2, and 3 identify the operation mode 
	X                        (011 = square wave).  Bit 0 identifies          
	X                        binary vs. BCD (0 = binary). 
	X         
	X                        Put all together, we have:
	X         
	X                        10110110 = 0xb6 
	X
	X         2.     Load the frequency desired into the timer chip.
	X
	X                The Microsoft C statement to accomplish this is:
	X         
	X                outp(0x42, count); 
	X         
	X                0x42    is, again, the port.  This time its the port 
	X                        for channel 2. 
	X
	X                count   is the countdown value we want the chip to 
	X                        count.  Every time the chip counts down to 0 
	X                        from this value, it will pulse into the 
	X                        speaker.  See the next section for details 
	X                        about how to figure out this value. 
	X
	X         3.     Turn the speaker on.
	X
	X                We must use multiple statements to accomplish this 
	X                step.  We must ensure that all bits except the two in 
	X                which we're interested remain the same.  To do this, 
	X                we must read in the value from the port and alter just 
	X                two bits.
	X          
	X                inp(0x61, i);
	X                outp(0x61, i & 0x03);
	X         
	X                0x61    is, again, the port.  This time it is the 8255 
	X                        Programmable Peripheral Interface.
	X         
	X                0x03    turns on bits 0 and 1.  Bit 0 indicates that 
	X                        speaker control will come from channel 2 of 
	X                        the 8253 timer.  Bit 1 turns the speaker on. 
	X
	X         Here is a good place to note that once the speaker is turned 
	X         on, it will stay on until you turn it off.
	X
	X         Making music, not just sound - 
	X
	X         The count we pass on to the timer chip is not exactly the 
	X         frequency of the sound we wish to make.  In order to 
	X         determine this count, we must divide the frequency of the 
	X         chip (1.19318 MHz) by the frequency we wish to have played.  
	X         Because the count must fit into a 16-bit integer, you are 
	X         limited to playing frequencies between 18.2 hertz and 1.193 
	X         megahertz.  BASIC limits you even further.  You'll find, 
	X         however, that the practical limit is even less than that.  
	X         Here's a table of the frequencies required to produce several 
	X         octaves of musical notes:
	X         
	X         C          65.41    130.81    261.63     523.25
	X         C#         69.3     138.59    277.18     554.37
	X         D          73.42    146.83    293.66     587.33
	X         D#         77.78    155.56    311.13     622.25
	X         E          82.41    164.81    329.63     659.26
	X         F          87.31    174.61    349.23     698.46
	X         F#         92.5     185.0     369.99     739.99
	X         G          98.0     196.0     392.0      783.99
	X         G#        103.83    207.65    415.3      830.61
	X         A         110.0     220.0     440.0      880.0
	X         A#        116.54    233.08    466.16     932.33
	X         B         123.47    246.94    493.88     987.77
	X         C                                       1046.5
	X
	X         The tempo I've chosen for my song has a whole note being 1 
	X         second long.  Other notes are proportional to this time (e.g. 
	X         a half note is 1/2 second).  If this is too fast or two slow 
	X         for the song you want to play, the duration of the notes can 
	X         be adjusted accordingly.
	X         
	X         Although I just wrote that a whole note would be 1 second 
	X         long, we'll really only be playing the note for 7/8 of that 
	X         time and we'll leave 1/8 silence.  This allows the notes to 
	X         be distinguished in a "normal" manner.  This, too, can be 
	X         adjusted if you wish to play Lagato (all sound and no 
	X         silence) or Staccato (3/4 sound and 1/4 silence).
	X         
	X         Time -
	X         
	X         Now that we've decided the amount of time each note will be 
	X         played and the amount of silence between each, we need a way 
	X         to measure it.  This can be quite tricky.  Consider an eighth 
	X         note.  We want to have this note be .125 seconds long.  
	X         .10938 seconds of this will be sound and .01562 seconds will 
	X         be silent.  This is a very small amount of time to be 
	X         calculating with any accuracy.
	X         
	X         We could use a simple for loop.  Any value we calculate for 
	X         the duration of the loop will produce different results if 
	X         run on a machine with a CPU speed different than that for 
	X         which it was originally developed.  It is possible to have 
	X         your program calculate the number of iterations necessary and 
	X         it would therefore change as the program was moved to a 
	X         faster machine.
	X         
	X         The way to accomplish this is to count 91 clicks of the timer 
	X         (91 clicks = 18.2 clicks/second * 5 seconds).  Microsoft C 
	X         has a function (_bios_timeofday) to return the number of 
	X         clicks since midnight.  You simply write a loop that will 
	X         wait until the value returned has increased by 91 and the 
	X         number of iterations accomplished divided by 5 is the number 
	X         of iterations that can be accomplished in 1 second for this 
	X         machine.  This value is then divided by the length of the 
	X         event you'd like to time and another for loop is executed to 
	X         time the event.
	X         
	X         Note that you must leave the call to _bios_timeofday in your 
	X         timing loop or it will execute faster than the original 
	X         pacing loop.  You may also need to turn off optimization 
	X         around this loop.  If the compiler figures out that you are 
	X         never using the values from the function call, it may decide 
	X         that it really doesn't need to be in a loop at all and throw 
	X         the whole thing away. 
	X         
	X         There are several problems with this approach.  Firstly, your 
	X         program must pause for 5 seconds before the song can begin 
	X         (remember that you must count 91 clicks).  I believe this to 
	X         be the largest objection to this approach.  In addition, you 
	X         must take into account whether midnight has occurred since 
	X         you started the timing operation.  Lastly, if you have a 
	X         machine in which you can control the speed of the processor 
	X         "on the fly," your song will speed up or slow down if the 
	X         speed is changed following execution of the pacing loop. 
	X
	X         The machine independent way of performing the same task is to 
	X         use the machine independent timer - the 8253 timer.   
	X         Remember, it clicks 18.2 times per second regardless of the 
	X         CPU speed of the machine on which it is running.  We can't 
	X         use this timer "as is" to time our event because it is just 
	X         not sensitive enough.  A click occurs every .05495 seconds.  
	X         We, however, need to time events as little as .01 seconds or 
	X         so.  Clearly this won't do. 
	X         
	X         I wouldn't be writing this article if there wasn't a way 
	X         around this problem.  The solution in this case is to make 
	X         our 8253 timer more sensitive (i.e. make it click more than 
	X         18.2 times per second).  More specifically, we'll make 
	X         channel 0 of the chip click more than 18.2 times per second.
	X         
	X         The way to change the clock timer is exactly the same as the 
	X         speaker output channel that we've already seen.  The only 
	X         difference is that the port in this case is 0x40 rather than 
	X         the 0x42 we used above. 
	X
	X         Again, there's a hitch.  If you change how frequently the 
	X         timer chip clicks, your system's clock will soon be 
	X         hopelessly wrong.  We intend to make the timer click 72.8 
	X         times per second.  This is 4 times the default rate.  If we 
	X         don't protect the system clock, a 1 minute song will appear 
	X         to take 4 minutes to your machine. If you intend to program 
	X         quite a snappy tune, you may wish to increase this even 
	X         further.  Be aware, however, that the faster you make the 
	X         timer click, the less time you will have to get your work 
	X         done between clicks.                                               
	X                  
	X         In order to maintain the correct time of day, we must replace 
	X         the interrupt in position 0x08 of the interrupt vector.  This 
	X         is the interrupt that is called every time the 8253 timer 
	X         pulses channel 0.  We'll be increasing the frequency of this 
	X         event, we'll want to replace the normal interrupt code with 
	X         our own code that counts our more frequent clicks and calls 
	X         the normal interrupt code every fourth time through.
	X         
	X         This code is pretty straight forward.  The major trick is the 
	X         call to outp at the end of the routine.  Because we are 
	X         playing around with a hardware interrupt, we must tell the 
	X         8259 that the interrupt has finished.  This code accomplishes 
	X         that.
	X         
	X         We must also be very careful to replace the original 
	X         interrupt vector when we are through with the timer and have 
	X         slowed it back to its normal rate.
	X         
	X         Whew! - 
	X         
	X         This seems to be a very long way to go to get a song from C.  
	X         If the coding is done in a modular way, however, it can be 
	X         added to any program you write and all you need worry about 
	X         is the music that you wish to play.  I've tried to show how 
	X         the code could be modular in my standalone example. 
	X
	X         I'd like to acknowledge The New Peter Norton Programmer's 
	X         Guide to the IBM PC & PS/2 for the table of frequencies I've 
	X         presented. 
SHAR_EOF
if test 13330 -ne "`wc -c < 'c_sing.txt'`"
then
	echo shar: "error transmitting 'c_sing.txt'" '(should have been 13330
characters)'
fi
chmod +x 'c_sing.txt'
fi
exit 0
#	End of shell archive