Real-Time Modeling With MS-DOS

Even though MS-DOS isn't multitasking, it can still be used for some real-time applications, and David shows how.


February 01, 1989
URL:http://www.drdobbs.com/embedded-systems/real-time-modeling-with-ms-dos/184408082

Figure 1


Copyright © 1989, Dr. Dobb's Journal

Figure 1a


Copyright © 1989, Dr. Dobb's Journal

Figure 1b


Copyright © 1989, Dr. Dobb's Journal

Figure 1c


Copyright © 1989, Dr. Dobb's Journal

Figure 2


Copyright © 1989, Dr. Dobb's Journal

Figure 2b


Copyright © 1989, Dr. Dobb's Journal

Figure 3


Copyright © 1989, Dr. Dobb's Journal

FEB89: REAL-TIME MODELING WITH MS-DOS

REAL-TIME MODELING WITH MS-DOS

Who says MS-DOS isn't a real-time operating system?

David Bowling

David Bowling is a project engineer at Honeywell Defense Avionics Systems Division and is responsible for the software design and development of non-linear real-time simulators. His current projects include simulators for the B-52, C-135, C-130, and C-17 aircraft. He can be reached at 6213 Antigua NE, Apt. F, Albuquerque, NM 87111.


The phrase real-time has been thrown around a lot in the past few years, mostly as a buzzword to impress people. Yet the question still asked is, what is a real-time system? Basically, real-time systems are those systems that are able to respond to specific events within a specified time. Given this definition, even Unix-based computers are real-time systems if we specify our response time in seconds -- which, in some cases, is very acceptable. Because the temperature in a 100,000-sq ft building can't change much in a few minutes, a heating and cooling system in a large building will work fine if it can respond to a change in the building's temperature in less than a minute. At the other end of the spectrum, the stuff I work with -- flight simulators and flight controllers -- requires response times down in the milliseconds. For example, an autopilot on a small fighter aircraft must respond to any disturbances in less than about 20 milliseconds to keep the airplane from going unstable (crashing).

As stated earlier, real-time systems respond to events. An event can be almost anything. The most common type of real-time event in a computer system is a hardware interrupt. One common hardware interrupt is generated when a key is pressed. Generally, the desired real-time response to this hardware interrupt is the appearance of a character on the computer screen. On a heavily loaded minicomputer, this simple task can sometimes take seconds. The delay from the time the event occurs to the time the computer responds is called interrupt latency. Interrupt latency is a scale by which real-time computers are measured. Using this scale, MS-DOS excels as a real-time operating system. Because of the standard hardware and the wealth of information on how to program it, functions can be directly tied to hardware interrupts. The interrupt latency with MS-DOS ends up being the time required to jump to an interrupt service routine -- which is not very long.

When modeling something in real time, the event we respond to is a clock-generated interrupt. The clock generates its hardware interrupt at set intervals known as the frame time. In real-time modeling, we respond to the clock interrupt by computing or modeling what a system will do over the frame. If, at every clock pulse, we model what our system did since the last clock pulse (over one frame), we end up predicting what the system will do at the precise time the actual system would do it. A familiar example of real-time modeling is the game Flight Simulator. Flight Simulator models, or predicts, what a Cessna 182 will do at the precise time an actual Cessna 182 would do it.

Designing a Real-Time Multitasking Environment

The first thing we need to implement a real-time model is a multitasking environment. Because MS-DOS isn't multitasking, we're going to implement our own simple, real-time multitasking system. Our system will have two tasks -- one foreground and one background. The foreground task will do the real-time modeling; the background task will be used to input data and display the output of the model.

The best way to illustrate how the two tasks interact is with time lines (see Figure 1). Every time we get a clock interrupt, the foreground task is activated. The foreground task then runs to completion (see Figure 1a). The obvious limitation of the foreground task is that it must take less time to complete than the duration of one frame. The background task will be an endless loop that continuously checks for input and displays output (see Figure 1b). But, the only time allotted for the background task is the time between the end of the foreground task and the next clock interrupt. Because the background task has no knowledge of the real-time task the trick is getting both of the tasks to work together. The foreground task interrupts the background task, does its thing, and when finished resumes the background task where it was interrupted (see Figure 1c).

