PC Parallel Port - Sonar Control and Precise Timing Using External Counters

copyright Floyd Ledgister, Christine M. Samuels, Hollis P Roach,
Towanda L. Malone and Peter H. Anderson, June 13, 96
the Chanda / ECSEL Project
Morgan State University
Baltimore, MD 21239

It is difficult to fetch times off a PC's system clock with any degree of resolution. We recently posed just how much resolution as an inquiry to a number of Internet news groups and the responses varied from 10 to 18 msec. Unfortunately, this poses a s erious limitation in many applications, particularly in Sonar ranging where 1.818 msec translates into a distance of one foot.

This discussion describes how times may be resolved to accuracies of in the tens of micro seconds using an external count down chain which is controlled and read by a PC parallel port. The discussion is extended to include interfacing a PC with a Polaro id 6500 Sonar Ranging Module.

The Importance of Timing.

Refer to Figure 1.

Volume I of "Use of a Printer Port for Control and Data Acquisition" discusses the use of interrupts to measure the time between two events. An example is offered involving the use of a 555 free running multi vibrator where the period is a function of a thermistor's resistance. By measuring the period, the resistance of the thermistor can be determined and from this, the temperature can be then calculated. Note that this approach uses time, rather than voltage to measure temperature.

This is an important concept. As electrical engineers, we are quick to convert physical quantities; temperature, light intensity, capacitance, etc to a voltage and then use an A/D converter. All to often, we overlook time, and historically, we have been able to measure time far more accurately (and inexpensively) than voltage.

The technique used in the 555 multi vibrator application is to fetch the time off the system clock on each interrupt exerted on the /ACK input to the printer port using the TurboC "ftime" function and then subtract the previous time from the current time . Unfortunately, if the period of the 555 is nominally 10.0 msec, there is a serious problem; the inability to resolve the time to anything better than 10 msec.

A work around in the 555 example is to use an external counter such that an interrupt occurs nominally every 8 seconds. For example, if a 555 running at 1.0 kHz drives a divide by 2^13 counter, the period is nominally 8.192 secs. Then the inability to r esolve time to anything better than 10 msec amounts to about 0.12 percent.

Of course, this length of time may not be practical in many applications where there is no periodicity as there is in the above example. Many practical applications are one time events. For example, dropping a translucent bar with equally spaced opaque bands through an opto interrupter so as to determine the gravitational constant g or with SONAR when a pulse is launched and the time for the echo is measured. An external countdown chain is an attractive alternative.

Use of an External Countdown Chain.

Consider the countdown circuitry illustrated in Figure 2. For the moment, ignore the interrupt source on the /ACK input to the parallel port.

Note that a TTL clock oscillator having a frequency of 3.2768 MHZ drives a CMOS 4020B 14-stage ripple counter. In the example, this counter is used as a divide by 128 counter. Thus, the output is 25.6 kHz corresponding to a period of 39.0625 usecs. As multiple outputs are not being decoded, we need not be concerned with the hazard states associated with ripple counters.

The output of the 4024 then drives an 8-bit synchronous counter (74LS590) which consists of an 8-bit counter and associated flip flops. Thus, the output of the 8-bit counter has a periodicity of 256 * 39.0625 usecs or 10 msec, with a resolution of 39.06 25 usecs.

When reading the count, the counter's content is copied to the internal flip flops by momentarily bringing printer port output Data_4 high and again low. The counter is then read by using Status Bits 7, 5, 4 and Control Bits 3, 2, 1 and 0. (Note that b it 6 on the Status Port, /ACK is reserved for interfacing with an interrupt source. Our choice of Data_4 was simply that we already had a stepping motor fixture configured for the lower four bits on the Data port).

Program EXT_TM1.C is included for illustration. The program simply fetches the count, performs a time wasting task, in this case an empty "for" loop, again fetches the count and reports the time difference.

Notes on program EXT_TM1.C.

Note that the count is read using bits 7, 5, 4 and 3 on the Status port and the lower four bits on the Control port. Recall that these four bits on the Control port may be used as inputs if the open collector outputs are driven high. This is done in lin e 22. Recall that as there are hardware inversions on bits 3, 1 and 0, these outputs are written as zeros to achieve the desired logic one at the output. This is the reason for the exclusive oring with 0x0b.

