[comp.sys.handhelds] HPRead.c v1.3

pete%slack.UUCP@cs.utah.edu (Pete Ashdown) (02/16/90)

Here is HPREAD v1.3 for all who have been requesting it.  It is Turbo C source
for the IBM.  I'll post the uuencoded compiled version in the next message for
people who don't have Turbo C.
---------------------------------->8 Cut Here 8<-----------------------------

/*

HPREAD
Copyright (C) 1990  Mark Adler  Pasadena, CA  All rights reserved.

This source file may be freely copied and distributed, so long as it
is not modified in any way.  No part of this program may be used in
any product for sale without the written permission of the author.

This program is user supported (shareware), which means that users
are expected to make a (modest) contribution of $5 to the author. 
The check or money order should be made out to Mark Adler, and sent
to:

        Mark Adler
        P.O. Box 60998
        Pasadena, CA 91116

The $5 entitles you to the sense of well-being that comes from
knowing that you have supported this sort of independent effort to
make your life easier, and that you are encouraging the continuing
enhancement of this program.  Feel free to send comments and
suggestions on HPREAD to the same address, whether or not you are
making the $5 contribution.

Version history -

1.0     13 Jan 1990     First public version.
1.1     14 Jan 1990     Improve error correction and correct and
                        improve the documentation.
1.2     22 Jan 1990     Defer decoding and error correction until all
                        data is received, unless the new /f option is
                        specified, in which case decode on the fly.
                        Count successfully corrected ("fixed") bytes.
                        Distinguish imbedded from trailing errors.
                        Disable interrupts during transfers, new /e
                        option enables.
                        Check compiler and memory model during compile.
1.3     31 Jan 1990     Optionally restore the timer count from the
                        real-time cmos clock (if an AT or greater) after
                        a transfer.

Thanks to Paul Dujmich and Steve Haehnichen for providing test
results on HPREAD, and to Paul for suggesting a schematic in the
documentation.


HPREAD reads data sent by infrared pulses from an HP calculator (such
as an HP-28S) to a Radio Shack $3.49 GP1U52X Infrared Receiver
(catalog #276-137).  The output pin of the receiver is connected to a
a bit of an input port on an IBM PC or compatible.  HPREAD then reads
from the specified port and bit and writes the received data to
stdout.  The data is terminated by a pause of more than three
seconds.  HPREAD also writes the number of bytes transferred and the
number of unrecoverable errors to stderr.  For example:

     C>hpread >foo
     1043 bytes transferred (17 fixed) with 0 errors

writes the file "foo" with the transferred data.  By default, HPREAD
converts any 0x04 or 0x0A characters that the HP sends into a new
line sequence (carriage return, line feed).  If the "/n" option is
specified, no translation of the data is performed.

By default, HPREAD assumes that the receiver is connected to the
parallel printer port's ERROR* line (pin 15 on the DB-25).  Some
parallel printers supply +5V power on one of the lines of the
printer-end connector.  For example, my printer (a C.Itoh 8510A)
provides +5V at 50 mA on pin 18 of the 36 pin Centronics connector
(NOT the DB-25).  This can truly minimize the external hardware for
the detector.  Just put a switch on the ERROR* line to the computer
to select either the printer's ERROR* line or the output of the
detector, and connect the power and ground of the detector to the
printer's ground and +5V lines.  (For this to work, of course, the
printer must be on.)  Schematic:

                               SPDT switch
     Printer (DB-25) pin 15 -----*      (used to go to port pin 15)
     Detector Vout (pin 1) ------*<---o------ Parallel port pin 15
     Detector GND (pin 3) ------------------- Parallel port pin 18
     Detector Vcc (pin 2) ------------------- Printer supplied +5v
     Metal case of detector ----------------- Detector GND (pin 3)

If an easy source of +5V is not available, then you only need three
more parts to supply it.  An AC adapter that puts out DC anywhere in
the range of +6V to +30V (whatever current it puts out is enough), a
0.047 uF or 0.1 uF capacitor, and a 7805 or 78L05 voltage regulator. 
You can get all of this from (you guessed it) Radio Shack.  (No, I do
not have stock in Radio Shack.  It's just it seems there are more of
them than McDonald's.)  Connect the output of the AC adapter to the
input of the voltage regulator with the capacitor across that (and
near the regulator).  Make sure to check the polarity of the AC
adapter---some put the positive lead and some put the negative lead
on the outside of the connector.  Connect the output of the regulator
to the detector's power and ground.  An output capacitor is not
needed, since the detector has a built in 5 uF capacitor for power
buffering.  Schematic:

                               SPDT switch
     Printer (DB-25) pin 15 -----*      (used to go to port pin 15)
     Detector Vout (pin 1) ------*<---o------ Parallel port pin 15
     Detector GND (pin 3) ------------------- Parallel port pin 18
     Adapter +DC--------|---Regulator In
              0.047 uF ===  Regulator Out---- Detector Vcc (pin 2)
     Adapter -DC--------|---Regulator GND---- Detector GND (pin 3)
     Metal case of detector ----------------- Detector GND (pin 3)

Wherever power comes from, be sure to connect the metal case of the
detector to ground to reduce noise in the detector.

If a different port or bit is used, then the port and bit mask (and
possibly an invert option) must be specified on the HPREAD command
line.  For example:

     C>hpread 22e2 1 /i >foo
     1043 bytes transferred (13 fixed) with 0 errors

reads the data from port 0x22e2, bit 0, with the input bit inverted.
Both the port and the mask must be specified in hexadecimal.  The bit
mask must be one of 1, 2, 4, 8, 10, 20, 40, or 80.  If there are
other operations necessary to initialize the port before it can be
used, then another program could be written to do the initialization,
or the operations could be added to this program in main() after
memory is allocated, and the program recompiled.

If a byte is received with no errors, or it is received with errors
and is correctable, then the byte is written to stdout and counted in
the bytes transferred.  If the byte required correction, then it is
also counted as a "fixed" byte.  The main use of the "fixed" count is
to judge the quality of the link.  Fixed bytes are almost certainly
good.

If a byte (or at least the illusion of start bits) is received, and
the data is in error and not correctable, then nothing is written to
stdout and the byte counts as an error.  If there are trailing
errors, i.e. errors with no good bytes that follow them, then
imbedded errors and trailing errors are counted separately.  If a
transfer results in no imbedded errors, but some trailing errors,
then the transfer may very well be good, since the trailing errors
might just be noise received while waiting for a silence of three
seconds.  For example:

519 bytes transferred (8 fixed) with 0 imbedded errors and 1 trailing error

HPREAD has been tested and works on 6 MHz 286 systems and on 8 MHz
8088 systems.  It is not known at this time whether this program will
work on 4.77 MHz 8088 systems or not.  The program utilizes the
system timer which runs at the same speed on all machines (1.193182
MHz), regardless of the processor's clock speed.  HPREAD should work
on any system faster than those mentioned above (for example, 8 MHz
8086 systems, 16 MHz 386 systems, 1000 GHz 786 systems, etc.).

There are two options for faster machines: /f (fast) does the data
decoding and error correction between bytes, and /e (enable) enables
interrupts between bytes.  /f reduces the memory requirements of the
program by 128K, and /e prevents the clock from losing (much) time by
allowing timer tick interrupts during the transfer.  If either option
is not appropriate for your machine, you will simply get errors
reported for the transfer.  Even with /e, you can still lose the
three seconds it takes to determine that the transmission is over.

If you have an AT compatible machine, then you can use the /c option
to restore the current clock from the real time battery backed-up
clock after the transfer is complete.  If /c is specified, but there
is an error reading the time from the real time clock (for example,
if it's an XT), then HPREAD will warn you that it did not set the
current clock:

     !warning: unable read the time from the real time clock

If you have an XT compatible machine with an add-on battery backed-up
clock, then that clock came with a program that sets the current
clock (typically executed from AUTOEXEC.BAT).  You can write a batch
file that executes HPREAD, and then executes the program to reset the
current clock.

If more than 32,768 (32K) bytes are sent, then only the first 32,768
bytes are written to stdout.

The command format and options can be displayed using the "/h" (help)
option.  If /h is specified, no other action is performed.

When used in a batch file, HPREAD returns the following errorlevels:

   0 - No error or /h specified.
   1 - Error in data, or data exceeded MAXFER (32,768) bytes.
   2 - Error writing data to stdout.
   3 - Could not allocate a MAXFER byte buffer, or if /f not specified,
       could not allocate a 4*MAXFER byte buffer in the far heap.
   4 - Invalid arguments.

This program was written to be compiled under Borland Turbo C 2.0
using the small or tiny models and register variables on.

*/



