8-bit ZN448E-based ADC to interface to a PC parallel port

© Copyright W.A. Steer 1995
 Note on Parallel Port notation

 The usual I/O base addresses for parallel ports are:

   3BCh  Port on Monochrome display adapter  <- Often not used
   378h  Second parallel port                <- Probably your first PP!
   278h  Third parallel port                 <-   "       "   second PP!

 All references to parallel port hardware/firmware follow the conventions
 used in the Parallel Port FAQ:

   Dn  represents bit n in the Printer Data Register        base addr
   Sn  represents bit n in the Printer Status Register      base addr+1
   Cn  represents bit n in the Printer Control Register     base addr+2

 Where the register bit is followed by a negative sign, e.g. C3-, the
 hardware input/output associated with it is inverted, i.e. a "1" in
 the register bit corresponds to a logic LOW, and and "0" to a logic HIGH
 on the connector. Where a plus sign (or no sign) is given, the signal
 is not inverted and a "0" corresponds to a logic LOW, "1" to a HIGH.
 The parallel port pins are named according to their use when interfacing
 a printer. Presence or absence of preceeding minus signs here are not
 of relevance to this project. 

 Pin numbers are given for "DB25" connectors - the standard 25-way
 D connector used on IBM PCs for the parallel port.

   Reg     i/o     DB25    Centronics
   bit             pin     name

   D0      o/p      2      D0          \
   D1      o/p      3      D1           |
   D2      o/p      4      D2           |   can act as inputs
   D3      o/p      5      D3            >  as well as outputs 
   D4      o/p      6      D4           |   on BIDIRECTIONAL
   D5      o/p      7      D5           |   parallel ports.
   D6      o/p      8      D6           |
   D7      o/p      9      D7          /

   S3+     i/p     15      -Error
   S4+     i/p     13      Select
   S5+     i/p     12      PaperEnd
   S6+     i/p     10      -Ack
   S7-     i/p     11      Busy

   C0-     o/p      1      -Strobe
   C1-     o/p     14      -AutoFd
   C2+     o/p     16      -Init
   C3-     o/p     17      -SelectIn
   GND      -      18-25   GND

The Analog to Digital Converter IC

The GEC-Plessey ZN448E analog to digital converter IC is a good
general-purpose ADC which I have used for numerous computer
interfacing projects. It's been around for many years and should
be available from all the usual component suppliers.

ZN448E  basic spec:

 8-bit successive approximation ADC
 9us conversion time (110kHz max sampling rate)
 +5 volt (4.5 to 5.5v) supply, 25mA current consumption
 inbuilt 2.55 volt reference
 Max error: +/- 0.5LSB
 18-pin DIL package

cost: around 8 pounds sterling (one-off quantities).

     -Busy  1|     |18  D0  \   LSB
       -RD  2|     |17  D1   | 
     CLOCK  3|     |16  D2   |
       -WR  4|     |15  D3   |
     R ext  5|     |14  D4    > Digital outputs
       Vin  6|     |13  D5   |
   Vref in  7|     |12  D6   |
  Vref out  8|     |11  D7  /   MSB
       GND  9|     |10  Vcc

N.B. signals preceeded by a "-" are active-low

Typical circuit:
                                                     Parallel       Reg
                                                    DB25 pins       Bit

                                 r-------------<---     1 (-Strobe) C0-
     +5v --@------@--            |      r------<---    16 (-Init)   C2+
           |      |              |      |
           |   -----------------------------
     330R []  |  10              2      4   |            
          []  | Vcc             -RD    -WR  |           |
           |  |                (-OE)        |    LSB    v
           |  |                        D0 18|-->--      2 (D0)      D0
  ---->----|--|6 Analog                D1 17|-->--      3 (D1)      D1
           |  |  in                    D2 16|-->--      4 (D2)      D2
           |  |            ZN448E      D3 15|-->--      5 (D3)      D3
           |  |                        D4 14|-->--      6 (D4)      D4
           @--|8 Vref out              D5 13|-->--      7 (D5)      D5
           |  |                        D6 12|-->--      8 (D6)      D6
           @--|7 Vref in               D7 11|-->--      9 (D7)      D7
           |  |                             |    MSB    ^
           |  |  CLOCK   GND  R ext   -Busy |           | *WARNING
           |  |    3      9     5       1   |              see text
           |   -----------------------------                   
           |       |      |     |       |        
      1uF ---     ---     |     [] 82k  |  (optional)
          ---     ---     |     []       ------>---    15 (-Error)  S3+
           |       |100pF |     |                    
           |       |      |     |
      GND -@-------@------@-----|------------------    18-25 (GND)
                          -5v --@--
			*see text

In this circuit the analog input should be in the range 0 to 2.55v.