Refer to function get_count at lines 39 - 53. The content of the counter is first latched into the flip-flops by momentarily bringing Data_4 on the Data port to a high. In modifying the output, note that only the specific bit is modified using the & an d | operators at lines 43-46. That is, the other seven bits are left alone. Although, not really necessary in this application, this technique does become important when using different bits on the same port for many purposes; say using the higher bits to control this count down chain while using the lower nibble to control a stepping motor.

Note that in this routine, we declared the variable "data" globally, such that it could be changed in any function. Of course, the same could be accomplished declaring it in main and passing a pointer to each function or by first reading the data port pr ior to each output;


     outportb(DATA, inport(DATA) | 0x10);

Reading the eight bits involves reading the status bits. Note that bit 7 is inverted. Note that bit 6 was not used and thus bits 5, 4 and 3 are isolated and shifted into the 6, 5 and 4 bit positions of the result. The Control port is then read, and bit s 3, 1 and 0 are inverted. The result is then obtained by oring all of these together. Multiplexing is discussed elsewhere.

/*
* Program EXT_TM1.C
*

Illustrates use of external count down chain on printer port to
measure the time between two events with a resolution of 39.0625 usecs.

Program fetches count from count down chain.  Then performs a task.  In
this example this is simply a for (n=0 ... ) loop.  Then again fetches
count, calculates the difference and then the time.

* P. H. Anderson, MSU, 28 May, '96
*/

#include <stdio.h>                                            /* 1 */
#include <bios.h>                                             /* 2 */
#include <dos.h>                                              /* 3 */
                                                              /* 4 */
#define DATA 0x03bc                                           /* 5 */
#define STATUS DATA+1                                         /* 6 */
#define CONTROL DATA+2                                        /* 7 */
                                                              /* 8 */
#define TRUE 1                                                /* 9 */
#define FALSE 0                                               /* 10 */
                                                              /* 11 */
int get_count(void);                                          /* 12 */
int diff_count(int count2, int count1);                       /* 13 */
                                                              /* 14 */
int data = 0x00;                                              /* 15 */
                                                              /* 16 */
int main(void)                                                /* 17 */
{                                                             /* 18 */
   int count1, count2, count_diff, n;                         /* 19 */
   float t_diff;                                              /* 20 */
                                                              /* 21 */
   outportb(CONTROL, 0x0f^0x0b);                              /* 22 */
   /* set lower four bits on control port to logic one at     /* 23 */
   ** output.  note that this involves inverting bits 3,      /* 24 */
   ** 1 and 0 */                                              /* 25 */
                                                              /* 26 */
   count1 = get_count();  /* fetch count 1 */                 /* 27 */
                                                              /* 28 */
   for (n=0; n<30000; n++) /* loop */   ; /* do a task */     /* 29 */
                                                              /* 30 */
   count2 = get_count();  /* fetch the second count */        /* 31 */
   count_diff = diff_count(count2, count1);                   /* 32 */
                                                              /* 33 */
   t_diff = (float) count_diff * 39.0625e-6;                  /* 34 */
        /* calc and display t */                              /* 35 */
   printf("%x %x %f\n", count2, count1, t_diff);              /* 36 */
}                                                             /* 37 */
                                                              /* 38 */
int get_count(void)                                           /* 39 */
{                                                             /* 40 */
                                                    /* 41 */     
   int tmp1, tmp2, tmp3;                                      /* 42 */
   data = data | 0x10;                                        /* 43 */
   outportb(DATA, data);  /* latch data in 374 */             /* 44 */
   data = data & (~0x10);                                     /* 45 */
   outportb(DATA, data);                                      /* 46 */
   tmp1 = inportb(STATUS);                                    /* 47 */
   tmp2 = (tmp1^0x80)&0x80;  /* most sig bit */               /* 48 */
   tmp3 = (tmp1 << 1) & 0x70; /* bits 6, 5, 4 */              /* 49 */
   tmp1 = (inportb(CONTROL) ^ 0x0b) & 0x0f;                   /* 50 */
                  /* bits 3, 2, 1, 0 */                       /* 51 */
   return(tmp1 | tmp2 | tmp3);                                /* 52 */
}                                                             /* 53 */
                                                              /* 54 */
int diff_count(int count2, int count1)                        /* 55 */
{                                                             /* 56 */
   if (count2>count1)                                         /* 57 */
   {                                                          /* 58 */
      return(count2-count1);                                 /* 59 */
   }                                                          /* 60 */
   else                                                       /* 61 */
   {                                                          /* 62 */
      return(0x100+(count2-count1));                         /* 63 */
   }                                                          /* 64 */
}                                                             /* 65 */
                                                              /* 66 */
                                                              /* 67 */

