
## 8.6  DYNAMIC FAST TICK PERIODS

In the Morse code player sample program, once the new fast tick rate has been
chosen and programmed, it is not modified until the program terminates.  The
interrupt keeps occurring regularly at the fast tick rate.  However, it is
possible to dynamically change the fast tick rate on a per-interrupt basis.

There are several reasons why you might want to do this - I can think of
four applications, there may be more:

         You might want to create a signal with an uneven or completely
          arbitrary duty cycle, such as 5 ms high, 40 ms low (this example
          could also be done using a constant fast tick at 5 ms intervals
          and counting eight interrupts to get the 40 ms delay),

         You might be using the timer interrupt to schedule things which
          happen at irregular intervals, with some long gaps, some short,

         You might want your background interrupt routine to be able to
          adjust its speed according to user actions, such as keypresses
          which control the program 'speed',

         You might want an exact number of interrupts per second, which
          is not possible with a fixed divisor - see section  8.7 for
          a sample program that does this.

All of these requirements can be handled in the same way.  The technique
involves the interrupt routine adjusting the value in the Reload register
according to its requirements, to adjust the period between interrupts in a
dynamic fashion.

When the fast timer tick interrupt handler reprograms the Reload register, the
new Reload register value does not affect the current countdown in progress,
i.e. the length of time until the next interrupt, it affects the length of
time between the next interrupt and the interrupt after that.  In other words,
you could say there is a one interrupt delay before the new value takes effect.

## 8.7  SAMPLE PROGRAM: DYNAMIC FAST TICK INTERRUPT HANDLER

This sample program gives a fast tick rate of exactly 1000 fast ticks per
second, using an effective divisor of 1193.18166666....

This cannot be achieved with a static divisor - the closest static divisors
of 1193 and 1194 produce 1000.152277 and 999.3146287 interrupts per second
respectively.  To get exactly 1000 fast ticks per second, the divisor must
be changed dynamically to give an effective divisor of 1193.181666...  by
cycling through the appropriate sequence of 1193 and 1194 divisors.  Over a
short period of time, the tick rate will rapidly approach exactly 1000 ticks
per second (ignoring the error due to crystal inaccuracies, etc).

The sequence of divisors is determined as follows.  For 1000 ticks per second
the divisor is 1193.18166666... which is 1193 plus 9/50 (0.18) plus 1/600
(0.0016666...), which is also 1193 plus 1/5 minus 1/50, plus 1/600.

Count every fifth interrupt.  On four out of every five interrupts, use a
divisor of 1193, but on every fifth interrupt, when the divide-by-five counter
carries, prepare to use 1194, and count a divide by 10 counter (which is really
dividing by 50).  If the counter _doesn't_ carry, use 1194.  These two counters
in combination add the 9/50.  If the divide by 10 counter carries, prepare to
use 1193, and count down a divide by 12 counter, which is actually counting
1/600ths; if it carries, use 1194.

A similar approach could be used to get 200 fast tick interrupts per second
(i.e. a 5ms fast tick interval).  The divisor is 5965.90833333333, which is
5965 plus 9/10 plus 1/120, so you would use 5966 for 9 of every 10 cycles and
use 5965 on the tenth, except if it is the twelfth tenth cycle in which case
use 5966.

Mode two must be used for this technique.  See the description of behaviour
with odd divisors in section  7.8.5 for the reasons.

See section  6.22 for the explanation of the pushf/cli/popf technique.

-------------------------------- snip snip snip --------------------------------
/*
Sample program #14
Demonstrates dynamic timer tick rates
Part of the PC Timing FAQ / Application notes
By K. Heidenstrom (kheidens@actrix.gen.nz)

Save and assemble the critical error module CRIT_ERR
Save this sample code to SAMPLE14.C
Compile this module with:
        bcc -c -I<inc_path> -ms sample14.c
Link the modules with:
        tlink /c /x <c0_path>\c0s.obj sample14.obj crit_err.obj,
                sample14, nul, <lib_path>\cs
Where inc_path is the path to your C header files, c0_path is the path to your
startup modules C0x.OBJ and lib_path is the path to your C libraries Cx.LIB.
*/

#pragma inline;         /* Required for asm pushf, popf, and cli */

#include <bios.h>       /* Needed for bioskey() */
#include <dos.h>        /* Needed for MK_FP() */
#include <io.h>         /* Needed for _write() */
#include <stdio.h>      /* Needed for printf() */
#include <stdlib.h>     /* Needed for exit() */

#define FALSE 0
#define TRUE 1

#define STDERR 2        /* DOS handle for standard error */

#define BASETICK 1193

void crit_err_intercept(void);                  /* Provided in CRIT_ERR.OBJ */
unsigned int is_at_crit_prompt(void);           /* Provided in CRIT_ERR.OBJ */

typedef void interrupt (far *intfuncp)();       /* Pointer to interrupt handler */

intfuncp old_int8 = (intfuncp)0xFFFFFFFFL;

static volatile unsigned long milliseconds = 0; /* Milliseconds counter */

