CGS parallel port adapter for IBM PCs

Jump to navigation Jump to search

The Ken Stone parallel port adapter for IBM PCs project grew out of his need to control a number of digital oscillators and other aspects of his pre-MIDI vintage synthesizer.

Introduction

With the constant advancement of personal computers, the older models are falling into disuse, and are being discarded or disposed of cheaply. These machines can easily be put to dedicated control applications, controlling model railways, home grown light shows, or whatever you can think of.

The PC Printer Port Interface presented here allows a significant number of data latches, or ports to be controlled from a single PC parallel / printer port. Standard and bidirectional ports are supported. Enhanced ports are not required. Fully expanded, and with 8 bit latches connected to each sub-address, it is possible to address 1024 individual 1 bit lines, for turning on or off lights, motors or whatever. Alternatively, where numeric control is required, e.g. D to A converters, 128 8 bit ports can be addressed.

The project is modular, allowing constructors to purchase only the parts they require, and to expand as needed. Two primary cards form the heart of the design. Each has an on-board regulator, though a power supply outputting smoothed DC of at least 8 volts will be required. Alternately, the cards could be powered from a surplus PC power supply.

On the interface card, there are 16 selector (strobe) lines for driving 16 external latches, as well as the circuitry needed to add another 7 banks of 16 lines each. Also on-board is the bus buffering to drive the first bank of 16 latches. Two latches, one input and one output are also included, so that the card can be used as a stand alone, though limited interface if required. Further latches will need to be included in the circuits to which you are interfacing.

The expansion cards provide the decoding of the additional banks of selector lines and the buffers for driving further latches. For each additional 16 latches you wish to drive, an additional expansion card will be required.

While provisions for an input latch and an input bus are included on the PCB for experimenters, it is important to note that this should only be used with bidirectional printer ports. While it is possible to force some single direction printer ports to operate in a bidirectional mode, it is not recommended as chip damage may result.

Warning!

Many modern PC's have the parallel port built into the motherboards. If there was a circuit problem and the parallel port was damaged, it may be necessary to replace the entire motherboard. For this reason, it is safer to experiment with a plug-in parallel port card.

Build and use this project at your own risk. As individual construction skills, and uses to which this project can be put are beyond the control of the author, publisher and kit manufacturer, absolutely no guarantees or warranties can be given. The cost of repair or replacement of the kit, or any equipment to which it has been connected lies entirely with the user. The author, publisher or kit manufacturer can not be held responsible for any damage or injury caused by the correct or incorrect use of the project described in these articles.

How it works

Schematic diagram of the parallel port adapter

The PC parallel port consists of essentially three buses. They are the data bus, the control bus and the status bus. The latter two are a combination of lines coming out to the printer port connector, and lines monitoring or controlling internal aspects of the port.

This project is really only concerned with the data bus and a few of the control bus lines, specifically SELECT (C3) and STROBE (C0), pins 17 and 1 respectively, and C5, an internal control line responsible for enabling the tri-state output of the data bus output latch. Unfortunately, C5 has not been implemented on all cards, and its presence is the difference between a standard and a bidirectional port.

Operation of the cards is fairly straight forward, with the expansion card simply mimicking two aspects of the interface card. The behavior of both cards is directly under control of user written software, so for the following description, we will assume suitable software is driving the port.

The interface card

There are basically three sections to the circuit, the data latching section, the addressing section, and the buffer.

IC1, a 74LS244 octal line driver, is the buffer. Data from the parallel port data bus is fed through this chip to reduce loading on the parallel port's internal chips. The fan-out of the 74LS244 is sufficient to drive approximately sixteen other LS devices.

Connected to this bus are IC2, the address register of the card, and IC7, the first data port latch. Both IC2 and IC7 are 74LS374 octal latches. Other data port latches and further buffering will be connected to this bus also, as the number of required ports is increased. All additional data port latches will behave in the same manner as IC7, so only this chip will be discussed.

Writing data to the port

