Unleashing An Old 8-bit ISA IO Card
This monster had been sitting in my box-o'-stuff for a while... actually, it seems next month is its three year anniversary in the box... jeesh. This was part of a dismantled 386 PC, used at the Siding Spring Observatory where I assume they needed a lot of IO to control telescopes? Anyway, I've been cleaning/clearing out a lot of junk lately and decided to finally check this thing out. As per that post above, I'd guessed it might be similar to the ACCESS - IOD-64, but I wasn't too sure.
As you can see it's a full length card with four 50-pin IDC headers along the top. There are very few markings on the card, making it near-impossible to work out who the manufacturer was. The bottom left tag area indicates D6004039 and the top-right IC has a serial number indicating 5215001. The card is 8-bit.
Base IO Header Status
Before getting started with anything like this, record everything you have in its initial state. Take photos of all the switches, all the jumpers, all the traces (handy to point fingers at who scratched what), all the pins, all the headers... one always ends up changing too-many-variables and needs a reference point to return to! Even if that point is incorrect (say 0x220 is a furphy?), it's a base to begin testing from again.
In this situation, I also wanted to know what the four ports were outputting, voltage-wise. Turns out the basic idea is as follows:
01 | 03 | 05 | 07 | 09 | 11 | 13 | 15 | 17 | 19 | 21 | 23 | 25 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v |
+12v | +12v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v |
02 | 04 | 06 | 08 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 |
27 | 29 | 31 | 33 | 35 | 37 | 39 | 41 | 43 | 45 | 47 | 49 |
---|---|---|---|---|---|---|---|---|---|---|---|
~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | ~1.4v | +5v |
0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v | 0v |
28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 |
So, the two tables above should be butted together at the 20s... they're just too long to display in one hit. The table, as a whole, describes the first IO 50-pin IDC plug on the board. This is the one closest to the end plate. Note that the port is keyed, that a SCSI cable fits perfectly and that pin 1 lines up with the red wire.
From the image above, pin 1 is in the bottom-right and pin 50 top-left. We can determine that the even pins (top row) 6 to 50 are all ground/common since you can see them bridged via a horizontal trace. Pins 2 and 4 are providing 12v and pin 49 gives you 5v. I may have accidentally grounded all of these supply lines whilst initially testing, but it doesn't seem to have hurt the card! It did nearly melt my test wire though.
One quick thing we could determine here was the "V/G" jumper aligned with each header. My guess that it was either 'voltage' or 'ground' and would probably control the 'rail' of second pins from 6 to 48. A quick swap of the jumper on the first header proved that assumption totally wrong. Instead, when set to V, as they all were at the start, pins 2 and 4 were 12v. When on G, pins 2 and 4 then became GND, just like the entire rest of the row. This jumper does not affect the 5v on pin 49.
I never did work out what the EDP jumper setting did!
Programming Choices
I was going to install Visual Basic 6.0 (mount it with Daemon Tools) and build a pretty windows form, but I quickly realised that there's no direct hardware access to IO ports? I might dig into that again. Instead I downloaded QBasic 4.5 (it's also on the Win98 CD!) and slapped it in a folder on C:\. Note that if you want to play with QBasic on Windows 7++ then use dosbox.
QBasic supports the INP() Function which simply (and unrestricted-ly) reads a byte from an IO address. I had always thought this was only for serial/parallel ports, but it then occurred to me that any port in the system is really just a direct mapping (hopefully with some electrical isolation) of the data lines into the ISA bus. Therefore, if you give the IO address of anything else on the bus, you'll read data from those locations! Here's a list of standard ports for x86 which can, in this scenario, give you an idea of areas to avoid.
Hardware Configuration
Trying to work this out was a blast.... no, really... it took quite a lot of trial and error. If you look at the images above, you'll see the IO Address setting is set to 0-1-0-0-0-0-0-0, and if you look below, you'll even note a sharpie'd address written on the side of the first IDC port! 0x220 base IO address!
Crap, that'll conflict directly with any SoundBlaster... if that's what you happened to have installed. Using my new-found knowledge above, I wrote a really simple QBASIC script to get the data from a range of addresses near 0x220.
FOR PORT# = 220 TO 230 PRINT INP(PORT#) NEXT PORT#
The output was upsetting... just multiple outputs of the number 255. I wondered what the chances were that the dipswitches had been tampered with? Who knows if that inscription of 0x220 was, at all, correct? At the same time, if my pins were floating, then would the card report a 1 or a 0? Knowing where the supply voltage pins where, I danced around them and grounded a few of (what I expected were) the IO lines.
I still didn't get any results around 0x220, so I started searching... but, of course, where do I start searching? A DOS terminal screen gives me about 8 good rows of text space in text-mode, so I set a larger offset and started panning through the addresses. Using the IO Port Mapping for x86, I knew there were a few locations to skip and so I just kept digging.
This got pretty annoying after a while, so I went graphical... it really helped the next step!
DECLARE FUNCTION PortIsFree! (portNum#) DIM DATACACHE(0 TO &H900) AS INTEGER DIM OnOffPoints(0 TO 128) AS INTEGER DIM OnPoint AS INTEGER DIM OffPoint AS INTEGER DIM COLWIDTH AS INTEGER DIM ROWHEIGHT AS INTEGER DIM COLUMNS AS INTEGER DIM MAXADDRESSES AS INTEGER COLWIDTH = 8 ROWHEIGHT = 16 COLUMNS = 75 MAXADDRESSES = &H5A SCREEN 12 RESTORE OffPointData FOR PY = 0 TO 5 FOR PX = 0 TO 3 READ A PSET (PX, PY), A NEXT PX NEXT PY RESTORE OnPointData FOR PY = 0 TO 5 FOR PX = 4 TO 7 READ A PSET (PX, PY), A NEXT PX NEXT PY RESTORE OffCrapData FOR PY = 0 TO 5 FOR PX = 8 TO 11 READ A PSET (PX, PY), A NEXT PX NEXT PY RESTORE OnCrapData FOR PY = 0 TO 5 FOR PX = 12 TO 15 READ A PSET (PX, PY), A NEXT PX NEXT PY OnPoint = 0 OffPoint = 33 otherOnPOint = 64 OtherOffPoint = 80 GET (0, 0)-(3, 5), OnOffPoints(OnPoint) GET (4, 0)-(7, 5), OnOffPoints(OffPoint) GET (8, 0)-(11, 5), OnOffPoints(otherOnPOint) GET (12, 0)-(15, 5), OnOffPoints(OtherOffPoint) PORTSTART# = &H220 SELPORT# = &H220 PREVSELPORT# = &H220 FIRSTLOOP = 1 DATAOUT = 0 CLS DO DIM row AS INTEGER DIM col AS INTEGER col = 1 row = 1 FOR PORT# = PORTSTART# TO (PORTSTART# + MAXADDRESSES) IF (PORT# > 0) THEN IF (FIRSTLOOP = 1 OR SELPORT# <> PREVSELPORT#) THEN LOCATE row, col IF (SELPORT# = PORT#) THEN COLOR 12 ELSE COLOR 15 END IF PRINT HEX$(PORT#); END IF freeport = PortIsFree(PORT#) IF (freeport = 1 OR 1 = 1) THEN DATAIN# = INP(PORT#) CACHED = DATACACHE(PORT#) IF DATAIN# <> CACHED OR FIRSTLOOP = 1 THEN DATACACHE(PORT#) = DATAIN# dotrow = (row * ROWHEIGHT) offset = (col - 1) * COLWIDTH FOR bit = 8 TO 1 STEP -1 IF (DATAIN# AND (2 ^ (bit - 1))) THEN IF (freeport = 1) THEN PUT (offset, dotrow), OnOffPoints(OnPoint), PSET ELSE PUT (offset, dotrow), OnOffPoints(otherOnPOint), PSET END IF ELSE IF freeport = 1 THEN PUT (offset, dotrow), OnOffPoints(OffPoint), PSET ELSE PUT (offset, dotrow), OnOffPoints(OtherOffPoint), PSET END IF END IF offset = offset + 4 NEXT bit LOCATE row + 2, col PRINT " " COLOR 15 LOCATE row + 2, col PRINT DATAIN# END IF END IF END IF LOCATE 1, 1 col = col + 6 IF (col > COLUMNS) THEN col = 1 row = row + 4 END IF NEXT PORT# PREVSELPORT# = SELPORT# FIRSTLOOP = 0 LET K$ = INKEY$ LOCATE 29, 20 IF K$ = "q" OR K$ = "Q" THEN EXIT DO ELSEIF K$ = "/" THEN SELPORT# = SELPORT# + 20 ELSEIF K$ = CHR$(0) + CHR$(77) THEN SELPORT# = SELPORT# + 1 ELSEIF K$ = CHR$(0) + CHR$(75) THEN SELPORT# = SELPORT# - 1 ELSEIF K$ = CHR$(0) + CHR$(72) THEN CLS FIRSTLOOP = 1 PORTSTART# = PORTSTART# - MAXADDRESSES SELPORT# = PORTSTART# ELSEIF K$ = CHR$(0) + CHR$(80) THEN CLS FIRSTLOOP = 1 PORTSTART# = PORTSTART# + MAXADDRESSES SELPORT# = PORTSTART# ELSEIF K$ >= "0" AND K$ < = "9" THEN IF (DATASTEP = 0) THEN DATAOUT = VAL(K$) ELSE DATAOUT = VAL(K$) + (DATAOUT * 10) END IF DATASTEP = DATASTEP + 1 IF DATASTEP > 2 THEN DATASTEP = 0 END IF LOCATE 29, 1 PRINT " "; LOCATE 29, 1 PRINT DATAOUT; ELSEIF K$ = "s" THEN OUT SELPORT#, DATAOUT ELSEIF K$ <> "" THEN PRINT K$; END IF LOOP END OffPointData: DATA 00,10,10,00,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,00,10,10,00 OnPointData: DATA 00,12,12,00,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,00,12,12,00 OffCrapData: DATA 00,9,9,00,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,00,9,9,00 OnCrapData: DATA 00,1,1,00,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,00,1,1,00 FUNCTION PortIsFree (portNum#) IF (portNum# >= &H2F8 AND portNum# < = &H2FF) THEN PortIsFree = 0 ELSEIF (portNum# >= &H170 AND portNum# < = &H177) THEN PortIsFree = 0 ELSEIF (portNum# >= &H1F0 AND portNum# < = &H1F7) THEN PortIsFree = 0 ELSEIF (portNum# >= &H278 AND portNum# < = &H27A) THEN PortIsFree = 0 ELSEIF (portNum# >= &H3B0 AND portNum# < = &H3DF) THEN PortIsFree = 0 ELSEIF (portNum# >= &H3F0 AND portNum# < = &H3F7) THEN PortIsFree = 0 ELSEIF (portNum# >= &H3F8 AND portNum# < = &H3FF) THEN PortIsFree = 0 ELSE PortIsFree = 1 END IF END FUNCTION
Ahh... christmas... this now made it very easy for the next step... determination of the IO address switch. If you look at the photo of the monitor, you'll see from 680 that the first and last pins are grounded. This continues for 3 bytes, then skips a lot and continues again. That was the pattern I created on the cables to make it easier to find the card when I was adjusting the switches below.
Decoding the switch for IO address
Here comes the binary! I checked out the manual for that card I mentioned at the very start and it had a very convoluted way of adjusting the port offset. I chose to have more fun with the qbasic app above which simply displays the IO map of the system. Changing the cards' dipswitches whilst the machine was still on instantly affected the bytes displayed on-screen! The pattern of grounded wires, set on the first and last IO ports on the card, made it easy to distinguish... and, well, as dangerous as it sounds, it worked perfectly! The only slightly concerning thing was the address sharing... some of the areas had a duplicated area 0x400 bytes higher in the port table.
SW1 | SW2 | SW3 | SW4 | SW5 | SW6 | SW7 | SW8 | Output |
---|---|---|---|---|---|---|---|---|
0x3F0,0x7F0 | ||||||||
X | 0x1F0 | |||||||
X | 0x2F0 | |||||||
X | X | 0x0F0 | ||||||
X | 0x370,0x770 | |||||||
X | X | 0x170 | ||||||
X | X | 0x270 | ||||||
X | X | X | 0x470 | |||||
X | 0x3B0,0x7B0 | |||||||
X | X | 0x5B0 | ||||||
X | X | 0x2B0 | ||||||
X | X | X | 0x4B0 | |||||
X | X | 0x330,0x730 | ||||||
X | X | X | 0x130 | |||||
X | X | X | 0x230 | |||||
X | X | X | X | 0x430 | ||||
X | 0x3D0,0x7D0 | |||||||
X | X | 0x1D0 | ||||||
X | X | 0x2D0,0x6D0 | ||||||
X | X | X | 0x4D0 | |||||
X | X | 0x350 | ||||||
X | X | X | 0x550 | |||||
X | X | X | 0x250,0x650 | |||||
X | X | X | X | 0x450 | ||||
X | X | 0x390 | ||||||
X | X | X | 0x190,0x590 | |||||
X | X | X | 0x290,0x690 | |||||
X | X | X | X | 0x490,0x890 | ||||
X | X | X | 0x310,0x710 | |||||
X | X | X | X | 0x110,0x510 | ||||
X | X | X | X | 0x210,0x610 | ||||
X | X | X | X | X | 0x410 | |||
X | 0x3E0 | |||||||
X | X | 0x5E0 | ||||||
X | X | 0x2E0,0x6E0 | ||||||
X | X | X | 0x4E0 | |||||
X | X | 0x760 | ||||||
X | X | X | 0x560 | |||||
X | X | X | 0x660 | |||||
X | X | X | X | 0x460 | ||||
X | X | 0x3A0 | ||||||
X | X | X | 0x5A0 | |||||
X | X | X | 0x2A0,0x6A0 | |||||
X | X | X | X | 0x4A0 | ||||
X | X | X | 0x720 | |||||
X | X | X | X | 0x520 | ||||
X | X | X | X | 0x620 | ||||
X | X | X | X | X | 0x420 | |||
X | X | 0x7C0 | ||||||
X | X | X | 0x1C0 | |||||
X | X | X | 0x6C0 | |||||
X | X | X | X | 0x4C0 | ||||
X | X | X | 0x740 | |||||
X | X | X | X | 0x540 | ||||
X | X | X | X | 0x640 | ||||
X | X | X | X | X | 0x440 | |||
X | X | X | 0x380 | |||||
X | X | X | X | 0x580 | ||||
X | X | X | X | 0x680 | ||||
X | X | X | X | X | 0x480 | |||
X | X | X | X | 0x300 | ||||
X | X | X | X | X | 0x500 | |||
X | X | X | X | X | 0x600 | |||
X | X | X | X | X | X | 0x400 | ||
X | 0x7F0 | |||||||
X | X | 0x5F0 | ||||||
X | X | 0x6F0 | ||||||
X | X | X | 0x4F0 | |||||
X | X | 0x770 | ||||||
X | X | X | 0x570 | |||||
X | X | X | 0x670 | |||||
X | X | X | X | 0x470 | ||||
X | X | 0x7B0 | ||||||
X | X | X | 0x5B0 | |||||
X | X | X | 0x6B0 | |||||
X | X | X | X | 0x4B0 | ||||
X | X | X | 0x730 | |||||
X | X | X | X | 0x530 | ||||
X | X | X | X | 0x630 | ||||
X | X | X | X | X | 0x430 | |||
X | X | 0x7D0 | ||||||
X | X | X | 0x5D0 | |||||
X | X | X | 0x6D0 | |||||
X | X | X | X | 0x4D0 | ||||
X | X | X | 0x750 | |||||
X | X | X | X | 0x550 | ||||
X | X | X | X | 0x650 | ||||
X | X | X | X | X | 0x450 | |||
X | X | X | 0x790 | |||||
X | X | X | X | 0x590 | ||||
X | X | X | X | 0x690 | ||||
X | X | X | X | X | 0x490 | |||
X | X | X | X | 0x710 | ||||
X | X | X | X | X | 0x510 | |||
X | X | X | X | X | 0x610 | |||
X | X | X | X | X | X | 0x410 | ||
X | X | 0x7E0 | ||||||
X | X | X | 0x5E0 | |||||
X | X | X | 0x6E0 | |||||
X | X | X | X | 0x4E0 | ||||
X | X | X | 0x760 | |||||
X | X | X | X | 0x560 | ||||
X | X | X | X | 0x660 | ||||
X | X | X | X | X | 0x460 | |||
X | X | X | 0x7A0 | |||||
X | X | X | X | 0x5A0 | ||||
X | X | X | X | 0x6A0 | ||||
X | X | X | X | X | 0x4A0 | |||
X | X | X | X | 0x720 | ||||
X | X | X | X | X | 0x520 | |||
X | X | X | X | X | 0x620 | |||
X | X | X | X | X | X | 0x420 | ||
X | X | X | 0x7C0 | |||||
X | X | X | X | 0x5C0 | ||||
X | X | X | X | 0x6C0 | ||||
X | X | X | X | X | 0x4C0 | |||
X | X | X | X | 0x740 | ||||
X | X | X | X | X | 0x540 | |||
X | X | X | X | X | 0x640 | |||
X | X | X | X | X | X | 0x440 | ||
X | X | X | X | 0x780 | ||||
X | X | X | X | X | 0x580 | |||
X | X | X | X | X | 0x680 | |||
X | X | X | X | X | X | 0x480 | ||
X | X | X | X | X | 0x700,0x300 | |||
X | X | X | X | X | X | 0x500 | ||
X | X | X | X | X | X | 0x600 | ||
X | X | X | X | X | X | X | 0x400 | |
X | 0x7F0,0x3F0 | |||||||
X | X | 0x5F0 | ||||||
X | X | 0x6F0 | ||||||
X | X | X | 0x4F0 | |||||
X | X | 0x770 | ||||||
X | X | X | 0x570 | |||||
X | X | X | 0x670 | |||||
X | X | X | X | 0x470 | ||||
X | X | 0x7B0 | ||||||
X | X | X | 0x5B0 | |||||
X | X | X | 0x6B0 | |||||
X | X | X | X | 0x4B0 | ||||
X | X | X | 0x730 | |||||
X | X | X | X | 0x530 | ||||
X | X | X | X | 0x630 | ||||
X | X | X | X | X | 0x430 | |||
X | X | 0x0x7D0,0x3D0 | ||||||
X | X | X | 0x5D0 | |||||
X | X | X | 0x6D0 | |||||
X | X | X | X | 0x4D0 | ||||
X | X | X | 0x350 | |||||
X | X | X | X | 0x550 | ||||
X | X | X | X | 0x650 | ||||
X | X | X | X | X | 0x450 | |||
X | X | X | 0x390 | |||||
X | X | X | X | 0x590 | ||||
X | X | X | X | 0x690 | ||||
X | X | X | X | X | 0x490 | |||
X | X | X | X | 0x310 | ||||
X | X | X | X | X | 0x510 | |||
X | X | X | X | X | 0x610 | |||
X | X | X | X | X | X | 0x410 | ||
X | X | 0x3E0 | ||||||
X | X | X | 0x5E0 | |||||
X | X | X | 0x6E0 | |||||
X | X | X | X | 0x4E0 | ||||
X | X | X | 0x360 | |||||
X | X | X | X | 0x560 | ||||
X | X | X | X | 0x660 | ||||
X | X | X | X | X | 0x460 | |||
X | X | X | 0x3A0 | |||||
X | X | X | X | 0x5A0 | ||||
X | X | X | X | 0x6A0 | ||||
X | X | X | X | X | 0x4A0 | |||
X | X | X | X | 0x320 | ||||
X | X | X | X | X | 0x520 | |||
X | X | X | X | X | 0x620 | |||
X | X | X | X | X | X | 0x420 | ||
X | X | X | 0x7C0 | |||||
X | X | X | X | 0x5C0 | ||||
X | X | X | X | 0x6C0 | ||||
X | X | X | X | X | 0x4C0 | |||
X | X | X | X | 0x740,0x340 | ||||
X | X | X | X | X | 0x540 | |||
X | X | X | X | X | 0x640 | |||
X | X | X | X | X | X | 0x440 | ||
X | X | X | X | 0x380,0x780 | ||||
X | X | X | X | X | 0x580 | |||
X | X | X | X | X | 0x680 | |||
X | X | X | X | X | X | 0x480 | ||
X | X | X | X | X | 0x300,0x700 | |||
X | X | X | X | X | X | 0x500 | ||
X | X | X | X | X | X | 0x600 | ||
X | X | X | X | X | X | X | 0x400 | |
X | X | 0x3F0,0x7F0 | ||||||
X | X | X | 0x5F0 | |||||
X | X | X | 0x6F0 | |||||
X | X | X | X | 0x4F0 | ||||
X | X | X | 0x770,0x370 | |||||
X | X | X | X | 0x570 | ||||
X | X | X | X | 0x670 | ||||
X | X | X | X | X | 0x470 | |||
X | X | X | 0x3B0,0x7B0 | |||||
X | X | X | X | 0x5B0 | ||||
X | X | X | X | 0x6B0 | ||||
X | X | X | X | X | 0x4B0 | |||
X | X | X | X | 0x330,0x730 | ||||
X | X | X | X | X | 0x530 | |||
X | X | X | X | X | 0x630 | |||
X | X | X | X | X | X | 0x430 | ||
X | X | X | 0x3D0,0x7D0 | |||||
X | X | X | X | 0x5D0 | ||||
X | X | X | X | 0x6D0 | ||||
X | X | X | X | X | 0x4D0 | |||
X | X | X | X | 0x350,0x750 | ||||
X | X | X | X | X | 0x550 | |||
X | X | X | X | X | 0x650 | |||
X | X | X | X | X | X | 0x450 | ||
X | X | X | X | 0x790,0x390 | ||||
X | X | X | X | X | 0x590 | |||
X | X | X | X | X | 0x690 | |||
X | X | X | X | X | X | 0x490 | ||
X | X | X | X | X | 0x710,0x310 | |||
X | X | X | X | X | X | 0x510 | ||
X | X | X | X | X | X | 0x610 | ||
X | X | X | X | X | X | X | 0x410 | |
X | X | X | 0x3E0,0x7E0 | |||||
X | X | X | X | 0x5E0 | ||||
X | X | X | X | 0x6E0 | ||||
X | X | X | X | X | 0x4E0 | |||
X | X | X | X | 0x760,0x360 | ||||
X | X | X | X | X | 0x560 | |||
X | X | X | X | X | 0x660 | |||
X | X | X | X | X | X | 0x460 | ||
X | X | X | X | 0x7A0,0x3A0 | ||||
X | X | X | X | X | 0x5A0 | |||
X | X | X | X | X | 0x6A0 | |||
X | X | X | X | X | X | 0x4A0 | ||
X | X | X | X | X | 0x720,0x320 | |||
X | X | X | X | X | X | 0x520 | ||
X | X | X | X | X | X | 0x620 | ||
X | X | X | X | X | X | X | 0x420 | |
X | X | X | X | 0x7C0,0x3C0 | ||||
X | X | X | X | X | 0x5C0 | |||
X | X | X | X | X | 0x6C0 | |||
X | X | X | X | X | X | 0x4C0 | ||
X | X | X | X | X | 0x740 | |||
X | X | X | X | X | X | 0x540 | ||
X | X | X | X | X | X | 0x640 | ||
X | X | X | X | X | X | X | 0x440 | |
X | X | X | X | X | 0x780 | |||
X | X | X | X | X | X | 0x580 | ||
X | X | X | X | X | X | 0x680 | ||
X | X | X | X | X | X | X | 0x480 | |
X | X | X | X | X | X | 0x700,0x300 | ||
X | X | X | X | X | X | X | 0x500 | |
X | X | X | X | X | X | X | 0x600 | |
X | X | X | X | X | X | X | X | 0x400,0x800 |
So from above... the magical calculation is... dunno... but the weird sharing of XXX with XXX+400h was confusing and slightly dangerous when both ranges could conflict with other hardware in the system!
Port Mapping
So now we know how to set the base address, how do we then actually work with the data? The card needs 16 consecutive IO Ports, so please make sure you find a space in the address table that has the capacity. You don't want it interfering with other hardware in your system. Actually, the code above gives you a really good idea as to what areas in use and which are free. Also make note of the 'ghosting' that seems to occur with the lower address ranges. The table below expects you've set the above address to 0x220, but this means the data will also be visible on 620! This could easily interfere with an AWE Soundcard.
Port | Usage |
---|---|
0x220 | CN1 pins 33,35,37,39,41,43,45,47 |
0x221 | CN1 pins 17,19,21,23,25,27,29,31 |
0x222 | CN1 pins 01,03,05,07,09,11,13,15 |
0x223 | Control Register for CN1 |
0x224 | CN2 pins 33,35,37,39,41,43,45,47 |
0x225 | CN2 pins 17,19,21,23,25,27,29,31 |
0x226 | CN2 pins 01,03,05,07,09,11,13,15 |
0x227 | Control Register for CN2 |
0x228 | CN3 pins 33,35,37,39,41,43,45,47 |
0x229 | CN3 pins 17,19,21,23,25,27,29,31 |
0x22A | CN3 pins 01,03,05,07,09,11,13,15 |
0x22B | Control Register for CN3 |
0x22C | CN4 pins 33,35,37,39,41,43,45,47 |
0x22D | CN4 pins 17,19,21,23,25,27,29,31 |
0x22E | CN4 pins 01,03,05,07,09,11,13,15 |
0x22F | Control Register for CN4 |
Writing to the ports
With the QBASIC code above, you can use the Up and Down arrows to paginate. You can then use the left and right to select a specific address. Use the forward-slash key to skip 10 addresses at a time. Once on an address, you can write a value to the selected port by typing that number in and pressing the letter S. From here I worked out the following:
- You can only write out values to the bytes if you send a value to the control register first.
- If you write a number to one of the three blocks of pins, then it'll set all associated bits HIGH.
- Sending a 0 therefore sets them all LOW, 255 will set them all HIGH.
- The final control register allows you to set all three bytes to 0 or 255 quickly. But I haven't totally decoded what the required values are.
I'm still trying to ascertain how it works. The EDP jumper seems to determine if one is allowed to send bytes to the ports, but I haven't correctly determined exactly how.
As is, the card could easily be used to control/read binary bits in its default setting.
Let's control a model railway!
This card would've been perfect when I was younger. I remember back at Lyneham Hockey Courts in Canberra, there was an engineer who'd built a smallish model railway at one of the conventions and it was totally computer controlled. There was this hulking XT/AT machine next to the layout with ribbon cables running everywhere. A card with this much IO would've been fantastic for sensors, point control and even throttles. I might do that now, just for fun, using some of the off-the-shelf Arduino modules in my box-o'-junk. Sure, it's cheating, but I'll still be using the full power of the card.
An L298 can provide the throttle, but it needs a variable resistance fed into this. We can use a Resistor Ladder to provide this, as we have so many pins available.
I might just put it back in its box for the next rainy day. If anyone has further documentation on this card, then please leave a comment!