Periodic Timing using Interrupts

copyright, Peter H. Anderson, Dept of Electrical Engineering
Morgan State University, Baltimore, MD 21239
Jan 5, 97

Figure #1 illustrates an arrangement to generate an interrupt at precise periodic intervals determined by an external clock and by eight DIP switches.

I have not included specific applications of this capability.

The heart of the design is a 74LS592 8-bit counter which may be preloaded by manipulating the RCK and /CLOAD inputs. The counter is clocked at CLK and when the counter reaches the terminal count (0xff), the RCO output goes low causing an interrupt on the /ACK parallel port input.

Thus, if preloaded with 0xfa, the counter will then clock to 0xfb, 0xfc, 0xfd, 0xfe and finally to 0xff at which time an interrupt occurs. Thus, the time from the preload to the interrupt is 5 times the clock period fed to the 74LS592.

In Figure #1 the 74LS592 input clock is 250 Hz (T=4.0 msec). It is derived from a 2.048 MHZ clock oscillator and a 14 stage ripple counter. Thus, in the above example, with a preload of 0xfa, interrupt will occur 20 ms later.

The data is preloaded into registers in the 74LS592 on the positive transition of the RCK input. However, this is not propagated to the counter chain until /CLOAD is brought low. Thus, the sequence;

RCK  /CLOAD

0    1
1    1    RCK transition from 0 to 1.  Data transferred to registers. 
0    0    Registers now loaded to counter.
0    1    End of clock load.

Note that this is easily done using Data Port outputs Data_1 and Data_0.

     outportb(DATA, 0x01);
     outportb(DATA, 0x03);
     outportb(DATA, 0x00);
     outportb(DATA, 0x01);

However, if other outputs on the data port; e.g., Data_2 - Data_7, etc are used to control other things, the following sequence will not disturb the upper bits.

     outportb(DATA, (data & 0xfc) | 0x01);
     outportb(DATA, (data & 0xfc) | 0x03);
     outportb(DATA, (data & 0xfc) | 0x00);
     outportb(DATA, (data & 0xfc) | 0x01);

In each case, the upper six bits on the Data port are retained. The two least significant bits are set to zero and then logically ored with the desired two bits.

Preload Vaue set Using Switches

In Figure #1, the value which is preloaded into the counter is set by switches. This assumes the user is using this concept in an application where the desired periodicity of the interrupt is always the same. (An arrangement for setting this from the Data port is discussed later).