#ifndef __TURBOC__
#error Need Borland Turbo C to compile HPREAD
#endif
#if !defined(__SMALL__) && !defined(__TINY__)
#error Must use small or tiny models to compile HPREAD
#endif



char help[] = "HPREAD 1.3  31 Jan 1990\n\
Copyright (C) 1990  Mark Adler  Pasadena, CA  All rights reserved\n\
\n\
HPREAD reads data sent by an HP calculator to an infrared receiver\n\
connected to one bit of an input port.  The data is written to\n\
stdout, and statistics are sent to stderr.\n\
\n\
Usage:\n\
   hpread               - read from printer port ERROR* line (pin 15)\n\
   hpread PORT          - read from hexadecimal I/O port PORT, bit 3\n\
   hpread PORT MASK     - use hexadecimal mask instead of bit 3\n\
\n\
Options (can be combined after a single slash):\n\
   /i                   - invert the sense of the detector bit\n\
   /n                   - no translation of data\n\
   /c                   - restore the current clock from the real time\n\
                          battery backed-up clock\n\
   /e                   - enable interrupts between bytes\n\
   /f                   - fast machine---do correction on the fly\n\
   /h                   - show this help message\n";



#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <dos.h>
#include <io.h>
#include <fcntl.h>

#define MAXFER 32768u   /* Maximum bytes in one transfer */