You might have noticed that this scheme sounds a lot like a simple interrupt service routine. Well, that's all it is. All we really did was give all the functions different titles. The program becomes the background task and the interrupt service routine becomes the real-time task. The only real difference in implementation is that the computer spends more time in the interrupt service routine (our real-time task) than in the program (our background task).

Creating a Program Structure

By creating an interrupt-driven foreground task that interrupts a continuous-loop background task, we can implement a real-time system with MS-DOS. But the problem of how to create and start both tasks from our main program still remains.

Our main program can be viewed as consisting of an initialization section, a background task loop, and a termination section. In the initialization section, we set up our foreground and background tasks and initiate the foreground interrupt. In the termination section, we disable the foreground interrupt and restore normal operation. Although all this can be done in one long main program, I find a modular approach to programming facilitates understanding as well as modification.

Using a modular approach, I developed a generic main program for any simple real-time model (see Listing One, page 78). 1 used C because of its powerful instruction set, which lends itself to manipulating hardware interrupts and timer chip programming.

The initialization section of the generic program consist of calling three setup modules: two set_up_user...modules to perform any special foreground or background initialization for our particular real-time application and one to set up the real-time interrupt. Similarly the termination section of the generic program consists of an error handler and three set-down modules: one to turn off the real-time interrupt and two set_down_user ... modules to perform any special foreground or background termination for our real_time application. As an example, in the simple spring-mass system discussed later in this article, I use the set_up_user_background_task( ) and set_down_user_background_task( ) functions to get the EGA card in and out of graphics mode.

Spawning the Real-Time Task

But what's inside the functions set_up_real_time_task( ) and set_down_real_time_task( ) ? As in setting up any other interrupt service routine, we redirect the interrupt vector to our real-time function (real_time_task( )). This is where Borland's Turbo C really comes in handy. Turbo C has two functions -- getvect( ) and setvect( ) that make manipulating the interrupt vectors a snap (see Listing Two, page 78).

Setting up the real_time task involves four simple steps:

    1. Save the clock interrupt service routine address (hardware interrupt vector 0x08). The Turbo C function getvect( ) is used for this.

    2. Set an unused interrupt vector to point to the original clock interrupt (I used interrupt 0x50). We will want to continue calling the clock interrupt so we don't alter our computer's system time. The Turbo C function setvect( ) is used for this.

    3. Overwrite the address of the original clock interrupt with the address of our real-time function. The Turbo C function setvect( ) is also used for this.

    4. Reprogram the system clock. I'll talk more about this in the next section, "Setting the Frame Time."

Setting down the real-time task is even simpler:

    1. Overwrite the address of our real-time task with the original clock interrupt. Remember we saved it when setting up the real-time task.

    2. Reset the system clock to its boot-up configuration.

Actually, I lied earlier. We can't tie our real-time function directly to the interrupt; we have to do a little housekeeping before we call the actual real-time task, user_defined_real_time_task( ) (see Listing Three, page 78). This is the function we will write to do our modeling. When we called set up real time task( ), we overwrote the vector to the clock interrupt that keeps the system time. If we do nothing, time will stand still while the program is running. The first time I realized this, the computer told me it was 10:30 when actually the time was 1 o'clock in the morning.

So, to avoid losing track of time, we need to call the original clock interrupt routine in conjunction with our real-time task. It is also a good idea to enable interrupts during the real-time function to make sure things such as the keyboard are still serviced. But most important, we need to have some way to check and control if we reenter the real-time function. If the real-time task takes longer to run than the frame time, we end up reentering the function. In simpler, more confusing, terms if we haven't finished the real-time function when the next clock interrupt occurs, we call the real-time function again, before we finish it the first time. This has a tendency to bring the computer to a halt very quickly (see time line of Figure 2).

