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); }