The following routine, EX_TM2.C, illustrates measuring the time between interrupts. See Figure #2. Note that interrupt occurs on a negative going transition on the /ACK input (Status bit 6).

On interrupt, the state of the 74590 counter is stored in the internal flip-flops which are then read by the parallel port. On the next interrupt, the state of the 74590 is again stored in the flip-flops and then read by the printer port. The difference is then calculated by simply subtracting the two counts. The time difference is then calculated by multiplying by 39.0625 usecs.

Some notes.

In line 33, Control bit 4 (IRQ) is set to a logic one in addition to bringing Control outputs 3, 2, 1 and 0 to a logic one. This enables the interrupt circuitry associated with the /ACK input.

Note that as interrupts are presumably occurring at nominally 5.0 to 10.0 msec intervals, any time consuming printfs and disk file operations are avoided. Rather, ten measurements were performed and then printed.

Other than these minor variations, this routine is simply a meld of the program discussed above and program TIME_INT.C from Volume 1.

To avoid making what already appears as a rather complex routine even more complex, we left well enough alone and settled for ten time differences. However, it would now be an easy matter to operate on these time difference so as to determine the tempera ture of a thermistor or the gravitational constant g.

/*
* Program EXT_TM2.C
*

Uses interrupt service routine to note interrupt from printer port.
The interrupt is caused by a negative transition on /ACK input on
Printer Port.

On interrupt, fetches count off external counter and determines number
of counts between this and the previous interrupt.  Calculates the time
and saves in an array.  After ten events, prints the times and exits.

* P. H. Anderson, MSU, 27 May, '96
*/

#include <stdio.h>                                            /* 1 */
#include <bios.h>                                             /* 2 */
#include <dos.h>                                              /* 3 */
                                                              /* 4 */
#define DATA 0x0378                                           /* 5 */
#define STATUS DATA+1                                         /* 6 */
#define CONTROL DATA+2                                        /* 7 */
                                                              /* 8 */
#define TRUE 1                                                /* 9 */
#define FALSE 0                                               /* 10 */
                                                              /* 11 */
int get_count(void);                                          /* 12 */
int diff_count(int count2, int count1);                       /* 13 */
                                                              /* 14 */
void open_intserv(void);                                      /* 15 */
void close_intserv(void);                                     /* 16 */
void int_processed(void);                                     /* 17 */
void interrupt far intserv(void);                             /* 18 */
                                                              /* 19 */
int intlev = 0x0f;/*interrupt level associated with IRQ7*/    /* 20 */
void interrupt far (*oldfunc)();                              /* 21 */
                                                              /* 22 */
int int_occurred = FALSE;  /* Note global definitions */      /* 23 */
int data = 0x00;                                              /* 24 */
                                                              /* 25 */
int main(void)                                                /* 26 */
{                                                             /* 27 */
   int first=FALSE, n;                                        /* 28 */
   int count1, count2, count_diff;                            /* 29 */
   float t_diff[10];                                          /* 30 */
                                                              /* 31 */
   open_intserv();                                            /* 32 */
   outportb(CONTROL, 0x1f^0x0b);                              /* 33 */
   /* set bit 4 on control port (irq enable) to logic         /* 34 */
   ** one.  set lower four bits on control port to logic      /* 35 */
   ** one at output. note that this involves inverting bits   /* 36 */
                                                              /* 37 */
   ** 3, 1 and 0 */                                           /* 38 */
                                                              /* 39 */
   for(n=0; n&10; )                                           /* 40 */
   {                                                          /* 41 */
      if (int_occurred)                                      /* 42 */
      {                                                      /* 43 */
         int_occurred = FALSE;                               /* 44 */
         if (first == FALSE)                                 /* 45 */
         /* if this is the first interrupt, just fetch       /* 46 */
                    the count */                          /* 47 */
         {                                                   /* 48 */
             count2 = get_count();                          /* 49 */
             first=TRUE;                                    /* 50 */
         }                                                   /* 51 */
         else                                                /* 52 */
         {                                                   /* 53 */
             count1 = count2;  /* otherwise, save old time, /* 54 */
                                  fetch new */          /* 55 */
             count2 = get_count();                          /* 56 */
             count_diff = diff_count(count2, count1);       /* 57 */
             t_diff[n] = (float) count_diff * 39.0625e-6;   /* 58 */
               ++n;
          }                                                 /* 59 */
      }                                                      /* 60 */
   }                                                          /* 61 */
   close_intserv();                                           /* 62 */
   /* now print out the values */                             /* 63 */
   for(n=0; n<10; n++)                                        /* 64 */
   {                                                          /* 65 */
      printf("%d %f\n", n, t_diff[n]);                       /* 66 */
   }                                                          /* 67 */
                                                              /* 68 */
   return(0);                                                 /* 69 */
}                                                             /* 70 */
                                                              /* 71 */
                                                              /* 72 */