The STROBE line of the parallel port is used to latch data into IC7 and any other data port latches through the address decoder, which will be described below. For the moment, it is enough to know that when the strobe line of IC7 (pin 11), goes low, IC7 which is falling edge triggered, will latch the data that is currently at the output of the parallel port data bus, and will continue to hold it until such a time as it is again strobed.

Getting the STROBE signal from the parallel port to the right data port latch is the job of the address register and decoder chips.

IC2 acts as the address register of the card. By writing a binary value into this latch, we can select one of our new data ports though the associated decoders. When the SELECT line from the parallel port (pin 17) goes low, IC2 will latch the data on the parallel port data bus. The SELECT line will then go high again, ready for next time it is required.

IC3, IC4 and IC5, all 74LS138 3 line to 8 line demultiplexers, form the address decoder. Assuming the chip is enabled, applying a number (in binary) from 0 to 7 to the address (A) inputs, a single corresponding output (O) line will go LOW. In addition to the address inputs, 74LS138 also have three enable inputs, one active HIGH and two active LOW.

In the case of IC4 and IC5, the address inputs are connected to outputs O0, O1 and O2, the least significant bits of the address latch IC2. By tying the active LOW input on IC5 and an active HIGH input on IC4, and driving them from Q4 from IC2, we are now able to select one output at a time from the sixteen combined outputs of the two chips, giving us a 4 line to sixteen line decoder. One active LOW enable line from each of IC4 and IC5 are tied together and driven from O0 of IC5. The remaining enable lines are tied HIGH in the case of IC5 and LOW in the case of IC4.

The address inputs of IC3 are connected to O4, O5 and O6 of IC2. It behaves in much the same way as IC5, except that it follows the data on the upper nibble of the bus instead of the lower. O7 seven of IC2 is connected an active LOW enable pin of IC3, disabling it for address values 128 (80 in hex) and above, thus limiting the address range from 0 to 127. The other active LOW enable pin of IC3 is connected to the STROBE line from the parallel port (pin 1 of the parallel port).

When the STROBE line from the parallel port (pin 1) is HIGH, it disables IC3, sending all of it's outputs HIGH. In turn IC4 and IC5, their enable pins held high by IC3 will also be disabled, as will any other decoders connected to IC3. This is the natural state of the address decoder. Data on the parallel port address bus can change without disturbing the address decoder while in this disabled state.

When the STROBE line from the parallel port (pin 1) goes LOW, it enables the appropriate output of IC3 as addressed by the address port latch IC2. The output of IC3 in turn will enable IC4/IC5 (or another decoder pair on an expansion board) and as addressed by the address port latch IC2, one of their sixteen outputs going low.

This output, connected to a data port latch, will cause that data port latch to be loaded with the data currently held on the parallel port data bus.

To summarize. The target port address is written onto the parallel port bus. The parallel port SELECT line goes LOW, then returns to HIGH, to load this value into the address register IC2. The data to be loaded into the target port is then written onto the parallel port bus. The parallel port STROBE line goes LOW, then returns to HIGH, to load this value into the desired data port latch (eg IC7).

Reading data through the port

Reading data into the parallel port data bus is similar to writing. When the strobe line of input latches, such as IC6, a 74LS373 tri-state octal transparent latch, is taken LOW by the address decoder, the selected input latch will enable its outputs and force the data present at its inputs onto the parallel port data bus. Obviously, if the parallel port data bus is still acting as an output, we will have a bus conflict. This is where control register bit C5 comes in. By disabling the tri-state output of the parallel port's inbuilt latch, data can then be read into the parallel port. Needless to say, this requires a bidirectional parallel port card.

Some older parallel port cards may be modified to convert them into a bidirectional port, as almost all of the required circuitry is onboard. It is just a matter of connecting the output enable of the latch to the appropriate latched control register line. The details of this I will leave for the more adventurous to work out for themselves, as as there are hundreds of different parallel port cards available. Of course, the ones with discrete chips are more likely to be successfully modified.