For an AC-coupled input, suitable for connecting to the output of an
audio pre-amplifier or a radio/tape headphone output, bias the analog input
(pin 6) at 1.28v by tying it to the centre of a potential divider formed of
two 10k resistors between Vref out (pin 8) and GND (pin 9). Then couple the
audio input to analog in (pin 6) via a 4.7uF or larger capacitor (use of a
smaller capacitor will adversely affect bass response).

* A negative supply (-5v) is required on pin 5 via an 82k resistor for the
ADC to digitise properly down to 0v analog input. I have successfully used
-3v from two AA cells with a 47k resistor (don't ask) during prototyping,
but having not seen the IC's data sheet I don't really know what this pin
does, and cannot guarantee that practice!
[IC's for generating negative supplies from positive ones at low current are
available at moderate cost.]

* -WR is really a Start Conversion input. Pulse this line low momentarily
(I've used 0.5us quite reliably, but don't know the spec.) to begin
conversion. Pin 1, -Busy, will go low while converting, and return high
on completion. Often you just wait at least 9us after starting before
attempting to reading the data. (If you jump the gun you get to see the
successive-approximation in progress!)

* WARNING. Only connect the ADC output DIRECTLY to your parallel port data
lines if they are BIDIRECTIONAL. The ZN448 asserts data when -RD
(an Output Enable) is LOW, outputs are tri-stated (high impedance) when -RD
is HIGH. If -RD is driven by one of the parallel port control lines
then you can tell the ZN448 to assert data only once the parallel port has
been configured for input.
On my computer at least, the control outputs default to C3: LOW,
C2,C1,C0: HIGH. C3 & C2 change state several times during the boot-up cycle.
[The fancy printer status monitor program supplied with HP LaserJet printers
(if enabled & expecting a printer on the port) writes to the printer control
register periodically, every 10 or 30 seconds. Such programs should be
disabled before using the port for other purposes.]
I selected to use the C0- control line as the ZN448E output enable as it
seems to consistently stay high during all normal circumstances. For added
safety, you might still wish to put 1k5 resistors in series with each data
line. [Don't fall into the trap of specifying a higher resistance
(supposedly for added safety) as this is likely to prevent the parallel port
(TTL) inputs from sourcing enough current to register logic low when
required! TTL inputs source around 0.25mA: with a 10k resistor to ground the
input will still be at 2.5 volts!]

If you've only got a standard output-only port, you're not sure, you need
the compatibility, or you just want to be ultra-safe, then use the circuit
below. It should work on ANY PC parallel port, and uses a 74LS157 to feed
the data, as two nibbles, into 4 parallel port "printer status" inputs.

         S  1|     |16  Vcc        -ENABLE is held low for normal  
       I0a  2|     |15  -ENABLE            functioning
       I1a  3|     |14  I0c
        Qa  4|     |13  I1c         S: LOW selects I0,
       I0b  5|     |12  Qc             HIGH selects I1 to appear on
       I1b  6|     |11  I0d                             the Q outputs
        Qb  7|     |10  I1d 
       GND  8|     |9   Qd

Suggested circuit:

                           ---@--- +5v
  -------             ---------------            Parallel           Reg
 ZN448E  |           |        16     |          DB25 pins           Bit
         |   LSB     |        V+     |
    D0 18|-->---->---|10 I1d      S 1|---<--       17 (-SelectIn)   C3-
    D1 17|-->---->---|13 I1c         |
    D2 16|-->---->---|6  I1b         |
    D3 15|-->---->---|3  I1a    Qd  9|--->--       13 (Select)      S4+
    D4 14|-->---->---|11 I0d    Qc 12|--->--       12 (PaperEnd)    S5+
    D5 13|-->---->---|14 I0c    Qb  7|--->--       10 (-Ack)        S6+
    D6 12|-->---->---|5  I0b    Qa  4|--->--       11 (Busy)        S7-
    D7 11|-->---->---|2  I0a         |
         |   MSB     |               |
    -RD  |           |   GND   -E    |
     2   |           |    8    15    | 74LS157
  -------             ---------------
     |                    |     |
     |                    |     |
   --@--------------------@-----@--- GND

In this configuration the output of the ZN448E should be permanently
enabled. The ZN448E's -RD pin (pin 2) is now connected directly to GND
rather than to the parallel port -Strobe pin (pin 1). All other connections
to the ZN448E are as before. The parallel port data lines are unused.


The program to run the first arrangement, that for bi-directional ports and
using the data lines for input, is very simple:

 Set the parallel port data lines for input, usually by setting C5 to a 1 
 Set C0- to a 1 to enable the ADC output

  Set C2+ to 0 briefly, then to 1    to initiate a sample.
  Wait at least 9us,
        or until S3+ goes to a 1    (if optional connection made)            

  Read the data from the data register, D

 Set C0- to a 0 to tri-state the ADC outputs when you have finished.

This arrangement will give the fastest data-throughput.

If you're using the second arrangement, with the 74LS157, then data is
input through the control lines and you need the program below:

 Set C2+ to 0 briefly, then to 1    to initiate a sample.
 Wait at least 9us,
        or until S3+ goes to a 1    (if optional connection made)          
 Set C3- to 0                       to select the less significant nibble
 Read S,                        
   XOR with 128                     to correct sign of S7-
   AND with 240                     to discard unwanted bits
   Arithmetic shift right 4 places  to slide into the low-nibble position
   Put the result to one side
 Set C3- to 1                       to select the more significant nibble
 Read S,
   XOR with 128                     to correct sign of S7-
   AND with 240			   to discard unwanted bits 

   OR the result with low nibble stored earlier

 The final result is the sample. Use or store as desired.

 Repeat for the next sample.

The data throughput with this setup will be slower as the data has two be
loaded in two stages, but I still achieved about 35,000 samples per second
on a 486sx20.

Accessing the ports

In QBASIC, I/O ports can be accessed using the INP and OUT commands.

e.g. PRINT INP(&H379)       to read I/O address 379h (printer status register)

     OUT &H37A,16           to write the value 16 to address 37Ah (ctrl reg.)

From C or PASCAL, you'll probably have to revert to in-line assembler.

To read a port:

  mov dx,portaddr         // 'portaddr' the I/O address (an unsigned int)
  in al,dx
  mov value,al            // 'value' is of type unsigned char (or BYTE)

On exit, 'value' contains the contents of the port.

To write to a port:

  mov dx,portaddr         // 'portaddr' the I/O address (an unsigned int)
  mov al,value            // 'value' is the byte to be written
  out dx,al

Tried & Tested example program (data-input through printer-status lines)

// Program to read ADC connected to parallel port.
// Tested & works  11/9/95
// Can achieve 500000 samples in 14 seconds on my 486sx20
// (with numerical output inhibited of course)
// - equivalent to a mean rate of 35kHz.
// (C) W.A.Steer 1995

#include <<>iostream.h<>>

void main()
 unsigned char value, tmp, adcval;
 unsigned int Port, PortCtrl, PortStat;

 Port=0x0378;       // Parallel port base address

 for (long x=0; x<500000; x++)
   mov dx,PortCtrl

   mov al,0          // Set C0,1,2,3 to 0
   out dx,al         // C2=0 sets -WR on ADC to LOW

   mov al,4          // Returns C2 (-WR on ADC) HIGH again
   out dx,al         // initiates a sample.

    mov dx,PortStat       
    in al,dx              // Read the port status register
    mov value,al
  } while (!(value & 8)); // Wait until C3 turns to 1: conversion complete

   mov dx,PortStat
   in al,dx           // Read the port status register
   xor al,128         // Invert top bit
   and al,240         // Discard all but top 4 bits
   shr al,4           // Low nibble, so shift right
   mov tmp,al

   mov dx,PortCtrl
   mov al,12          // Set C3 high (without changing C2), causes 
   out dx,al          // the 74LS157 to select the high nibble

   mov dx,PortStat
   in al,dx           // Read the port status register
   xor al,128         // Invert top bit
   and al,240         // Discard all but top 4 bits

   or al,tmp          // OR with the low nibble collected earlier
   mov adcval,al      // store result in "adcval"

  // remark out whole of line below for speed evaluation
  cout << (unsigned int)adcval << endl;  // Display the sample