void open_intserv(void)                                       /* 73 */
/* enables IRQ7 interrupt.  On interrupt (low on /ACK) jump   /* 74 */
s                                                             /* 75 */
** to intserv. all interrupts disabled during this function   /* 76 */
;                                                             /* 77 */
** enabled on exit. */                                        /* 78 */
{                                                             /* 79 */
   int int_mask;                                              /* 80 */
   disable();  /*disable all ints*/                           /* 81 */
   oldfunc = getvect(intlev);  /* save any old vector*/       /* 82 */
   setvect (intlev, intserv); /* set up for new int serv */   /* 83 */
                                                              /* 84 */
   int_mask = inportb (0x21);  /* 1101 1111 */                /* 85 */
   outportb (0x21, int_mask & ~0x80);/*set bit 7 to zero */   /* 86 */
                                                              /* 87 */
                          /* -leave others alone*/       /* 88 */
   enable();                                                  /* 89 */
}                                                             /* 90 */
                                                              /* 91 */
void close_intserv(void)                                      /* 92 */
/* disables IRQ7 interrupt */                                 /* 93 */
{                                                             /* 94 */
   int int_mask;                                              /* 95 */
   disable();                                                 /* 96 */
   setvect(intlev, oldfunc);                                  /* 97 */
   int_mask = inportb (0x21) | 0x80;  /* bit 7 to one */      /* 98 */
   outportb (0x21, int_mask);                                 /* 99 */
   enable();                                                  /* 100 */
}                                                             /* 101 */
                                                              /* 102 */
void int_processed(void)                                      /* 103 */
/* signals 8259 in PC that interrupt has been processed */    /* 104 */
{                                                             /* 105 */
   outportb (0x20, 0x20);                                     /* 106 */
}                                                             /* 107 */
                                                              /* 108 */
void interrupt far intserv(void)                              /* 109 */
/* This is written by the user.  Note that the source of      /* 110 */
** the interrupt must be cleared and then the PC 8259         /* 111 */
** cleared using function int_processed().  */                /* 112 */
{                                                             /* 113 */
   disable();                                                 /* 114 */
   int_processed();                                           /* 115 */
   int_occurred = TRUE;                                       /* 116 */
   enable();                                                  /* 117 */
}                                                             /* 118 */
                                                              /* 119 */
int get_count(void)                                           /* 120 */
{                                                             /* 121 */
   int tmp1, tmp2, tmp3;                                      /* 122 */
   data = data | 0x10;                                        /* 123 */
   outportb(DATA, data);  /* latch data in 374 */             /* 124 */
   data = data & (~0x10);                                     /* 125 */
   outportb(DATA, data);                                      /* 126 */
   tmp1 = inportb(STATUS);                                    /* 127 */
   tmp2 = (tmp1^0x80)&0x80;  /* most sig bit */               /* 128 */
   tmp3 = (tmp1 << 1) & 0x70; /* bits 6, 5, 4 */              /* 129 */
   tmp1 = (inportb(CONTROL) ^ 0x0b) & 0x0f;                   /* 130 */
                           /* bits 3, 2, 1, 0 */         /* 131 */
   return(tmp1 | tmp2 | tmp3);                                /* 132 */
}                                                             /* 133 */
                                                              /* 134 */
int diff_count(int count2, int count1)                        /* 135 */
{                                                             /* 136 */
   if (count2>count1)                                         /* 137 */
   {                                                          /* 138 */
      return(count2-count1);                                 /* 139 */
   }                                                          /* 140 */
   else                                                       /* 141 */
   {                                                          /* 142 */
      return(0x100+(count2-count1));                         /* 143 */
   }                                                          /* 144 */
}                                                             /* 145 */
                                                              /* 146 */
                                                              /* 147 */

Timing Limitations.

