[comp.sys.handhelds] HPREAD 1.1, Use Radio Shack IR Detector

madler@tybalt.caltech.edu (Mark Adler) (01/15/90)

/*

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     Improved error correction and corrected
                        and extended 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 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 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).  Many
parallel printers supply +5V power on one of the lines on the 34-pin
connector.  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.)  Wherever power comes from, 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 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.

This program has been tested on a 6 MHz 286 system.  It is not known
if this program will work on a 5 MHz 8088 system or not.

There is a possibility of receive errors due to resident programs
using too much time during timer tick interrupts.  This can be cured
by specifying the "/d" (disable) option which does not allow any
interrupts at all during the transfer.  Normally, interrupts are
allowed between bytes.

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
   4 - invalid arguments

This program was written to be compiled under Borland Turbo C 2.0.

*/


char help[] = "HPREAD 1.1  14 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 specified anywhere in the command line):\n\
   /i                   - invert the sense of the detector bit\n\
   /d                   - don't translate 0x04's to line feeds\n\
   /n                   - do not allow interrupts in between bytes\n\
   /h                   - get this help message\n\
\n\
Example: read from bit 0, port 0x22e2, inverted, to the file 'foo' -\n\
\n\
   hpread 22e2 1 /i >foo\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 for looking at the IR detector signal */
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. */

/* Global that is true if interrupts should be allowed between bytes. */
int introk = 1;

/* If true, this global indicates that 0x04 (HP newline character)
   should be translated to 0x0A (everyone else's newline character) */
int nltolf = 1;


int hpread(char *h)
/* Read a byte frame from the IR port.  Interrupts should be disabled
   when this routine is called.  h[] is set to 27 ones and zeros for the
   occurence or not of pulses in the 27 half-bit times.

   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;
  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 1;

  /* 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;
  do {
    j = *p++;                   /* Length of pulse in half-bit times */
    k = *p++;                   /* Time from this pulse to next one */
    do {
      *h++ = j > 0;             /* One's for pulse, zero's for quiet */
      j--;
    } while (--k);
  } while (*p);
  return 0;
}


int hpfix(char *h)
/* Do all possible error corrections on the data in h[].  Return either
   the corrected byte, or -1 if there were uncorrectable errors.  h[] is
   assumed to be 24 half-bit times with ones and zeros for pulses or not
   in each slot.  This is the h[] returned by hpread(), but with the
   start bits (the first three half-bit times) ignored. */
{
  register int e, j;
  int d, i;

  /* 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 = 0; j < 24; j+=2)     /* Examine the 12 full-bit times */ 
  {
    d <<= 1;
    e <<= 1;
    if (h[j] ^ h[j+1])          /* If it is 10 or 01, */
      d |= h[j];                /*  then it's a good bit (10 is 1), */
    else
      e |= 1;                   /*  otherwise, it is an erasure. */
  }

  /* Correct as many errors as possible */
  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 */
  return d & 0xff;
}


void err(int n, char *m)
{
  fputs(m, stderr);
  exit(n);
}


void main(int argc, char *argv[])
/* Read a stream of bytes from the HP and write them to stdout */
{
  int r;
  unsigned n, e;
  char *b, h[27];


  /* 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 */
  e = 0;
  for (n = 1; n < argc; n++)
    if (argv[n][0] != '/')
      if (e == 0)
        if (sscanf(argv[n], "%x", &irport) != 1)
          err(4, "?invalid argument---use HPREAD /H for help\n");
        else
          e++;
      else if (e == 1)
        if (sscanf(argv[n], "%x", &r) != 1)
          err(4, "?invalid argument---use HPREAD /H for help\n");
        else
        {
          irmask = (char) r;
          e++;
        }
      else
        err(4, "?invalid argument---use HPREAD /H for help\n");
    else
      for (r = 1; argv[n][r]; r++)
        switch (argv[n][r] & 0x5f)
        {
        case 'I': irinvt = 0; break;
        case 'D': introk = 0; break;
        case 'N': nltolf = 0; break;
        case 'H': err(0, help);
        default:
          err(4, "?invalid argument---use HPREAD /H for help\n");
        }
        

  /* Allocate memory for data */
  if ((b = malloc(MAXFER)) == NULL)
    err(3, "!unable to allocate buffer---aborting HPREAD\n");

  /* Read bytes until timeout (or out of memory) */
  n = e = 0;
  while (n < MAXFER)
  {
    disable();                  /* Disable interrupts during hpread() */
    r = hpread(h);              /* Read a byte frame */
    if (introk)                 /* If allowed, enable interrupts */
      enable();                 /*  between bytes. */
    if (r)                      /* If timeout, then done */
      break;
    if ((r = hpfix(h+3)) == -1) /* Try to correct errors, if any */
      e++;                      /* If failure, skip the byte and */
    else                        /*  update the error count. */
      b[n++] = nltolf && r==4 ? 10 : r; /* Save good byte */
  }
  enable();                     /* Re-enable interrupts */

  /* Summarize activity */
  fprintf(stderr, "%u byte%s transferred with %u error%s\n",
          n, n==1 ? "" : "s", e, e==1 ? "" : "s");

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