To prevent frame overruns from locking up the computer and to report the occurrence of the overrun we use two flags--the flag running and the flag over_run. By setting the flag running to TRUE in the interrupt routine, before the user-defined function is called, and then resetting it to FALSE before returning to the background task, it provides us with an indication of task completion within the allotted frame time. If we enter our foreground interrupt service routine and running is TRUE this means we are in the middle of the user-defined function from the last interrupt and we've just reentered the function. When this happens we set the flag over_run to TRUE and immediately return to our previous foreground routine, still executing (see Figure 2b). Once over_run is TRUE, we never again call the user-defined function. Upon the completion of each background loop we examine over_run in the main program. If set, we jump out of the background loop, enter the termination section, and report the overrun error to the user.

Setting the Frame Time

It turns out that the frame time of the system clock is never what we want. I usually find it's too large. This can be easily corrected by reprogramming the timer that controls the system clock. But we also need to take into account that once we change the clock timer, the computer might no longer keep accurate time. I choose to limit the reprogramming of the timer to integer divisions of the original frame time. If we use only integer divisors, we can maintain the time of day simply by calling the original clock interrupt every "divisor" frames. Remember, because we saved the address of the original clock interrupt in old_clock_func and then set interrupt 0x50 to point to it, we can call the original clock interrupt by generating a software interrupt 0x50.

PC clones use variations of the Intel 8253 timer chip to generate the system clock interrupts. When MS-DOS boots, it sets up the 8253 to generate interrupts every 54.9 msecs. This happens to be the longest interval that a PC can generate, the shortest being 838 nsecs. The longest interrupt is generated by setting the counter to count 65,536 clock ticks (this is done by setting the 16-bit decrement counter to 0). Because the timer counter is an integer, not only do we need to use a divisor that's an integer but also it needs to divide evenly into 65,536 (no remainder). Any power of 2 will work. We set the divisor in the function set_up_user_real_time_task( ) by assigning it to the external variable user_define_divisor. For example, if we want interrupts about every 14 msecs, we program the clock with 65,536/4=16,384 (user_define_divisor = 4). This actually gives a frame time of 13.73 msecs. Just remember to use 13.73 msecs in all your real-time equations. The actual code that sets the timer is shown in Listing Four, page 78. Once reprogrammed, we only need to call the original clock interrupt every four clock interrupts (see Listing Three).

Communicating Between Tasks

Now that we have the two tasks running, we need to get them talking to each other. The easy way to do this is with memory common to each function (external variables). The common term for this memory is DATAPOOL. The best way to demonstrate this is with a simple example (see Listing Five, page 78). The code has a background task that continually prints out the value of i, and the real-time task continually changes i at the rate of the frame time. The variable i is common to both tasks and allows them to communicate with each other.

Limitations

There aren't too many limitations to either the background or the foreground task, but if ignored all heck win break out:

Debugging Real-Time Tasks

Debugging the real-time part of a system can pose a problem. If something happens, the interrupts run wild and the computer usually crashes.

Debuggers not designed for real-time systems usually don't work. In effect, debuggers stop the program at each line in the code. This is fine except a debugger will not stop the clock interrupts. This means that if the debugger is in the real-time function, at the next clock interrupt, the real-time function will reenter itself. This is sure to cause a system crash.

The old-fashion-way to debug a program is by placing print statements at strategic locations in the code. This works fine if the code doesn't have to be completed in a specified time. The problem with a real-time task, again, is that the print statement will usually take longer to complete than the framed time, causing the task to reenter itself.

The way to get around this is not to debug in real time. Set up both the background and real-time functions as background tasks, calling one after the other (see Listing Seven, page 78). Now you can use any method of debugging.

A Simple Spring-Mass System

Now what we've all been waiting for, a real-time model. I've included a real-time program that models a simple spring-mass system. You're probably wondering why you need a real-time system to model a spring-mass system. You really don't, but you can impress you're friends when you show them the program and tell them time model. Actually, a spring-mass system is an excellent application to demonstrate the concepts of real-time modeling. It turns out that the frequency at which the system should oscillate (how often it bounces back and forth) can be computed easily.