To this point, the speed of the PC has not entered the discussion. PC speed becomes a consideration in the design in determining whether the PC can do all it has to do in processing one event and being ready for the next event. The EXT_TM2.C routine was run on about the oldest PC we could find. It worked for a periodic interrupt of 10.0 msecs. That is, this old PC was able to respond to the interrupt, go into the interrupt service routine and return, perform the get_count() function and the floating p oint multiplication and be positioned to entertain another interrupt within 10 msec. We then increased the interrupt rate and began to experience failures at just under 5.0 msecs.

What does this means for you. One might be tempted to reason that if this machine running at 8.0 MHZ could handle periodic interrupts at 6.0 msecs, a Pentium running at 80 MHZ could handle ten times the interrupt speed. However, this logic may be flawed as we are uncertain that CPU speed and ISA bus speed are one and the same. Rather, about the only conclusion we feel certain in offering is that if our old junk could handle interrupts at 5.0 ms for our code, we assume any machine you have can as well. I always encourage my students to be truthful and I have to say, I really can't offer much more.

It is worthy of note that in the case of measuring the time between multiple interrupts, the execution times between the interrupt event and the latching of the counter into the flip-flops would be the same from one interrupt event to the next and thus th e measured times would be very accurate.

Multiplexing.

In the timing circuit shown in Figure #2, no multiplexing was used. Thus, in an interrupt driven application which uses the /ACK input, all possible inputs on the printer port are used. Two other possibilities along with the revised code for the get_cou nt() function are shown in Figures #3 and #4.

Figure #5 presents a number of points for illustration.

1. In the above, the clock frequency was 3.2768 MHZ or a period of 0.30517578 usecs., which when multiplied by 32,768 (2^15) is exactly 10 ms. Thus, if 3.2768 MHZ is divided by 15 binary counter stages the period is 10 ms. In Figure #5, a clock frequen cy of 2.048 MHZ was used. This has a similar property. 2.048 MHZ / (2^11) = 1000 Hz. Thus, dividing by 11 binary counter results in a period of 1.0 ms. The use of the 2.048 MHZ clock in this circuit was intended for illustration only.

2. Note that two 74590 8-bit counters have been cascaded to implement a 16 bit counter. Thus, the period of the 16-bit counter is 32.0 ms (1/31.25 Hz) and the resolution is 0.976 usecs. We did not build this circuit and can't offer any opinions as to w hether such resolution makes sense on an old PC.

3. Note that the 74590 outputs are tri-state. In all previous designs, the outputs have been enabled by hard-wiring the G input to ground. However, in this design, either the outputs of the "low" or "high" counter are enabled using the parallel port o utput Data_7. Similarly, either the low or high nibble of the selected counter is selected using parallel output Data_6.

Sonar.

Figures #6A and #6B illustrate the circuitry used to control a Polaroid 6500 sonar ranging module. Sources where you can obtain these ranging modules are discussed below.

Note that the timing circuitry is similar to that shown in Figure #2, except that the multiplexing scheme is similar to that used in Figure #4. Note that the 74590 clocking is done by using the 6.4 kHz tap on the 4020 prescaler. This provides a period o f 40 ms with a resolution of 156.25 usecs.

The theory of the Polaroid 6500 Sonar Ranging Module is quite simple. The Sonar input INIT is brought high which causes a series of 16 sound pulses to be transmitted by the Sonar's transducer. The sound strikes the distant object and hopefully some is r eflected and is detected by the same transducer, which causes the Sonar's ECHO output to go high. [The module also provides features for over riding a blanking interval so as to detect objects at close range and for eliminating multiple echos. We did not use these features.]

In the case of a sonar ranging device, sound travels at nominally 1100 feet per second, or 550 round trip feet per second (1.818 ms / round trip foot). Thus, 40 msec with a resolution of 156.25 usecs translates into 22 feet with a resolution of 0.085 fee t (or about one inch).

Thus, in the following routine, SONAR.C, the INIT lead (controlled by Data_5) is brought high causing the pulse to be launched (line 26). The count of the 74590 is latched and read using the get_count() function described in Figure #4 (line 27).

Printer port input, /ACK is then constantly read looking for the receipt of ECHO from the Sonar (lines 31-36). Upon detection, the new count of the 74590 is latched and similarly read (line 48). The two counts are then subtracted, the elapsed time deter mined and the distance is calculated and displayed.