/* Globals */
unsigned fixed;         /* Count of bytes fixed by error correction */
int irport;             /* The (byte) port that the IR detector is
                           connected to. */
char irmask;            /* Mask for the bit the IR detector is connected
                           to (one of the following: 1, 2, 4, 8, 0x10,
                           0x20, 0x40, or 0x80). */
char irinvt;            /* 0xff if the bit is low when the detector is
                           illuminated by infrared, 0 if the bit is high
                           when illuminated. */
char introk = 0;        /* True if interrupts allowed between bytes */
char nltolf = 1;        /* True causes 0x04 to 0x0A translation on read
                           and 0x0A to 0x0D,0x0A translation on write */
char later = 1;         /* True defers decoding after transmission */
char clock = 0;         /* True restores the current clock from the real
                           time battery backed-up cmos clock */



long hpread(void)
/* Read a byte frame from the IR port.  Interrupts should be disabled
   when this routine is called.  The low 27 bits of the returned value
   is set to 27 ones and zeros for the occurence or not of pulses in the
   27 half-bit times.  If no pulses occur for three seconds, then zero
   is returned.

   Macros used:

   IRON - expression that is true if IR is on
   TIMR - used by wait to get the high byte of the tiny tick count
   WAIT - wait one "tick" and increment k
   LOW  - wait until IR goes off while counting ticks
   HIGH - wait until IR goes on, counting ticks and rejecting noise
*/
{
  register int j, k;
  int i;
  long h;
  unsigned char *p, t[53];

  #define IRON ((inportb(irport) ^ irinvt) & irmask)
  #define TIMR {outportb(67,0);inportb(64);_AX=inportb(64);}
  #define WAIT {do TIMR while(j==_AX); j=_AX; k++;}
  #define LOW(d) {do{WAIT if(!IRON)break;}while(k<d);}
  #define HIGH(d) {do{WAIT if(IRON){WAIT if(IRON)break;}}while(k<d);}


  /* Wait up to three seconds for a pulse */
  #define TIMEOUT 27965         /* ((14318180/12)/128)*3 */
  k = j = 0;                    /* Initial j doesn't really matter */
  LOW(TIMEOUT)
  HIGH(TIMEOUT)
  if (k >= TIMEOUT)
    return 0;


  /* Read pulses until 25 or 26 half-bit times have passed, or more than
     ten half-bit times have passed with no pulses.
     Mystery numbers (S=111,1=10,0=01,X=00,Y=11):
     k < 11 --- for worst case SYY10, SY1X0, or S1XX0 cases, all of
                whose high+low times are ten half-bit times.
     LOW(34) --- for worst case SYY1 = 8 high half-bit times,
     HIGH(42) --- for worst case ten total half-bit times (see k < 11),
     LOW(6) --- since last pulse should be less than one half-bit time.
  */
  p = t;                        /* Where to put times */
  i = 0;                        /* Half-bit times so far */
  do {                          /* Do loop for each high+low piece */
    k = 0;                      /* Initialize tick count */
    LOW(34)                     /* Wait for pulse to go low */
    *p++ = k<2 ? 1 : (k+2)>>2;  /* Time high rounded to half-bits */
    HIGH(42)                    /* Wait for start of next pulse */
    k++;  k++;  k >>= 2;        /* Time high+low rounded */
    *p++ = k;
    i += k;                     /* Update total time in half-bits */
  } while (k < 11 && i < 25);   /* Do until pause or enough data */
  if (k < 11 && i < 27)         /* If got a final bit, then put it in */
  {
    *p++ = 1;
    *p++ = 0;                   /* (This is fixed by next statement) */
  }
  p[-1] += 27-i;                /* Adjust last entry to make total=27 */
  *p = 0;                       /* Terminate list */
  LOW(6)                        /* Wait for last pulse to end */


  /* Copy data into half-bit time slots */
  p = t;
  h = 0;
  do {
    j = *p++;                   /* Length of pulse in half-bit times */
    k = *p++;                   /* Time from this pulse to next one */
    do {
      h <<= 1;
      if (j)                    /* One's for pulse, zero's for quiet */
      {
        *((char *)&h) |= 1;     /* (Trick compiler into optimizing) */
        j--;
      }
    } while (--k);
  } while (*p);


  /* Return the half-bit time slots (can't be zero) */
  return h;
}