It is also possible to force some standard parallel port cards to operate in a bidirectional manner, though this is not recommended as card damage may result. It involves setting the data lines of the port to high (FF hex) and allowing the input latches on the expansion card force the lines low. This works because TTL chip outputs can sink more current than they can source, meaning that if two outputs are fighting to control a common bus, a low signal on one device will pull the output of the other low, regardless of it's intended logic level.

The data being forced onto the parallel port data bus can then be read back through the parallel port data feedback register.

Not all cards will work in this manner. In fact I found some later cards with LSI chips refused to cooperate at all, their feedback registers reading back what had been written to the port regardless of the state of the outputs, even if they were shorted directly to ground.

The expansion card

Schematic digaram of the expansion card.

The chips on the expansion card behave exactly in the same manner as the corresponding chips on the interface card. The select line from the 74LS138 decoders is connected to one of the selector expansion outputs on the base card, e.g. pin 2 (B1), which will put this bank of selectors in the address range of 16 to 31.

The lower nibble data from the four pin connector on the interface card labeled D0-D3 is fed into the Address input on the expansion card, the Loop output going on to the next expansion card if required.

The buffer chip, the 74LS244 simply provides drive for any latches being driven from this card.

The expansion card.

Construction

The PC Printer Port Interface card and its expansion cards are built on single sided printed circuit boards. Some care will be needed with soldering because there are a number of tracks that run between IC pads. Before you start, check the board for etching faults. Look for any shorts between tracks, or open circuits due to over etching.

Check the diameter of the holes for the various connectors. Depending on the thickness of the pins of the chosen connectors, these holes may require enlarging. Take care if enlarging the holes, especially around the 10 pin DIL INPUT and OUTPUT connectors on the main card and the BUFFER INPUT and BUFFER OUTPUT on the expansion card, as the pads are closely spaced and small.

When you are happy with the printed circuit board, construction can proceed as normal, starting with the links first, then moving up from the shorter through to the taller components. The project has been tested with combinations of chip types, including LS TTL and HCT CMOS. Other varieties should also work, but current consumption and heatsinking requirements may differ. If using CMOS variants of any of the chips, use static sensitive device handling precautions.

There is no need to use connectors at all the locations on the printed circuit boards as some are given as alternatives to others. Those for the input bus and latch can be omitted altogether. If need be you can solder in wires, avoiding the use of connectors. The pins in the surplus pin headers used in the prototypes required pushing through the mounting strip to give pins of the correct length, and to provide somewhere to solder.

A mini-U heatsink is used with the voltage regulator, and is quite adequate for the 100ma the LS TTL version of the circuit draws when powered from around 8 volts. One or two extra latches drawing power from this should not be a problem, but if you plan to fully expand the project, an external power supply, such as a surplus one from a PC could be used, in which case the regulated 5 volts can be fed directly to the boards, and the regulators omitted. The spacing and order of the pads for power connection correspond to those used by hard drives, making for easy connection to PC power supplies. If you do this, be careful to solder the socket in the correct way, or the chips will be connected to 12 volts and destroyed. This connector can be salvaged from any old floppy drive or hard drive.

If you are going to connect a full complement of expansion cards to the interface card, buffering may be required on the four bit port selection data bus. You may be able to loop this through one of the on board buffers, or wire up an extra 74LS244 or similar to handle this.

Checking

Take time to go over your soldering, and check for correct orientation of the components. Once you are satisfied, connect the card to a power supply, 5 volts if you are not using a regulator, or 8 to 12 volts if you are. Use a multimeter to check the voltages on the power pins of all of the chips on the card to make sure they are correct.

Any further tests to the card require it to be connected to the parallel port of a computer and driven by appropriate software.

Driving the parallel port

There are three common addresses for the base port of the parallel port, depending on system configuration and the port you choose to use.