Note that in this routine, we did not use interrupts. Rather, the /ACK input is continually scanned for the presence of the ECHO (logic 1). If no Echo is detected after 3000 scans of the /ACK input, it is assumed none will occur (line 56).

If the interrupt feature had been used, an inverter between the ECHO output and the /ACK input would have been necessary as the interrupt is forced on the negative going transition on the /ACK input. Even in using an interrupt, some provision must be mad e for not receiving an Echo or the machine will hang waiting for an interrupt which will never occur.

Note that some inaccuracy in the timing creeps in. This is true whether the continual scan technique or the interrupt technique is used. Count_1 is captured at the moment Data_4 goes high after the launching of the sound pulse. Count_2 is captured when Data_4 is brought high after detection of the ECHO. There is certain to be amount difference in the execution times associated with those two different "afters".

However, for a specific PC, this difference in execution times will be close to constant and thus, the error in the distance is easily corrected by simply adding (or subtracting) a constant from the reported distance. For example, if a target is located at 5.0 feet and this program reports 5.215 feet, the correction is -0.215 feet.

/*
** SONAR.C
**
** Illustrates how to use Polaroid 6500 Ranging Module.  Makes measurements
** at 2 second intervals.
**
** Launches pulse (INIT to logic one) and fetches clock.  On receipt of
** ECHO going to logic one on /ACK input, fetches second count.  Computes
** difference, converts to time and converts to distance to target.
**
** Floyd Ledgister, Morgan State University, May 29, '96
**/

#include <stdio.h>                                            /* 1 */
#include <dos.h>                                              /* 2 */
                                                              /* 3 */
#define DATA 0x03bc                                           /* 4 */
#define STATUS DATA+1                                         /* 5 */
#define CONTROL DATA+2                                        /* 6 */
                                                              /* 7 */
void set_control_port(void);                                  /* 8 */
int get_count(void);                                          /* 9 */
int diff_time(int t2, int t1);                                /* 10 */
                                                              /* 11 */
int data = 0x00;                                              /* 12 */
                                                              /* 13 */
void main(void)                                               /* 14 */
{                                                             /* 15 */
   int count1, count2, count_d, n;                            /* 16 */
   float t_diff,dis;                                          /* 17 */
   set_control_port();                                        /* 18 */
   while(1)  /* about every two seconds */                    /* 19 */
   {                                                          /* 20 */
     delay(1000);                                            /* 21 */
     data=data & (~0x20);                                    /* 22 */
     outportb(DATA, data);                                   /* 23 */
     delay(1000);                                            /* 24 */
     data = data | 0x20;                                     /* 25 */
     outportb(DATA, data); /* launch pulse */                /* 26 */
     count1 = get_count();                                   /* 27 */
                                                              /* 28 */
     n=3000;                                                 /* 29 */
     /* maximum nuber of times to sample /ACK input */       /* 30 */
     while(1)                                                /* 31 */
     {                                                       /* 32 */
        if ((inportb(STATUS) & 0x40)!=0)                     /* 33 */
        {                                                    /* 34 */
          break; /* echo is detected */                     /* 35 */
        }                                                    /* 36 */
        else                                                 /* 37 */
        {                                                    /* 38 */
          --n;                                               /* 39 */
          if(n==0)                                          /* 40 */
          {                                                  /* 41 */
             break;                                         /* 42 */
          }                                                  /* 43 */
        }                                                    /* 44 */
      }                                                      /* 45 */
      if (n!=0)  /* an echo was received */                  /* 46 */
      {                                                      /* 47 */
         count2 = get_count(); /* get the second count */    /* 48 */
         count_d = diff_count(count2, count1);               /* 49 */
         t_diff = count_d * 156.25e-6;                       /* 50 */
         dis = t_diff / 1.818e-3;                            /* 51 */
         printf("the distance is %f\n",dis);                 /* 52 */
      }                                                      /* 53 */
      else                                                   /* 54 */
      {                                                      /* 55 */
         printf("No Echo received\n");                       /* 56 */
      }                                                      /* 57 */
   }                                                          /* 58 */
}                                                             /* 59 */
                                                              /* 60 */
void set_control_port(void)                                   /* 61 */
{                                                             /* 62 */
   outportb(CONTROL, 0x0f^0x0b);                              /* 63 */
            /* all outputs on control port to 1 */          /* 64 */
}                                                             /* 65 */
                                                              /* 66 */