The system shown in Figure 3 has a period of:

                                 
                        d=2m/k(1 - 2)

The term taud is how often the mass should bounce up and down in seconds. The spring-mass program uses the following values for m, k, and : m = 1.0182969 slugs, k = 10.0 lbs/inch, and - 0.1. These values give a period of 2.0 seconds.

This is the period of a real spring-mass system. If we went out and bought a lead weight that had a mass of m, hung it under a spring with a constant k and damping ratio , set the system in motion, and used a stopwatch to time how long it took to complete one cycle, the stopwatch would read 2.0 seconds.

                       
          d = 21.0134145/10.0 (1 - 0.01 2)

          d = 2.0 seconds

If we just write a normal program to model this (non-real-time), the rate at which the program will oscillate (the time as measured by the stopwatch) is anyone's guess. What we're going to do in our real-time model is model what the mass would do at the exact time the lead weight would do it. Now, if everything works, the program will display a graphical model of a mass having a period of two seconds. If we hang the lead weight next to the computer screen and start both the weight and computer at the same time, the lead weight and computer model will move in unison. You'll have to run the program to prove it to yourself.

And what's amazing is, it doesn't matter what computer you run the program on, from the slowest XT to the fastest 386 the graphics display will always have the same period--that is, as long as the computer has a coprocessor (you'll overrun the frame time without one). Ah, the magic of real time. The only thing that will change on different computers is how often the screen is updated. The faster your computer, the faster the display will be updated.

Implementing the Spring-Mass System

Using a little college-level dynamics, the differential equation of motion can be written for the system as follows:

  mx + cx + kx = 0

This equation gives us the acceleration of the mass; but what we want is the position of the mass. All we have to do to get the position is solve this equation (a second-order-homogeneous, linear differential equation)--that is:

  
x = e-nt (x0/ 1 - 2 sin 1 - 2nt +
         
x0cos1 - 2nt)

where:

Once we substitute these values into our position equation, we get:

  x = e-nt (x0 + nx0
                                
                         n1 - 2 sin 1 - 2nt
                                                 
                                      + x0 cos1 - 2nt)

This equation is the essence of our real-time model. The only thing left is the frame time. For our spring-mass system, I picked a frame time of 54.9/4 = 13.73 msec, or 0.01373 seconds.

Conclusion

The real-time modeling program is broken into three files. The first file contains all the real-time kernel functions (see realtime.c in Listing Eight, page 78). The next two files contain all the user functions. massfor.c ( Listing Nine, page 80) contains all the real-time, or foreground, functions--set_up_user_real_time_task( ), set_down_user_real_time_task( ), and user_defined_real_time_task( ). massbck.c (Listing Ten, page 80) contains all background functions -- set_up_user_background),set_down_user_background( ), and user_defined_background_task( ).

To implement your own real-time model, just replace massfor.c and massbck.c with your own . . ._user_. . . functions. And who says MS-DOS isn't a real-time operating system?

Bibliography

Foster, Caxton C. Real-Time Programing -- Neglected Topics. Reading, Mass.: Addison-Wesley, 1982.

Peterson, James L. and Silberschatz, Abraham. Operating System Concepts. Reading, Mass.: Addison-Wesley 1985.

Savitzky, Stephen. Real-Time Microprocessor System. New York: Van Nostrand Reinhold, 1985.

Thomson, William T. Theory of Vibration with Applications. Englewood Cliffs, N.J.: Prentice-Hall, 1981.

_Real-Time Modleing with MS-DOS_ by David Bowling