int hpfix(long h)
/* Do all possible error corrections on the data in h.  Return either
   the corrected byte, or -1 if there were uncorrectable errors.  The
   low 24 bits of h is assumed to be 24 half-bit times with ones and
   zeros for pulses or not in each bit.  This is the h returned by
   hpread(), but note that the three start bits are ignored.  The global
   'fixed' is incremented if the byte is good, but required correction.
*/
{
  register int e, j;
  int d, i;
  char g;

  /* Parity bits */
  #define H1 0x878
  #define H2 0x4e6
  #define H3 0x2d5
  #define H4 0x18b

  /* Macro to get the parity of a word (zero if even, non-zero if odd.
     The instructions emitted are: xor al,ah // jpo $+2 // sub ax,ax. */
  #define parity(w) (_AX=w,__emit__(0x30,0xe0,0x7b,0x02,0x29,0xc0),_AX)


  /* Convert h into a data word and an erasure word */
  d = e = 0;                    /* d is the data, e the erasures */
  for (j = 1; j < 4096; j<<=1)  /* Examine the 12 full-bit times */
  {
    g = (char)h;
    h >>= 1;
    if (((char)h ^ g) & 1)      /* If it is 10 or 01, */
    {                           /*  then it's a good bit (10 is 1), */
      if ((char)h & 1)
        d |= j;
    }
    else
      e |= j;                   /*  otherwise, it is an erasure. */
    h >>= 1;
  }


  /* Correct as many errors as possible */
  g = e != 0;                   /* Remember if there were errors */
  while (e)
  {
    i = e;                      /* Set i to the erasures to be fixed */
    do {
      j = ((i-1) & i) ^ i;      /* Make j the first one in i */
      if (((H1 ^ j) & e) == 0)  /* If we can, fix j using H1 */
        {if (parity(H1 & d)) d |= j; e ^= j; break;}
      if (((H2 ^ j) & e) == 0)  /* If we can, fix j using H2 */
        {if (parity(H2 & d)) d |= j; e ^= j; break;}
      if (((H3 ^ j) & e) == 0)  /* If we can, fix j using H3 */
        {if (parity(H3 & d)) d |= j; e ^= j; break;}
      if (((H4 ^ j) & e) == 0)  /* If we can, fix j using H4 */
        {if (parity(H4 & d)) d |= j; e ^= j; break;}
      i ^= j;                   /* None worked, try next bit */
    } while (i);
    if (i == 0)                 /* If no bits corrected, give up */
      break;
  }


  /* If there are still erasures or parity errors, then return error */
  if (e || parity(H1 & d) || parity(H2 & d) || parity(H3 & d) ||
      parity(H4 & d))
    return -1;


  /* Got a good byte---return it */
  if (g)
    fixed++;                    /* If had errors, update fixed */
  return d & 0xff;              /* Strip parity bits */
}



void err(int n, char *m)
/* Exit with return code n after printing message m to stderr */
{
  fputs(m, stderr);
  exit(n);
}