int get_count(void) /* uses dual 4 to 1 multiplexer */        /* 67 */
{                                                             /* 68 */
   int hi_nibble = 0x00, lo_nibble = 0x00, tmp;               /* 69 */
                                                              /* 70 */
   data = data | 0x10;  /* clock count latched in ff */       /* 71 */
   outportb(DATA, data);                                      /* 72 */
   data = data & 0x3f;   /* Data_4 set to 0 */                /* 73 */
   outportb(DATA, data);                                      /* 74 */
                                                              /* 75 */
   data = data & 0x3f;  /* 0 0 on bits 7 6 */                 /* 76 */
   outportb(DATA, data);                                      /* 77 */
   tmp = (inportb(CONTROL) ^ 0x0b) & 0x03;                    /* 78 */
   lo_nibble = lo_nibble | ((tmp) & 0x01);                    /* 79 */
   hi_nibble = hi_nibble | ((tmp>>1)&0x01);                   /* 80 */
                                                              /* 81 */
   data = data & 0x3f | 0x40;  /* 0 1 on bits 7 6 */          /* 82 */
   outportb(DATA, data);                                      /* 83 */
   tmp = (inportb(CONTROL) ^ 0x0b) & 0x03;                    /* 84 */
   lo_nibble = lo_nibble | (((tmp) & 0x01)<<1);               /* 85 */
   hi_nibble = hi_nibble | (((tmp>>1)&0x01)<<1);              /* 86 */
                                                              /* 87 */
   data = data & 0x3f | 0x80;  /* 1 0 on bits 7 6 */          /* 88 */
   outportb(DATA, data);                                      /* 89 */
   tmp = (inportb(CONTROL) ^ 0x0b) & 0x03;                    /* 90 */
   lo_nibble = lo_nibble | (((tmp) & 0x01)<<2);               /* 91 */
   hi_nibble = hi_nibble | (((tmp>>1)&0x01)<<2);              /* 92 */
                                                              /* 93 */
   data = data & 0xf3 | 0xc0;  /* 1 1 on bits 7 6 */          /* 94 */
   outportb(DATA, data);                                      /* 95 */
   tmp = (inportb(CONTROL) ^ 0x0b) & 0x03;                    /* 96 */
   lo_nibble = lo_nibble | (((tmp) & 0x01)<<3);               /* 97 */
   hi_nibble = hi_nibble | (((tmp>>1)&0x01)<<3);              /* 98 */
                                                              /* 99 */
   return((hi_nibble << 4) | lo_nibble);                      /* 100 */
}                                                             /* 101 */
                                                              /* 102 */
int diff_count(int count2, int count1)                        /* 103 */
{                                                             /* 104 */
   if (count2>count1)                                         /* 105 */
   {                                                          /* 106 */
      return(count2-count1);                                 /* 107 */
   }                                                          /* 108 */
   else                                                       /* 109 */
   {                                                          /* 110 */
      return(0x100+(count2-count1));                         /* 111 */
   }                                                          /* 112 */
}                                                             /* 113 */
                                                              /* 114 */
                                                              /* 115 */

Mapping a Room and Associated Graphics.

Christine Samuels, a Senior EE student, mounted a Sonar transducer on a platform which was turned using a stepping motor. The motor was controlled using the lower four bits on the Data Port. See Figure #7. The motor was turned slightly and then stoppe d to avoid any motor noise from falsely simulating an echo, and a distance measurement was made. This was repeated at a number of points, and the results were plotted as a polygon with the center of the screen corresponding to the transducer.

The results were mixed. When a Sonar pulse hit a smooth surface such as a chalk board at any appreciable angle, virtually none of the energy was returned to the Sonar. Thus, no echo was received and the distance was assumed to be the maximum limit.

However, our intent in developing this manual is not to make the reader an expert in navigation, but to encourage tinkering.

Her material may be useful to the reader. It is located at;

http://www.eng.morgan.edu/~samuels/sonar.html

Use of the Sonar for Fluid Level Detection.

The Sonar Ranging Module was also used to detect the amount of water in a coffee can as shown in Figure #8. Note that in the above discussion, a period of 40 msecs was used so as to permit distance measurements to nominally 22 feet (40 ms / 1.818 ms/foot ) with a resolution of 156.25 usecs corresponding to 0.085 feet or about 1.0 inches. In this application, we desired to measure the depth more accurately and set the clock rate to 19.53 usecs by tapping off the Q5 output of the ripple counter. Thus, the periodicity of the counter is 5 msec corresponding to a distance of 2.75 feet with a resolution of 19.53 usecs corresponding to 0.125 inches.