[LISTING ONE]




  #include <dos.h>

  #define  TRUE   -1
  #define  FALSE   0

  void user_defined_background_task();
  void set_up_user_background_task();
  void set_down_user_background_task();
  void set_up_user_real_time_task();
  void set_down_user_real_time_task();
  void set_up_real_time_task();
  void set_down_real_time_task();

  int not_done = TRUE;
  int over_run = FALSE;

  main(){
    set_up_user_real_time_task();     /*initialization section*/
    set_up_real_time_task();
    set_up_user_background_task();

    while( not_done && ! over_run){    /*background task loop*/
    user_defined_background_task();
    }

    set_down_real_time_task();         /*termination section*/
    set_down_user_background_task();
    set_down_user_real_time_task();

    if( over_run )
      printf("Error exit, frame over run\n");
   }







[LISTING TWO]


  void interrupt real_time_task();
  void interrupt (*old_clock_func)();
  void set_timer();
  int user_define_divisor = 1; /*initialize in case user forgets*/

  void set_up_real_time_task()
  {
    void interrupt (*getvect())();

    old_clock_func = getvect( 0x08 );/*save original clock vector*/
     setvect( 0x50, old_clock_func );/*store in unused location*/
     setvect( 0x08, real_time_task ); /*overwrite with real-time*/
    set_timer( user_define_divisor ); /*set system timer*/
  }

  void set_down_real_time_task(){
    setvect( 0x08, old_clock_func ); /*restore clock vector*/
    set_timer( 1 );                  /*reset system timer*/
  }






[LISTING THREE]



  int running = FALSE;
  int inter_count = 0;

  void interrupt real_time_task(){
    enable();

    if( !running && !over_run ){
      running = TRUE;
      user_defined_real_time_task(); /*real-time function*/
    }else{
      over_run = TRUE;
    };

    if( inter_count == user_define_divisor ){
      geninterrupt( 0x50 );     /*call system clock*/
      inter_count = 0;
    }else{
      outport( 0x20, 0x20 );    /*8259 end of interrupt routine*/
      inter_count += 1;
    };

    running = FALSE;
  }









[LISTING FOUR]


  void set_timer( divisor )
    int divisor;
  {
    int cnt;
    int lo, hi;

    cnt = 65536 / divisor;

    lo = cnt % 256;
    hi = cnt / 256;

    outportb( 0x43, 0x36 );
    outportb( 0x40, lo );    /*write tic counter*/
    outportb( 0x40, hi );
  }






[LISTING FIVE]


  int i = 0;  /* DATAPOOL */

  user_defined_background_task(){
    printf("i = %d\n", i );
  }

  user_defined_real_time_function(){
    i += 1;
  }








[LISTING SIX]


      union{
       char coprocessor_state[94];
       int control_word;
     }float_save;

      /* save coprocessor state */
      asm   fsave float_save.coprocessor_state
      asm   fldcw float_save.control_word
               .
               .
               .
      /* restore coprocessor state */
      asm   frstor float_save.coprocessor_state







[LISTING SEVEN]



    while( not_done && ! over_run ){   /* non real-time debugging*/
      user_defined_background_task();
      user_defined_real_time_task();
    }







[LISTING EIGHT]


  #include <dos.h>

  #define  TRUE   -1
  #define  FALSE   0

  void user_defined_background_task();
  void set_up_user_background_task();
  void set_down_user_background_task();
  void set_up_user_real_time_task();
  void set_down_user_real_time_task();
  void set_up_real_time_task();
  void set_down_real_time_task();

  int not_done = TRUE;
  int over_run = FALSE;

  main(){
    set_up_user_real_time_task();     /*initialization section*/
    set_up_real_time_task();
    set_up_user_background_task();

    while( not_done && ! over_run){    /*background task loop*/
      user_defined_background_task();
    }

    set_down_real_time_task();         /*termination section*/
    set_down_user_background_task();
    set_down_user_real_time_task();

    if( over_run )
      printf("Error exit, frame over run\n");
   }

/******************************************************/

  void interrupt real_time_task();
  void interrupt (*old_clock_func)();
  void set_timer();
  int user_define_divisor = 1; /* initialize in case user forgets */

  void set_up_real_time_task()
  {
    void interrupt (*getvect())();

    old_clock_func = getvect( 0x08 ); /*save original clock vector*/
    setvect( 0x50, old_clock_func );  /*store in unused location*/
    setvect( 0x08, real_time_task );  /*overwrite with real-time*/
    set_timer( user_define_divisor ); /*set system timer*/
  }

  void set_down_real_time_task(){
    setvect( 0x08, old_clock_func ); /*restore clock vector*/
    set_timer( 1 );                  /*reset system timer*/

  }