A typical program is illustrated in 592.C. All the program does is print a dot every 1.000 seconds. Note that the switches are set to 0x05 (255 decimal minus 250 decimal. Thus, the count begins at 5 and interrupt occurs 250 clock ticks later.

Note that the material relating to interrupts is discussed in Vol 1.

/*
* Program 592.C
*
* Prints a dot every 1.000 seconds using periodic interrupts.
*
* P. H. Anderson, MSU, 5 Jan, '97
*/

#include <stdio.h>
#include <dos.h>
#include <conio.h>

#define DATA 0x03bc
#define STATUS DATA+1
#define CONTROL DATA+2

#define TRUE 1
#define FALSE 0

void open_intserv(void);
void close_intserv(void);
void int_processed(void);
void interrupt intserv(void);
void preload_counter(void);

int intlev = 0x0f;/*interrupt level associated with IRQ7*/
void interrupt far (*oldfunc)();

int int_occurred = FALSE;  /* Note global definitions */
int data_byte = 0x00, control_byte = 0x00;

int main(void)
{
   int n;
   clrscr();
   open_intserv();
   /*IRQ enabled on parallel port */
   outportb(CONTROL, (control_byte | 0x10)^0x0b);
   preload_counter(); /* fetch the switches */
   enable(); /* enable interrupts */
   for(n=0; n<50; ) /* do it 50 times */
   {
      if(int_occurred)
      {
         preload_counter();
         ++n;
         int_occurred = FALSE;
         printf(".");
      }
   }
   close_intserv();
   outportb(CONTROL, (control_byte & (~0x10))^0x0b); 
                            /*IRQ set to zero */
}

void preload_counter(void)
{
   outportb(DATA, data_byte & 0xfc | 0x01);
   outportb(DATA, data_byte & 0xfc | 0x03);
   outportb(DATA, data_byte & 0xfc); /* 0 0 */
   outportb(DATA, data_byte & 0xfc | 0x01);
}

void interrupt intserv(void)
/* This is written by the user.  Note that the source of
** the interrupt must be cleared and then the PC 8259
** cleared using function int_processed().  */
{
   disable();
   int_processed();
   int_occurred = TRUE;
   enable();
}

void open_intserv(void)
/* enables IRQ7 interrupt.  On interrupt (low on /ACK) jumps
** to intserv. all interrupts disabled during this function;
** enabled on exit. */
{
   int int_mask;
   disable();  /*disable all ints*/
   oldfunc = getvect(intlev);  /* save any old vector*/
   setvect (intlev, intserv); /* set up for new int serv */
   oldfunc = getvect(intlev);  /* save any old vector*/
   int_mask = inportb (0x21);  /* 0111 1111 */
   outportb (0x21, int_mask & (~0x80));/*set bit 7 to zero */
                /* -leave others alone*/
   int_mask = inportb (0x21);  /* 0111 1111 */
   enable();
}

void close_intserv(void)
/* disables IRQ7 interrupt */
{
   int int_mask;
   disable();
   setvect(intlev, oldfunc);
   int_mask = inportb (0x21) | 0x80;  /* bit 7 to one */
   outportb (0x21, int_mask);
   enable();
}

void int_processed(void)
/* signals 8259 in PC that interrupt has been processed */
{
   outportb (0x20, 0x20);
}

One might argue that the timing is somewhat inaccurate as the time from the preload to the first clock pulse may be anything from a full pulse to next to nothing. This is certainly true on the first preload. However, thereafter, when operated in a periodic mode, it is very accurate provided that after interrupt, the counter is again preloaded before the next clock pulse.

Programmable Preload.

Figure #2 illustrates an arrangement where the DIP switches have been replaced with outputs of a 74LS259 addressable latch. This permits the user to modify the interrupt timing under software control.

Note that the number of clock ticks in the desired delay between interrupts is passed to function preload_counter(). The actual value to be preloaded is 255 minus the desired number of clock ticks.

In function preload_counter, the preload value is first written to the 74259 using function out_latch_byte(). The use of a 74LS259 is treated in detail in another section. Note that for each bit, Data_5 is high, the state of the specific logic bit is output on Data_7 and the address of the bit on Data_2 - Data_4. Data_5 is then brought momentarily low, causing the logic bit to appear on the desired output of the 259.

When all eight bits have been set, the 592 is preloaded in the same manner as described in the program using the fixed switches.

/*
** Program 592_LOAD.C
**
** Illustrates how to program the preloaded value.
**
** P. H. Anderson, MSU, 5 Jan, '97
*/

#include <stdio.h>
#include <dos.h>
#include <conio.h>

#define DATA 0x03bc
#define STATUS DATA+1
#define CONTROL DATA+2

#define TRUE 1
#define FALSE 0

void open_intserv(void);
void close_intserv(void);
void int_processed(void);
void interrupt intserv(void);
void preload_counter(int clk_ticks);
void out_latch_byte(int value);
void output_bit (int bit_pos, int bit_state);

int intlev = 0x0f;/*interrupt level associated with IRQ7*/
void interrupt far (*oldfunc)();

int int_occurred = FALSE;  /* Note global definitions */
int data_byte = 0x00, control_byte = 0x00;

int main(void)
{
   int n;
   clrscr();
   open_intserv();
   /*IRQ enabled on parallel port */
   outportb(CONTROL, (control_byte | 0x10)^0x0b);
   preload_counter(250);
   enable(); /* enable interrupts */
   for(n=0; n<50; )  /* do 50 times */
   {
      if(int_occurred)
      {
         preload_counter(250);
         n++;
         int_occurred = FALSE;
         printf(".");
      }
   }
   close_intserv();
   outportb(CONTROL, (control_byte & (~0x10))^0x0b);
}

void preload_counter(int clk_ticks)
{
   int value;
   value = 255 - clk_ticks;
   out_latch_byte(value);
   outportb(DATA, data_byte & 0xfc | 0x01);
   outportb(DATA, data_byte & 0xfc | 0x03);
   outportb(DATA, data_byte & 0xfc); /* 0 0 */
   outportb(DATA, data_byte & 0xfc | 0x01);
}

void out_latch_byte(int value)
/* outputs state of each of the 8 bits */
{
   int bit_state, bit_pos;
   for(bit_pos=0; bit_pos<8; bit_pos++)
   {
     bit_state = value & 0x01;
     output_bit (bit_pos, bit_state);
     value = value>>1;
   }
}

void output_bit (int bit_pos, int bit_state)
{
/* sets output at defined bit position (0-7) to 1 or 0 */

   outportb(DATA, data_byte | 0x20);/* Data 5 set to one */
   data_byte =
      (data_byte & 0x03) | (bit_state <<7)
      | 0x20 | (bit_pos<<2);
   outportb(DATA, data_byte);
   data_byte = data_byte & (~0x20); /* bring Data_5 low */
   outportb(DATA, data_byte);
   data_byte = data_byte | 0x20;  /* and then high */
   outportb(DATA, data_byte);
}

void interrupt intserv(void)
/* This is written by the user.  Note that the source of
** the interrupt must be cleared and then the PC 8259
** cleared using function int_processed().  */
{
   disable();
   int_processed();
   int_occurred = TRUE;
   enable();
}

void open_intserv(void)
/* enables IRQ7 interrupt.  On interrupt (low on /ACK) jumps
** to intserv. all interrupts disabled during this function;
** enabled on exit. */
{
   int int_mask;
   disable();  /*disable all ints*/
   oldfunc = getvect(intlev);  /* save any old vector*/
   setvect (intlev, intserv); /* set up for new int serv */
   oldfunc = getvect(intlev);  /* save any old vector*/
   int_mask = inportb (0x21);  /* 0111 1111 */
   outportb (0x21, int_mask & (~0x80));/*set bit 7 to zero */
                          /* -leave others alone*/
   int_mask = inportb (0x21);  /* 0111 1111 */
   enable();
}

void close_intserv(void)
/* disables IRQ7 interrupt */
{
   int int_mask;
   disable();
   setvect(intlev, oldfunc);
   int_mask = inportb (0x21) | 0x80;  /* bit 7 to one */
   outportb (0x21, int_mask);
   enable();
}

void int_processed(void)
/* signals 8259 in PC that interrupt has been processed */
{
   outportb (0x20, 0x20);
}