Note that the minimum range of the Sonar is 1.33 feet. This results from the blanking of the receiver for 2.18 msec to prevent any ringing of the transducer from being interpreted as an echo. Thus, in our arrangement, we were able to measure liquids in a range of 1.33 to 2.75 feet from the transducer with an uncertainty of 0.125 inches.

The actual depth was determined by ranging on a float on the bottom of an empty coffee can. On adding water, the distance from the transducer to the surface of the float was measured and this was subtracted from the distance to the top of the coffee can.

The program is not included as it is very similar to distance.c.

Miscellaneous Notes.

It is critical to note that the Echo output of the Sonar module is open-collector and thus in interfacing with the /ACK parallel port input, this must be pulled up to +5V through a pull-up resistor as shown in Fig #6B.

The Sonar ranging module generates 300-400 V pulses which are applied to the transducer to transmit high level sound pulses. Thus, the Polaroid 6500 module requires peak currents of 2000 mA. We were initially using a supply which limited current to 0.5 A and were convinced the unit was not working.

Although we did not have a problem with these 300 V transients inducing noise into the digital logic associated with the counter, it is wise to keep the transducer leads well away from the TTL logic. We also used separate power supply feeds, one for the Sonar unit itself and another for the TTL logic associated with the counter circuitry to avoid the current spikes from getting into the TTL counters.

In the receive mode, the transducer leads carry the very small signal associated with the echo. Although we did not have a problem with the TTL coupling into the transducer leads, it is a possibility. Again, isolate the TTL from the transducer leads.

We have yet to look at using multiple transducers which are switched to the same 6500 module so as to rapidly perform measurements in different directions. We have followed discussions of frustration by others on various Internet news groups with attempt ing to perform this switching using semiconductor devices. The transducer leads are rather unique in handling 300-400 V pulses during the transmit and very low level signals when receiving the echo and we would be inclined to avoid semiconductor switches and use mechanical relays with contact ratings greater than 3A. Avoid dry-reed contacts. A typical switching circuit is shown in Figure #9.

Sources of Parts.

All of the parts associated with the timing circuitry are available from virtually anyone. We happen to prefer Jameco as the price is usually right, they ship quickly and with an occasional exception, they have everything in stock.

We used a TTL clock oscillator having a frequency of 3.2768 MHZ which is available from DigiKey. This frequency is 100 * 2^15. That is, if 3.2768e6 is divided by 2^15 it produces a square wave of 100 pulses per second or a period of 10 ms. Our only rea son for doing this was for clarity in the discussion.

The Polaroid 6500 may be obtained from;

Wirz Electronics, 6100 Pershing, #2-A, St Louis, MO, Tel (314) 727-7038, http://userfs.cec.wustl.edu/~blw2. Ben Wirz sells the board ($35.00) and transducer ($20.00) or both ($50.00).

Circuit Cellar, Inc, 4 Park Street, Vernon, CT 06066, Tel (860) 875-2751, FAX (860) 872-2204. Order T101 Sonar Ranging Kit which includes the 6500 Board, one transducer and documentation ($79.00).

Other Resources.

Although URLs tend to go out of date quickly, the following are noted;

For More Information on Interfacing with the Parallel Port.

This discussion is a part of the Chanda / ECSEL Project in the Department of Electrical Engineering at Morgan State University to encourage tinkering by students using an IBM clone printer port for interfacing with real world devices. A 125 page manual titled, "Use of an IBM Printer Port for Control and Data Acquisition" is available from the authors for $10.00 plus $3.00 postage.

The material is being developed for undergraduate engineering and computer science majors and assumes a knowledge of C programming, some digital logic and some electrical circuit circuit analysis.

Topics include the basics of the printer port, how to determine the address locations, how to write and read, bit manipulation, how to interface with a breadboard, how to use a junk PC power supply, the design of driver circuits, opto-isolators, groundin g and other noise considerations, expanding the number of output bits, the use of multiplexers, control of a variable speed DC motor, control of a stepping motor, infrared remote control, radio remote control, D/A and A/D. Other topics include, the acqui sition of data using an NTC thermistor, forcing interrupts and use of interrupts to measure temperature.

This discussion will be included in a new manual scheduled for release in Septembe, 96.

For more information, see http://www.access.digex.net/~pha or send e-mail to pha@eng.morgan.edu.