/******************************************************/

  union{
    char coprocessor_state[94];
    int control_word;
  } float_save;

  int running = FALSE;
  int inter_count = 0;

  void interrupt real_time_task(){
    /* save coprocessor state */
    asm   fsave float_save.coprocessor_state
    asm   fldcw float_save.control_word

    enable();

    if( !running && !over_run ){
      running = TRUE;
      user_defined_real_time_task(); /*real-time function*/
    }else{
      over_run = TRUE;
    };

    if( inter_count == user_define_divisor ){
      geninterrupt( 0x50 );     /*call system clock*/
      inter_count = 0;
    }else{
      outport( 0x20, 0x20 );    /*8259 end of interrupt routine*/
      inter_count += 1;
    };

    running = FALSE;

    /* restore coprocessor state */
    asm   frstor float_save.coprocessor_state
  }

/******************************************************/

  void set_timer( divisor )
    int divisor;
  {
    int cnt;
    int lo, hi;

    cnt = 65536 / divisor;

    lo = cnt % 256;
    hi = cnt / 256;

    outportb( 0x43, 0x36 );
    outportb( 0x40, lo );    /*write tic counter*/
    outportb( 0x40, hi );
  }

/******************************************************/







[LISTING NINE]


double x;                     /* DATAPOOL */
extern int user_define_divisor;

#define  m     1.0134145    /* define spring-mass constants */
#define  k     10.0
#define  zeta  0.01
#define  x_o   30.0

#define  frame_time 0.013725

double t = 0.0;   /* real-time */
double c1;    /* real-time constants */
double c2;
double c3;
double c4;

void set_up_user_real_time_task(){
  double omega;
  double temp;
  double sqrt();

  user_define_divisor = 4;   /* set user divisor counter */

  omega = sqrt( k / m );
  temp  = sqrt( 1.0 - zeta * zeta );

  c1 = - zeta * omega;     /* compute real-time constants */
  c2 = zeta * x_o / temp;
  c3 = temp * omega;
  c4 = x_o;
}

void set_down_user_real_time_task(){   /* no set down necessary */
}

void user_defined_real_time_task(){
  double cos();
  double sin();
  double exp();
                   /* spring-mass model */
  x = exp( c1 * t ) * ( c2 * sin( c3 * t ) + c4 * cos( c3 * t ) );

  t += frame_time;
}







[LISTING TEN]


#include "graphics.h"

#define FALSE  0
#define TRUE   -1

extern int not_done;
extern double x;   /* DATAPOOL */

int x_off = 320;
int y_off = 100;

stationary[11][4] = { {   0,   0,   0,  -5 },   /* base */
                      {   0,   0,   7,   0 },
                      { -40,  -5,  40,  -5 },
                      { -35,  -5, -30, -12 },
                      { -25,  -5, -20, -12 },
                      { -15,  -5, -10, -12 },
                      {  -5,  -5,   0, -12 },
                      {   5,  -5,  10, -12 },
                      {  15,  -5,  20, -12 },
                      {  25,  -5,  30, -12 },
                      {  35,  -5,  40, -12 } };

void set_up_user_background_task(){
  int  i, j;
  int  g_driver = EGA;
  int  g_mode   = EGAHI;
  char d_path[] = {""};
  int g_error;

  if( registerbgidriver( EGAVGA_driver ) < 0 ){   /* EGA driver */
    printf("ERROR: can't register ega/vga driver\n");
    exit();
  };

  initgraph( &g_driver, &g_mode, d_path );
  g_error = graphresult();
  if( g_error < 0 ){
    printf("ERROR: %s\n", grapherrormsg(g_error) );
    exit( 0 );
  };

  setcolor( YELLOW );

  for( i = 0; i < 2; ++i ){   /* setup spring */
    setactivepage( i );
    for( j = 0; j < 11; ++j ){
      line( stationary[j][0] + x_off, stationary[j][1] + y_off,
            stationary[j][2] + x_off, stationary[j][3] + y_off);
    };
  };

}