// --------- end of program -----------


The best way to check that all is well is to connect the wiper of a 10k
potentiometer to the ADC input, one end of the pot to the Vref and the other
to GND. Rotating the potentiometer through its full sweep should yield
digital outputs smoothly going from 0 through to 255. Any missing, inverted,
or crossed data bits will be very apparent here. 

Ideas - uses

1) Use it as a digital voltmeter with range 0 to 2.55 volts in steps of 0.01 volt.
2) Write programs to use the hardware as a digital oscilloscope.
3) Write a program to do a Fourier analysis of the waveform & create
   a spectrum analyser.
4) Use it to digitise sound, & build up a collection of samples.
5) By adding a header, your stream of digitised sound can be made into a
   legitimate .wav file, playable with a sound card or using PC-Speaker.
6) Connect a DAC (e.g. ZN428E-8) to the data bits of the parallel port,
   (assuming you've used the latter ADC setup which does not use the
   data lines), and play back the sound by writing the 8-bit sampled values
   to the port data register. (see below)
7) Using the hardware from (6), experiment with real-time delays, echoes,
   pitch-shifting etc. etc.

Digital to Analog converter

The ZN428E-8 is a good complement to the ZN448E.

 8-bit DAC
 5 volt (4.5 to 5.5v) supply, 20mA current consumption
 Linearity error: +/- 0.5LSB
 inbuilt 2.55 volt reference
 output impedance: 4k
 settling time: 800ns

 cost: about 6 pounds sterling.

         bit 1  1|     |16  bit 2
   (LSB) bit 0  2|     |15  bit 3
            NC  3|     |14  bit 4
       -ENABLE  4|     |13  bit 5
    Analog OUT  5|     |12  bit 6
       Vref in  6|     |11  bit 7 (MSB)
      Vref out  7|     |10  Vcc  +5v
    Analog GND  8|     |9   Digital GND