If you have a Monochrome Display and Printer Adapter i.e. the two combined on a single card (MDPA) the address of this parallel port will be 3BC hex. If present, this will be LPT1, displacing any other ports by 1.

The most common port address is 378 hex. This is LPT1 on a standard system, or LPT2 if you also have an MDPA.

The third address is 278 hex. This is usually LPT2 if present. If an MDPA is also in the system it becomes LPT3.

Of course we are not interested in the LPT number, as we will be directly addressing the port, so that give us the following.

3BC hex. Monochrome Display and Printer Adapter. Only likely if you are using an old XT. 378 hex. If you have one port, this will be it. 278 hex. If you have installed a second card specially to drive the project, this will be it.

Programming

Most of my programming for this project has been done in Borland Delphi 1.0 under Window 3.1 or Window 95, though the code transports easily to DOS versions of Pascal. The examples here are just the relevant portions of the program.

Note that some of the control lines in the Parallel port are inverted, so what we write to the control register must take this in to account.

Declarations:-

const  OUTREG   <nowiki>= $378; </nowiki>         {This is the base port address.
                                  Change to match port you are using}
        INREG   <nowiki>= OUTREG + 1; </nowiki>   {Status inputs}
       CONREG   <nowiki>= OUTREG + 2; </nowiki>   {Control register}

{The following are the bytes to write to the control register}
{        byte    D3  D2  D1  D0}
{       value     8   4   2   1}
      cAllOff   <nowiki>= </nowiki>    4;         {This turns off (sets to HIGH) the
                                  SELECT and STROBE lines}
      cSelect   <nowiki>= 8 + 4; </nowiki>        {This sets the SELECT line LOW}
      cStrobe   <nowiki>= </nowiki>    4 + 0 + 1; {This sets the STROBE line LOW}
     cNibbleL   <nowiki>= </nowiki>    4 + 2;     {Used by input port adapter
                                  (Pin 14 is inverted)}
     cNibbleH   <nowiki>= </nowiki>    4;         {Used by input port adapter}
    cTristate   <nowiki>= $20; </nowiki>          {This is for setting C5 to disable the
                                  tristate outputs of a bidirectional
                                  parallel port. $20
                                 (20 hex) = 32 decimal}

var  v_port:byte;  {0 to 15. Selects "S" port on interface card}
     v_bank:byte;  {0 to 7. Selects expansion cards.
                    0 for interface card.}
     v_data:byte;  {0 to 255. The data to write to the selected latch}

Code for writing to the Address register of the interface card.

   port[OUTREG] := v_port + (v_bank *16); {Put address on parallel
                                           port data bus}
   port[CONREG] := cSelect;   {Latch into interface card's address
                               register (IC2)}
   port[CONREG] := cAllOff;   {Reset the select line.}

Code for writing to the selected latch, e.g, IC7 on the interface card. The address of the latch must have previously been written into the interface card address register.

   port[OUTREG] := v_data;     {Put address on parallel port data bus}
   port[CONREG] := cStrobe;    {Latch into the selected latch. e.g IC7}
   port[CONREG] := cAllOff;    {Reset the strobe line.}

Code for reading from the STATUS bus (Pins 10-13 and 15 on parallel port connector)

   data := port[INREG] and $F8; {Mask off bits 0-2. bit 7 is inverted}

Code for reading from the data bus from a selected latch, e.g, IC6 on the interface card, when using a bidirectional parallel port card. The address of the latch must have previously been written into the interface card address register.

   port[CONREG] :=  cTristate + cStrobe; {Set outputs to tristate and
                                          enable input latch}
   data = port[OUTREG];                  {Read data from input latch}
   port[CONREG] := cAllOff;              {Reset the strobe line.}