void set_down_user_background_task()
{
  closegraph();
}

double stretch[12][4] = { {  7.0,  0.0, -7.0,  5.0 },   /* spring */
                          { -7.0,  5.0,  7.0, 10.0 },
                          {  7.0, 10.0, -7.0, 15.0 },
                          { -7.0, 15.0,  7.0, 20.0 },
                          {  7.0, 20.0, -7.0, 25.0 },
                          { -7.0, 25.0,  7.0, 30.0 },
                          {  7.0, 30.0, -7.0, 35.0 },
                          { -7.0, 35.0,  7.0, 40.0 },
                          {  7.0, 40.0, -7.0, 45.0 },
                          { -7.0, 45.0,  7.0, 50.0 },
                          {  7.0, 50.0, -7.0, 55.0 },
                          { -7.0, 55.0,  7.0, 60.0 } };
int move[ 6][4] = { { -30,  5,  30,   5 },              /* mass */
                    { -30, 40,  30,  40 },
                    { -30,  5, -30,  40 },
                    {  30,  5,  30,  40 },
                    {   0,  0,   0,   5 },
                    {   0,  0,   7,   0 } };

void user_defined_background_task(){
  double ratio;
  int x_spring;
  int i, j;
  static int start = 1;
  static int buff[2][100][4];
  static int cnt[2];
  static int b = 0;
  static int p = 0;

  if( start ){
    set_page( p );

    p = ( p )? 0: 1;
    setactivepage( p );
  };

  if( kbhit() ){
    not_done = FALSE;
  };

  x_spring = x + 30.0;
  ratio = 1.0 + ( (double)x / 60.0 );

  cnt[b]  = 0;

  setcolor( RED );                             /* draw mass */
  for( i = 0, j = cnt[b]; i < 6; ++i, ++j ){
    buff[b][j][0] = move[i][0] + x_off;
    buff[b][j][1] = move[i][1] + y_off + x_spring + 30;
    buff[b][j][2] = move[i][2] + x_off;
    buff[b][j][3] = move[i][3] + y_off + x_spring + 30;
    line( buff[b][j][0], buff[b][j][1], buff[b][j][2], buff[b][j][3] );
  };
  cnt[b] += 6;

  setcolor( GREEN );                           /* draw spring */
  for( i = 0, j = cnt[b]; i < 12; ++i, ++j ){
    buff[b][j][0] = stretch[i][0] + x_off;
    buff[b][j][1] = (int)( stretch[i][1] * ratio ) + y_off;
    buff[b][j][2] = stretch[i][2] + x_off;
    buff[b][j][3] = (int)( stretch[i][3] * ratio ) + y_off;
    line( buff[b][j][0], buff[b][j][1], buff[b][j][2], buff[b][j][3] );
  };
  cnt[b] += 12;

  b = ( b )? 0: 1;

  set_page( p );
  p = ( p )? 0: 1;
  setactivepage( p );   /* switch page */

  if( ! start ){
    setcolor( BLACK );                /* undraw picture */
    for( i = 0; i < cnt[b]; ++i )
      line( buff[b][i][0], buff[b][i][1], buff[b][i][2], buff[b][i][3] );
  }else{
    start = 0;
  };
}

set_page(n)      /* set visual page */
  int n;
{
  int far *farptr;
  int addr;

  setvisualpage( n );

  farptr = (int far *)0x00400063;     /* status register address */
  addr = *(farptr) + 6;

  while( ( inport( addr ) & 0x08 ) == 0x08 );  /* while in vert retrace */
  while( ( inport( addr ) & 0x08 ) != 0x08 );  /* while not in vert retrace */

}











Copyright © 1989, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.