/* The interrupt handler is responsible for updating the tick divisor to give
   exactly 1000 ticks per second.  It also increments a 32-bit counter which
   is used by the mainline.  */

void interrupt int8_handler(void) {
        static unsigned int div_5 = 2;
        static unsigned int div_5_10 = 5;
        static unsigned int div_5_10_12 = 6;
        static unsigned int int8sched = 0;
        static unsigned int fastdiv = 0;
        asm {
                mov     ax,1193         /* Prepare to use 1193 */
                dec     [div_5]         /* Count down divide by 5 */
                jns     GotNewDiv       /* If not reached one fifth yet */
                mov     [div_5],4       /* Reset dividing register */
                inc     ax              /* Prepare to use 1194 */
                dec     [div_5_10]      /* Count down nested divide by 10 */
                jns     GotNewDiv       /* If not reached 1/10 of 1/5 yet */
                mov     [div_5_10],9    /* Reset dividing register */
                dec     ax              /* Prepare to use 1193 */
                dec     [div_5_10_12]   /* Count down nested divide by 12 */
                jns     GotNewDiv       /* If not reached 1/12 of 1/10 of 1/5 */
                inc     ax              /* The 1/600th! */
                mov     [div_5_10_12],11 /* Reset dividing register */
                }
GotNewDiv:
        asm     {
                cmp     ax,[fastdiv]    /* Got divisor in AX - did it change? */
                je      SameDiv         /* If not, don't reprogram CTC 0 */
                mov     [fastdiv],ax    /* Store new value */
                out     40h,al          /* Write new lobyte */
                mov     al,ah           /* Get hibyte */
                out     40h,al          /* Write new hibyte */
                }
SameDiv:                                /* End of inline assembly */
        ++milliseconds;                 /* Increment millisecond count */
        int8sched += fastdiv;
        if (int8sched < fastdiv) {      /* If carried */
                (old_int8)();           /* Chain to BIOS */
                }
        else
                /** note - may not support Microchannel machines */
                outportb(0x20, 0x20);   /* Send EOI if not chaining */
        return;                         /* From interrupt */
        }

void restore_normal(void) {
        asm pushf;
        asm cli;
        outportb(0x43, 0x36);
        outportb(0x40, 0);
        outportb(0x40, 0);              /* Restore normal divisor */
        asm popf;
        return;
        }

void abort_cleanup(int dos_is_safe) {
        if (dos_is_safe) {
                if (old_int8 != (intfuncp)0xFFFFFFFFL) {
                        setvect(0x08, old_int8);
                        old_int8 = (intfuncp)0xFFFFFFFFL;
                        }
                }
        else {
                if (old_int8 != (intfuncp)0xFFFFFFFFL) {
                        *((intfuncp far *)MK_FP(0, 0x08 << 2)) = old_int8;
                        old_int8 = (intfuncp)0xFFFFFFFFL;
                        }
                }
        restore_normal();
        return;
        }

void interrupt ctrl_c_handler(void) {
        static char message[] = "\r\nProgram terminated by Ctrl-Break or Ctrl-C\r\n";
        if (is_at_crit_prompt())
                abort_cleanup(FALSE);
        else {
                abort_cleanup(TRUE);
                _write(STDERR, &message, sizeof(message));
                }
        exit(255);
        }

void poll_exit(void) {
        if (bioskey(1)) {
                if ((bioskey(0) & 0xFF) == 27) {
                        setvect(0x08, old_int8);
                        old_int8 = (intfuncp)0xFFFFFFFFL;
                        restore_normal();
                        exit(0);
                        }
                }
        return;
        }

void main(void) {
        unsigned long ms;

        printf("Sample program #14 - Millisecond timer demonstrating dynamic timer tick\n");
        printf("Part of the PC Timing FAQ / Application notes\n");
        printf("By K. Heidenstrom (kheidens@actrix.gen.nz)\n\n");
        printf("Press <Esc> to exit\n\n");

        crit_err_intercept();           /* Trap critical errors */
        setvect(0x23, ctrl_c_handler);  /* Trap Ctrl-C interrupt */
        old_int8 = (intfuncp)getvect(0x08);
        setvect(0x08, int8_handler);

        asm cli;
        outportb(0x43, 0x34);           /* Must use mode two! */
        outportb(0x40, BASETICK & 0xFF);
        outportb(0x40, BASETICK >> 8);
        asm sti;

        while (1) {
                asm cli;
                ms = milliseconds;
                asm sti;
                printf("%010ld ms\r", ms);
                poll_exit();
                }
        }
-------------------------------- snip snip snip --------------------------------

Note the order in which things are restored to normal in poll_exit().  The call
to restore_normal() to set the tick rate back to normal, must appear after the
fast tick handler has been disconnected.  If the fast tick handler was still
connected after the tick rate was set to normal, it could reprogram the CTC
again, and the program would terminate with the tick running with a divisor of
1193 or 1194 (at roughly 1ms intervals)!

This program could be used to measure the execution time of another program
with 1ms resolution, provided that the other program did not use CTC channel
zero itself, and provided that the other program did not lock interrupts out
for more than 1ms at a time.