Code for reading from the data bus from a selected latch, e.g, IC6 on the interface card, when NOT using a bidirectional parallel port card. The address of the latch must have previously been written into the interface card address register. This will not work on all cards. If you must try it use only on ports that use a 74LS374 as the output device. NOT RECOMMENDED. USE AT OWN RISK as this may damage the parallel port card in your PC. DO NOT USE ON MODERN PORTS THAT ARE INTEGRAL WITH THE MOTHERBOARD.

   port[OUTREG] := $FF;                  {Set data bus lines to high}
   port[CONREG] := cStrobe;              {Enable input latch}
   data = port[OUTREG];                  {Read data from input latch}
   port[CONREG] := cAllOff;              {Reset the strobe line.}

Notes

  • On faster machines, you may find that that incorrect data is latched or read. This can be caused by a motherboard that is operating too fast for the chips used in the parallel port adapter. This can be solved by programming a delay after each time you write to the adapter. When using a 486DX66 with an adapter fitted with 74LS chips, I found that no delays where needed, though when used with a Pentium 166 they were.

Input port adapter

Schematic diagram of the input port adapter.

So you've only got a standard parallel port, but you need to read data in. This simple one chip adapter and a little code will solve your problem.

How it works

The PC parallel port has a number of status inputs that can be used as input lines. Here we are using four of them with a multiplexer to create an 8 bit input bus. The outputs from 74LS373 input latches cab be fed into it, and individual input ports addressed in exactly the same way as the output ports described previously.

The multiplexer chosen is a quad two line to one line device and can be either a 74LS157 or a 74LS257. The chip is used in a way that negates any difference between them. Each multiplexer input is wired to a bit on the input bus in such a way that setting the input select pin (pin 1) on the multiplexer will select either the low or high nibble of the input bus.

The input adapter.

Construction and connection

Before you start assembly, check the board for etching faults. Look for any shorts between tracks, or open circuits due to over etching.

Check the diameter of the holes for the various connectors. Depending on the thickness of the pins of the chosen connectors, these holes may require enlarging. Take care if enlarging the holes.

When you are happy with the printed circuit board, construction can proceed as normal, starting with the links first, then moving to the to the taller components.

If you wish, you can avoid the use of connectors by soldering wires directly to the connector pads. The pins in the surplus pin headers used in the prototype required pushing through the mounting strip to give pins of the correct length, and to provide somewhere to solder.

Two SIP headers were used, the input bus, and the multiplexed output bus. Each pin on the input bus is clearly labelled with the bit identification. Power connections for +5 volts and 0 volts are also via this header.

Each pin of the multiplexed output bears the number of the corresponding pin on the parallel port D25 connector. The PC Printer Port Interface PCB has four unmarked pads near the D25 connector that connect directly to these pins. Inserting another 4 pin SIP will allow for easy connection between the two boards. Of course, they may also be hard wired if desired. The remaining connection that must be made is between pin 14 on the D25 connector and the hole on the adapter marked H/L NIBBLE. There are no spare pads connected to this pin on the PC Printer Port Interface PCB, so you will need to solder directly to the pad through which the pin is inserted. The pin number can usually be determined by looking into the D25 connector itself, as these usually have each pin numbered. It pays to double check here though, because once after a period of constant wiring errors, I found the cheap D9 connector I was using had the pins numbered in opposite ways, depending upon if I was reading the connector from in front or behind! I suspect they had mixed up the male and female mouldings for the rear of the connectors.

The 100n capacitor provides power supply decoupling.

Driving the input port adapter

The constants declared are identical to those used previously. In fact the two required constants cNibbleL and cNibbleH were declared previously in anticipation. Of note is that, like cSelect and cStrobe, we are setting bit 1 HIGH in order to drive the corresponding pin of the parallel port (pin 14) LOW.