void main(int argc, char *argv[])
/* Read a stream of bytes from the HP and write them to stdout.  See
   comments above for the command format. */
{
  register int r;
  register unsigned i;
  unsigned j, e, t;
  long h;
  char *b;
  long huge *d, huge *p;
  union REGS g;


  /* Initialize irport to the first printer's status port, set irmask to
     the ERROR* line (pin 15 on the DB-25), and the bit is input as
     presented, so it must be inverted since the detector output is
     active low. */
  irport = *((int far *)MK_FP(0, 0x408)) + 1;
  irmask = 8;
  irinvt = 0xff;


  /* Process arguments */
  j = 0;                        /* Count of non-option arguments */
  for (i = 1; i < argc; i++)
    if (argv[i][0] != '/')      /* Non-option */
      if (j == 0)               /* First non-option arg */
        if (sscanf(argv[i], "%x", &irport) != 1)
          err(4, "?invalid argument---use HPREAD /H for help\n");
        else
          j++;
      else if (j == 1)          /* Second non-option arg */
        if (sscanf(argv[i], "%x", &e) != 1)
          err(4, "?invalid argument---use HPREAD /H for help\n");
        else
        {
          irmask = (char) e;
          j++;
        }
      else                      /* Third non-option arg */
        err(4, "?invalid argument---use HPREAD /H for help\n");
    else                        /* Option(s) */
      for (r = 1; argv[i][r]; r++)
        switch (argv[i][r] & 0x5f)
        {
        case 'C': clock = 1; break;
        case 'E': introk = 1; break;
        case 'F': later = 0; break;
        case 'H': err(0, help); break;
        case 'I': irinvt = 0; break;
        case 'N': nltolf = 0; break;
        default:
          err(4, "?invalid argument---use HPREAD /H for help\n");
        }
        

  /* Allocate memory for data */
  if ((b = malloc(MAXFER)) == NULL ||
      (later && (p = d = farcalloc(MAXFER, sizeof(long))) == NULL))
    err(3, "!not enough memory---aborting HPREAD\n");


  /* Read bytes until timeout or MAXFER bytes exceeded */
  t = e = i = 0;
  while (i < MAXFER)
  {
    disable();                  /* Disable interrupts during hpread() */
    if ((h = hpread()) == 0)    /* Read a frame---if timeout, */
      break;                    /*  then done. */
    if (introk)                 /* If allowed, enable interrupts */
      enable();                 /*  between bytes. */
    if (later)                  /* If decoding deferred, */
    {                           /*  just save the half-bit pulses */
      *p++ = h;
      i++;
    }
    else                        /*  else, do the work between bytes */
      if ((r = hpfix(h)) == -1) /* Try to correct errors, if any */
        t++;                    /* If failure, skip the byte and */
      else                      /*  update the trailing error count. */
      {
        b[i++] = nltolf && r==4 ? 10 : r;       /* Save good byte */
        e += t;                 /* Update the main error count and */
        t = 0;                  /*  reset the trailing error count */
      }
  }
  enable();                     /* Re-enable interrupts */


  /* Restore the timer count from the real-time cmos clock (if any).
     Uses the 0x1a interrupt with AH=2 to get the cmos clock time.  If
     the machine has an XT compatible BIOS (and therefore, no AT
     compatible clock), then AH will come back as 1.  If the machine is
     AT compatible, then AH will come back as 0 and carry will be either
     clear if it got the time, or set if there was an error reading the
     clock.  It is necessary to use emitted code here to control the
     carry flag before doing the interrupt. */
  if (clock)
  {
    _AH = 2;
    /* Code emitted is: clc, int 1ah, sbb al,al */
    __emit__(0xf8,0xcd,0x1a,0x18,0xc0);
    if (_AX == 0)
    {
      /* Compute the new timer value (18.207 ticks = 1 second) */
      j = _CX;
      r = _DH;
      #define bcd(a) (((a) & 0x0f) + 10*(((a) >> 4) & 0x0f))
      h = ((bcd(r) + 60L*(bcd(j) + 60L*bcd(j >> 8)))*18207+500)/1000;

      /* Set the new timer value */
      g.x.dx = (unsigned int) h;
      g.x.cx = (unsigned int) (h >> 16);
      g.h.ah = 1;
      int86(0x1a, &g, &g);
    }
    else
      fputs("!warning: unable read the time from the real time clock\n",
            stderr);
  }


  /* If deferred, then do the decoding and error correction */
  if (later)
  {
    p = d;
    j = 0;
    for (; i; i--)
      if ((r = hpfix(*p++)) == -1)      /* Try to correct errors */
        t++;                    /* If failure, skip the byte and */
      else                      /*  update the trailing error count. */
      {
        b[j++] = nltolf && r==4 ? 10 : r;       /* Save good byte */
        e += t;                 /* Update the main error count and */
        t = 0;                  /*  reset the trailing error count */
      }
    i = j;
  }


  /* Summarize activity */
  fprintf(stderr,
    "%u byte%s transferred (%u fixed) with %u %serror%s",
    i, i==1 ? "" : "s", fixed,
    e, t ? "imbedded " : "", e==1 ? "" : "s");
  if (t)
    fprintf(stderr, " and %u trailing error%s\n",
            t, t==1 ? "" : "s");
  putc('\n', stderr);


  /* Write data to stdout */
  if (!nltolf)                  /* If no translation, write in binary */
    setmode(stdout->fd, O_BINARY);
  if (fwrite(b, i, 1, stdout) != 1)
    err(2, "?error writing transferred data to stdout\n");
  if (e || i == MAXFER)
    exit(1);
  else
    exit(0);
}