Suggested circuit:

     +5v --@------@--
           |      |                                                 Reg
           |   -----------------------------          DB25 pins     Bit
     330R []  |  10                         |            
          []  | Vcc                         |
           |  |                             |    LSB
           |  |                        D0  2|--<--      2 (D0)      D0
  ----<----|--|5 Analog                D1  1|--<--      3 (D1)      D1
           |  |  out                   D2 16|--<--      4 (D2)      D2
           |  |            ZN428E-8    D3 15|--<--      5 (D3)      D3
           |  |                        D4 14|--<--      6 (D4)      D4
           @--|7 Vref out              D5 13|--<--      7 (D5)      D5
           |  |                        D6 12|--<--      8 (D6)      D6
           @--|6 Vref in               D7 11|--<--      9 (D7)      D7
           |  |                             |    MSB
           |  |        Analog Digital       |
           |  | -Enable  GND    GND         |
           |  |    4      8      9          |
           |   -----------------------------
           |       |      |      |
      1uF ---      |      |      |
          ---      |      |      |
           |       |      |      |
      GND -@-------@------@------@------------------    18-25 (GND)

N.B. If -Enable is made high the output will latch.
The output (2.5v p-p) can be connected to the line-level input of an audio
amplifier via a 4.7uF capacitor.

[If you did use bidirectional data lines for ADC input, you could still use
them for DAC output by appropriately tri-stating the ADC output, asserting
and then latching DAC data (drive the DAC's -Enable input using C1 or C3
to control data-latching), then returning the parallel port to input-status
before asserting & reading the ADC output.]

Higher-speed sampling (for advanced constructors!)

If, despite efficient machine-code programming you can't achieve the
data rate you would like, but can tolerate the data only coming in
limited-size chunks (e.g. for a computer-oscilloscope) then you can devise
a circuit to clock the ADC and store the data (rapidly) in an outboard RAM,
ready for slower downloading to the PC. I have used a 6264 CMOS static RAM
in the past, but more recently, for video-speed projects I found a high
speed (22MHz) FIFO (first in first out) memory to be more convenient since
they do not require external address-generator. With FIFOs it's also easy to
start to read out data before you've finished sampling that "block". 

General Advice

I have used ZN448 circuits very successfully with other computers, but
am relatively new to PC parallel ports. The ZN448E/74LS157 circuit is
thoruoghly tried and tested, but I have not been able to use the simpler
data-line input arrangement as my parallel port is not bidirectional.

Take all the usual ESD precautions, and avoid modifying the circuit while
it is plugged in to the computer. It would be advisable to experiment with
a parallel port on a plug-in card rather than one built onto the motherboard,
for obvious reasons, just in case of disaster.

5 volt supplies (up to 100mA) can be obtained using a 78L05 regulator,
itself powered by batteries or a wall plug-in (7 volts or more). [If using
batteries, make sure they are in good condition. If the regulator output
drops much below 5 volts the data may not be forthcoming, or worse still
the circuit may largely appear to function, but you spend hours trying to
figure out why certain ADC values cannot be yeilded!]   

It may be convenient to wire a 20-way ribbon cable to the 25-way D connector,
making connections starting from one side of the cable in the order given in
my first table (you only need one ground connection). Rainbow-cable makes
for easier identification, but for prototyping I recommend using the IDC
cable and fitting a 20-way DIL IDC Header to the 'electronics' end of the
cable. The header will easily and positively plug into a proto-board, and
avoids the problem of individual wires popping out of the board, and
possibly then shorting when the cable is moved. 

Legal Note:
 You should check my diagrams and be sure in YOUR OWN MIND that everything
 is safe, similarly check any physical circuits you build against the
 diagrams BEFORE plugging in and/or applying power.

 I shall not be held responsible for any calamity you may have!

Take care, & HAVE FUN!


See also my home (root) page

Please send any queries to w.steer@ucl.ac.uk .

This page ref.: http://www-server.bcc.ac.uk/~zcapl31/LptADC.html

Last modified 13th September 1995