const OUTREG    <nowiki>= $378;</nowiki>
       INREG    <nowiki>= OUTREG + 1;</nowiki>
      CONREG    <nowiki>= OUTREG + 2;</nowiki>
{The following are the bytes to write to the control register}
{        byte    D3  D2  D1  D0}
{       value     8   4   2   1}
      cAllOff   <nowiki>= </nowiki>    4;         {This turns off (sets to HIGH) the
                                  SELECT and STROBE lines}
      cSelect   <nowiki>= 8 + 4; </nowiki>        {This sets the SELECT line LOW}
      cStrobe   <nowiki>= </nowiki>    4     + 1; {This sets the STROBE line LOW}
     cNibbleL   <nowiki>= </nowiki>    4 + 2;     {Used by input port adapter
                                  (Pin 14 is inverted)}
     cNibbleH   <nowiki>= </nowiki>    4;         {Used by input port adapter}
    cTristate   <nowiki>= $20; </nowiki>          {This is for setting C5 to disable the
                                  tristate outputs of a bidirectional
                                  parallel port. $20
                                 (20 hex) = 32 decimal}

The code is best presented and used as function, allowing the complexities of reading through the adapter to be coded once. The example given below simply reads the data at the port. Additional code as described in the PC Printer Port Interface article would be required to select which latch was being read.

The pins that are being used to read in data into the input adapter are the upper nibble of the parallel port's Status Register. Bit 7 (pin 11) is also inverted. This means the code must invert bit 7. The code also, for the data of the lower nibble must shift it 4 bits to the right once read. Remember this when examining the sample code.

Function ReadPort;
var
  highNibble : byte;
  lowNibble  : byte;
begin
  port[CONREG] := cNibbleL;              {Set Multiplexer to read LOW nibble
                                          of input bus}
  lowNibble := port[INREG] AND $F0;      {Read then AND off unwanted lower
                                          nibble}
  lowNibble := lowNibble XOR $80;        {Invert Bit 7}
  lowNibble := lowNibble shr 4;          {Move 4 bits to right, so data is now
                                          sitting as the lower nibble in the
                                          variable.}
  port[CONREG] := cNibbleH;              {Set Multiplexer to read HIGH nibble
                                          of input bus}
  highNibble := port[INREG] AND $F0;     {Read then AND off unwanted lower
                                          nibble}
  highNibble := highNibble XOR $80;      {Invert Bit 7}
  ReadPort := highNibble OR lowNibble;   {OR upper and lower nibbles together
                                          and return the result to the
                                          calling routine}
end;

Latch board

Schematic diagram of the latch.

Not all circuits you may wish to interface to will include an on board latch. This board simply provides somewhere to put the latch. Like most other boards in the series, it contains both SIL and DIL connectors to give some wiring flexibility.

The board can also be used as an input port or output port depending upon the choice of chip and position of a link.

To operate as an output port using either a 74L374 or 74LS377 latch, the link should run between pads A and D. To operate as an output port using the 74LS273, the link should run between pads A and B. The connector marked as "INPUT" is connected to the "OUTPUT BUS" of the Interface Card.

To operate as an input port, a 74L373 latch should be installed, and the link should run between pads A and C. The connector marked as "OUTPUT" is connected to the "INPUT BUS" of the Interface Card or to the "INPUT PORT" on the Input Port Adapter.

The board is fed with 5 volts though the connectors, drawing its power from the power supply on either the Interface Card, Expansion Card or the circuit being driven. If hard wiring, make sure you include connections to the power supply. Also take care not to overload either the Interface Card's power supply or data bus. Refer to the Interface Card/Expansion Card for more details.

The only other component on the board, a 100n capacitor provides power supply decoupling.

The latch board.

Parts list

This is a guide only. Parts needed will vary with individual constructor's needs.

Input adapter
Part Quantity
Capacitors
100n 1
Semi's
74LS157 OR 74LS257 1
Misc
4 pin header 1
10 pin header 1
Tinned copper wire 10cm
CGS17 PCB 1
Latch board
Part Quantity
Capacitors
100n 1
Semi's
74LS374 OR 74LS373 1
Misc
10 pin DIL IDC header 2
10 pin SIL header 2
Tinned copper wire 5cm
CGS16 PCB 1

CC-BY-NC

Readers are permitted to construct these circuits for their own personal use only. Ken Stone retains all rights to his work.

See also

References

External links